From f1e71327d462d2dae0b46677bbc478afb0d1b2f7 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Mon, 24 Oct 2022 22:13:33 +0300 Subject: Client: Add support for high-resolution scrolling With wl_pointer version 8, the axis_discrete event is replaced with the axis_value120 event. The main difference between axis_discrete and axis_value120 is that the latter carries scroll deltas that can be fractions of 120, e.g. 30, etc. See also https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/72 Change-Id: I4f724ead7ba146dde6d8975fa4edfcfca761769d Reviewed-by: David Edmundson Reviewed-by: Qt CI Bot Reviewed-by: Shawn Rutledge --- src/3rdparty/protocol/wayland.xml | 224 ++++++++++++++++++++++++++---- src/client/qwaylandinputdevice.cpp | 37 ++++- src/client/qwaylandinputdevice_p.h | 3 +- tests/auto/client/CMakeLists.txt | 1 + tests/auto/client/seat/tst_seat.cpp | 52 ++++--- tests/auto/client/seatv7/CMakeLists.txt | 13 ++ tests/auto/client/seatv7/tst_seatv7.cpp | 130 +++++++++++++++++ tests/auto/client/shared/coreprotocol.cpp | 7 + tests/auto/client/shared/coreprotocol.h | 3 +- 9 files changed, 409 insertions(+), 61 deletions(-) create mode 100644 tests/auto/client/seatv7/CMakeLists.txt create mode 100644 tests/auto/client/seatv7/tst_seatv7.cpp diff --git a/src/3rdparty/protocol/wayland.xml b/src/3rdparty/protocol/wayland.xml index 471daf66..01bc267a 100644 --- a/src/3rdparty/protocol/wayland.xml +++ b/src/3rdparty/protocol/wayland.xml @@ -187,7 +187,7 @@ - + A compositor. This object is a singleton global. The compositor is in charge of combining the contents of multiple @@ -258,6 +258,12 @@ for the pool from the file descriptor passed when the pool was created, but using the new size. This request can only be used to make the pool bigger. + + This request only changes the amount of bytes that are mmapped + by the server and does not touch the file corresponding to the + file descriptor passed at creation time. It is the client's + responsibility to ensure that the file is at least as big as + the new pool size. @@ -271,8 +277,8 @@ Clients can create wl_shm_pool objects using the create_pool request. - At connection setup time, the wl_shm object emits one or more - format events to inform clients about the valid pixel formats + On binding the wl_shm object one or more format events + are emitted to inform clients about the valid pixel formats that can be used for buffers. @@ -296,6 +302,9 @@ The drm format codes match the macros defined in drm_fourcc.h, except argb8888 and xrgb8888. The formats actually supported by the compositor will be reported by the format event. + + For all wl_shm formats and unless specified in another protocol + extension, pre-multiplied alpha is used for pixel values. @@ -403,6 +412,10 @@ + + + + @@ -431,10 +444,15 @@ A buffer provides the content for a wl_surface. Buffers are - created through factory interfaces such as wl_drm, wl_shm or - similar. It has a width and a height and can be attached to a - wl_surface, but the mechanism by which a client provides and - updates the contents is defined by the buffer factory interface. + created through factory interfaces such as wl_shm, wp_linux_buffer_params + (from the linux-dmabuf protocol extension) or similar. It has a width and + a height and can be attached to a wl_surface, but the mechanism by which a + client provides and updates the contents is defined by the buffer factory + interface. + + If the buffer uses a format that has an alpha channel, the alpha channel + is assumed to be premultiplied in the color channels unless otherwise + specified. @@ -878,7 +896,7 @@ which will subsequently be used in either the data_device.enter event (for drag-and-drop) or the data_device.selection event (for selections). Immediately - following the data_device_data_offer event, the new data_offer + following the data_device.data_offer event, the new data_offer object will send out data_offer.offer events to describe the mime types it offers. @@ -948,9 +966,10 @@ immediately before receiving keyboard focus and when a new selection is set while the client has keyboard focus. The data_offer is valid until a new data_offer or NULL is received - or until the client loses keyboard focus. The client must - destroy the previous selection data_offer, if any, upon receiving - this event. + or until the client loses keyboard focus. Switching surface with + keyboard focus within the same client doesn't mean a new selection + will be sent. The client must destroy the previous selection + data_offer, if any, upon receiving this event. @@ -1038,7 +1057,8 @@ a basic surface. Note! This protocol is deprecated and not intended for production use. - For desktop-style user interfaces, use xdg_shell. + For desktop-style user interfaces, use xdg_shell. Compositors and clients + should not implement this interface. @@ -1332,7 +1352,7 @@ - + A surface is a rectangular area that may be displayed on zero or more outputs, and shown any number of times at the compositor's @@ -1384,6 +1404,7 @@ + @@ -1406,7 +1427,14 @@ buffer's upper left corner, relative to the current buffer's upper left corner, in surface-local coordinates. In other words, the x and y, combined with the new surface size define in which - directions the surface's size changes. + directions the surface's size changes. Setting anything other than 0 + as x and y arguments is discouraged, and should instead be replaced + with using the separate wl_surface.offset request. + + When the bound wl_surface version is 5 or higher, passing any + non-zero x or y is a protocol violation, and will result in an + 'invalid_offset' error being raised. To achieve equivalent semantics, + use wl_surface.offset. Surface contents are double-buffered state, see wl_surface.commit. @@ -1434,9 +1462,12 @@ from the same backing storage or use wp_linux_buffer_release. Destroying the wl_buffer after wl_buffer.release does not change - the surface contents. However, if the client destroys the - wl_buffer before receiving the wl_buffer.release event, the surface - contents become undefined immediately. + the surface contents. Destroying the wl_buffer before wl_buffer.release + is allowed as long as the underlying buffer storage isn't re-used (this + can happen e.g. on client process termination). However, if the client + destroys the wl_buffer before receiving the wl_buffer.release event and + mutates the underlying buffer storage, the surface contents become + undefined immediately. If wl_surface.attach is sent with a NULL wl_buffer, the following wl_surface.commit will remove the surface content. @@ -1734,9 +1765,30 @@ + + + + + + The x and y arguments specify the location of the new pending + buffer's upper left corner, relative to the current buffer's upper + left corner, in surface-local coordinates. In other words, the + x and y, combined with the new surface size define in which + directions the surface's size changes. + + Surface location offset is double-buffered state, see + wl_surface.commit. + + This request is semantically equivalent to and the replaces the x and y + arguments in the wl_surface.attach request in wl_surface versions prior + to 5. See wl_surface.attach for details. + + + + - + A seat is a group of keyboards, pointer and touch devices. This object is published as a global during start up, or when such a @@ -1838,9 +1890,22 @@ - In a multiseat configuration this can be used by the client to help - identify which physical devices the seat represents. Based on - the seat configuration used by the compositor. + In a multi-seat configuration the seat name can be used by clients to + help identify which physical devices the seat represents. + + The seat name is a UTF-8 string with no convention defined for its + contents. Each name is unique among all wl_seat globals. The name is + only guaranteed to be unique for the current compositor instance. + + The same seat names are used for all clients. Thus, the name can be + shared across processes to refer to a specific wl_seat global. + + The name event is sent after binding to the seat global. This event is + only sent once per seat object, and the name does not change over the + lifetime of the wl_seat global. + + Compositors may re-use the same seat name if the wl_seat global is + destroyed and re-created later. @@ -1856,7 +1921,7 @@ - + The wl_pointer interface represents one or more input devices, such as mice, which control the pointer location and pointer_focus @@ -1905,6 +1970,10 @@ wl_surface is no longer used as the cursor. When the use as a cursor ends, the current and pending input regions become undefined, and the wl_surface is unmapped. + + The serial parameter must match the latest wl_pointer.enter + serial number sent to the client. Otherwise the request will be + ignored. + + + + Discrete high-resolution scroll information. + + This event carries high-resolution wheel scroll information, + with each multiple of 120 representing one logical scroll step + (a wheel detent). For example, an axis_value120 of 30 is one quarter of + a logical scroll step in the positive direction, a value120 of + -240 are two logical scroll steps in the negative direction within the + same hardware event. + Clients that rely on discrete scrolling should accumulate the + value120 to multiples of 120 before processing the event. + + The value120 must not be zero. + + This event replaces the wl_pointer.axis_discrete event in clients + supporting wl_pointer version 8 or later. + + Where a wl_pointer.axis_source event occurs in the same + wl_pointer.frame, the axis source applies to this event. + + The order of wl_pointer.axis_value120 and wl_pointer.axis_source is + not guaranteed. + + + + - + The wl_keyboard interface represents one or more keyboards associated with a seat. @@ -2193,13 +2294,14 @@ + summary="libxkbcommon compatible, null-terminated string; to determine the xkb keycode, clients must add 8 to the key event keycode"/> This event provides a file descriptor to the client which can be - memory-mapped to provide a keyboard mapping description. + memory-mapped in read-only mode to provide a keyboard mapping + description. From version 7 onwards, the fd must be mapped with MAP_PRIVATE by the recipient, as MAP_SHARED may fail. @@ -2305,7 +2407,7 @@ - + The wl_touch interface represents a touchscreen associated with a seat. @@ -2449,7 +2551,7 @@ - + An output describes part of the compositor geometry. The compositor works in the 'compositor coordinate system' and an @@ -2505,12 +2607,15 @@ The physical size can be set to zero if it doesn't make sense for this output (e.g. for projectors or virtual outputs). + The geometry event will be followed by a done event (starting from + version 2). + Note: wl_output only advertises partial information about the output position and identification. Some compositors, for instance those not implementing a desktop-style output layout or those exposing virtual outputs, might fake this information. Instead of using x and y, clients should use xdg_output.logical_position. Instead of using make and model, - clients should use xdg_output.name and xdg_output.description. + clients should use name and description. @@ -2566,6 +2671,9 @@ The vertical refresh rate can be set to zero if it doesn't make sense for this output (e.g. for virtual outputs). + The mode event will be followed by a done event (starting from + version 2). + Clients should not use the refresh rate to schedule frames. Instead, they should use the wl_surface.frame event or the presentation-time protocol. @@ -2612,6 +2720,8 @@ the scale of the output. That way the compositor can avoid scaling the surface, and the client can supply a higher detail image. + + The scale event will be followed by a done event. @@ -2624,6 +2734,62 @@ use the output object anymore. + + + + + + Many compositors will assign user-friendly names to their outputs, show + them to the user, allow the user to refer to an output, etc. The client + may wish to know this name as well to offer the user similar behaviors. + + The name is a UTF-8 string with no convention defined for its contents. + Each name is unique among all wl_output globals. The name is only + guaranteed to be unique for the compositor instance. + + The same output name is used for all clients for a given wl_output + global. Thus, the name can be shared across processes to refer to a + specific wl_output global. + + The name is not guaranteed to be persistent across sessions, thus cannot + be used to reliably identify an output in e.g. configuration files. + + Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do + not assume that the name is a reflection of an underlying DRM connector, + X11 connection, etc. + + The name event is sent after binding the output object. This event is + only sent once per output object, and the name does not change over the + lifetime of the wl_output global. + + Compositors may re-use the same output name if the wl_output global is + destroyed and re-created later. Compositors should avoid re-using the + same name if possible. + + The name event will be followed by a done event. + + + + + + + Many compositors can produce human-readable descriptions of their + outputs. The client may wish to know this description as well, e.g. for + output selection purposes. + + The description is a UTF-8 string with no convention defined for its + contents. The description is not guaranteed to be unique among all + wl_output globals. Examples might include 'Foocorp 11" Display' or + 'Virtual X11 output via :1'. + + The description event is sent after binding the output object and + whenever the description changes. The description is optional, and may + not be sent at all. + + The description event will be followed by a done event. + + + diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp index 56c89688..4b4e54bd 100644 --- a/src/client/qwaylandinputdevice.cpp +++ b/src/client/qwaylandinputdevice.cpp @@ -375,7 +375,7 @@ QWaylandInputDevice::Touch::~Touch() } QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, uint32_t id) - : QtWayland::wl_seat(display->wl_registry(), id, qMin(version, 7)) + : QtWayland::wl_seat(display->wl_registry(), id, qMin(version, 8)) , mQDisplay(display) , mDisplay(display->wl_display()) { @@ -982,14 +982,16 @@ void QWaylandInputDevice::Pointer::pointer_axis_discrete(uint32_t axis, int32_t if (!focusWindow()) return; + const int32_t delta120 = value * 15 * 8; + switch (axis) { case axis_vertical_scroll: qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_discrete vertical:" << value; - mFrameData.discreteDelta.ry() += value; + mFrameData.delta120.ry() += delta120; break; case axis_horizontal_scroll: qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_discrete horizontal:" << value; - mFrameData.discreteDelta.rx() += value; + mFrameData.delta120.rx() += delta120; break; default: //TODO: is this really needed? @@ -998,6 +1000,26 @@ void QWaylandInputDevice::Pointer::pointer_axis_discrete(uint32_t axis, int32_t } } +void QWaylandInputDevice::Pointer::pointer_axis_value120(uint32_t axis, int32_t value) +{ + if (!focusWindow()) + return; + + switch (axis) { + case axis_vertical_scroll: + qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_value120 vertical:" << value; + mFrameData.delta120.ry() += value; + break; + case axis_horizontal_scroll: + qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_value120 horizontal:" << value; + mFrameData.delta120.rx() += value; + break; + default: + qCWarning(lcQpaWaylandInput) << "wl_pointer.axis_value120: Unknown axis:" << axis; + return; + } +} + void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event) { qCDebug(lcQpaWaylandInput) << "Setting frame event " << event->type; @@ -1016,7 +1038,7 @@ void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event) void QWaylandInputDevice::Pointer::FrameData::resetScrollData() { - discreteDelta = QPoint(); + delta120 = QPoint(); delta = QPointF(); axisSource = axis_source_wheel; } @@ -1060,15 +1082,16 @@ QPoint QWaylandInputDevice::Pointer::FrameData::pixelDeltaAndError(QPointF *accu QPoint QWaylandInputDevice::Pointer::FrameData::angleDelta() const { - if (discreteDelta.isNull()) { + if (delta120.isNull()) { // If we didn't get any discrete events, then we need to fall back to // the continuous information. return (delta * -12).toPoint(); //TODO: why multiply by 12? } // The angle delta is in eights of degrees, and our docs says most mice have - // 1 click = 15 degrees. It's also in the opposite direction of surface space. - return -discreteDelta * 15 * 8; + // 1 click = 15 degrees, i.e. 120 is one click. It's also in the opposite + // direction of surface space. + return -delta120; } Qt::MouseEventSource QWaylandInputDevice::Pointer::FrameData::wheelEventSource() const diff --git a/src/client/qwaylandinputdevice_p.h b/src/client/qwaylandinputdevice_p.h index b9c69ee1..fbc26008 100644 --- a/src/client/qwaylandinputdevice_p.h +++ b/src/client/qwaylandinputdevice_p.h @@ -308,6 +308,7 @@ protected: void pointer_axis_stop(uint32_t time, uint32_t axis) override; void pointer_axis_discrete(uint32_t axis, int32_t value) override; void pointer_frame() override; + void pointer_axis_value120(uint32_t axis, int32_t value120) override; private slots: void handleFocusDestroyed() { invalidateFocus(); } @@ -343,7 +344,7 @@ public: QWaylandPointerEvent *event = nullptr; QPointF delta; - QPoint discreteDelta; + QPoint delta120; axis_source axisSource = axis_source_wheel; void resetScrollData(); diff --git a/tests/auto/client/CMakeLists.txt b/tests/auto/client/CMakeLists.txt index dd5c6e5b..749e6b83 100644 --- a/tests/auto/client/CMakeLists.txt +++ b/tests/auto/client/CMakeLists.txt @@ -17,6 +17,7 @@ if (NOT WEBOS) add_subdirectory(output) add_subdirectory(primaryselectionv1) add_subdirectory(seatv4) + add_subdirectory(seatv7) add_subdirectory(seat) add_subdirectory(surface) add_subdirectory(tabletv2) diff --git a/tests/auto/client/seat/tst_seat.cpp b/tests/auto/client/seat/tst_seat.cpp index 68749486..8a9375d6 100644 --- a/tests/auto/client/seat/tst_seat.cpp +++ b/tests/auto/client/seat/tst_seat.cpp @@ -18,7 +18,7 @@ public: removeAll(); uint capabilities = MockCompositor::Seat::capability_pointer | MockCompositor::Seat::capability_touch; - int version = 7; + int version = 8; add(capabilities, version); }); } @@ -40,8 +40,7 @@ private slots: void fingerScroll(); void fingerScrollSlow(); void continuousScroll(); - void wheelDiscreteScroll_data(); - void wheelDiscreteScroll(); + void highResolutionScroll(); // Touch tests void createsTouch(); @@ -56,13 +55,13 @@ private slots: void tst_seat::bindsToSeat() { QCOMPOSITOR_COMPARE(get()->resourceMap().size(), 1); - QCOMPOSITOR_COMPARE(get()->resourceMap().first()->version(), 7); + QCOMPOSITOR_COMPARE(get()->resourceMap().first()->version(), 8); } void tst_seat::createsPointer() { QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().size(), 1); - QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().first()->version(), 7); + QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().first()->version(), 8); } void tst_seat::setsCursorOnEnter() @@ -320,28 +319,38 @@ void tst_seat::fingerScrollSlow() QCOMPARE(accumulated.y(), -1); } -void tst_seat::wheelDiscreteScroll_data() -{ - QTest::addColumn("source"); - QTest::newRow("wheel") << uint(Pointer::axis_source_wheel); - QTest::newRow("wheel tilt") << uint(Pointer::axis_source_wheel_tilt); -} - -void tst_seat::wheelDiscreteScroll() +void tst_seat::highResolutionScroll() { WheelWindow window; QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); - QFETCH(uint, source); - exec([=] { auto *p = pointer(); auto *c = client(); p->sendEnter(xdgToplevel()->surface(), {32, 32}); p->sendFrame(c); - p->sendAxisSource(c, Pointer::axis_source(source)); - p->sendAxisDiscrete(c, Pointer::axis_vertical_scroll, 1); // 1 click downwards - p->sendAxis(c, Pointer::axis_vertical_scroll, 1.0); + p->sendAxisSource(c, Pointer::axis_source_wheel); + p->sendAxisValue120(c, Pointer::axis_vertical_scroll, 30); // quarter of a click + p->sendAxis(c, Pointer::axis_vertical_scroll, 3.75); + p->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::NoScrollPhase); + QVERIFY(qAbs(e.angleDelta.x()) <= qAbs(e.angleDelta.y())); // Vertical scroll + QCOMPARE(e.angleDelta, QPoint(0, -30)); + // Click scrolls are not continuous and should not have a pixel delta + QCOMPARE(e.pixelDelta, QPoint(0, 0)); + } + + exec([=] { + auto *p = pointer(); + auto *c = client(); + p->sendAxisSource(c, Pointer::axis_source_wheel); + p->sendAxisValue120(c, Pointer::axis_vertical_scroll, 90); // complete the click + p->sendAxis(c, Pointer::axis_vertical_scroll, 11.25); p->sendFrame(c); }); @@ -350,10 +359,7 @@ void tst_seat::wheelDiscreteScroll() auto e = window.m_events.takeFirst(); QCOMPARE(e.phase, Qt::NoScrollPhase); QVERIFY(qAbs(e.angleDelta.x()) <= qAbs(e.angleDelta.y())); // Vertical scroll - // According to the docs the angle delta is in eights of a degree and most mice have - // 1 click = 15 degrees. The angle delta should therefore be: - // 15 degrees / (1/8 eights per degrees) = 120 eights of degrees. - QCOMPARE(e.angleDelta, QPoint(0, -120)); + QCOMPARE(e.angleDelta, QPoint(0, -90)); // Click scrolls are not continuous and should not have a pixel delta QCOMPARE(e.pixelDelta, QPoint(0, 0)); } @@ -388,7 +394,7 @@ void tst_seat::continuousScroll() void tst_seat::createsTouch() { QCOMPOSITOR_TRY_COMPARE(touch()->resourceMap().size(), 1); - QCOMPOSITOR_TRY_COMPARE(touch()->resourceMap().first()->version(), 7); + QCOMPOSITOR_TRY_COMPARE(touch()->resourceMap().first()->version(), 8); } class TouchWindow : public QRasterWindow { diff --git a/tests/auto/client/seatv7/CMakeLists.txt b/tests/auto/client/seatv7/CMakeLists.txt new file mode 100644 index 00000000..cf3ade8c --- /dev/null +++ b/tests/auto/client/seatv7/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_seatv7 Test: +##################################################################### + +qt_internal_add_test(tst_seatv7 + SOURCES + tst_seatv7.cpp + LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/seatv7/tst_seatv7.cpp b/tests/auto/client/seatv7/tst_seatv7.cpp new file mode 100644 index 00000000..c341ec96 --- /dev/null +++ b/tests/auto/client/seatv7/tst_seatv7.cpp @@ -0,0 +1,130 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "mockcompositor.h" +#include +#include +#include + +using namespace MockCompositor; + +class SeatCompositor : public DefaultCompositor { +public: + explicit SeatCompositor() + { + exec([this] { + m_config.autoConfigure = true; + + removeAll(); + + uint capabilities = MockCompositor::Seat::capability_pointer | MockCompositor::Seat::capability_touch; + int version = 7; + add(capabilities, version); + }); + } +}; + +class tst_seatv7 : public QObject, private SeatCompositor +{ + Q_OBJECT +private slots: + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void bindsToSeat(); + + // Pointer tests + void wheelDiscreteScroll_data(); + void wheelDiscreteScroll(); +}; + +void tst_seatv7::bindsToSeat() +{ + QCOMPOSITOR_COMPARE(get()->resourceMap().size(), 1); + QCOMPOSITOR_COMPARE(get()->resourceMap().first()->version(), 7); +} + +class WheelWindow : QRasterWindow { +public: + WheelWindow() + { + resize(64, 64); + show(); + } + void wheelEvent(QWheelEvent *event) override + { + QRasterWindow::wheelEvent(event); +// qDebug() << event << "angleDelta" << event->angleDelta() << "pixelDelta" << event->pixelDelta(); + + if (event->phase() != Qt::ScrollUpdate && event->phase() != Qt::NoScrollPhase) { + // Shouldn't have deltas in the these phases + QCOMPARE(event->angleDelta(), QPoint(0, 0)); + QCOMPARE(event->pixelDelta(), QPoint(0, 0)); + } + + // The axis vector of the event is already in surface space, so there is now way to tell + // whether it is inverted or not. + QCOMPARE(event->inverted(), false); + + // We didn't press any buttons + QCOMPARE(event->buttons(), Qt::NoButton); + + m_events.append(Event{event}); + } + struct Event // Because I didn't find a convenient way to copy it entirely + { + explicit Event() = default; + explicit Event(const QWheelEvent *event) + : phase(event->phase()) + , pixelDelta(event->pixelDelta()) + , angleDelta(event->angleDelta()) + , source(event->source()) + { + } + Qt::ScrollPhase phase{}; + QPoint pixelDelta; + QPoint angleDelta; // eights of a degree, positive is upwards, left + Qt::MouseEventSource source{}; + }; + QList m_events; +}; + +void tst_seatv7::wheelDiscreteScroll_data() +{ + QTest::addColumn("source"); + QTest::newRow("wheel") << uint(Pointer::axis_source_wheel); + QTest::newRow("wheel tilt") << uint(Pointer::axis_source_wheel_tilt); +} + +void tst_seatv7::wheelDiscreteScroll() +{ + WheelWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + QFETCH(uint, source); + + exec([=] { + auto *p = pointer(); + auto *c = client(); + p->sendEnter(xdgToplevel()->surface(), {32, 32}); + p->sendFrame(c); + p->sendAxisSource(c, Pointer::axis_source(source)); + p->sendAxisDiscrete(c, Pointer::axis_vertical_scroll, 1); // 1 click downwards + p->sendAxis(c, Pointer::axis_vertical_scroll, 1.0); + p->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.phase, Qt::NoScrollPhase); + QVERIFY(qAbs(e.angleDelta.x()) <= qAbs(e.angleDelta.y())); // Vertical scroll + // According to the docs the angle delta is in eights of a degree and most mice have + // 1 click = 15 degrees. The angle delta should therefore be: + // 15 degrees / (1/8 eights per degrees) = 120 eights of degrees. + QCOMPARE(e.angleDelta, QPoint(0, -120)); + // Click scrolls are not continuous and should not have a pixel delta + QCOMPARE(e.pixelDelta, QPoint(0, 0)); + } +} + +QCOMPOSITOR_TEST_MAIN(tst_seatv7) +#include "tst_seatv7.moc" diff --git a/tests/auto/client/shared/coreprotocol.cpp b/tests/auto/client/shared/coreprotocol.cpp index 005151f7..4f3d21d8 100644 --- a/tests/auto/client/shared/coreprotocol.cpp +++ b/tests/auto/client/shared/coreprotocol.cpp @@ -423,6 +423,13 @@ void Pointer::sendFrame(wl_client *client) send_frame(r->handle); } +void Pointer::sendAxisValue120(wl_client *client, QtWaylandServer::wl_pointer::axis axis, int value120) +{ + const auto pointerResources = resourceMap().values(client); + for (auto *r : pointerResources) + send_axis_value120(r->handle, axis, value120); +} + void Pointer::pointer_set_cursor(Resource *resource, uint32_t serial, wl_resource *surface, int32_t hotspot_x, int32_t hotspot_y) { Q_UNUSED(resource); diff --git a/tests/auto/client/shared/coreprotocol.h b/tests/auto/client/shared/coreprotocol.h index 370b8cd8..74f07afb 100644 --- a/tests/auto/client/shared/coreprotocol.h +++ b/tests/auto/client/shared/coreprotocol.h @@ -296,7 +296,7 @@ class Seat : public Global, public QtWaylandServer::wl_seat { Q_OBJECT public: - explicit Seat(CoreCompositor *compositor, uint capabilities = Seat::capability_pointer | Seat::capability_keyboard | Seat::capability_touch, int version = 7); + explicit Seat(CoreCompositor *compositor, uint capabilities = Seat::capability_pointer | Seat::capability_keyboard | Seat::capability_touch, int version = 8); ~Seat() override; void send_capabilities(Resource *resource, uint capabilities) = delete; // Use wrapper instead void send_capabilities(uint capabilities) = delete; // Use wrapper instead @@ -346,6 +346,7 @@ public: void sendAxisSource(wl_client *client, axis_source source); void sendAxisStop(wl_client *client, axis axis); void sendFrame(wl_client *client); + void sendAxisValue120(wl_client *client, axis axis, int value120); Seat *m_seat = nullptr; QList m_enterSerials; -- cgit v1.2.1