diff --git a/autotests/client/test_wayland_seat.cpp b/autotests/client/test_wayland_seat.cpp --- a/autotests/client/test_wayland_seat.cpp +++ b/autotests/client/test_wayland_seat.cpp @@ -78,6 +78,7 @@ void testPointerSwipeGesture(); void testPointerPinchGesture_data(); void testPointerPinchGesture(); + void testPointerAxis(); void testKeyboardSubSurfaceTreeFromPointer(); void testCursor(); void testCursorDamage(); @@ -1151,6 +1152,103 @@ QVERIFY(spy->wait()); } +void TestWaylandSeat::testPointerAxis() +{ + using namespace KWayland::Client; + using namespace KWayland::Server; + + // first create the pointer + QSignalSpy hasPointerChangedSpy(m_seat, &Seat::hasPointerChanged); + QVERIFY(hasPointerChangedSpy.isValid()); + m_seatInterface->setHasPointer(true); + QVERIFY(hasPointerChangedSpy.wait()); + QScopedPointer pointer(m_seat->createPointer()); + QVERIFY(pointer); + + // now create a surface + QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(surfaceCreatedSpy.isValid()); + QScopedPointer surface(m_compositor->createSurface()); + QVERIFY(surfaceCreatedSpy.wait()); + auto serverSurface = surfaceCreatedSpy.first().first().value(); + QVERIFY(serverSurface); + m_seatInterface->setFocusedPointerSurface(serverSurface); + QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface); + QVERIFY(m_seatInterface->focusedPointer()); + QSignalSpy frameSpy(pointer.data(), &Pointer::frame); + QVERIFY(frameSpy.isValid()); + QVERIFY(frameSpy.wait()); + QCOMPARE(frameSpy.count(), 1); + + // let's scroll vertically + QSignalSpy axisSourceSpy(pointer.data(), &Pointer::axisSourceChanged); + QVERIFY(axisSourceSpy.isValid()); + QSignalSpy axisSpy(pointer.data(), &Pointer::axisChanged); + QVERIFY(axisSpy.isValid()); + QSignalSpy axisDiscreteSpy(pointer.data(), &Pointer::axisDiscreteChanged); + QVERIFY(axisDiscreteSpy.isValid()); + QSignalSpy axisStoppedSpy(pointer.data(), &Pointer::axisStopped); + QVERIFY(axisStoppedSpy.isValid()); + + quint32 timestamp = 1; + m_seatInterface->setTimestamp(timestamp++); + m_seatInterface->pointerAxisV5(Qt::Vertical, 10, 1, PointerAxisSource::Wheel); + QVERIFY(frameSpy.wait()); + QCOMPARE(frameSpy.count(), 2); + QCOMPARE(axisSourceSpy.count(), 1); + QCOMPARE(axisSourceSpy.last().at(0).value(), Pointer::AxisSource::Wheel); + QCOMPARE(axisDiscreteSpy.count(), 1); + QCOMPARE(axisDiscreteSpy.last().at(0).value(), Pointer::Axis::Vertical); + QCOMPARE(axisDiscreteSpy.last().at(1).value(), 1); + QCOMPARE(axisSpy.count(), 1); + QCOMPARE(axisSpy.last().at(0).value(), quint32(1)); + QCOMPARE(axisSpy.last().at(1).value(), Pointer::Axis::Vertical); + QCOMPARE(axisSpy.last().at(2).value(), 10.0); + QCOMPARE(axisStoppedSpy.count(), 0); + + // let's scroll using fingers + m_seatInterface->setTimestamp(timestamp++); + m_seatInterface->pointerAxisV5(Qt::Horizontal, 42, 0, PointerAxisSource::Finger); + QVERIFY(frameSpy.wait()); + QCOMPARE(frameSpy.count(), 3); + QCOMPARE(axisSourceSpy.count(), 2); + QCOMPARE(axisSourceSpy.last().at(0).value(), Pointer::AxisSource::Finger); + QCOMPARE(axisDiscreteSpy.count(), 1); + QCOMPARE(axisSpy.count(), 2); + QCOMPARE(axisSpy.last().at(0).value(), quint32(2)); + QCOMPARE(axisSpy.last().at(1).value(), Pointer::Axis::Horizontal); + QCOMPARE(axisSpy.last().at(2).value(), 42.0); + QCOMPARE(axisStoppedSpy.count(), 0); + + // lift the fingers off the device + m_seatInterface->setTimestamp(timestamp++); + m_seatInterface->pointerAxisV5(Qt::Horizontal, 0, 0, PointerAxisSource::Finger); + QVERIFY(frameSpy.wait()); + QCOMPARE(frameSpy.count(), 4); + QCOMPARE(axisSourceSpy.count(), 3); + QCOMPARE(axisSourceSpy.last().at(0).value(), Pointer::AxisSource::Finger); + QCOMPARE(axisDiscreteSpy.count(), 1); + QCOMPARE(axisSpy.count(), 2); + QCOMPARE(axisStoppedSpy.count(), 1); + QCOMPARE(axisStoppedSpy.last().at(0).value(), 3); + QCOMPARE(axisStoppedSpy.last().at(1).value(), Pointer::Axis::Horizontal); + + // if the device is unknown, no axis_source event should be sent + m_seatInterface->setTimestamp(timestamp++); + m_seatInterface->pointerAxisV5(Qt::Horizontal, 42, 1, PointerAxisSource::Unknown); + QVERIFY(frameSpy.wait()); + QCOMPARE(frameSpy.count(), 5); + QCOMPARE(axisSourceSpy.count(), 3); + QCOMPARE(axisDiscreteSpy.count(), 2); + QCOMPARE(axisDiscreteSpy.last().at(0).value(), Pointer::Axis::Horizontal); + QCOMPARE(axisDiscreteSpy.last().at(1).value(), 1); + QCOMPARE(axisSpy.count(), 3); + QCOMPARE(axisSpy.last().at(0).value(), quint32(4)); + QCOMPARE(axisSpy.last().at(1).value(), Pointer::Axis::Horizontal); + QCOMPARE(axisSpy.last().at(2).value(), 42.0); + QCOMPARE(axisStoppedSpy.count(), 1); +} + void TestWaylandSeat::testKeyboardSubSurfaceTreeFromPointer() { // this test verifies that when clicking on a sub-surface the keyboard focus passes to it diff --git a/src/client/pointer.h b/src/client/pointer.h --- a/src/client/pointer.h +++ b/src/client/pointer.h @@ -55,6 +55,12 @@ Vertical, Horizontal }; + enum class AxisSource { + Wheel, + Finger, + Continuous, + WheelTilt + }; explicit Pointer(QObject *parent = nullptr); virtual ~Pointer(); @@ -169,6 +175,24 @@ * @param delta **/ void axisChanged(quint32 time, KWayland::Client::Pointer::Axis axis, qreal delta); + /** + * Indicates the source of scroll and other axes. + * + * @since 5.59 + **/ + void axisSourceChanged(KWayland::Client::Pointer::AxisSource source); + /** + * Discrete step information for scroll and other axes. + * + * @since 5.59 + **/ + void axisDiscreteChanged(KWayland::Client::Pointer::Axis axis, qint32 discreteDelta); + /** + * Stop notification for scroll and other axes. + * + * @since 5.59 + **/ + void axisStopped(quint32 time, KWayland::Client::Pointer::Axis axis); /** * Indicates the end of a set of events that logically belong together. @@ -188,5 +212,6 @@ Q_DECLARE_METATYPE(KWayland::Client::Pointer::ButtonState) Q_DECLARE_METATYPE(KWayland::Client::Pointer::Axis) +Q_DECLARE_METATYPE(KWayland::Client::Pointer::AxisSource) #endif diff --git a/src/client/pointer.cpp b/src/client/pointer.cpp --- a/src/client/pointer.cpp +++ b/src/client/pointer.cpp @@ -31,6 +31,18 @@ namespace Client { +static Pointer::Axis wlAxisToPointerAxis(uint32_t axis) +{ + switch (axis) { + case WL_POINTER_AXIS_VERTICAL_SCROLL: + return Pointer::Axis::Vertical; + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + return Pointer::Axis::Horizontal; + } + + Q_UNREACHABLE(); +} + class Q_DECL_HIDDEN Pointer::Private { public: @@ -164,14 +176,7 @@ { auto p = reinterpret_cast(data); Q_ASSERT(p->pointer == pointer); - auto toAxis = [axis] { - if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { - return Axis::Horizontal; - } else { - return Axis::Vertical; - } - }; - emit p->q->axisChanged(time, toAxis(), wl_fixed_to_double(value)); + emit p->q->axisChanged(time, wlAxisToPointerAxis(axis), wl_fixed_to_double(value)); } void Pointer::Private::frameCallback(void *data, wl_pointer *pointer) @@ -183,28 +188,41 @@ void Pointer::Private::axisSourceCallback(void *data, wl_pointer *pointer, uint32_t axis_source) { - Q_UNUSED(data) - Q_UNUSED(pointer) - Q_UNUSED(axis_source) - // TODO: implement + auto p = reinterpret_cast(data); + Q_ASSERT(p->pointer == pointer); + AxisSource source; + switch (axis_source) { + case WL_POINTER_AXIS_SOURCE_WHEEL: + source = AxisSource::Wheel; + break; + case WL_POINTER_AXIS_SOURCE_FINGER: + source = AxisSource::Finger; + break; + case WL_POINTER_AXIS_SOURCE_CONTINUOUS: + source = AxisSource::Continuous; + break; + case WL_POINTER_AXIS_SOURCE_WHEEL_TILT: + source = AxisSource::WheelTilt; + break; + default: + Q_UNREACHABLE(); + break; + } + emit p->q->axisSourceChanged(source); } void Pointer::Private::axisStopCallback(void *data, wl_pointer *pointer, uint32_t time, uint32_t axis) { - Q_UNUSED(data) - Q_UNUSED(pointer) - Q_UNUSED(time) - Q_UNUSED(axis) - // TODO: implement + auto p = reinterpret_cast(data); + Q_ASSERT(p->pointer == pointer); + emit p->q->axisStopped(time, wlAxisToPointerAxis(axis)); } void Pointer::Private::axisDiscreteCallback(void *data, wl_pointer *pointer, uint32_t axis, int32_t discrete) { - Q_UNUSED(data) - Q_UNUSED(pointer) - Q_UNUSED(axis) - Q_UNUSED(discrete) - // TODO: implement + auto p = reinterpret_cast(data); + Q_ASSERT(p->pointer == pointer); + emit p->q->axisDiscreteChanged(wlAxisToPointerAxis(axis), discrete); } void Pointer::setCursor(Surface *surface, const QPoint &hotspot) diff --git a/src/server/pointer_interface.h b/src/server/pointer_interface.h --- a/src/server/pointer_interface.h +++ b/src/server/pointer_interface.h @@ -35,6 +35,8 @@ class SeatInterface; class SurfaceInterface; +enum class PointerAxisSource; + /** * @brief Resource for the wl_pointer interface. * @@ -73,6 +75,7 @@ void setFocusedSurface(SurfaceInterface *surface, quint32 serial); void buttonPressed(quint32 button, quint32 serial); void buttonReleased(quint32 button, quint32 serial); + void axis(Qt::Orientation orientation, qreal delta, qint32 discreteDelta, PointerAxisSource source); void axis(Qt::Orientation orientation, quint32 delta); void relativeMotion(const QSizeF &delta, const QSizeF &deltaNonAccelerated, quint64 microseconds); friend class SeatInterface; diff --git a/src/server/pointer_interface.cpp b/src/server/pointer_interface.cpp --- a/src/server/pointer_interface.cpp +++ b/src/server/pointer_interface.cpp @@ -322,6 +322,54 @@ d->sendFrame(); } +void PointerInterface::axis(Qt::Orientation orientation, qreal delta, qint32 discreteDelta, PointerAxisSource source) +{ + Q_D(); + Q_ASSERT(d->focusedSurface); + if (!d->resource) { + return; + } + + const quint32 version = wl_resource_get_version(d->resource); + + const auto wlOrientation = (orientation == Qt::Vertical) + ? WL_POINTER_AXIS_VERTICAL_SCROLL + : WL_POINTER_AXIS_HORIZONTAL_SCROLL; + + if (source != PointerAxisSource::Unknown && version >= WL_POINTER_AXIS_SOURCE_SINCE_VERSION) { + wl_pointer_axis_source wlSource; + switch (source) { + case PointerAxisSource::Wheel: + wlSource = WL_POINTER_AXIS_SOURCE_WHEEL; + break; + case PointerAxisSource::Finger: + wlSource = WL_POINTER_AXIS_SOURCE_FINGER; + break; + case PointerAxisSource::Continuous: + wlSource = WL_POINTER_AXIS_SOURCE_CONTINUOUS; + break; + case PointerAxisSource::WheelTilt: + wlSource = WL_POINTER_AXIS_SOURCE_WHEEL_TILT; + break; + default: + Q_UNREACHABLE(); + break; + } + wl_pointer_send_axis_source(d->resource, wlSource); + } + + if (delta != 0.0) { + if (discreteDelta && version >= WL_POINTER_AXIS_DISCRETE_SINCE_VERSION) { + wl_pointer_send_axis_discrete(d->resource, wlOrientation, discreteDelta); + } + wl_pointer_send_axis(d->resource, d->seat->timestamp(), wlOrientation, wl_fixed_from_double(delta)); + } else if (version >= WL_POINTER_AXIS_STOP_SINCE_VERSION) { + wl_pointer_send_axis_stop(d->resource, d->seat->timestamp(), wlOrientation); + } + + d->sendFrame(); +} + void PointerInterface::axis(Qt::Orientation orientation, quint32 delta) { Q_D(); diff --git a/src/server/seat_interface.h b/src/server/seat_interface.h --- a/src/server/seat_interface.h +++ b/src/server/seat_interface.h @@ -43,6 +43,34 @@ class SurfaceInterface; class TextInputInterface; +/** + * Describes the source types for axis events. This indicates to the + * client how an axis event was physically generated; a client may + * adjust the user interface accordingly. For example, scroll events + * from a "finger" source may be in a smooth coordinate space with + * kinetic scrolling whereas a "wheel" source may be in discrete steps + * of a number of lines. + * + * The "continuous" axis source is a device generating events in a + * continuous coordinate space, but using something other than a + * finger. One example for this source is button-based scrolling where + * the vertical motion of a device is converted to scroll events while + * a button is held down. + * + * The "wheel tilt" axis source indicates that the actual device is a + * wheel but the scroll event is not caused by a rotation but a + * (usually sideways) tilt of the wheel. + * + * @since 5.59 + **/ +enum class PointerAxisSource { + Unknown, + Wheel, + Finger, + Continuous, + WheelTilt +}; + /** * @brief Represents a Seat on the Wayland Display. * @@ -361,6 +389,20 @@ * @returns the last serial for @p button. **/ quint32 pointerButtonSerial(Qt::MouseButton button) const; + /** + * Sends axis events to the currently focused pointer surface. + * + * @param orientation The scroll axis. + * @param delta The length of a vector along the specified axis @p orientation. + * @param discreteDelta The number of discrete steps, e.g. mouse wheel clicks. + * @param source Describes how the axis event was physically generated. + * @since 5.59 + * @todo Drop V5 suffix with KF6. + **/ + void pointerAxisV5(Qt::Orientation orientation, qreal delta, qint32 discreteDelta, PointerAxisSource source); + /** + * @see pointerAxisV5 + **/ void pointerAxis(Qt::Orientation orientation, quint32 delta); /** * @returns true if there is a pressed button with the given @p serial diff --git a/src/server/seat_interface.cpp b/src/server/seat_interface.cpp --- a/src/server/seat_interface.cpp +++ b/src/server/seat_interface.cpp @@ -846,6 +846,20 @@ return it.value() == Private::Pointer::State::Pressed ? true : false; } +void SeatInterface::pointerAxisV5(Qt::Orientation orientation, qreal delta, qint32 discreteDelta, PointerAxisSource source) +{ + Q_D(); + if (d->drag.mode == Private::Drag::Mode::Pointer) { + // ignore + return; + } + if (d->globalPointer.focus.surface) { + for (auto it = d->globalPointer.focus.pointers.constBegin(), end = d->globalPointer.focus.pointers.constEnd(); it != end; ++it) { + (*it)->axis(orientation, delta, discreteDelta, source); + } + } +} + void SeatInterface::pointerAxis(Qt::Orientation orientation, quint32 delta) { Q_D();