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 <davidedmundson@kde.org>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
This commit is contained in:
Vlad Zahorodnii 2022-10-24 22:13:33 +03:00
parent 425cceca83
commit 165b770fc5
9 changed files with 409 additions and 61 deletions

View File

@ -187,7 +187,7 @@
</event> </event>
</interface> </interface>
<interface name="wl_compositor" version="4"> <interface name="wl_compositor" version="5">
<description summary="the compositor singleton"> <description summary="the compositor singleton">
A compositor. This object is a singleton global. The A compositor. This object is a singleton global. The
compositor is in charge of combining the contents of multiple 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 for the pool from the file descriptor passed when the pool was
created, but using the new size. This request can only be created, but using the new size. This request can only be
used to make the pool bigger. 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.
</description> </description>
<arg name="size" type="int" summary="new size of the pool, in bytes"/> <arg name="size" type="int" summary="new size of the pool, in bytes"/>
</request> </request>
@ -271,8 +277,8 @@
Clients can create wl_shm_pool objects using the create_pool Clients can create wl_shm_pool objects using the create_pool
request. request.
At connection setup time, the wl_shm object emits one or more On binding the wl_shm object one or more format events
format events to inform clients about the valid pixel formats are emitted to inform clients about the valid pixel formats
that can be used for buffers. that can be used for buffers.
</description> </description>
@ -296,6 +302,9 @@
The drm format codes match the macros defined in drm_fourcc.h, except The drm format codes match the macros defined in drm_fourcc.h, except
argb8888 and xrgb8888. The formats actually supported by the compositor argb8888 and xrgb8888. The formats actually supported by the compositor
will be reported by the format event. 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.
</description> </description>
<!-- Note to protocol writers: don't update this list manually, instead <!-- Note to protocol writers: don't update this list manually, instead
run the automated script that keeps it in sync with drm_fourcc.h. --> run the automated script that keeps it in sync with drm_fourcc.h. -->
@ -403,6 +412,10 @@
<entry name="nv15" value="0x3531564e" summary="2x2 subsampled Cr:Cb plane"/> <entry name="nv15" value="0x3531564e" summary="2x2 subsampled Cr:Cb plane"/>
<entry name="q410" value="0x30313451"/> <entry name="q410" value="0x30313451"/>
<entry name="q401" value="0x31303451"/> <entry name="q401" value="0x31303451"/>
<entry name="xrgb16161616" value="0x38345258" summary="[63:0] x:R:G:B 16:16:16:16 little endian"/>
<entry name="xbgr16161616" value="0x38344258" summary="[63:0] x:B:G:R 16:16:16:16 little endian"/>
<entry name="argb16161616" value="0x38345241" summary="[63:0] A:R:G:B 16:16:16:16 little endian"/>
<entry name="abgr16161616" value="0x38344241" summary="[63:0] A:B:G:R 16:16:16:16 little endian"/>
</enum> </enum>
<request name="create_pool"> <request name="create_pool">
@ -431,10 +444,15 @@
<interface name="wl_buffer" version="1"> <interface name="wl_buffer" version="1">
<description summary="content for a wl_surface"> <description summary="content for a wl_surface">
A buffer provides the content for a wl_surface. Buffers are A buffer provides the content for a wl_surface. Buffers are
created through factory interfaces such as wl_drm, wl_shm or created through factory interfaces such as wl_shm, wp_linux_buffer_params
similar. It has a width and a height and can be attached to a (from the linux-dmabuf protocol extension) or similar. It has a width and
wl_surface, but the mechanism by which a client provides and a height and can be attached to a wl_surface, but the mechanism by which a
updates the contents is defined by the buffer factory interface. 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.
</description> </description>
<request name="destroy" type="destructor"> <request name="destroy" type="destructor">
@ -878,7 +896,7 @@
which will subsequently be used in either the which will subsequently be used in either the
data_device.enter event (for drag-and-drop) or the data_device.enter event (for drag-and-drop) or the
data_device.selection event (for selections). Immediately 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 object will send out data_offer.offer events to describe the
mime types it offers. mime types it offers.
</description> </description>
@ -948,9 +966,10 @@
immediately before receiving keyboard focus and when a new immediately before receiving keyboard focus and when a new
selection is set while the client has keyboard focus. The selection is set while the client has keyboard focus. The
data_offer is valid until a new data_offer or NULL is received data_offer is valid until a new data_offer or NULL is received
or until the client loses keyboard focus. The client must or until the client loses keyboard focus. Switching surface with
destroy the previous selection data_offer, if any, upon receiving keyboard focus within the same client doesn't mean a new selection
this event. will be sent. The client must destroy the previous selection
data_offer, if any, upon receiving this event.
</description> </description>
<arg name="id" type="object" interface="wl_data_offer" allow-null="true" <arg name="id" type="object" interface="wl_data_offer" allow-null="true"
summary="selection data_offer object"/> summary="selection data_offer object"/>
@ -1038,7 +1057,8 @@
a basic surface. a basic surface.
Note! This protocol is deprecated and not intended for production use. 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.
</description> </description>
<enum name="error"> <enum name="error">
@ -1332,7 +1352,7 @@
</event> </event>
</interface> </interface>
<interface name="wl_surface" version="4"> <interface name="wl_surface" version="5">
<description summary="an onscreen surface"> <description summary="an onscreen surface">
A surface is a rectangular area that may be displayed on zero 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 or more outputs, and shown any number of times at the compositor's
@ -1384,6 +1404,7 @@
<entry name="invalid_scale" value="0" summary="buffer scale value is invalid"/> <entry name="invalid_scale" value="0" summary="buffer scale value is invalid"/>
<entry name="invalid_transform" value="1" summary="buffer transform value is invalid"/> <entry name="invalid_transform" value="1" summary="buffer transform value is invalid"/>
<entry name="invalid_size" value="2" summary="buffer size is invalid"/> <entry name="invalid_size" value="2" summary="buffer size is invalid"/>
<entry name="invalid_offset" value="3" summary="buffer offset is invalid"/>
</enum> </enum>
<request name="destroy" type="destructor"> <request name="destroy" type="destructor">
@ -1406,7 +1427,14 @@
buffer's upper left corner, relative to the current buffer's upper buffer's upper left corner, relative to the current buffer's upper
left corner, in surface-local coordinates. In other words, the left corner, in surface-local coordinates. In other words, the
x and y, combined with the new surface size define in which 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. 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. from the same backing storage or use wp_linux_buffer_release.
Destroying the wl_buffer after wl_buffer.release does not change Destroying the wl_buffer after wl_buffer.release does not change
the surface contents. However, if the client destroys the the surface contents. Destroying the wl_buffer before wl_buffer.release
wl_buffer before receiving the wl_buffer.release event, the surface is allowed as long as the underlying buffer storage isn't re-used (this
contents become undefined immediately. 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 If wl_surface.attach is sent with a NULL wl_buffer, the
following wl_surface.commit will remove the surface content. following wl_surface.commit will remove the surface content.
@ -1734,9 +1765,30 @@
<arg name="width" type="int" summary="width of damage rectangle"/> <arg name="width" type="int" summary="width of damage rectangle"/>
<arg name="height" type="int" summary="height of damage rectangle"/> <arg name="height" type="int" summary="height of damage rectangle"/>
</request> </request>
<!-- Version 5 additions -->
<request name="offset" since="5">
<description summary="set the surface contents offset">
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.
</description>
<arg name="x" type="int" summary="surface-local x coordinate"/>
<arg name="y" type="int" summary="surface-local y coordinate"/>
</request>
</interface> </interface>
<interface name="wl_seat" version="7"> <interface name="wl_seat" version="8">
<description summary="group of input devices"> <description summary="group of input devices">
A seat is a group of keyboards, pointer and touch devices. This 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 object is published as a global during start up, or when such a
@ -1838,9 +1890,22 @@
<event name="name" since="2"> <event name="name" since="2">
<description summary="unique identifier for this seat"> <description summary="unique identifier for this seat">
In a multiseat configuration this can be used by the client to help In a multi-seat configuration the seat name can be used by clients to
identify which physical devices the seat represents. Based on help identify which physical devices the seat represents.
the seat configuration used by the compositor.
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.
</description> </description>
<arg name="name" type="string" summary="seat identifier"/> <arg name="name" type="string" summary="seat identifier"/>
</event> </event>
@ -1856,7 +1921,7 @@
</interface> </interface>
<interface name="wl_pointer" version="7"> <interface name="wl_pointer" version="8">
<description summary="pointer input device"> <description summary="pointer input device">
The wl_pointer interface represents one or more input devices, The wl_pointer interface represents one or more input devices,
such as mice, which control the pointer location and pointer_focus 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 wl_surface is no longer used as the cursor. When the use as a
cursor ends, the current and pending input regions become cursor ends, the current and pending input regions become
undefined, and the wl_surface is unmapped. 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.
</description> </description>
<arg name="serial" type="uint" summary="serial number of the enter event"/> <arg name="serial" type="uint" summary="serial number of the enter event"/>
<arg name="surface" type="object" interface="wl_surface" allow-null="true" <arg name="surface" type="object" interface="wl_surface" allow-null="true"
@ -2152,6 +2221,9 @@
This event carries the axis value of the wl_pointer.axis event in This event carries the axis value of the wl_pointer.axis event in
discrete steps (e.g. mouse wheel clicks). discrete steps (e.g. mouse wheel clicks).
This event is deprecated with wl_pointer version 8 - this event is not
sent to clients supporting version 8 or later.
This event does not occur on its own, it is coupled with a This event does not occur on its own, it is coupled with a
wl_pointer.axis event that represents this axis value on a wl_pointer.axis event that represents this axis value on a
continuous scale. The protocol guarantees that each axis_discrete continuous scale. The protocol guarantees that each axis_discrete
@ -2159,7 +2231,8 @@
axis number within the same wl_pointer.frame. Note that the protocol axis number within the same wl_pointer.frame. Note that the protocol
allows for other events to occur between the axis_discrete and allows for other events to occur between the axis_discrete and
its coupled axis event, including other axis_discrete or axis its coupled axis event, including other axis_discrete or axis
events. events. A wl_pointer.frame must not contain more than one axis_discrete
event per axis type.
This event is optional; continuous scrolling devices This event is optional; continuous scrolling devices
like two-finger scrolling on touchpads do not have discrete like two-finger scrolling on touchpads do not have discrete
@ -2177,9 +2250,37 @@
<arg name="axis" type="uint" enum="axis" summary="axis type"/> <arg name="axis" type="uint" enum="axis" summary="axis type"/>
<arg name="discrete" type="int" summary="number of steps"/> <arg name="discrete" type="int" summary="number of steps"/>
</event> </event>
<event name="axis_value120" since="8">
<description summary="axis high-resolution scroll event">
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.
</description>
<arg name="axis" type="uint" enum="axis" summary="axis type"/>
<arg name="value120" type="int" summary="scroll distance as fraction of 120"/>
</event>
</interface> </interface>
<interface name="wl_keyboard" version="7"> <interface name="wl_keyboard" version="8">
<description summary="keyboard input device"> <description summary="keyboard input device">
The wl_keyboard interface represents one or more keyboards The wl_keyboard interface represents one or more keyboards
associated with a seat. associated with a seat.
@ -2193,13 +2294,14 @@
<entry name="no_keymap" value="0" <entry name="no_keymap" value="0"
summary="no keymap; client must understand how to interpret the raw keycode"/> summary="no keymap; client must understand how to interpret the raw keycode"/>
<entry name="xkb_v1" value="1" <entry name="xkb_v1" value="1"
summary="libxkbcommon compatible; to determine the xkb keycode, clients must add 8 to the key event keycode"/> summary="libxkbcommon compatible, null-terminated string; to determine the xkb keycode, clients must add 8 to the key event keycode"/>
</enum> </enum>
<event name="keymap"> <event name="keymap">
<description summary="keyboard mapping"> <description summary="keyboard mapping">
This event provides a file descriptor to the client which can be 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 From version 7 onwards, the fd must be mapped with MAP_PRIVATE by
the recipient, as MAP_SHARED may fail. the recipient, as MAP_SHARED may fail.
@ -2305,7 +2407,7 @@
</event> </event>
</interface> </interface>
<interface name="wl_touch" version="7"> <interface name="wl_touch" version="8">
<description summary="touchscreen input device"> <description summary="touchscreen input device">
The wl_touch interface represents a touchscreen The wl_touch interface represents a touchscreen
associated with a seat. associated with a seat.
@ -2449,7 +2551,7 @@
</event> </event>
</interface> </interface>
<interface name="wl_output" version="3"> <interface name="wl_output" version="4">
<description summary="compositor output region"> <description summary="compositor output region">
An output describes part of the compositor geometry. The An output describes part of the compositor geometry. The
compositor works in the 'compositor coordinate system' and an 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 The physical size can be set to zero if it doesn't make sense for this
output (e.g. for projectors or virtual outputs). 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 Note: wl_output only advertises partial information about the output
position and identification. Some compositors, for instance those not position and identification. Some compositors, for instance those not
implementing a desktop-style output layout or those exposing virtual implementing a desktop-style output layout or those exposing virtual
outputs, might fake this information. Instead of using x and y, clients outputs, might fake this information. Instead of using x and y, clients
should use xdg_output.logical_position. Instead of using make and model, 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.
</description> </description>
<arg name="x" type="int" <arg name="x" type="int"
summary="x position within the global compositor space"/> summary="x position within the global compositor space"/>
@ -2566,6 +2671,9 @@
The vertical refresh rate can be set to zero if it doesn't make The vertical refresh rate can be set to zero if it doesn't make
sense for this output (e.g. for virtual outputs). 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, Clients should not use the refresh rate to schedule frames. Instead,
they should use the wl_surface.frame event or the presentation-time they should use the wl_surface.frame event or the presentation-time
protocol. protocol.
@ -2612,6 +2720,8 @@
the scale of the output. That way the compositor can the scale of the output. That way the compositor can
avoid scaling the surface, and the client can supply avoid scaling the surface, and the client can supply
a higher detail image. a higher detail image.
The scale event will be followed by a done event.
</description> </description>
<arg name="factor" type="int" summary="scaling factor of output"/> <arg name="factor" type="int" summary="scaling factor of output"/>
</event> </event>
@ -2624,6 +2734,62 @@
use the output object anymore. use the output object anymore.
</description> </description>
</request> </request>
<!-- Version 4 additions -->
<event name="name" since="4">
<description summary="name of this output">
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.
</description>
<arg name="name" type="string" summary="output name"/>
</event>
<event name="description" since="4">
<description summary="human-readable description of this output">
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.
</description>
<arg name="description" type="string" summary="output description"/>
</event>
</interface> </interface>
<interface name="wl_region" version="1"> <interface name="wl_region" version="1">

View File

@ -375,7 +375,7 @@ QWaylandInputDevice::Touch::~Touch()
} }
QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, uint32_t id) 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) , mQDisplay(display)
, mDisplay(display->wl_display()) , mDisplay(display->wl_display())
{ {
@ -982,14 +982,16 @@ void QWaylandInputDevice::Pointer::pointer_axis_discrete(uint32_t axis, int32_t
if (!focusWindow()) if (!focusWindow())
return; return;
const int32_t delta120 = value * 15 * 8;
switch (axis) { switch (axis) {
case axis_vertical_scroll: case axis_vertical_scroll:
qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_discrete vertical:" << value; qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_discrete vertical:" << value;
mFrameData.discreteDelta.ry() += value; mFrameData.delta120.ry() += delta120;
break; break;
case axis_horizontal_scroll: case axis_horizontal_scroll:
qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_discrete horizontal:" << value; qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_discrete horizontal:" << value;
mFrameData.discreteDelta.rx() += value; mFrameData.delta120.rx() += delta120;
break; break;
default: default:
//TODO: is this really needed? //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) void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event)
{ {
qCDebug(lcQpaWaylandInput) << "Setting frame event " << event->type; qCDebug(lcQpaWaylandInput) << "Setting frame event " << event->type;
@ -1016,7 +1038,7 @@ void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event)
void QWaylandInputDevice::Pointer::FrameData::resetScrollData() void QWaylandInputDevice::Pointer::FrameData::resetScrollData()
{ {
discreteDelta = QPoint(); delta120 = QPoint();
delta = QPointF(); delta = QPointF();
axisSource = axis_source_wheel; axisSource = axis_source_wheel;
} }
@ -1060,15 +1082,16 @@ QPoint QWaylandInputDevice::Pointer::FrameData::pixelDeltaAndError(QPointF *accu
QPoint QWaylandInputDevice::Pointer::FrameData::angleDelta() const 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 // If we didn't get any discrete events, then we need to fall back to
// the continuous information. // the continuous information.
return (delta * -12).toPoint(); //TODO: why multiply by 12? return (delta * -12).toPoint(); //TODO: why multiply by 12?
} }
// The angle delta is in eights of degrees, and our docs says most mice have // 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. // 1 click = 15 degrees, i.e. 120 is one click. It's also in the opposite
return -discreteDelta * 15 * 8; // direction of surface space.
return -delta120;
} }
Qt::MouseEventSource QWaylandInputDevice::Pointer::FrameData::wheelEventSource() const Qt::MouseEventSource QWaylandInputDevice::Pointer::FrameData::wheelEventSource() const

View File

@ -308,6 +308,7 @@ protected:
void pointer_axis_stop(uint32_t time, uint32_t axis) override; void pointer_axis_stop(uint32_t time, uint32_t axis) override;
void pointer_axis_discrete(uint32_t axis, int32_t value) override; void pointer_axis_discrete(uint32_t axis, int32_t value) override;
void pointer_frame() override; void pointer_frame() override;
void pointer_axis_value120(uint32_t axis, int32_t value120) override;
private slots: private slots:
void handleFocusDestroyed() { invalidateFocus(); } void handleFocusDestroyed() { invalidateFocus(); }
@ -343,7 +344,7 @@ public:
QWaylandPointerEvent *event = nullptr; QWaylandPointerEvent *event = nullptr;
QPointF delta; QPointF delta;
QPoint discreteDelta; QPoint delta120;
axis_source axisSource = axis_source_wheel; axis_source axisSource = axis_source_wheel;
void resetScrollData(); void resetScrollData();

View File

@ -17,6 +17,7 @@ if (NOT WEBOS)
add_subdirectory(output) add_subdirectory(output)
add_subdirectory(primaryselectionv1) add_subdirectory(primaryselectionv1)
add_subdirectory(seatv4) add_subdirectory(seatv4)
add_subdirectory(seatv7)
add_subdirectory(seat) add_subdirectory(seat)
add_subdirectory(surface) add_subdirectory(surface)
add_subdirectory(tabletv2) add_subdirectory(tabletv2)

View File

@ -18,7 +18,7 @@ public:
removeAll<Seat>(); removeAll<Seat>();
uint capabilities = MockCompositor::Seat::capability_pointer | MockCompositor::Seat::capability_touch; uint capabilities = MockCompositor::Seat::capability_pointer | MockCompositor::Seat::capability_touch;
int version = 7; int version = 8;
add<Seat>(capabilities, version); add<Seat>(capabilities, version);
}); });
} }
@ -40,8 +40,7 @@ private slots:
void fingerScroll(); void fingerScroll();
void fingerScrollSlow(); void fingerScrollSlow();
void continuousScroll(); void continuousScroll();
void wheelDiscreteScroll_data(); void highResolutionScroll();
void wheelDiscreteScroll();
// Touch tests // Touch tests
void createsTouch(); void createsTouch();
@ -56,13 +55,13 @@ private slots:
void tst_seat::bindsToSeat() void tst_seat::bindsToSeat()
{ {
QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().size(), 1); QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().size(), 1);
QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().first()->version(), 7); QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().first()->version(), 8);
} }
void tst_seat::createsPointer() void tst_seat::createsPointer()
{ {
QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().size(), 1); 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() void tst_seat::setsCursorOnEnter()
@ -320,28 +319,19 @@ void tst_seat::fingerScrollSlow()
QCOMPARE(accumulated.y(), -1); QCOMPARE(accumulated.y(), -1);
} }
void tst_seat::wheelDiscreteScroll_data() void tst_seat::highResolutionScroll()
{
QTest::addColumn<uint>("source");
QTest::newRow("wheel") << uint(Pointer::axis_source_wheel);
QTest::newRow("wheel tilt") << uint(Pointer::axis_source_wheel_tilt);
}
void tst_seat::wheelDiscreteScroll()
{ {
WheelWindow window; WheelWindow window;
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
QFETCH(uint, source);
exec([=] { exec([=] {
auto *p = pointer(); auto *p = pointer();
auto *c = client(); auto *c = client();
p->sendEnter(xdgToplevel()->surface(), {32, 32}); p->sendEnter(xdgToplevel()->surface(), {32, 32});
p->sendFrame(c); p->sendFrame(c);
p->sendAxisSource(c, Pointer::axis_source(source)); p->sendAxisSource(c, Pointer::axis_source_wheel);
p->sendAxisDiscrete(c, Pointer::axis_vertical_scroll, 1); // 1 click downwards p->sendAxisValue120(c, Pointer::axis_vertical_scroll, 30); // quarter of a click
p->sendAxis(c, Pointer::axis_vertical_scroll, 1.0); p->sendAxis(c, Pointer::axis_vertical_scroll, 3.75);
p->sendFrame(c); p->sendFrame(c);
}); });
@ -350,10 +340,26 @@ void tst_seat::wheelDiscreteScroll()
auto e = window.m_events.takeFirst(); auto e = window.m_events.takeFirst();
QCOMPARE(e.phase, Qt::NoScrollPhase); QCOMPARE(e.phase, Qt::NoScrollPhase);
QVERIFY(qAbs(e.angleDelta.x()) <= qAbs(e.angleDelta.y())); // Vertical scroll 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 QCOMPARE(e.angleDelta, QPoint(0, -30));
// 1 click = 15 degrees. The angle delta should therefore be: // Click scrolls are not continuous and should not have a pixel delta
// 15 degrees / (1/8 eights per degrees) = 120 eights of degrees. QCOMPARE(e.pixelDelta, QPoint(0, 0));
QCOMPARE(e.angleDelta, QPoint(0, -120)); }
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);
});
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, -90));
// Click scrolls are not continuous and should not have a pixel delta // Click scrolls are not continuous and should not have a pixel delta
QCOMPARE(e.pixelDelta, QPoint(0, 0)); QCOMPARE(e.pixelDelta, QPoint(0, 0));
} }
@ -388,7 +394,7 @@ void tst_seat::continuousScroll()
void tst_seat::createsTouch() void tst_seat::createsTouch()
{ {
QCOMPOSITOR_TRY_COMPARE(touch()->resourceMap().size(), 1); 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 { class TouchWindow : public QRasterWindow {

View File

@ -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
)

View File

@ -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 <QtOpenGL/QOpenGLWindow>
#include <QtGui/QRasterWindow>
#include <QtGui/QEventPoint>
using namespace MockCompositor;
class SeatCompositor : public DefaultCompositor {
public:
explicit SeatCompositor()
{
exec([this] {
m_config.autoConfigure = true;
removeAll<Seat>();
uint capabilities = MockCompositor::Seat::capability_pointer | MockCompositor::Seat::capability_touch;
int version = 7;
add<Seat>(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<Seat>()->resourceMap().size(), 1);
QCOMPOSITOR_COMPARE(get<Seat>()->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<Event> m_events;
};
void tst_seatv7::wheelDiscreteScroll_data()
{
QTest::addColumn<uint>("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"

View File

@ -423,6 +423,13 @@ void Pointer::sendFrame(wl_client *client)
send_frame(r->handle); 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) void Pointer::pointer_set_cursor(Resource *resource, uint32_t serial, wl_resource *surface, int32_t hotspot_x, int32_t hotspot_y)
{ {
Q_UNUSED(resource); Q_UNUSED(resource);

View File

@ -296,7 +296,7 @@ class Seat : public Global, public QtWaylandServer::wl_seat
{ {
Q_OBJECT Q_OBJECT
public: 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; ~Seat() override;
void send_capabilities(Resource *resource, uint capabilities) = delete; // Use wrapper instead void send_capabilities(Resource *resource, uint capabilities) = delete; // Use wrapper instead
void send_capabilities(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 sendAxisSource(wl_client *client, axis_source source);
void sendAxisStop(wl_client *client, axis axis); void sendAxisStop(wl_client *client, axis axis);
void sendFrame(wl_client *client); void sendFrame(wl_client *client);
void sendAxisValue120(wl_client *client, axis axis, int value120);
Seat *m_seat = nullptr; Seat *m_seat = nullptr;
QList<uint> m_enterSerials; QList<uint> m_enterSerials;