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 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | namespace QtWaylandClient { |
21 | |
22 | std::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 | |
35 | QWaylandCursorTheme::~QWaylandCursorTheme() |
36 | { |
37 | wl_cursor_theme_destroy(theme: m_theme); |
38 | } |
39 | |
40 | wl_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 | |
210 | QWaylandCursorShape::QWaylandCursorShape(::wp_cursor_shape_device_v1 *object) |
211 | : QtWayland::wp_cursor_shape_device_v1(object) |
212 | {} |
213 | |
214 | QWaylandCursorShape::~QWaylandCursorShape() |
215 | { |
216 | destroy(); |
217 | } |
218 | |
219 | static 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 | |
276 | void QWaylandCursorShape::setShape(uint32_t serial, Qt::CursorShape shape) |
277 | { |
278 | set_shape(serial, qtCursorShapeToWaylandShape(shape)); |
279 | } |
280 | |
281 | QWaylandCursor::QWaylandCursor(QWaylandDisplay *display) |
282 | : mDisplay(display) |
283 | { |
284 | } |
285 | |
286 | QSharedPointer<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 | |
309 | void 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 | |
323 | void QWaylandCursor::pointerEvent(const QMouseEvent &event) |
324 | { |
325 | mLastPos = event.globalPosition().toPoint(); |
326 | } |
327 | |
328 | QPoint QWaylandCursor::pos() const |
329 | { |
330 | return mLastPos; |
331 | } |
332 | |
333 | void 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 | |
341 | QT_END_NAMESPACE |
342 | |