Vlad Zahorodnii 1 year ago
parent 9f18d5fdfc
commit 6cd56d5192

@ -203,7 +203,7 @@ set_package_properties(Wayland PROPERTIES
PURPOSE "Required for building KWin with Wayland support"
)
find_package(WaylandProtocols 1.31)
find_package(WaylandProtocols 1.32)
set_package_properties(WaylandProtocols PROPERTIES
TYPE REQUIRED
PURPOSE "Collection of Wayland protocols that add functionality not available in the Wayland core protocol"

@ -15,6 +15,7 @@ qt6_generate_wayland_protocol_client_sources(KWinIntegrationTestFramework
${WaylandProtocols_DATADIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml
${WaylandProtocols_DATADIR}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml
${WaylandProtocols_DATADIR}/staging/fractional-scale/fractional-scale-v1.xml
${WaylandProtocols_DATADIR}/staging/cursor-shape/cursor-shape-v1.xml
${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-output-device-v2.xml
${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-output-management-v2.xml
${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-screen-edge-v1.xml

@ -20,6 +20,7 @@
#include <KWayland/Client/surface.h>
#include <optional>
#include "qwayland-cursor-shape-v1.h"
#include "qwayland-fractional-scale-v1.h"
#include "qwayland-idle-inhibit-unstable-v1.h"
#include "qwayland-input-method-unstable-v1.h"
@ -42,6 +43,7 @@ class Compositor;
class Output;
class PlasmaShell;
class PlasmaWindowManagement;
class Pointer;
class PointerConstraints;
class Seat;
class ServerSideDecorationManager;
@ -504,6 +506,21 @@ public:
~AutoHideScreenEdgeV1() override;
};
class CursorShapeManagerV1 : public QObject, public QtWayland::wp_cursor_shape_manager_v1
{
Q_OBJECT
public:
~CursorShapeManagerV1() override;
};
class CursorShapeDeviceV1 : public QObject, public QtWayland::wp_cursor_shape_device_v1
{
Q_OBJECT
public:
CursorShapeDeviceV1(CursorShapeManagerV1 *manager, KWayland::Client::Pointer *pointer);
~CursorShapeDeviceV1() override;
};
enum class AdditionalWaylandInterface {
Seat = 1 << 0,
Decoration = 1 << 1,
@ -523,6 +540,7 @@ enum class AdditionalWaylandInterface {
FractionalScaleManagerV1 = 1 << 15,
ScreencastingV1 = 1 << 16,
ScreenEdgeV1 = 1 << 17,
CursorShapeV1 = 1 << 18,
};
Q_DECLARE_FLAGS(AdditionalWaylandInterfaces, AdditionalWaylandInterface)
@ -669,6 +687,7 @@ XdgPopup *createXdgPopupSurface(KWayland::Client::Surface *surface, XdgSurface *
XdgToplevelDecorationV1 *createXdgToplevelDecorationV1(XdgToplevel *toplevel, QObject *parent = nullptr);
IdleInhibitorV1 *createIdleInhibitorV1(KWayland::Client::Surface *surface);
AutoHideScreenEdgeV1 *createAutoHideScreenEdgeV1(KWayland::Client::Surface *surface, uint32_t border);
CursorShapeDeviceV1 *createCursorShapeDeviceV1(KWayland::Client::Pointer *pointer);
/**
* Creates a shared memory buffer of @p size in @p color and attaches it to the @p surface.

@ -10,6 +10,7 @@
#include "core/output.h"
#include "cursor.h"
#include "cursorsource.h"
#include "libkwineffects/kwineffects.h"
#include "options.h"
#include "pointer_input.h"
@ -107,6 +108,7 @@ private Q_SLOTS:
void testMouseActionActiveWindow_data();
void testMouseActionActiveWindow();
void testCursorImage();
void testCursorShapeV1();
void testEffectOverrideCursorImage();
void testPopup();
void testDecoCancelsPopup();
@ -159,7 +161,7 @@ void PointerInputTest::initTestCase()
void PointerInputTest::init()
{
QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::Decoration | Test::AdditionalWaylandInterface::XdgDecorationV1));
QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::Decoration | Test::AdditionalWaylandInterface::XdgDecorationV1 | Test::AdditionalWaylandInterface::CursorShapeV1));
QVERIFY(Test::waitForWaylandPointer());
m_compositor = Test::waylandCompositor();
m_seat = Test::waylandSeat();
@ -1151,6 +1153,51 @@ void PointerInputTest::testCursorImage()
QCOMPARE(kwinApp()->cursorImage().image(), fallbackCursor);
}
static QByteArray currentCursorShape()
{
if (auto source = qobject_cast<ShapeCursorSource *>(Cursors::self()->currentCursor()->source())) {
return source->shape();
}
return QByteArray();
}
void PointerInputTest::testCursorShapeV1()
{
// this test verifies the integration of the cursor-shape-v1 protocol
// get the pointer
std::unique_ptr<KWayland::Client::Pointer> pointer(m_seat->createPointer());
std::unique_ptr<Test::CursorShapeDeviceV1> cursorShapeDevice(Test::createCursorShapeDeviceV1(pointer.get()));
// move cursor somewhere the new window won't open
input()->pointer()->warp(QPointF(800, 800));
QCOMPARE(currentCursorShape(), QByteArray("left_ptr"));
// create a window
std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::cyan);
QVERIFY(window);
// move the pointer to the center of the window
QSignalSpy enteredSpy(pointer.get(), &KWayland::Client::Pointer::entered);
input()->pointer()->warp(window->frameGeometry().center());
QVERIFY(enteredSpy.wait());
// set a custom cursor shape
QSignalSpy cursorChanged(Cursors::self(), &Cursors::currentCursorChanged);
cursorShapeDevice->set_shape(enteredSpy.last().at(0).value<quint32>(), Test::CursorShapeDeviceV1::shape_text);
QVERIFY(cursorChanged.wait());
QCOMPARE(currentCursorShape(), QByteArray("text"));
// cursor shape won't be changed if the window has no pointer focus
input()->pointer()->warp(QPointF(800, 800));
QCOMPARE(currentCursorShape(), QByteArray("left_ptr"));
cursorShapeDevice->set_shape(enteredSpy.last().at(0).value<quint32>(), Test::CursorShapeDeviceV1::shape_grab);
QVERIFY(Test::waylandSync());
QCOMPARE(currentCursorShape(), QByteArray("left_ptr"));
}
class HelperEffect : public Effect
{
Q_OBJECT
@ -1168,16 +1215,14 @@ void PointerInputTest::testEffectOverrideCursorImage()
// this test verifies the effect cursor override handling
// we need a pointer to get the enter event and set a cursor
auto pointer = m_seat->createPointer(m_seat);
QVERIFY(pointer);
QVERIFY(pointer->isValid());
QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
QSignalSpy leftSpy(pointer, &KWayland::Client::Pointer::left);
std::unique_ptr<KWayland::Client::Pointer> pointer(m_seat->createPointer());
std::unique_ptr<Test::CursorShapeDeviceV1> cursorShapeDevice(Test::createCursorShapeDeviceV1(pointer.get()));
QSignalSpy enteredSpy(pointer.get(), &KWayland::Client::Pointer::entered);
QSignalSpy leftSpy(pointer.get(), &KWayland::Client::Pointer::left);
QSignalSpy cursorChanged(Cursors::self(), &Cursors::currentCursorChanged);
// move cursor somewhere the new window won't open
input()->pointer()->warp(QPointF(800, 800));
// here we should have the fallback cursor
const QImage fallback = kwinApp()->cursorImage().image();
QVERIFY(!fallback.isNull());
// now let's create a window
QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
@ -1194,34 +1239,26 @@ void PointerInputTest::testEffectOverrideCursorImage()
QVERIFY(!exclusiveContains(window->frameGeometry(), QPoint(800, 800)));
input()->pointer()->warp(window->frameGeometry().center());
QVERIFY(enteredSpy.wait());
// cursor image should still be fallback
QCOMPARE(kwinApp()->cursorImage().image(), fallback);
cursorShapeDevice->set_shape(enteredSpy.last().at(0).value<quint32>(), Test::CursorShapeDeviceV1::shape_wait);
QVERIFY(cursorChanged.wait());
QCOMPARE(currentCursorShape(), QByteArray("wait"));
// now create an effect and set an override cursor
std::unique_ptr<HelperEffect> effect(new HelperEffect);
effects->startMouseInterception(effect.get(), Qt::SizeAllCursor);
const QImage sizeAll = kwinApp()->cursorImage().image();
QVERIFY(!sizeAll.isNull());
QVERIFY(sizeAll != fallback);
QVERIFY(leftSpy.wait());
QCOMPARE(currentCursorShape(), QByteArray("size_all"));
// let's change to arrow cursor, this should be our fallback
effects->defineCursor(Qt::ArrowCursor);
QCOMPARE(kwinApp()->cursorImage().image(), fallback);
QCOMPARE(currentCursorShape(), QByteArray("left_ptr"));
// back to size all
effects->defineCursor(Qt::SizeAllCursor);
QCOMPARE(kwinApp()->cursorImage().image(), sizeAll);
QCOMPARE(currentCursorShape(), QByteArray("size_all"));
// move cursor outside the window area
input()->pointer()->warp(QPointF(800, 800));
// and end the override, which should switch to fallback
effects->stopMouseInterception(effect.get());
QCOMPARE(kwinApp()->cursorImage().image(), fallback);
// start mouse interception again
effects->startMouseInterception(effect.get(), Qt::SizeAllCursor);
QCOMPARE(kwinApp()->cursorImage().image(), sizeAll);
QCOMPARE(currentCursorShape(), QByteArray("size_all"));
// move cursor to area of window
input()->pointer()->warp(window->frameGeometry().center());
@ -1232,7 +1269,9 @@ void PointerInputTest::testEffectOverrideCursorImage()
// after ending the interception we should get an enter event
effects->stopMouseInterception(effect.get());
QVERIFY(enteredSpy.wait());
QVERIFY(kwinApp()->cursorImage().image().isNull());
cursorShapeDevice->set_shape(enteredSpy.last().at(0).value<quint32>(), Test::CursorShapeDeviceV1::shape_crosshair);
QVERIFY(cursorChanged.wait());
QCOMPARE(currentCursorShape(), QByteArray("cross"));
}
void PointerInputTest::testPopup()

@ -28,6 +28,7 @@
#include <KWayland/Client/output.h>
#include <KWayland/Client/plasmashell.h>
#include <KWayland/Client/plasmawindowmanagement.h>
#include <KWayland/Client/pointer.h>
#include <KWayland/Client/pointerconstraints.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/seat.h>
@ -244,6 +245,21 @@ AutoHideScreenEdgeV1::~AutoHideScreenEdgeV1()
destroy();
}
CursorShapeManagerV1::~CursorShapeManagerV1()
{
destroy();
}
CursorShapeDeviceV1::CursorShapeDeviceV1(CursorShapeManagerV1 *manager, KWayland::Client::Pointer *pointer)
: QtWayland::wp_cursor_shape_device_v1(manager->get_pointer(*pointer))
{
}
CursorShapeDeviceV1::~CursorShapeDeviceV1()
{
destroy();
}
static struct
{
KWayland::Client::ConnectionThread *connection = nullptr;
@ -275,6 +291,7 @@ static struct
FractionalScaleManagerV1 *fractionalScaleManagerV1 = nullptr;
ScreencastingV1 *screencastingV1 = nullptr;
ScreenEdgeManagerV1 *screenEdgeManagerV1 = nullptr;
CursorShapeManagerV1 *cursorShapeManagerV1 = nullptr;
} s_waylandConnection;
MockInputMethod *inputMethod()
@ -460,6 +477,12 @@ bool setupWaylandConnection(AdditionalWaylandInterfaces flags)
return;
}
}
if (flags &AdditionalWaylandInterface::CursorShapeV1) {
if (interface == wp_cursor_shape_manager_v1_interface.name) {
s_waylandConnection.cursorShapeManagerV1 = new CursorShapeManagerV1();
s_waylandConnection.cursorShapeManagerV1->init(*registry, name, version);
}
}
});
QSignalSpy allAnnounced(registry, &KWayland::Client::Registry::interfacesAnnounced);
@ -590,6 +613,8 @@ void destroyWaylandConnection()
s_waylandConnection.screencastingV1 = nullptr;
delete s_waylandConnection.screenEdgeManagerV1;
s_waylandConnection.screenEdgeManagerV1 = nullptr;
delete s_waylandConnection.cursorShapeManagerV1;
s_waylandConnection.cursorShapeManagerV1 = nullptr;
delete s_waylandConnection.queue; // Must be destroyed last
s_waylandConnection.queue = nullptr;
@ -1009,6 +1034,17 @@ AutoHideScreenEdgeV1 *createAutoHideScreenEdgeV1(KWayland::Client::Surface *surf
return new AutoHideScreenEdgeV1(manager, surface, border);
}
CursorShapeDeviceV1 *createCursorShapeDeviceV1(KWayland::Client::Pointer *pointer)
{
CursorShapeManagerV1 *manager = s_waylandConnection.cursorShapeManagerV1;
if (!manager) {
qWarning() << "Could not create a wp_cursor_shape_device_v1 because wp_cursor_shape_manager_v1 global is not bound";
return nullptr;
}
return new CursorShapeDeviceV1(manager, pointer);
}
bool waitForWindowClosed(Window *window)
{
QSignalSpy closedSpy(window, &Window::closed);

@ -1956,27 +1956,35 @@ public:
explicit SurfaceCursor(KWaylandServer::TabletToolV2Interface *tool)
: Cursor(tool)
{
connect(tool, &KWaylandServer::TabletToolV2Interface::cursorChanged, this, [this](KWaylandServer::TabletCursorV2 *tcursor) {
if (!tcursor || tcursor->enteredSerial() == 0) {
if (!m_defaultSource) {
m_defaultSource = std::make_unique<ShapeCursorSource>();
connect(tool, &KWaylandServer::TabletToolV2Interface::cursorChanged, this, [this](const KWaylandServer::TabletCursorSourceV2 &cursor) {
if (auto surfaceCursor = std::get_if<KWaylandServer::TabletSurfaceCursorV2 *>(&cursor)) {
// If the cursor is unset, fallback to the cross cursor.
if ((*surfaceCursor) && (*surfaceCursor)->enteredSerial()) {
if (!m_surfaceSource) {
m_surfaceSource = std::make_unique<SurfaceCursorSource>();
}
m_surfaceSource->update((*surfaceCursor)->surface(), (*surfaceCursor)->hotspot());
setSource(m_surfaceSource.get());
return;
}
static WaylandCursorImage defaultCursor;
m_defaultSource->setTheme(defaultCursor.theme());
m_defaultSource->setShape(Qt::CrossCursor);
setSource(m_defaultSource.get());
}
QByteArray shape;
if (auto shapeCursor = std::get_if<QByteArray>(&cursor)) {
shape = *shapeCursor;
} else {
if (!m_surfaceSource) {
m_surfaceSource = std::make_unique<SurfaceCursorSource>();
}
m_surfaceSource->update(tcursor->surface(), tcursor->hotspot());
setSource(m_surfaceSource.get());
shape = QByteArrayLiteral("cross");
}
static WaylandCursorImage defaultCursor;
m_shapeSource->setTheme(defaultCursor.theme());
m_shapeSource->setShape(shape);
setSource(m_shapeSource.get());
});
}
private:
std::unique_ptr<ShapeCursorSource> m_defaultSource;
std::unique_ptr<ShapeCursorSource> m_shapeSource;
std::unique_ptr<SurfaceCursorSource> m_surfaceSource;
};

@ -862,7 +862,8 @@ CursorImage::CursorImage(PointerInputRedirection *parent)
m_moveResizeCursor = std::make_unique<ShapeCursorSource>();
m_windowSelectionCursor = std::make_unique<ShapeCursorSource>();
m_decoration.cursor = std::make_unique<ShapeCursorSource>();
m_serverCursor.cursor = std::make_unique<SurfaceCursorSource>();
m_serverCursor.surface = std::make_unique<SurfaceCursorSource>();
m_serverCursor.shape = std::make_unique<ShapeCursorSource>();
#if KWIN_BUILD_SCREENLOCKER
if (waylandServer()->hasScreenLockerIntegration()) {
@ -886,6 +887,7 @@ CursorImage::CursorImage(PointerInputRedirection *parent)
m_moveResizeCursor->setTheme(m_waylandImage.theme());
m_windowSelectionCursor->setTheme(m_waylandImage.theme());
m_decoration.cursor->setTheme(m_waylandImage.theme());
m_serverCursor.shape->setTheme(m_waylandImage.theme());
connect(&m_waylandImage, &WaylandCursorImage::themeChanged, this, [this] {
m_effectsCursor->setTheme(m_waylandImage.theme());
@ -893,6 +895,7 @@ CursorImage::CursorImage(PointerInputRedirection *parent)
m_moveResizeCursor->setTheme(m_waylandImage.theme());
m_windowSelectionCursor->setTheme(m_waylandImage.theme());
m_decoration.cursor->setTheme(m_waylandImage.theme());
m_serverCursor.shape->setTheme(m_waylandImage.theme());
});
KWaylandServer::PointerInterface *pointer = waylandServer()->seat()->pointer();
@ -907,8 +910,8 @@ CursorImage::~CursorImage() = default;
void CursorImage::updateCursorOutputs(const QPointF &pos)
{
if (m_currentSource == m_serverCursor.cursor.get()) {
auto cursorSurface = m_serverCursor.cursor->surface();
if (m_currentSource == m_serverCursor.surface.get()) {
auto cursorSurface = m_serverCursor.surface->surface();
if (cursorSurface) {
const QRectF cursorGeometry(pos - m_currentSource->hotspot(), m_currentSource->size());
cursorSurface->setOutputs(waylandServer()->display()->outputsIntersecting(cursorGeometry.toAlignedRect()));
@ -918,8 +921,8 @@ void CursorImage::updateCursorOutputs(const QPointF &pos)
void CursorImage::markAsRendered(std::chrono::milliseconds timestamp)
{
if (m_currentSource == m_serverCursor.cursor.get()) {
auto cursorSurface = m_serverCursor.cursor->surface();
if (m_currentSource == m_serverCursor.surface.get()) {
auto cursorSurface = m_serverCursor.surface->surface();
if (cursorSurface) {
cursorSurface->frameRendered(timestamp.count());
}
@ -970,9 +973,15 @@ void CursorImage::updateMoveResize()
reevaluteSource();
}
void CursorImage::updateServerCursor(KWaylandServer::Cursor *cursor)
void CursorImage::updateServerCursor(const KWaylandServer::PointerCursor &cursor)
{
m_serverCursor.cursor->update(cursor->surface(), cursor->hotspot());
if (auto surfaceCursor = std::get_if<KWaylandServer::Cursor *>(&cursor)) {
m_serverCursor.surface->update((*surfaceCursor)->surface(), (*surfaceCursor)->hotspot());
m_serverCursor.cursor = m_serverCursor.surface.get();
} else if (auto shapeCursor = std::get_if<QByteArray>(&cursor)) {
m_serverCursor.shape->setShape(*shapeCursor);
m_serverCursor.cursor = m_serverCursor.shape.get();
}
reevaluteSource();
}
@ -1040,7 +1049,7 @@ void WaylandCursorImage::updateCursorTheme()
void CursorImage::reevaluteSource()
{
if (waylandServer()->isScreenLocked()) {
setSource(m_serverCursor.cursor.get());
setSource(m_serverCursor.cursor);
return;
}
if (input()->isSelectingWindow()) {
@ -1061,7 +1070,7 @@ void CursorImage::reevaluteSource()
}
const KWaylandServer::PointerInterface *pointer = waylandServer()->seat()->pointer();
if (pointer && pointer->focusedSurface()) {
setSource(m_serverCursor.cursor.get());
setSource(m_serverCursor.cursor);
return;
}
setSource(m_fallbackCursor.get());

@ -222,7 +222,7 @@ Q_SIGNALS:
private:
void reevaluteSource();
void updateServerCursor(KWaylandServer::Cursor *cursor);
void updateServerCursor(const std::variant<KWaylandServer::Cursor *, QByteArray> &cursor);
void updateDecoration();
void updateDecorationCursor();
void updateMoveResize();
@ -246,7 +246,9 @@ private:
struct
{
QMetaObject::Connection connection;
std::unique_ptr<SurfaceCursorSource> cursor;
std::unique_ptr<SurfaceCursorSource> surface;
std::unique_ptr<ShapeCursorSource> shape;
CursorSource *cursor = nullptr;
} m_serverCursor;
};

@ -212,6 +212,10 @@ ecm_add_qtwayland_server_protocol_kde(WaylandProtocols_xml
PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-screen-edge-v1.xml
BASENAME kde-screen-edge-v1
)
ecm_add_qtwayland_server_protocol_kde(WaylandProtocols_xml
PROTOCOL ${WaylandProtocols_DATADIR}/staging/cursor-shape/cursor-shape-v1.xml
BASENAME cursor-shape-v1
)
target_sources(kwin PRIVATE
abstract_data_source.cpp
@ -222,6 +226,7 @@ target_sources(kwin PRIVATE
compositor_interface.cpp
contenttype_v1_interface.cpp
contrast_interface.cpp
cursorshape_v1_interface.cpp
datacontroldevice_v1_interface.cpp
datacontroldevicemanager_v1_interface.cpp
datacontroloffer_v1_interface.cpp
@ -305,6 +310,7 @@ install(FILES
compositor_interface.h
contenttype_v1_interface.h
contrast_interface.h
cursorshape_v1_interface.h
datacontroldevice_v1_interface.h
datacontroldevicemanager_v1_interface.h
datacontroloffer_v1_interface.h

@ -1328,7 +1328,7 @@ void TestWaylandSeat::testCursor()
p->setCursor(nullptr);
QVERIFY(cursorChangedSpy.wait());
QCOMPARE(cursorChangedSpy.count(), 1);
auto cursor = cursorChangedSpy.last().first().value<KWaylandServer::Cursor *>();
auto cursor = std::get<KWaylandServer::Cursor *>(cursorChangedSpy.last().first().value<KWaylandServer::PointerCursor>());
QVERIFY(cursor);
QVERIFY(!cursor->surface());
QCOMPARE(cursor->hotspot(), QPoint());

@ -0,0 +1,209 @@
/*
SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "wayland/cursorshape_v1_interface.h"
#include "wayland/display.h"
#include "wayland/pointer_interface.h"
#include "wayland/surface_interface.h"
#include "wayland/tablet_v2_interface.h"
#include <QPointer>
#include "qwayland-server-cursor-shape-v1.h"
namespace KWaylandServer
{
static constexpr int s_version = 1;
class CursorShapeManagerV1InterfacePrivate : public QtWaylandServer::wp_cursor_shape_manager_v1
{
public:
CursorShapeManagerV1InterfacePrivate(Display *display);
protected:
void wp_cursor_shape_manager_v1_destroy(Resource *resource) override;
void wp_cursor_shape_manager_v1_get_pointer(Resource *resource, uint32_t cursor_shape_device, struct ::wl_resource *pointer) override;
void wp_cursor_shape_manager_v1_get_tablet_tool_v2(Resource *resource, uint32_t cursor_shape_device, struct ::wl_resource *tablet_tool) override;
};
class CursorShapeDeviceV1Interface : public QtWaylandServer::wp_cursor_shape_device_v1
{
public:
CursorShapeDeviceV1Interface(PointerInterface *pointer, wl_resource *resource);
CursorShapeDeviceV1Interface(TabletToolV2Interface *tabletTool, wl_resource *resource);
QPointer<PointerInterface> pointer;
QPointer<TabletToolV2Interface> tabletTool;
protected:
void wp_cursor_shape_device_v1_destroy_resource(Resource *resource) override;
void wp_cursor_shape_device_v1_destroy(Resource *resource) override;
void wp_cursor_shape_device_v1_set_shape(Resource *resource, uint32_t serial, uint32_t shape) override;
};
CursorShapeManagerV1InterfacePrivate::CursorShapeManagerV1InterfacePrivate(Display *display)
: QtWaylandServer::wp_cursor_shape_manager_v1(*display, s_version)
{
}
void CursorShapeManagerV1InterfacePrivate::wp_cursor_shape_manager_v1_destroy(Resource *resource)
{
wl_resource_destroy(resource->handle);
}
void CursorShapeManagerV1InterfacePrivate::wp_cursor_shape_manager_v1_get_pointer(Resource *resource, uint32_t cursor_shape_device, struct ::wl_resource *pointer)
{
wl_resource *device = wl_resource_create(resource->client(), &wp_cursor_shape_device_v1_interface, resource->version(), cursor_shape_device);
if (!device) {
wl_resource_post_no_memory(resource->handle);
return;
}
new CursorShapeDeviceV1Interface(PointerInterface::get(pointer), device);
}
void CursorShapeManagerV1InterfacePrivate::wp_cursor_shape_manager_v1_get_tablet_tool_v2(Resource *resource, uint32_t cursor_shape_device, struct ::wl_resource *tablet_tool)
{
wl_resource *device = wl_resource_create(resource->client(), &wp_cursor_shape_device_v1_interface, resource->version(), cursor_shape_device);
if (!device) {
wl_resource_post_no_memory(resource->handle);
return;
}
new CursorShapeDeviceV1Interface(TabletToolV2Interface::get(tablet_tool), device);
}
CursorShapeManagerV1Interface::CursorShapeManagerV1Interface(Display *display, QObject *parent)
: QObject(parent)
, d(std::make_unique<CursorShapeManagerV1InterfacePrivate>(display))
{
}
CursorShapeManagerV1Interface::~CursorShapeManagerV1Interface()
{
}
CursorShapeDeviceV1Interface::CursorShapeDeviceV1Interface(PointerInterface *pointer, wl_resource *resource)
: QtWaylandServer::wp_cursor_shape_device_v1(resource)
, pointer(pointer)
{
}
CursorShapeDeviceV1Interface::CursorShapeDeviceV1Interface(TabletToolV2Interface *tabletTool, wl_resource *resource)
: QtWaylandServer::wp_cursor_shape_device_v1(resource)
, tabletTool(tabletTool)
{
}
void CursorShapeDeviceV1Interface::wp_cursor_shape_device_v1_destroy_resource(Resource *resource)
{
delete this;
}
void CursorShapeDeviceV1Interface::wp_cursor_shape_device_v1_destroy(Resource *resource)
{
wl_resource_destroy(resource->handle);
}
static QByteArray shapeName(uint32_t shape)
{
switch (shape) {
case QtWaylandServer::wp_cursor_shape_device_v1::shape_default:
return QByteArrayLiteral("default");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_context_menu:
return QByteArrayLiteral("context-menu");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_help:
return QByteArrayLiteral("whats_this");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_pointer:
return QByteArrayLiteral("pointing_hand");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_progress:
return QByteArrayLiteral("left_ptr_watch");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_wait:
return QByteArrayLiteral("wait");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_cell:
return QByteArrayLiteral("cell");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_crosshair:
return QByteArrayLiteral("cross");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_text:
return QByteArrayLiteral("text");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_vertical_text:
return QByteArrayLiteral("vertical-text");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_alias:
return QByteArrayLiteral("dnd-link");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_copy:
return QByteArrayLiteral("dnd-copy");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_move:
return QByteArrayLiteral("dnd-move");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_no_drop:
return QByteArrayLiteral("no-drop");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_not_allowed:
return QByteArrayLiteral("not-allowed");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_grab:
return QByteArrayLiteral("openhand");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_grabbing:
return QByteArrayLiteral("closedhand");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_e_resize:
return QByteArrayLiteral("e-resize");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_n_resize:
return QByteArrayLiteral("n-resize");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_ne_resize:
return QByteArrayLiteral("ne-resize");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_nw_resize:
return QByteArrayLiteral("nw-resize");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_s_resize:
return QByteArrayLiteral("s-resize");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_se_resize:
return QByteArrayLiteral("se-resize");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_sw_resize:
return QByteArrayLiteral("sw-resize");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_w_resize:
return QByteArrayLiteral("w-resize");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_ew_resize:
return QByteArrayLiteral("size_hor");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_ns_resize:
return QByteArrayLiteral("size_ver");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_nesw_resize:
return QByteArrayLiteral("size_bdiag");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_nwse_resize:
return QByteArrayLiteral("size_fdiag");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_col_resize:
return QByteArrayLiteral("col-resize");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_row_resize:
return QByteArrayLiteral("row-resize");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_all_scroll:
return QByteArrayLiteral("all-scroll");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_zoom_in:
return QByteArrayLiteral("zoom-in");
case QtWaylandServer::wp_cursor_shape_device_v1::shape_zoom_out:
return QByteArrayLiteral("zoom-out");
default:
return QByteArrayLiteral("left_ptr");
}
}
void CursorShapeDeviceV1Interface::wp_cursor_shape_device_v1_set_shape(Resource *resource, uint32_t serial, uint32_t shape)
{
if (shape < shape_default || shape > shape_zoom_out) {
wl_resource_post_error(resource->handle, error_invalid_shape, "unknown cursor shape");
return;
}
if (pointer) {
if (!pointer->focusedSurface() || pointer->focusedSurface()->client()->client() != resource->client()) {
return;
}
if (pointer->focusedSerial() == serial) {
Q_EMIT pointer->cursorChanged(shapeName(shape));
}
} else if (tabletTool) {
if (!tabletTool->currentSurface() || tabletTool->currentSurface()->client()->client() != resource->client()) {
return;
}
if (tabletTool->proximitySerial() == serial) {
Q_EMIT tabletTool->cursorChanged(shapeName(shape));
}
}
}
} // namespace KWaylandServer

@ -0,0 +1,31 @@
/*
SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#pragma once
#include "kwin_export.h"
#include <QObject>
namespace KWaylandServer
{
class CursorShapeManagerV1InterfacePrivate;
class Display;
class KWIN_EXPORT CursorShapeManagerV1Interface : public QObject
{
Q_OBJECT
public:
explicit CursorShapeManagerV1Interface(Display *display, QObject *parent = nullptr);
~CursorShapeManagerV1Interface() override;
private:
std::unique_ptr<CursorShapeManagerV1InterfacePrivate> d;
};
} // namespace KWaylandServer

@ -147,6 +147,11 @@ SurfaceInterface *PointerInterface::focusedSurface() const
return d->focusedSurface;
}
quint32 PointerInterface::focusedSerial() const
{
return d->focusedSerial;
}
void PointerInterface::sendEnter(SurfaceInterface *surface, const QPointF &position, quint32 serial)
{
if (d->focusedSurface == surface) {

@ -25,6 +25,8 @@ class SurfaceInterface;
enum class PointerAxisSource;
enum class PointerButtonState : quint32;
using PointerCursor = std::variant<Cursor *, QByteArray>;
/**
* The PointerInterface class represents one or more input devices such as mice, which control
* the pointer location. It corresponds to the Wayland interface @c wl_pointer.
@ -42,6 +44,7 @@ public:
* the effective focused surface.
*/
SurfaceInterface *focusedSurface() const;
quint32 focusedSerial() const;
/**
* Returns the seat to which this pointer belongs to.
@ -65,7 +68,7 @@ Q_SIGNALS:
* This signal is emitted whenever the cursor surface changes. As long as there is no
* any focused surface, the cursor cannot be changed.
*/
void cursorChanged(KWaylandServer::Cursor *cursor);
void cursorChanged(const PointerCursor &cursor);
/**
* This signal is emitted whenever the focused pointer surface changes.
*/

@ -8,6 +8,7 @@
#include "display.h"
#include "seat_interface.h"
#include "surface_interface.h"
#include "utils.h"
#include "qwayland-server-tablet-unstable-v2.h"
#include <QHash>
@ -68,10 +69,10 @@ TabletPadV2Interface *TabletV2Interface::pad() const
return d->m_pad;
}
class TabletCursorV2Private
class TabletSurfaceCursorV2Private
{
public:
TabletCursorV2Private(TabletCursorV2 *q)
TabletSurfaceCursorV2Private(TabletSurfaceCursorV2 *q)
: q(q)
{
}
@ -88,32 +89,32 @@ public:
}
}
TabletCursorV2 *const q;
TabletSurfaceCursorV2 *const q;
quint32 m_serial = 0;
QPointer<SurfaceInterface> m_surface;
QPoint m_hotspot;
};
TabletCursorV2::TabletCursorV2()
TabletSurfaceCursorV2::TabletSurfaceCursorV2()
: QObject()
, d(new TabletCursorV2Private(this))
, d(new TabletSurfaceCursorV2Private(this))
{
}
TabletCursorV2::~TabletCursorV2() = default;
TabletSurfaceCursorV2::~TabletSurfaceCursorV2() = default;
QPoint TabletCursorV2::hotspot() const
QPoint TabletSurfaceCursorV2::hotspot() const
{
return d->m_hotspot;
}
quint32 TabletCursorV2::enteredSerial() const
quint32 TabletSurfaceCursorV2::enteredSerial() const
{
return d->m_serial;
}
SurfaceInterface *TabletCursorV2::surface() const
SurfaceInterface *TabletSurfaceCursorV2::surface() const
{
return d->m_surface;
}
@ -162,17 +163,18 @@ public:
void zwp_tablet_tool_v2_bind_resource(QtWaylandServer::zwp_tablet_tool_v2::Resource *resource) override
{
TabletCursorV2 *&c = m_cursors[resource->handle];
TabletSurfaceCursorV2 *&c = m_cursors[resource->handle];
if (!c)
c = new TabletCursorV2;
c = new TabletSurfaceCursorV2;
}
void zwp_tablet_tool_v2_set_cursor(Resource *resource, uint32_t serial, struct ::wl_resource *_surface, int32_t hotspot_x, int32_t hotspot_y) override
{
TabletCursorV2 *c = m_cursors[resource->handle];
TabletSurfaceCursorV2 *c = m_cursors[resource->handle];
c->d->update(serial, SurfaceInterface::get(_surface), {hotspot_x, hotspot_y});
if (resource->handle == targetResource())
q->cursorChanged(c);
if (resource->handle == targetResource()) {
Q_EMIT q->cursorChanged(c);
}
}
void zwp_tablet_tool_v2_destroy_resource(Resource *resource) override
@ -189,6 +191,7 @@ public:
}
Display *const m_display;
quint32 m_proximitySerial = 0;
bool m_cleanup = false;
bool m_removed = false;
QPointer<SurfaceInterface> m_surface;
@ -197,7 +200,7 @@ public:
const uint32_t m_hardwareSerialHigh, m_hardwareSerialLow;
const uint32_t m_hardwareIdHigh, m_hardwareIdLow;
const QVector<TabletToolV2Interface::Capability> m_capabilities;
QHash<wl_resource *, TabletCursorV2 *> m_cursors;
QHash<wl_resource *, TabletSurfaceCursorV2 *> m_cursors;
TabletToolV2Interface *const q;
};
@ -220,11 +223,24 @@ TabletToolV2Interface::~TabletToolV2Interface()
}
}
TabletToolV2Interface *TabletToolV2Interface::get(wl_resource *resource)
{
if (TabletToolV2InterfacePrivate *tabletToolPrivate = resource_cast<TabletToolV2InterfacePrivate *>(resource)) {
return tabletToolPrivate->q;
}
return nullptr;
}
bool TabletToolV2Interface::hasCapability(Capability capability) const
{
return d->m_capabilities.contains(capability);
}
SurfaceInterface *TabletToolV2Interface::currentSurface() const
{
return d->m_surface;
}
void TabletToolV2Interface::setCurrentSurface(SurfaceInterface *surface)
{
if (d->m_surface == surface)
@ -247,6 +263,11 @@ void TabletToolV2Interface::setCurrentSurface(SurfaceInterface *surface)
Q_EMIT cursorChanged(d->m_cursors.value(d->targetResource()));
}
quint32 TabletToolV2Interface::proximitySerial() const
{
return d->m_proximitySerial;
}
bool TabletToolV2Interface::isClientSupported() const
{
return d->m_surface && d->targetResource();
@ -310,7 +331,10 @@ void TabletToolV2Interface::sendWheel(int32_t degrees, int32_t clicks)
void TabletToolV2Interface::sendProximityIn(TabletV2Interface *tablet)
{
wl_resource *tabletResource = tablet->d->resourceForSurface(d->m_surface);
d->send_proximity_in(d->targetResource(), d->m_display->nextSerial(), tabletResource, d->m_surface->resource());
quint32 serial = d->m_display->nextSerial();
d->send_proximity_in(d->targetResource(), serial, tabletResource, d->m_surface->resource());
d->m_proximitySerial = serial;
d->m_lastTablet = tablet;
}

@ -11,14 +11,16 @@
#include <QVector>
#include <memory>
struct wl_resource;
namespace KWaylandServer
{
class ClientConnection;
class Display;
class SeatInterface;
class SurfaceInterface;
class TabletCursorV2;
class TabletCursorV2Private;
class TabletSurfaceCursorV2;
class TabletSurfaceCursorV2Private;
class TabletManagerV2InterfacePrivate;
class TabletSeatV2Interface;
class TabletSeatV2InterfacePrivate;
@ -54,6 +56,26 @@ private:
std::unique_ptr<TabletManagerV2InterfacePrivate> d;
};
class KWIN_EXPORT TabletSurfaceCursorV2 : public QObject
{
Q_OBJECT
public:
~TabletSurfaceCursorV2() override;
QPoint hotspot() const;
quint32 enteredSerial() const;
SurfaceInterface *surface() const;
Q_SIGNALS:
void changed();
private:
TabletSurfaceCursorV2();
const std::unique_ptr<TabletSurfaceCursorV2Private> d;
friend class TabletToolV2InterfacePrivate;
};
using TabletCursorSourceV2 = std::variant<TabletSurfaceCursorV2 *, QByteArray>;
class KWIN_EXPORT TabletToolV2Interface : public QObject
{
Q_OBJECT
@ -93,8 +115,12 @@ public:
* @see TabletV2Interface::isSurfaceSupported
*/
void setCurrentSurface(SurfaceInterface *surface);
SurfaceInterface *currentSurface() const;
bool isClientSupported() const;
quint32 proximitySerial() const;
void sendProximityIn(TabletV2Interface *tablet);
void sendProximityOut();
void sendUp();
@ -109,8 +135,10 @@ public:
void sendFrame(quint32 time);
void sendMotion(const QPointF &pos);
static TabletToolV2Interface *get(wl_resource *resource);
Q_SIGNALS:
void cursorChanged(TabletCursorV2 *cursor) const;
void cursorChanged(const TabletCursorSourceV2 &cursor);
private:
friend class TabletSeatV2InterfacePrivate;
@ -125,24 +153,6 @@ private:
std::unique_ptr<TabletToolV2InterfacePrivate> d;
};
class KWIN_EXPORT TabletCursorV2 : public QObject
{
Q_OBJECT
public:
~TabletCursorV2() override;
QPoint hotspot() const;
quint32 enteredSerial() const;
SurfaceInterface *surface() const;
Q_SIGNALS:
void changed();
private:
TabletCursorV2();
const std::unique_ptr<TabletCursorV2Private> d;
friend class TabletToolV2InterfacePrivate;
};
class KWIN_EXPORT TabletPadV2Interface : public QObject
{
Q_OBJECT

@ -23,6 +23,7 @@
#include "wayland/appmenu_interface.h"
#include "wayland/compositor_interface.h"
#include "wayland/contenttype_v1_interface.h"
#include "wayland/cursorshape_v1_interface.h"
#include "wayland/datacontroldevicemanager_v1_interface.h"
#include "wayland/datadevicemanager_interface.h"
#include "wayland/display.h"
@ -413,6 +414,7 @@ bool WaylandServer::init(InitializationFlags flags)
new RelativePointerManagerV1Interface(m_display, m_display);
m_dataDeviceManager = new DataDeviceManagerInterface(m_display, m_display);
new DataControlDeviceManagerV1Interface(m_display, m_display);
new CursorShapeManagerV1Interface(m_display, m_display);
const auto kwinConfig = kwinApp()->config();
if (kwinConfig->group("Wayland").readEntry("EnablePrimarySelection", true)) {

Loading…
Cancel
Save