diff --git a/src/plugins/platforms/wayland/qwaylandcallback_p.h b/src/plugins/platforms/wayland/qwaylandcallback_p.h new file mode 100644 index 00000000000..b9afa18c6a0 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandcallback_p.h @@ -0,0 +1,31 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDCALLBACK_H +#define QWAYLANDCALLBACK_H + +#include "qwayland-wayland.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class WlCallback : public QtWayland::wl_callback +{ +public: + explicit WlCallback(::wl_callback *callback, std::function fn) + : QtWayland::wl_callback(callback), m_fn(fn) + { + } + ~WlCallback() override { wl_callback_destroy(object()); } + void callback_done(uint32_t callback_data) override { m_fn(callback_data); } + +private: + std::function m_fn; +}; + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDCALLBACK_H diff --git a/src/plugins/platforms/wayland/qwaylandcursorsurface_p.h b/src/plugins/platforms/wayland/qwaylandcursorsurface_p.h new file mode 100644 index 00000000000..3e011ead28f --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandcursorsurface_p.h @@ -0,0 +1,81 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDCURSORSURFACE_H +#define QWAYLANDCURSORSURFACE_H + +#include "qwaylandsurface_p.h" +#include "qwaylandcallback_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +#if QT_CONFIG(cursor) +template +class CursorSurface : public QWaylandSurface +{ +public: + explicit CursorSurface(InputDevice *pointer, QWaylandDisplay *display) + : QWaylandSurface(display), m_pointer(pointer) + { + connect(this, &QWaylandSurface::screensChanged, m_pointer, &InputDevice::updateCursor); + } + + void reset() + { + m_setSerial = 0; + m_hotspot = QPoint(); + } + + // Size and hotspot are in surface coordinates + void update(wl_buffer *buffer, const QPoint &hotspot, const QSize &size, int bufferScale, + bool animated = false) + { + // Calling code needs to ensure buffer scale is supported if != 1 + Q_ASSERT(bufferScale == 1 || version() >= 3); + + auto enterSerial = m_pointer->mEnterSerial; + if (m_setSerial < enterSerial || m_hotspot != hotspot) { + m_pointer->set_cursor(m_pointer->mEnterSerial, object(), hotspot.x(), hotspot.y()); + m_setSerial = enterSerial; + m_hotspot = hotspot; + } + + if (version() >= 3) + set_buffer_scale(bufferScale); + + attach(buffer, 0, 0); + damage(0, 0, size.width(), size.height()); + m_frameCallback.reset(); + if (animated) { + m_frameCallback.reset(new WlCallback(frame(), [this](uint32_t time) { + Q_UNUSED(time); + m_pointer->cursorFrameCallback(); + })); + } + commit(); + } + + int outputScale() const + { + int scale = 0; + for (auto *screen : m_screens) + scale = qMax(scale, screen->scale()); + return scale; + } + +private: + QScopedPointer m_frameCallback; + InputDevice *m_pointer = nullptr; + uint m_setSerial = 0; + QPoint m_hotspot; +}; + +#endif // QT_CONFIG(cursor) + +} // namespace QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDCURSORSURFACE_H diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp index 9bfa2b0f4af..e2b788171a0 100644 --- a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp +++ b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp @@ -29,6 +29,8 @@ #include "qwaylandtextinputinterface_p.h" #include "qwaylandinputcontext_p.h" #include "qwaylandinputmethodcontext_p.h" +#include "qwaylandcallback_p.h" +#include "qwaylandcursorsurface_p.h" #include #include @@ -152,80 +154,6 @@ QWaylandWindow *QWaylandInputDevice::Pointer::focusWindow() const #if QT_CONFIG(cursor) -class WlCallback : public QtWayland::wl_callback { -public: - explicit WlCallback(::wl_callback *callback, std::function fn) - : QtWayland::wl_callback(callback) - , m_fn(fn) - {} - ~WlCallback() override { wl_callback_destroy(object()); } - void callback_done(uint32_t callback_data) override { - m_fn(callback_data); - } -private: - std::function m_fn; -}; - -class CursorSurface : public QWaylandSurface -{ -public: - explicit CursorSurface(QWaylandInputDevice::Pointer *pointer, QWaylandDisplay *display) - : QWaylandSurface(display) - , m_pointer(pointer) - { - connect(this, &QWaylandSurface::screensChanged, - m_pointer, &QWaylandInputDevice::Pointer::updateCursor); - } - - void reset() - { - m_setSerial = 0; - m_hotspot = QPoint(); - } - - // Size and hotspot are in surface coordinates - void update(wl_buffer *buffer, const QPoint &hotspot, const QSize &size, int bufferScale, bool animated = false) - { - // Calling code needs to ensure buffer scale is supported if != 1 - Q_ASSERT(bufferScale == 1 || version() >= 3); - - auto enterSerial = m_pointer->mEnterSerial; - if (m_setSerial < enterSerial || m_hotspot != hotspot) { - m_pointer->set_cursor(m_pointer->mEnterSerial, object(), hotspot.x(), hotspot.y()); - m_setSerial = enterSerial; - m_hotspot = hotspot; - } - - if (version() >= 3) - set_buffer_scale(bufferScale); - - attach(buffer, 0, 0); - damage(0, 0, size.width(), size.height()); - m_frameCallback.reset(); - if (animated) { - m_frameCallback.reset(new WlCallback(frame(), [this](uint32_t time){ - Q_UNUSED(time); - m_pointer->cursorFrameCallback(); - })); - } - commit(); - } - - int outputScale() const - { - int scale = 0; - for (auto *screen : m_screens) - scale = qMax(scale, screen->scale()); - return scale; - } - -private: - QScopedPointer m_frameCallback; - QWaylandInputDevice::Pointer *m_pointer = nullptr; - uint m_setSerial = 0; - QPoint m_hotspot; -}; - int QWaylandInputDevice::Pointer::idealCursorScale() const { if (seat()->mQDisplay->compositor()->version() < 3) { @@ -342,7 +270,8 @@ void QWaylandInputDevice::Pointer::updateCursor() qCWarning(lcQpaWayland) << "Unable to change to cursor" << shape; } -CursorSurface *QWaylandInputDevice::Pointer::getOrCreateCursorSurface() +CursorSurface * +QWaylandInputDevice::Pointer::getOrCreateCursorSurface() { if (!mCursor.surface) mCursor.surface.reset(new CursorSurface(this, seat()->mQDisplay)); @@ -688,6 +617,9 @@ void QWaylandInputDevice::setCursor(const QCursor *cursor, const QSharedPointer< if (mPointer) mPointer->updateCursor(); + + if (mTabletSeat) + mTabletSeat->updateCursor(); } #endif diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h index 4e74df1e182..0d79ef93376 100644 --- a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h +++ b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h @@ -65,6 +65,7 @@ class QWaylandTextInputMethod; #if QT_CONFIG(cursor) class QWaylandCursorTheme; class QWaylandCursorShape; +template class CursorSurface; #endif @@ -294,7 +295,7 @@ public: void updateCursor(); void cursorTimerCallback(); void cursorFrameCallback(); - CursorSurface *getOrCreateCursorSurface(); + CursorSurface *getOrCreateCursorSurface(); #endif QWaylandInputDevice *seat() const { return mParent; } @@ -336,7 +337,7 @@ public: QScopedPointer shape; QWaylandCursorTheme *theme = nullptr; int themeBufferScale = 0; - QScopedPointer surface; + QScopedPointer> surface; QTimer frameTimer; bool gotFrameCallback = false; bool gotTimerCallback = false; diff --git a/src/plugins/platforms/wayland/qwaylandtabletv2.cpp b/src/plugins/platforms/wayland/qwaylandtabletv2.cpp index 627f8746f88..6a8c84c44a8 100644 --- a/src/plugins/platforms/wayland/qwaylandtabletv2.cpp +++ b/src/plugins/platforms/wayland/qwaylandtabletv2.cpp @@ -5,7 +5,16 @@ #include "qwaylandinputdevice_p.h" #include "qwaylanddisplay_p.h" #include "qwaylandsurface_p.h" +#include "qwaylandscreen_p.h" +#include "qwaylandbuffer_p.h" +#include "qwaylandcursorsurface_p.h" +#include "qwaylandcursor_p.h" + +#include #include +#include + +#include QT_BEGIN_NAMESPACE @@ -16,6 +25,148 @@ using namespace Qt::StringLiterals; Q_LOGGING_CATEGORY(lcQpaInputDevices, "qt.qpa.input.devices") Q_DECLARE_LOGGING_CATEGORY(lcQpaWaylandInput) +#if QT_CONFIG(cursor) +int QWaylandTabletToolV2::idealCursorScale() const +{ + if (m_tabletSeat->seat()->mQDisplay->compositor()->version() < 3) { + return 1; + } + + if (auto *s = mCursor.surface.data()) { + if (s->outputScale() > 0) + return s->outputScale(); + } + + return m_tabletSeat->seat()->mCursor.fallbackOutputScale; +} + +void QWaylandTabletToolV2::updateCursorTheme() +{ + QString cursorThemeName; + QSize cursorSize; + + if (const QPlatformTheme *platformTheme = QGuiApplicationPrivate::platformTheme()) { + cursorThemeName = platformTheme->themeHint(QPlatformTheme::MouseCursorTheme).toString(); + cursorSize = platformTheme->themeHint(QPlatformTheme::MouseCursorSize).toSize(); + } + + if (cursorThemeName.isEmpty()) + cursorThemeName = QStringLiteral("default"); + if (cursorSize.isEmpty()) + cursorSize = QSize(24, 24); + + int scale = idealCursorScale(); + int pixelSize = cursorSize.width() * scale; + auto *display = m_tabletSeat->seat()->mQDisplay; + mCursor.theme = display->loadCursorTheme(cursorThemeName, pixelSize); + + if (!mCursor.theme) + return; // A warning has already been printed in loadCursorTheme + + if (auto *arrow = mCursor.theme->cursor(Qt::ArrowCursor)) { + int arrowPixelSize = qMax(arrow->images[0]->width, + arrow->images[0]->height); // Not all cursor themes are square + while (scale > 1 && arrowPixelSize / scale < cursorSize.width()) + --scale; + } else { + qCWarning(lcQpaWayland) << "Cursor theme does not support the arrow cursor"; + } + mCursor.themeBufferScale = scale; +} + +void QWaylandTabletToolV2::updateCursor() +{ + if (mEnterSerial == 0) + return; + + auto shape = m_tabletSeat->seat()->mCursor.shape; + + if (shape == Qt::BlankCursor) { + if (mCursor.surface) + mCursor.surface->reset(); + set_cursor(mEnterSerial, nullptr, 0, 0); + return; + } + + if (shape == Qt::BitmapCursor) { + auto buffer = m_tabletSeat->seat()->mCursor.bitmapBuffer; + if (!buffer) { + qCWarning(lcQpaWayland) << "No buffer for bitmap cursor, can't set cursor"; + return; + } + auto hotspot = m_tabletSeat->seat()->mCursor.hotspot; + int bufferScale = m_tabletSeat->seat()->mCursor.bitmapScale; + getOrCreateCursorSurface()->update(buffer->buffer(), hotspot, buffer->size(), bufferScale); + return; + } + + if (mCursor.shape) { + if (mCursor.surface) { + mCursor.surface->reset(); + } + mCursor.shape->setShape(mEnterSerial, shape); + return; + } + + if (!mCursor.theme || idealCursorScale() != mCursor.themeBufferScale) + updateCursorTheme(); + + if (!mCursor.theme) + return; + + // Set from shape using theme + uint time = m_tabletSeat->seat()->mCursor.animationTimer.elapsed(); + + if (struct ::wl_cursor *waylandCursor = mCursor.theme->cursor(shape)) { + uint duration = 0; + int frame = wl_cursor_frame_and_duration(waylandCursor, time, &duration); + ::wl_cursor_image *image = waylandCursor->images[frame]; + + struct wl_buffer *buffer = wl_cursor_image_get_buffer(image); + if (!buffer) { + qCWarning(lcQpaWayland) << "Could not find buffer for cursor" << shape; + return; + } + int bufferScale = mCursor.themeBufferScale; + QPoint hotspot = QPoint(image->hotspot_x, image->hotspot_y) / bufferScale; + QSize size = QSize(image->width, image->height) / bufferScale; + bool animated = duration > 0; + if (animated) { + mCursor.gotFrameCallback = false; + mCursor.gotTimerCallback = false; + mCursor.frameTimer.start(duration); + } + getOrCreateCursorSurface()->update(buffer, hotspot, size, bufferScale, animated); + return; + } + + qCWarning(lcQpaWayland) << "Unable to change to cursor" << shape; +} + +CursorSurface *QWaylandTabletToolV2::getOrCreateCursorSurface() +{ + if (!mCursor.surface) + mCursor.surface.reset( + new CursorSurface(this, m_tabletSeat->seat()->mQDisplay)); + return mCursor.surface.get(); +} + +void QWaylandTabletToolV2::cursorTimerCallback() +{ + mCursor.gotTimerCallback = true; + if (mCursor.gotFrameCallback) + updateCursor(); +} + +void QWaylandTabletToolV2::cursorFrameCallback() +{ + mCursor.gotFrameCallback = true; + if (mCursor.gotTimerCallback) + updateCursor(); +} + +#endif // QT_CONFIG(cursor) + QWaylandTabletManagerV2::QWaylandTabletManagerV2(QWaylandDisplay *display, uint id, uint version) : zwp_tablet_manager_v2(display->wl_registry(), id, qMin(version, uint(1))) { @@ -127,6 +278,12 @@ void QWaylandTabletV2::zwp_tablet_v2_done() QWindowSystemInterface::registerInputDevice(this); } +void QWaylandTabletSeatV2::updateCursor() +{ + for (auto tool : m_tools) + tool->updateCursor(); +} + void QWaylandTabletSeatV2::toolRemoved(QWaylandTabletToolV2 *tool) { m_tools.removeOne(tool); @@ -147,8 +304,20 @@ QWaylandTabletToolV2::QWaylandTabletToolV2(QWaylandTabletSeatV2 *tabletSeat, ::z , m_tabletSeat(tabletSeat) { // TODO get the number of buttons somehow? + +#if QT_CONFIG(cursor) + if (auto cursorShapeManager = m_tabletSeat->seat()->mQDisplay->cursorShapeManager()) { + mCursor.shape.reset( + new QWaylandCursorShape(cursorShapeManager->get_tablet_tool_v2(object()))); + } + + mCursor.frameTimer.setSingleShot(true); + mCursor.frameTimer.callOnTimeout(this, [&]() { cursorTimerCallback(); }); +#endif } +QWaylandTabletToolV2::~QWaylandTabletToolV2() = default; + void QWaylandTabletToolV2::zwp_tablet_tool_v2_type(uint32_t tool_type) { QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); @@ -250,6 +419,7 @@ void QWaylandTabletToolV2::zwp_tablet_tool_v2_proximity_in(uint32_t serial, zwp_ Q_UNUSED(tablet); m_tabletSeat->seat()->mSerial = serial; + mEnterSerial = serial; if (Q_UNLIKELY(!surface)) { qCDebug(lcQpaWayland) << "Ignoring zwp_tablet_tool_v2_proximity_v2 with no surface"; @@ -257,6 +427,11 @@ void QWaylandTabletToolV2::zwp_tablet_tool_v2_proximity_in(uint32_t serial, zwp_ } m_pending.enteredSurface = true; m_pending.proximitySurface = QWaylandSurface::fromWlSurface(surface); + +#if QT_CONFIG(cursor) + // Depends on mEnterSerial being updated + updateCursor(); +#endif } void QWaylandTabletToolV2::zwp_tablet_tool_v2_proximity_out() diff --git a/src/plugins/platforms/wayland/qwaylandtabletv2_p.h b/src/plugins/platforms/wayland/qwaylandtabletv2_p.h index b101826e4ac..94b687ee3ea 100644 --- a/src/plugins/platforms/wayland/qwaylandtabletv2_p.h +++ b/src/plugins/platforms/wayland/qwaylandtabletv2_p.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,13 @@ class QWaylandTabletV2; class QWaylandTabletToolV2; class QWaylandTabletPadV2; +#if QT_CONFIG(cursor) +class QWaylandCursorTheme; +class QWaylandCursorShape; +template +class CursorSurface; +#endif + class Q_WAYLANDCLIENT_EXPORT QWaylandTabletManagerV2 : public QtWayland::zwp_tablet_manager_v2 { public: @@ -54,6 +62,7 @@ public: QWaylandInputDevice *seat() const { return m_seat; } + void updateCursor(); void toolRemoved(QWaylandTabletToolV2 *tool); protected: @@ -89,6 +98,9 @@ class Q_WAYLANDCLIENT_EXPORT QWaylandTabletToolV2 : public QPointingDevice, publ Q_OBJECT public: QWaylandTabletToolV2(QWaylandTabletSeatV2 *tabletSeat, ::zwp_tablet_tool_v2 *tool); + ~QWaylandTabletToolV2() override; + + void updateCursor(); protected: void zwp_tablet_tool_v2_type(uint32_t tool_type) override; @@ -112,8 +124,37 @@ protected: void zwp_tablet_tool_v2_frame(uint32_t time) override; private: +#if QT_CONFIG(cursor) + int idealCursorScale() const; + void updateCursorTheme(); + void cursorTimerCallback(); + void cursorFrameCallback(); + CursorSurface *getOrCreateCursorSurface(); +#endif + QWaylandTabletSeatV2 *m_tabletSeat; + // Static state (sent before done event) + QPointingDevice::PointerType m_pointerType = QPointingDevice::PointerType::Unknown; + QInputDevice::DeviceType m_tabletDevice = QInputDevice::DeviceType::Unknown; + zwp_tablet_tool_v2::type m_toolType = type_pen; + bool m_hasRotation = false; + quint64 m_uid = 0; + + uint32_t mEnterSerial = 0; +#if QT_CONFIG(cursor) + struct + { + QScopedPointer shape; + QWaylandCursorTheme *theme = nullptr; + int themeBufferScale = 0; + QScopedPointer> surface; + QTimer frameTimer; + bool gotFrameCallback = false; + bool gotTimerCallback = false; + } mCursor; +#endif + // Accumulated state (applied on frame event) struct State { bool down = false; @@ -130,6 +171,9 @@ private: //auto operator<=>(const Point&) const = default; // TODO: use this when upgrading to C++20 bool operator==(const State &o) const; } m_pending, m_applied; + + template + friend class CursorSurface; }; class Q_WAYLANDCLIENT_EXPORT QWaylandTabletPadV2 : public QPointingDevice, public QtWayland::zwp_tablet_pad_v2