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