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