1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2023 David Edmundson <davidedmundson@kde.org>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qwaylandcursor_p.h"
6
7#include "qwaylanddisplay_p.h"
8#include "qwaylandinputdevice_p.h"
9#include "qwaylandshmbackingstore_p.h"
10
11#include <QtGui/QImageReader>
12#include <QDebug>
13
14#include <wayland-cursor.h>
15
16#include <algorithm>
17
18QT_BEGIN_NAMESPACE
19
20namespace QtWaylandClient {
21
22std::unique_ptr<QWaylandCursorTheme> QWaylandCursorTheme::create(QWaylandShm *shm, int size, const QString &themeName)
23{
24 QByteArray nameBytes = themeName.toLocal8Bit();
25 struct ::wl_cursor_theme *theme = wl_cursor_theme_load(nameBytes.constData(), size, shm->object());
26
27 if (!theme) {
28 qCWarning(lcQpaWayland) << "Could not load cursor theme" << themeName << "size" << size;
29 return nullptr;
30 }
31
32 return std::unique_ptr<QWaylandCursorTheme>{new QWaylandCursorTheme(theme)};
33}
34
35QWaylandCursorTheme::~QWaylandCursorTheme()
36{
37 wl_cursor_theme_destroy(theme: m_theme);
38}
39
40wl_cursor *QWaylandCursorTheme::requestCursor(WaylandCursor shape)
41{
42 if (struct wl_cursor *cursor = m_cursors[shape])
43 return cursor;
44
45 static Q_CONSTEXPR struct ShapeAndName {
46 WaylandCursor shape;
47 const char name[33];
48 } cursorNamesMap[] = {
49 {.shape: ArrowCursor, .name: "left_ptr"},
50 {.shape: ArrowCursor, .name: "default"},
51 {.shape: ArrowCursor, .name: "top_left_arrow"},
52 {.shape: ArrowCursor, .name: "left_arrow"},
53
54 {.shape: UpArrowCursor, .name: "up_arrow"},
55
56 {.shape: CrossCursor, .name: "cross"},
57
58 {.shape: WaitCursor, .name: "wait"},
59 {.shape: WaitCursor, .name: "watch"},
60 {.shape: WaitCursor, .name: "0426c94ea35c87780ff01dc239897213"},
61
62 {.shape: IBeamCursor, .name: "ibeam"},
63 {.shape: IBeamCursor, .name: "text"},
64 {.shape: IBeamCursor, .name: "xterm"},
65
66 {.shape: SizeVerCursor, .name: "size_ver"},
67 {.shape: SizeVerCursor, .name: "ns-resize"},
68 {.shape: SizeVerCursor, .name: "v_double_arrow"},
69 {.shape: SizeVerCursor, .name: "00008160000006810000408080010102"},
70
71 {.shape: SizeHorCursor, .name: "size_hor"},
72 {.shape: SizeHorCursor, .name: "ew-resize"},
73 {.shape: SizeHorCursor, .name: "h_double_arrow"},
74 {.shape: SizeHorCursor, .name: "028006030e0e7ebffc7f7070c0600140"},
75
76 {.shape: SizeBDiagCursor, .name: "size_bdiag"},
77 {.shape: SizeBDiagCursor, .name: "nesw-resize"},
78 {.shape: SizeBDiagCursor, .name: "50585d75b494802d0151028115016902"},
79 {.shape: SizeBDiagCursor, .name: "fcf1c3c7cd4491d801f1e1c78f100000"},
80
81 {.shape: SizeFDiagCursor, .name: "size_fdiag"},
82 {.shape: SizeFDiagCursor, .name: "nwse-resize"},
83 {.shape: SizeFDiagCursor, .name: "38c5dff7c7b8962045400281044508d2"},
84 {.shape: SizeFDiagCursor, .name: "c7088f0f3e6c8088236ef8e1e3e70000"},
85
86 {.shape: SizeAllCursor, .name: "size_all"},
87
88 {.shape: BlankCursor, .name: "blank"},
89
90 {.shape: SplitVCursor, .name: "split_v"},
91 {.shape: SplitVCursor, .name: "row-resize"},
92 {.shape: SplitVCursor, .name: "sb_v_double_arrow"},
93 {.shape: SplitVCursor, .name: "2870a09082c103050810ffdffffe0204"},
94 {.shape: SplitVCursor, .name: "c07385c7190e701020ff7ffffd08103c"},
95
96 {.shape: SplitHCursor, .name: "split_h"},
97 {.shape: SplitHCursor, .name: "col-resize"},
98 {.shape: SplitHCursor, .name: "sb_h_double_arrow"},
99 {.shape: SplitHCursor, .name: "043a9f68147c53184671403ffa811cc5"},
100 {.shape: SplitHCursor, .name: "14fef782d02440884392942c11205230"},
101
102 {.shape: PointingHandCursor, .name: "pointing_hand"},
103 {.shape: PointingHandCursor, .name: "pointer"},
104 {.shape: PointingHandCursor, .name: "hand1"},
105 {.shape: PointingHandCursor, .name: "e29285e634086352946a0e7090d73106"},
106
107 {.shape: ForbiddenCursor, .name: "forbidden"},
108 {.shape: ForbiddenCursor, .name: "not-allowed"},
109 {.shape: ForbiddenCursor, .name: "crossed_circle"},
110 {.shape: ForbiddenCursor, .name: "circle"},
111 {.shape: ForbiddenCursor, .name: "03b6e0fcb3499374a867c041f52298f0"},
112
113 {.shape: WhatsThisCursor, .name: "whats_this"},
114 {.shape: WhatsThisCursor, .name: "help"},
115 {.shape: WhatsThisCursor, .name: "question_arrow"},
116 {.shape: WhatsThisCursor, .name: "5c6cd98b3f3ebcb1f9c7f1c204630408"},
117 {.shape: WhatsThisCursor, .name: "d9ce0ab605698f320427677b458ad60b"},
118
119 {.shape: BusyCursor, .name: "left_ptr_watch"},
120 {.shape: BusyCursor, .name: "half-busy"},
121 {.shape: BusyCursor, .name: "progress"},
122 {.shape: BusyCursor, .name: "00000000000000020006000e7e9ffc3f"},
123 {.shape: BusyCursor, .name: "08e8e1c95fe2fc01f976f1e063a24ccd"},
124
125 {.shape: OpenHandCursor, .name: "openhand"},
126 {.shape: OpenHandCursor, .name: "fleur"},
127 {.shape: OpenHandCursor, .name: "5aca4d189052212118709018842178c0"},
128 {.shape: OpenHandCursor, .name: "9d800788f1b08800ae810202380a0822"},
129
130 {.shape: ClosedHandCursor, .name: "closedhand"},
131 {.shape: ClosedHandCursor, .name: "grabbing"},
132 {.shape: ClosedHandCursor, .name: "208530c400c041818281048008011002"},
133
134 {.shape: DragCopyCursor, .name: "dnd-copy"},
135 {.shape: DragCopyCursor, .name: "copy"},
136
137 {.shape: DragMoveCursor, .name: "dnd-move"},
138 {.shape: DragMoveCursor, .name: "move"},
139
140 {.shape: DragLinkCursor, .name: "dnd-link"},
141 {.shape: DragLinkCursor, .name: "link"},
142
143 {.shape: ResizeNorthCursor, .name: "n-resize"},
144 {.shape: ResizeNorthCursor, .name: "top_side"},
145
146 {.shape: ResizeSouthCursor, .name: "s-resize"},
147 {.shape: ResizeSouthCursor, .name: "bottom_side"},
148
149 {.shape: ResizeEastCursor, .name: "e-resize"},
150 {.shape: ResizeEastCursor, .name: "right_side"},
151
152 {.shape: ResizeWestCursor, .name: "w-resize"},
153 {.shape: ResizeWestCursor, .name: "left_side"},
154
155 {.shape: ResizeNorthWestCursor, .name: "nw-resize"},
156 {.shape: ResizeNorthWestCursor, .name: "top_left_corner"},
157
158 {.shape: ResizeSouthEastCursor, .name: "se-resize"},
159 {.shape: ResizeSouthEastCursor, .name: "bottom_right_corner"},
160
161 {.shape: ResizeNorthEastCursor, .name: "ne-resize"},
162 {.shape: ResizeNorthEastCursor, .name: "top_right_corner"},
163
164 {.shape: ResizeSouthWestCursor, .name: "sw-resize"},
165 {.shape: ResizeSouthWestCursor, .name: "bottom_left_corner"},
166 };
167
168 const auto byShape = [](ShapeAndName lhs, ShapeAndName rhs) {
169 return lhs.shape < rhs.shape;
170 };
171 Q_ASSERT(std::is_sorted(std::begin(cursorNamesMap), std::end(cursorNamesMap), byShape));
172 const auto p = std::equal_range(first: std::begin(arr: cursorNamesMap), last: std::end(arr: cursorNamesMap),
173 val: ShapeAndName{.shape: shape, .name: ""}, comp: byShape);
174 for (auto it = p.first; it != p.second; ++it) {
175 if (wl_cursor *cursor = wl_cursor_theme_get_cursor(theme: m_theme, name: it->name)) {
176 m_cursors[shape] = cursor;
177 return cursor;
178 }
179 }
180
181 // Fallback to arrow cursor
182 if (shape != ArrowCursor)
183 return requestCursor(shape: ArrowCursor);
184
185 // Give up
186 return nullptr;
187}
188
189::wl_cursor *QWaylandCursorTheme::cursor(Qt::CursorShape shape)
190{
191 struct wl_cursor *waylandCursor = nullptr;
192
193 if (shape < Qt::BitmapCursor) {
194 waylandCursor = requestCursor(shape: WaylandCursor(shape));
195 } else if (shape == Qt::BitmapCursor) {
196 qCWarning(lcQpaWayland) << "cannot create a wl_cursor_image for a CursorShape";
197 return nullptr;
198 } else {
199 //TODO: Custom cursor logic (for resize arrows)
200 }
201
202 if (!waylandCursor) {
203 qCWarning(lcQpaWayland) << "Could not find cursor for shape" << shape;
204 return nullptr;
205 }
206
207 return waylandCursor;
208}
209
210QWaylandCursorShape::QWaylandCursorShape(::wp_cursor_shape_device_v1 *object)
211 : QtWayland::wp_cursor_shape_device_v1(object)
212{}
213
214QWaylandCursorShape::~QWaylandCursorShape()
215{
216 destroy();
217}
218
219static QtWayland::wp_cursor_shape_device_v1::shape qtCursorShapeToWaylandShape(Qt::CursorShape cursorShape)
220{
221 using QtWayland::wp_cursor_shape_device_v1;
222
223 switch (cursorShape) {
224 case Qt::BlankCursor:
225 case Qt::CustomCursor:
226 case Qt::BitmapCursor:
227 // these should have been handled separately before using the shape protocol
228 Q_ASSERT(false);
229 break;
230 case Qt::ArrowCursor:
231 return wp_cursor_shape_device_v1::shape_default;
232 case Qt::SizeVerCursor:
233 return wp_cursor_shape_device_v1::shape_ns_resize;
234 case Qt::UpArrowCursor:
235 return wp_cursor_shape_device_v1::shape_n_resize;
236 case Qt::SizeHorCursor:
237 return wp_cursor_shape_device_v1::shape_ew_resize;
238 case Qt::CrossCursor:
239 return wp_cursor_shape_device_v1::shape_crosshair;
240 case Qt::SizeBDiagCursor:
241 return wp_cursor_shape_device_v1::shape_nesw_resize;
242 case Qt::IBeamCursor:
243 return wp_cursor_shape_device_v1::shape_text;
244 case Qt::SizeFDiagCursor:
245 return wp_cursor_shape_device_v1::shape_nwse_resize;
246 case Qt::WaitCursor:
247 return wp_cursor_shape_device_v1::shape_progress;
248 case Qt::SizeAllCursor:
249 return wp_cursor_shape_device_v1::shape_all_scroll;
250 case Qt::BusyCursor:
251 return wp_cursor_shape_device_v1::shape_wait;
252 case Qt::SplitVCursor:
253 return wp_cursor_shape_device_v1::shape_row_resize;
254 case Qt::ForbiddenCursor:
255 return wp_cursor_shape_device_v1::shape_not_allowed;
256 case Qt::SplitHCursor:
257 return wp_cursor_shape_device_v1::shape_col_resize;
258 case Qt::PointingHandCursor:
259 return wp_cursor_shape_device_v1::shape_pointer;
260 case Qt::OpenHandCursor:
261 return wp_cursor_shape_device_v1::shape_grab;
262 case Qt::WhatsThisCursor:
263 return wp_cursor_shape_device_v1::shape_help;
264 case Qt::ClosedHandCursor:
265 return wp_cursor_shape_device_v1::shape_grabbing;
266 case Qt::DragMoveCursor:
267 case Qt::DragCopyCursor:
268 case Qt::DragLinkCursor:
269 // drags on wayland are different, the compositor knows
270 // the drag type and can do something custom
271 return wp_cursor_shape_device_v1::shape_grab;
272 }
273 return wp_cursor_shape_device_v1::shape_default;
274}
275
276void QWaylandCursorShape::setShape(uint32_t serial, Qt::CursorShape shape)
277{
278 set_shape(serial, qtCursorShapeToWaylandShape(shape));
279}
280
281QWaylandCursor::QWaylandCursor(QWaylandDisplay *display)
282 : mDisplay(display)
283{
284}
285
286QSharedPointer<QWaylandBuffer> QWaylandCursor::cursorBitmapBuffer(QWaylandDisplay *display, const QCursor *cursor)
287{
288 Q_ASSERT(cursor->shape() == Qt::BitmapCursor);
289 QImage img = !cursor->pixmap().isNull() ? cursor->pixmap().toImage() : cursor->bitmap().toImage();
290
291 // convert to supported format if necessary
292 if (!display->shm()->formatSupported(format: img.format())) {
293 if (cursor->mask().isNull()) {
294 img.convertTo(f: QImage::Format_RGB32);
295 } else {
296 // preserve mask
297 img.convertTo(f: QImage::Format_ARGB32);
298 QPixmap pixmap = QPixmap::fromImage(image: img);
299 pixmap.setMask(cursor->mask());
300 img = pixmap.toImage();
301 }
302 }
303
304 QSharedPointer<QWaylandShmBuffer> buffer(new QWaylandShmBuffer(display, img.size(), img.format()));
305 memcpy(dest: buffer->image()->bits(), src: img.bits(), n: size_t(img.sizeInBytes()));
306 return buffer;
307}
308
309void QWaylandCursor::changeCursor(QCursor *cursor, QWindow *window)
310{
311 Q_UNUSED(window);
312 // Create the buffer here so we don't have to create one per input device
313 QSharedPointer<QWaylandBuffer> bitmapBuffer;
314 if (cursor && cursor->shape() == Qt::BitmapCursor)
315 bitmapBuffer = cursorBitmapBuffer(display: mDisplay, cursor);
316
317 int fallbackOutputScale = int(window->devicePixelRatio());
318 const auto seats = mDisplay->inputDevices();
319 for (auto *seat : seats)
320 seat->setCursor(cursor, cachedBuffer: bitmapBuffer, fallbackOutputScale);
321}
322
323void QWaylandCursor::pointerEvent(const QMouseEvent &event)
324{
325 mLastPos = event.globalPosition().toPoint();
326}
327
328QPoint QWaylandCursor::pos() const
329{
330 return mLastPos;
331}
332
333void QWaylandCursor::setPos(const QPoint &pos)
334{
335 Q_UNUSED(pos);
336 qCWarning(lcQpaWayland) << "Setting cursor position is not possible on wayland";
337}
338
339} // namespace QtWaylandClient
340
341QT_END_NAMESPACE
342

source code of qtwayland/src/client/qwaylandcursor.cpp