summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohan Klokkhammer Helsing <johan.helsing@qt.io>2019-12-17 12:08:54 +0100
committerJohan Klokkhammer Helsing <johan.helsing@qt.io>2020-01-08 15:33:50 +0100
commit222455cd643c128fa9730c9c527a7fdcadd0acfe (patch)
treec1a6c8f39cb9d1405ade2bc63d380a38ff4f36d3
parent7542dc765002d0512ed56f23f9f5727c77ae7aff (diff)
Client: Add basic support for tablet-unstable-v2
[ChangeLog][QPA plugin] Added support for drawing tablets through the tablet-unstable-v2 protocol. Adds support for the basic one tablet, one tool, no pads scenario. Besides the auto tests, I tested with a Huion Kamvas Pro 16 with a simple pressure sensitive pen with two buttons. As far as I can tell, it works the same way as on xcb on Sway and Gnome Shell. Also tested on a Wacom PTZ-630 with a mouse, art pen, airbrush and a stylus. Mapped the distance event to QTabletEvent::Z and slider to QTabletEvent::tangentialPressure. For now we send QTabletEvents even when there's a mouse or finger on the tablet. Those should ideally be sent as QMouseEvents/QTouchEvents, but that's out of scope for this patch, but at least we will send synthesized mouse events if the tablet events are not accepted. Change-Id: I93291ffa5f00fa2bb8533eddd8d873b84a3386b8 Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
-rw-r--r--src/3rdparty/protocol/qt_attribution.json18
-rw-r--r--src/3rdparty/protocol/tablet-unstable-v2.xml1178
-rw-r--r--src/client/client.pro3
-rw-r--r--src/client/qwaylanddisplay.cpp3
-rw-r--r--src/client/qwaylanddisplay_p.h3
-rw-r--r--src/client/qwaylandinputdevice.cpp3
-rw-r--r--src/client/qwaylandinputdevice_p.h5
-rw-r--r--src/client/qwaylandintegration.cpp2
-rw-r--r--src/client/qwaylandtabletv2.cpp332
-rw-r--r--src/client/qwaylandtabletv2_p.h191
-rw-r--r--sync.profile2
-rw-r--r--tests/auto/client/client.pro1
-rw-r--r--tests/auto/client/tabletv2/tabletv2.pro7
-rw-r--r--tests/auto/client/tabletv2/tst_tabletv2.cpp918
14 files changed, 2666 insertions, 0 deletions
diff --git a/src/3rdparty/protocol/qt_attribution.json b/src/3rdparty/protocol/qt_attribution.json
index c49ead4f..dad8c425 100644
--- a/src/3rdparty/protocol/qt_attribution.json
+++ b/src/3rdparty/protocol/qt_attribution.json
@@ -90,6 +90,24 @@ Copyright (c) 2013 BMW Car IT GmbH"
},
{
+ "Id": "wayland-tablet-protocol",
+ "Name": "Wayland Tablet Protocol",
+ "QDocModule": "qtwaylandcompositor",
+ "QtUsage": "Used in the Qt Wayland platform plugin",
+ "Files": "tablet-unstable-v2.xml",
+
+ "Description": "",
+ "Homepage": "https://wayland.freedesktop.org",
+ "Version": "unstable v2, version 1",
+ "DownloadLocation": "https://cgit.freedesktop.org/wayland/wayland-protocols/plain/unstable/tablet/tablet-unstable-v2.xml",
+ "LicenseId": "MIT",
+ "License": "MIT License",
+ "LicenseFile": "MIT_LICENSE.txt",
+ "Copyright": "Copyright 2014 © Stephen "Lyude" Chandler Paul
+Copyright 2015-2016 © Red Hat, Inc."
+ },
+
+ {
"Id": "wayland-viewporter-protocol",
"Name": "Wayland Viewporter Protocol",
"QDocModule": "qtwaylandcompositor",
diff --git a/src/3rdparty/protocol/tablet-unstable-v2.xml b/src/3rdparty/protocol/tablet-unstable-v2.xml
new file mode 100644
index 00000000..b286d964
--- /dev/null
+++ b/src/3rdparty/protocol/tablet-unstable-v2.xml
@@ -0,0 +1,1178 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="tablet_unstable_v2">
+
+ <copyright>
+ Copyright 2014 © Stephen "Lyude" Chandler Paul
+ Copyright 2015-2016 © Red Hat, Inc.
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the
+ next paragraph) shall be included in all copies or substantial
+ portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ </copyright>
+
+ <description summary="Wayland protocol for graphics tablets">
+ This description provides a high-level overview of the interplay between
+ the interfaces defined this protocol. For details, see the protocol
+ specification.
+
+ More than one tablet may exist, and device-specifics matter. Tablets are
+ not represented by a single virtual device like wl_pointer. A client
+ binds to the tablet manager object which is just a proxy object. From
+ that, the client requests wp_tablet_manager.get_tablet_seat(wl_seat)
+ and that returns the actual interface that has all the tablets. With
+ this indirection, we can avoid merging wp_tablet into the actual Wayland
+ protocol, a long-term benefit.
+
+ The wp_tablet_seat sends a "tablet added" event for each tablet
+ connected. That event is followed by descriptive events about the
+ hardware; currently that includes events for name, vid/pid and
+ a wp_tablet.path event that describes a local path. This path can be
+ used to uniquely identify a tablet or get more information through
+ libwacom. Emulated or nested tablets can skip any of those, e.g. a
+ virtual tablet may not have a vid/pid. The sequence of descriptive
+ events is terminated by a wp_tablet.done event to signal that a client
+ may now finalize any initialization for that tablet.
+
+ Events from tablets require a tool in proximity. Tools are also managed
+ by the tablet seat; a "tool added" event is sent whenever a tool is new
+ to the compositor. That event is followed by a number of descriptive
+ events about the hardware; currently that includes capabilities,
+ hardware id and serial number, and tool type. Similar to the tablet
+ interface, a wp_tablet_tool.done event is sent to terminate that initial
+ sequence.
+
+ Any event from a tool happens on the wp_tablet_tool interface. When the
+ tool gets into proximity of the tablet, a proximity_in event is sent on
+ the wp_tablet_tool interface, listing the tablet and the surface. That
+ event is followed by a motion event with the coordinates. After that,
+ it's the usual motion, axis, button, etc. events. The protocol's
+ serialisation means events are grouped by wp_tablet_tool.frame events.
+
+ Two special events (that don't exist in X) are down and up. They signal
+ "tip touching the surface". For tablets without real proximity
+ detection, the sequence is: proximity_in, motion, down, frame.
+
+ When the tool leaves proximity, a proximity_out event is sent. If any
+ button is still down, a button release event is sent before this
+ proximity event. These button events are sent in the same frame as the
+ proximity event to signal to the client that the buttons were held when
+ the tool left proximity.
+
+ If the tool moves out of the surface but stays in proximity (i.e.
+ between windows), compositor-specific grab policies apply. This usually
+ means that the proximity-out is delayed until all buttons are released.
+
+ Moving a tool physically from one tablet to the other has no real effect
+ on the protocol, since we already have the tool object from the "tool
+ added" event. All the information is already there and the proximity
+ events on both tablets are all a client needs to reconstruct what
+ happened.
+
+ Some extra axes are normalized, i.e. the client knows the range as
+ specified in the protocol (e.g. [0, 65535]), the granularity however is
+ unknown. The current normalized axes are pressure, distance, and slider.
+
+ Other extra axes are in physical units as specified in the protocol.
+ The current extra axes with physical units are tilt, rotation and
+ wheel rotation.
+
+ Since tablets work independently of the pointer controlled by the mouse,
+ the focus handling is independent too and controlled by proximity.
+ The wp_tablet_tool.set_cursor request sets a tool-specific cursor.
+ This cursor surface may be the same as the mouse cursor, and it may be
+ the same across tools but it is possible to be more fine-grained. For
+ example, a client may set different cursors for the pen and eraser.
+
+ Tools are generally independent of tablets and it is
+ compositor-specific policy when a tool can be removed. Common approaches
+ will likely include some form of removing a tool when all tablets the
+ tool was used on are removed.
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible changes
+ may be added together with the corresponding interface version bump.
+ Backward incompatible changes are done by bumping the version number in
+ the protocol and interface names and resetting the interface version.
+ Once the protocol is to be declared stable, the 'z' prefix and the
+ version number in the protocol and interface names are removed and the
+ interface version number is reset.
+ </description>
+
+ <interface name="zwp_tablet_manager_v2" version="1">
+ <description summary="controller object for graphic tablet devices">
+ An object that provides access to the graphics tablets available on this
+ system. All tablets are associated with a seat, to get access to the
+ actual tablets, use wp_tablet_manager.get_tablet_seat.
+ </description>
+
+ <request name="get_tablet_seat">
+ <description summary="get the tablet seat">
+ Get the wp_tablet_seat object for the given seat. This object
+ provides access to all graphics tablets in this seat.
+ </description>
+ <arg name="tablet_seat" type="new_id" interface="zwp_tablet_seat_v2"/>
+ <arg name="seat" type="object" interface="wl_seat" summary="The wl_seat object to retrieve the tablets for" />
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="release the memory for the tablet manager object">
+ Destroy the wp_tablet_manager object. Objects created from this
+ object are unaffected and should be destroyed separately.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwp_tablet_seat_v2" version="1">
+ <description summary="controller object for graphic tablet devices of a seat">
+ An object that provides access to the graphics tablets available on this
+ seat. After binding to this interface, the compositor sends a set of
+ wp_tablet_seat.tablet_added and wp_tablet_seat.tool_added events.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="release the memory for the tablet seat object">
+ Destroy the wp_tablet_seat object. Objects created from this
+ object are unaffected and should be destroyed separately.
+ </description>
+ </request>
+
+ <event name="tablet_added">
+ <description summary="new device notification">
+ This event is sent whenever a new tablet becomes available on this
+ seat. This event only provides the object id of the tablet, any
+ static information about the tablet (device name, vid/pid, etc.) is
+ sent through the wp_tablet interface.
+ </description>
+ <arg name="id" type="new_id" interface="zwp_tablet_v2" summary="the newly added graphics tablet"/>
+ </event>
+
+ <event name="tool_added">
+ <description summary="a new tool has been used with a tablet">
+ This event is sent whenever a tool that has not previously been used
+ with a tablet comes into use. This event only provides the object id
+ of the tool; any static information about the tool (capabilities,
+ type, etc.) is sent through the wp_tablet_tool interface.
+ </description>
+ <arg name="id" type="new_id" interface="zwp_tablet_tool_v2" summary="the newly added tablet tool"/>
+ </event>
+
+ <event name="pad_added">
+ <description summary="new pad notification">
+ This event is sent whenever a new pad is known to the system. Typically,
+ pads are physically attached to tablets and a pad_added event is
+ sent immediately after the wp_tablet_seat.tablet_added.
+ However, some standalone pad devices logically attach to tablets at
+ runtime, and the client must wait for wp_tablet_pad.enter to know
+ the tablet a pad is attached to.
+
+ This event only provides the object id of the pad. All further
+ features (buttons, strips, rings) are sent through the wp_tablet_pad
+ interface.
+ </description>
+ <arg name="id" type="new_id" interface="zwp_tablet_pad_v2" summary="the newly added pad"/>
+ </event>
+ </interface>
+
+ <interface name="zwp_tablet_tool_v2" version="1">
+ <description summary="a physical tablet tool">
+ An object that represents a physical tool that has been, or is
+ currently in use with a tablet in this seat. Each wp_tablet_tool
+ object stays valid until the client destroys it; the compositor
+ reuses the wp_tablet_tool object to indicate that the object's
+ respective physical tool has come into proximity of a tablet again.
+
+ A wp_tablet_tool object's relation to a physical tool depends on the
+ tablet's ability to report serial numbers. If the tablet supports
+ this capability, then the object represents a specific physical tool
+ and can be identified even when used on multiple tablets.
+
+ A tablet tool has a number of static characteristics, e.g. tool type,
+ hardware_serial and capabilities. These capabilities are sent in an
+ event sequence after the wp_tablet_seat.tool_added event before any
+ actual events from this tool. This initial event sequence is
+ terminated by a wp_tablet_tool.done event.
+
+ Tablet tool events are grouped by wp_tablet_tool.frame events.
+ Any events received before a wp_tablet_tool.frame event should be
+ considered part of the same hardware state change.
+ </description>
+
+ <request name="set_cursor">
+ <description summary="set the tablet tool's surface">
+ Sets the surface of the cursor used for this tool on the given
+ tablet. This request only takes effect if the tool is in proximity
+ of one of the requesting client's surfaces or the surface parameter
+ is the current pointer surface. If there was a previous surface set
+ with this request it is replaced. If surface is NULL, the cursor
+ image is hidden.
+
+ The parameters hotspot_x and hotspot_y define the position of the
+ pointer surface relative to the pointer location. Its top-left corner
+ is always at (x, y) - (hotspot_x, hotspot_y), where (x, y) are the
+ coordinates of the pointer location, in surface-local coordinates.
+
+ On surface.attach requests to the pointer surface, hotspot_x and
+ hotspot_y are decremented by the x and y parameters passed to the
+ request. Attach must be confirmed by wl_surface.commit as usual.
+
+ The hotspot can also be updated by passing the currently set pointer
+ surface to this request with new values for hotspot_x and hotspot_y.
+
+ The current and pending input regions of the wl_surface are cleared,
+ and wl_surface.set_input_region is ignored until the 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.
+
+ This request gives the surface the role of a wp_tablet_tool cursor. A
+ surface may only ever be used as the cursor surface for one
+ wp_tablet_tool. If the surface already has another role or has
+ previously been used as cursor surface for a different tool, a
+ protocol error is raised.
+ </description>
+ <arg name="serial" type="uint" summary="serial of the enter event"/>
+ <arg name="surface" type="object" interface="wl_surface" allow-null="true"/>
+ <arg name="hotspot_x" type="int" summary="surface-local x coordinate"/>
+ <arg name="hotspot_y" type="int" summary="surface-local y coordinate"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the tool object">
+ This destroys the client's resource for this tool object.
+ </description>
+ </request>
+
+ <enum name="type">
+ <description summary="a physical tool type">
+ Describes the physical type of a tool. The physical type of a tool
+ generally defines its base usage.
+
+ The mouse tool represents a mouse-shaped tool that is not a relative
+ device but bound to the tablet's surface, providing absolute
+ coordinates.
+
+ The lens tool is a mouse-shaped tool with an attached lens to
+ provide precision focus.
+ </description>
+ <entry name="pen" value="0x140" summary="Pen"/>
+ <entry name="eraser" value="0x141" summary="Eraser"/>
+ <entry name="brush" value="0x142" summary="Brush"/>
+ <entry name="pencil" value="0x143" summary="Pencil"/>
+ <entry name="airbrush" value="0x144" summary="Airbrush"/>
+ <entry name="finger" value="0x145" summary="Finger"/>
+ <entry name="mouse" value="0x146" summary="Mouse"/>
+ <entry name="lens" value="0x147" summary="Lens"/>
+ </enum>
+
+ <event name="type">
+ <description summary="tool type">
+ The tool type is the high-level type of the tool and usually decides
+ the interaction expected from this tool.
+
+ This event is sent in the initial burst of events before the
+ wp_tablet_tool.done event.
+ </description>
+ <arg name="tool_type" type="uint" enum="type" summary="the physical tool type"/>
+ </event>
+
+ <event name="hardware_serial">
+ <description summary="unique hardware serial number of the tool">
+ If the physical tool can be identified by a unique 64-bit serial
+ number, this event notifies the client of this serial number.
+
+ If multiple tablets are available in the same seat and the tool is
+ uniquely identifiable by the serial number, that tool may move
+ between tablets.
+
+ Otherwise, if the tool has no serial number and this event is
+ missing, the tool is tied to the tablet it first comes into
+ proximity with. Even if the physical tool is used on multiple
+ tablets, separate wp_tablet_tool objects will be created, one per
+ tablet.
+
+ This event is sent in the initial burst of events before the
+ wp_tablet_tool.done event.
+ </description>
+ <arg name="hardware_serial_hi" type="uint" summary="the unique serial number of the tool, most significant bits"/>
+ <arg name="hardware_serial_lo" type="uint" summary="the unique serial number of the tool, least significant bits"/>
+ </event>
+
+ <event name="hardware_id_wacom">
+ <description summary="hardware id notification in Wacom's format">
+ This event notifies the client of a hardware id available on this tool.
+
+ The hardware id is a device-specific 64-bit id that provides extra
+ information about the tool in use, beyond the wl_tool.type
+ enumeration. The format of the id is specific to tablets made by
+ Wacom Inc. For example, the hardware id of a Wacom Grip
+ Pen (a stylus) is 0x802.
+
+ This event is sent in the initial burst of events before the
+ wp_tablet_tool.done event.
+ </description>
+ <arg name="hardware_id_hi" type="uint" summary="the hardware id, most significant bits"/>
+ <arg name="hardware_id_lo" type="uint" summary="the hardware id, least significant bits"/>
+ </event>
+
+ <enum name="capability">
+ <description summary="capability flags for a tool">
+ Describes extra capabilities on a tablet.
+
+ Any tool must provide x and y values, extra axes are
+ device-specific.
+ </description>
+ <entry name="tilt" value="1" summary="Tilt axes"/>
+ <entry name="pressure" value="2" summary="Pressure axis"/>
+ <entry name="distance" value="3" summary="Distance axis"/>
+ <entry name="rotation" value="4" summary="Z-rotation axis"/>
+ <entry name="slider" value="5" summary="Slider axis"/>
+ <entry name="wheel" value="6" summary="Wheel axis"/>
+ </enum>
+
+ <event name="capability">
+ <description summary="tool capability notification">
+ This event notifies the client of any capabilities of this tool,
+ beyond the main set of x/y axes and tip up/down detection.
+
+ One event is sent for each extra capability available on this tool.
+
+ This event is sent in the initial burst of events before the
+ wp_tablet_tool.done event.
+ </description>
+ <arg name="capability" type="uint" enum="capability" summary="the capability"/>
+ </event>
+
+ <event name="done">
+ <description summary="tool description events sequence complete">
+ This event signals the end of the initial burst of descriptive
+ events. A client may consider the static description of the tool to
+ be complete and finalize initialization of the tool.
+ </description>
+ </event>
+
+ <event name="removed">
+ <description summary="tool removed">
+ This event is sent when the tool is removed from the system and will
+ send no further events. Should the physical tool come back into
+ proximity later, a new wp_tablet_tool object will be created.
+
+ It is compositor-dependent when a tool is removed. A compositor may
+ remove a tool on proximity out, tablet removal or any other reason.
+ A compositor may also keep a tool alive until shutdown.
+
+ If the tool is currently in proximity, a proximity_out event will be
+ sent before the removed event. See wp_tablet_tool.proximity_out for
+ the handling of any buttons logically down.
+
+ When this event is received, the client must wp_tablet_tool.destroy
+ the object.
+ </description>
+ </event>
+
+ <event name="proximity_in">
+ <description summary="proximity in event">
+ Notification that this tool is focused on a certain surface.
+
+ This event can be received when the tool has moved from one surface to
+ another, or when the tool has come back into proximity above the
+ surface.
+
+ If any button is logically down when the tool comes into proximity,
+ the respective button event is sent after the proximity_in event but
+ within the same frame as the proximity_in event.
+ </description>
+ <arg name="serial" type="uint"/>
+ <arg name="tablet" type="object" interface="zwp_tablet_v2" summary="The tablet the tool is in proximity of"/>
+ <arg name="surface" type="object" interface="wl_surface" summary="The current surface the tablet tool is over"/>
+ </event>
+
+ <event name="proximity_out">
+ <description summary="proximity out event">
+ Notification that this tool has either left proximity, or is no
+ longer focused on a certain surface.
+
+ When the tablet tool leaves proximity of the tablet, button release
+ events are sent for each button that was held down at the time of
+ leaving proximity. These events are sent before the proximity_out
+ event but within the same wp_tablet.frame.
+
+ If the tool stays within proximity of the tablet, but the focus
+ changes from one surface to another, a button release event may not
+ be sent until the button is actually released or the tool leaves the
+ proximity of the tablet.
+ </description>
+ </event>
+
+ <event name="down">
+ <description summary="tablet tool is making contact">
+ Sent whenever the tablet tool comes in contact with the surface of the
+ tablet.
+
+ If the tool is already in contact with the tablet when entering the
+ input region, the client owning said region will receive a
+ wp_tablet.proximity_in event, followed by a wp_tablet.down
+ event and a wp_tablet.frame event.
+
+ Note that this event describes logical contact, not physical
+ contact. On some devices, a compositor may not consider a tool in
+ logical contact until a minimum physical pressure threshold is
+ exceeded.
+ </description>
+ <arg name="serial" type="uint"/>
+ </event>
+
+ <event name="up">
+ <description summary="tablet tool is no longer making contact">
+ Sent whenever the tablet tool stops making contact with the surface of
+ the tablet, or when the tablet tool moves out of the input region
+ and the compositor grab (if any) is dismissed.
+
+ If the tablet tool moves out of the input region while in contact
+ with the surface of the tablet and the compositor does not have an
+ ongoing grab on the surface, the client owning said region will
+ receive a wp_tablet.up event, followed by a wp_tablet.proximity_out
+ event and a wp_tablet.frame event. If the compositor has an ongoing
+ grab on this device, this event sequence is sent whenever the grab
+ is dismissed in the future.
+
+ Note that this event describes logical contact, not physical
+ contact. On some devices, a compositor may not consider a tool out
+ of logical contact until physical pressure falls below a specific
+ threshold.
+ </description>
+ </event>
+
+ <event name="motion">
+ <description summary="motion event">
+ Sent whenever a tablet tool moves.
+ </description>
+ <arg name="x" type="fixed" summary="surface-local x coordinate"/>
+ <arg name="y" type="fixed" summary="surface-local y coordinate"/>
+ </event>
+
+ <event name="pressure">
+ <description summary="pressure change event">
+ Sent whenever the pressure axis on a tool changes. The value of this
+ event is normalized to a value between 0 and 65535.
+
+ Note that pressure may be nonzero even when a tool is not in logical
+ contact. See the down and up events for more details.
+ </description>
+ <arg name="pressure" type="uint" summary="The current pressure value"/>
+ </event>
+
+ <event name="distance">
+ <description summary="distance change event">
+ Sent whenever the distance axis on a tool changes. The value of this
+ event is normalized to a value between 0 and 65535.
+
+ Note that distance may be nonzero even when a tool is not in logical
+ contact. See the down and up events for more details.
+ </description>
+ <arg name="distance" type="uint" summary="The current distance value"/>
+ </event>
+
+ <event name="tilt">
+ <description summary="tilt change event">
+ Sent whenever one or both of the tilt axes on a tool change. Each tilt
+ value is in degrees, relative to the z-axis of the tablet.
+ The angle is positive when the top of a tool tilts along the
+ positive x or y axis.
+ </description>
+ <arg name="tilt_x" type="fixed" summary="The current value of the X tilt axis"/>
+ <arg name="tilt_y" type="fixed" summary="The current value of the Y tilt axis"/>
+ </event>
+
+ <event name="rotation">
+ <description summary="z-rotation change event">
+ Sent whenever the z-rotation axis on the tool changes. The
+ rotation value is in degrees clockwise from the tool's
+ logical neutral position.
+ </description>
+ <arg name="degrees" type="fixed" summary="The current rotation of the Z axis"/>
+ </event>
+
+ <event name="slider">
+ <description summary="Slider position change event">
+ Sent whenever the slider position on the tool changes. The
+ value is normalized between -65535 and 65535, with 0 as the logical
+ neutral position of the slider.
+
+ The slider is available on e.g. the Wacom Airbrush tool.
+ </description>
+ <arg name="position" type="int" summary="The current position of slider"/>
+ </event>
+
+ <event name="wheel">
+ <description summary="Wheel delta event">
+ Sent whenever the wheel on the tool emits an event. This event
+ contains two values for the same axis change. The degrees value is
+ in the same orientation as the wl_pointer.vertical_scroll axis. The
+ clicks value is in discrete logical clicks of the mouse wheel. This
+ value may be zero if the movement of the wheel was less
+ than one logical click.
+
+ Clients should choose either value and avoid mixing degrees and
+ clicks. The compositor may accumulate values smaller than a logical
+ click and emulate click events when a certain threshold is met.
+ Thus, wl_tablet_tool.wheel events with non-zero clicks values may
+ have different degrees values.
+ </description>
+ <arg name="degrees" type="fixed" summary="The wheel delta in degrees"/>
+ <arg name="clicks" type="int" summary="The wheel delta in discrete clicks"/>
+ </event>
+
+ <enum name="button_state">
+ <description summary="physical button state">
+ Describes the physical state of a button that produced the button event.
+ </description>
+ <entry name="released" value="0" summary="button is not pressed"/>
+ <entry name="pressed" value="1" summary="button is pressed"/>
+ </enum>
+
+ <event name="button">
+ <description summary="button event">
+ Sent whenever a button on the tool is pressed or released.
+
+ If a button is held down when the tool moves in or out of proximity,
+ button events are generated by the compositor. See
+ wp_tablet_tool.proximity_in and wp_tablet_tool.proximity_out for
+ details.
+ </description>
+ <arg name="serial" type="uint"/>
+ <arg name="button" type="uint" summary="The button whose state has changed"/>
+ <arg name="state" type="uint" enum="button_state" summary="Whether the button was pressed or released"/>
+ </event>
+
+ <event name="frame">
+ <description summary="frame event">
+ Marks the end of a series of axis and/or button updates from the
+ tablet. The Wayland protocol requires axis updates to be sent
+ sequentially, however all events within a frame should be considered
+ one hardware event.
+ </description>
+ <arg name="time" type="uint" summary="The time of the event with millisecond granularity"/>
+ </event>
+
+ <enum name="error">
+ <entry name="role" value="0" summary="given wl_surface has another role"/>
+ </enum>
+ </interface>
+
+ <interface name="zwp_tablet_v2" version="1">
+ <description summary="graphics tablet device">
+ The wp_tablet interface represents one graphics tablet device. The
+ tablet interface itself does not generate events; all events are
+ generated by wp_tablet_tool objects when in proximity above a tablet.
+
+ A tablet has a number of static characteristics, e.g. device name and
+ pid/vid. These capabilities are sent in an event sequence after the
+ wp_tablet_seat.tablet_added event. This initial event sequence is
+ terminated by a wp_tablet.done event.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the tablet object">
+ This destroys the client's resource for this tablet object.
+ </description>
+ </request>
+
+ <event name="name">
+ <description summary="tablet device name">
+ This event is sent in the initial burst of events before the
+ wp_tablet.done event.
+ </description>
+ <arg name="name" type="string" summary="the device name"/>
+ </event>
+
+ <event name="id">
+ <description summary="tablet device USB vendor/product id">
+ This event is sent in the initial burst of events before the
+ wp_tablet.done event.
+ </description>
+ <arg name="vid" type="uint" summary="USB vendor id"/>
+ <arg name="pid" type="uint" summary="USB product id"/>
+ </event>
+
+ <event name="path">
+ <description summary="path to the device">
+ A system-specific device path that indicates which device is behind
+ this wp_tablet. This information may be used to gather additional
+ information about the device, e.g. through libwacom.
+
+ A device may have more than one device path. If so, multiple
+ wp_tablet.path events are sent. A device may be emulated and not
+ have a device path, and in that case this event will not be sent.
+
+ The format of the path is unspecified, it may be a device node, a
+ sysfs path, or some other identifier. It is up to the client to
+ identify the string provided.
+
+ This event is sent in the initial burst of events before the
+ wp_tablet.done event.
+ </description>
+ <arg name="path" type="string" summary="path to local device"/>
+ </event>
+
+ <event name="done">
+ <description summary="tablet description events sequence complete">
+ This event is sent immediately to signal the end of the initial
+ burst of descriptive events. A client may consider the static
+ description of the tablet to be complete and finalize initialization
+ of the tablet.
+ </description>
+ </event>
+
+ <event name="removed">
+ <description summary="tablet removed event">
+ Sent when the tablet has been removed from the system. When a tablet
+ is removed, some tools may be removed.
+
+ When this event is received, the client must wp_tablet.destroy
+ the object.
+ </description>
+ </event>
+ </interface>
+
+ <interface name="zwp_tablet_pad_ring_v2" version="1">
+ <description summary="pad ring">
+ A circular interaction area, such as the touch ring on the Wacom Intuos
+ Pro series tablets.
+
+ Events on a ring are logically grouped by the wl_tablet_pad_ring.frame
+ event.
+ </description>
+
+ <request name="set_feedback">
+ <description summary="set compositor feedback">
+ Request that the compositor use the provided feedback string
+ associated with this ring. This request should be issued immediately
+ after a wp_tablet_pad_group.mode_switch event from the corresponding
+ group is received, or whenever the ring is mapped to a different
+ action. See wp_tablet_pad_group.mode_switch for more details.
+
+ Clients are encouraged to provide context-aware descriptions for
+ the actions associated with the ring; compositors may use this
+ information to offer visual feedback about the button layout
+ (eg. on-screen displays).
+
+ The provided string 'description' is a UTF-8 encoded string to be
+ associated with this ring, and is considered user-visible; general
+ internationalization rules apply.
+
+ The serial argument will be that of the last
+ wp_tablet_pad_group.mode_switch event received for the group of this
+ ring. Requests providing other serials than the most recent one will be
+ ignored.
+ </description>
+ <arg name="description" type="string" summary="ring description"/>
+ <arg name="serial" type="uint" summary="serial of the mode switch event"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the ring object">
+ This destroys the client's resource for this ring object.
+ </description>
+ </request>
+
+ <enum name="source">
+ <description summary="ring axis source">
+ Describes the source types for ring events. This indicates to the
+ client how a ring event was physically generated; a client may
+ adjust the user interface accordingly. For example, events
+ from a "finger" source may trigger kinetic scrolling.
+ </description>
+ <entry name="finger" value="1" summary="finger"/>
+ </enum>
+
+ <event name="source">
+ <description summary="ring event source">
+ Source information for ring events.
+
+ This event does not occur on its own. It is sent before a
+ wp_tablet_pad_ring.frame event and carries the source information
+ for all events within that frame.
+
+ The source specifies how this event was generated. If the source is
+ wp_tablet_pad_ring.source.finger, a wp_tablet_pad_ring.stop event
+ will be sent when the user lifts the finger off the device.
+
+ This event is optional. If the source is unknown for an interaction,
+ no event is sent.
+ </description>
+ <arg name="source" type="uint" enum="source" summary="the event source"/>
+ </event>
+
+ <event name="angle">
+ <description summary="angle changed">
+ Sent whenever the angle on a ring changes.
+
+ The angle is provided in degrees clockwise from the logical
+ north of the ring in the pad's current rotation.
+ </description>
+ <arg name="degrees" type="fixed" summary="the current angle in degrees"/>
+ </event>
+
+ <event name="stop">
+ <description summary="interaction stopped">
+ Stop notification for ring events.
+
+ For some wp_tablet_pad_ring.source types, a wp_tablet_pad_ring.stop
+ event is sent to notify a client that the interaction with the ring
+ has terminated. This enables the client to implement kinetic scrolling.
+ See the wp_tablet_pad_ring.source documentation for information on
+ when this event may be generated.
+
+ Any wp_tablet_pad_ring.angle events with the same source after this
+ event should be considered as the start of a new interaction.
+ </description>
+ </event>
+
+ <event name="frame">
+ <description summary="end of a ring event sequence">
+ Indicates the end of a set of ring events that logically belong
+ together. A client is expected to accumulate the data in all events
+ within the frame before proceeding.
+
+ All wp_tablet_pad_ring events before a wp_tablet_pad_ring.frame event belong
+ logically together. For example, on termination of a finger interaction
+ on a ring the compositor will send a wp_tablet_pad_ring.source event,
+ a wp_tablet_pad_ring.stop event and a wp_tablet_pad_ring.frame event.
+
+ A wp_tablet_pad_ring.frame event is sent for every logical event
+ group, even if the group only contains a single wp_tablet_pad_ring
+ event. Specifically, a client may get a sequence: angle, frame,
+ angle, frame, etc.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ </event>
+ </interface>
+
+ <interface name="zwp_tablet_pad_strip_v2" version="1">
+ <description summary="pad strip">
+ A linear interaction area, such as the strips found in Wacom Cintiq
+ models.
+
+ Events on a strip are logically grouped by the wl_tablet_pad_strip.frame
+ event.
+ </description>
+
+ <request name="set_feedback">
+ <description summary="set compositor feedback">
+ Requests the compositor to use the provided feedback string
+ associated with this strip. This request should be issued immediately
+ after a wp_tablet_pad_group.mode_switch event from the corresponding
+ group is received, or whenever the strip is mapped to a different
+ action. See wp_tablet_pad_group.mode_switch for more details.
+
+ Clients are encouraged to provide context-aware descriptions for
+ the actions associated with the strip, and compositors may use this
+ information to offer visual feedback about the button layout
+ (eg. on-screen displays).
+
+ The provided string 'description' is a UTF-8 encoded string to be
+ associated with this ring, and is considered user-visible; general
+ internationalization rules apply.
+
+ The serial argument will be that of the last
+ wp_tablet_pad_group.mode_switch event received for the group of this
+ strip. Requests providing other serials than the most recent one will be
+ ignored.
+ </description>
+ <arg name="description" type="string" summary="strip description"/>
+ <arg name="serial" type="uint" summary="serial of the mode switch event"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the strip object">
+ This destroys the client's resource for this strip object.
+ </description>
+ </request>
+
+ <enum name="source">
+ <description summary="strip axis source">
+ Describes the source types for strip events. This indicates to the
+ client how a strip event was physically generated; a client may
+ adjust the user interface accordingly. For example, events
+ from a "finger" source may trigger kinetic scrolling.
+ </description>
+ <entry name="finger" value="1" summary="finger"/>
+ </enum>
+
+ <event name="source">
+ <description summary="strip event source">
+ Source information for strip events.
+
+ This event does not occur on its own. It is sent before a
+ wp_tablet_pad_strip.frame event and carries the source information
+ for all events within that frame.
+
+ The source specifies how this event was generated. If the source is
+ wp_tablet_pad_strip.source.finger, a wp_tablet_pad_strip.stop event
+ will be sent when the user lifts their finger off the device.
+
+ This event is optional. If the source is unknown for an interaction,
+ no event is sent.
+ </description>
+ <arg name="source" type="uint" enum="source" summary="the event source"/>
+ </event>
+
+ <event name="position">
+ <description summary="position changed">
+ Sent whenever the position on a strip changes.
+
+ The position is normalized to a range of [0, 65535], the 0-value
+ represents the top-most and/or left-most position of the strip in
+ the pad's current rotation.
+ </description>
+ <arg name="position" type="uint" summary="the current position"/>
+ </event>
+
+ <event name="stop">
+ <description summary="interaction stopped">
+ Stop notification for strip events.
+
+ For some wp_tablet_pad_strip.source types, a wp_tablet_pad_strip.stop
+ event is sent to notify a client that the interaction with the strip
+ has terminated. This enables the client to implement kinetic
+ scrolling. See the wp_tablet_pad_strip.source documentation for
+ information on when this event may be generated.
+
+ Any wp_tablet_pad_strip.position events with the same source after this
+ event should be considered as the start of a new interaction.
+ </description>
+ </event>
+
+ <event name="frame">
+ <description summary="end of a strip event sequence">
+ Indicates the end of a set of events that represent one logical
+ hardware strip event. A client is expected to accumulate the data
+ in all events within the frame before proceeding.
+
+ All wp_tablet_pad_strip events before a wp_tablet_pad_strip.frame event belong
+ logically together. For example, on termination of a finger interaction
+ on a strip the compositor will send a wp_tablet_pad_strip.source event,
+ a wp_tablet_pad_strip.stop event and a wp_tablet_pad_strip.frame
+ event.
+
+ A wp_tablet_pad_strip.frame event is sent for every logical event
+ group, even if the group only contains a single wp_tablet_pad_strip
+ event. Specifically, a client may get a sequence: position, frame,
+ position, frame, etc.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ </event>
+ </interface>
+
+ <interface name="zwp_tablet_pad_group_v2" version="1">
+ <description summary="a set of buttons, rings and strips">
+ A pad group describes a distinct (sub)set of buttons, rings and strips
+ present in the tablet. The criteria of this grouping is usually positional,
+ eg. if a tablet has buttons on the left and right side, 2 groups will be
+ presented. The physical arrangement of groups is undisclosed and may
+ change on the fly.
+
+ Pad groups will announce their features during pad initialization. Between
+ the corresponding wp_tablet_pad.group event and wp_tablet_pad_group.done, the
+ pad group will announce the buttons, rings and strips contained in it,
+ plus the number of supported modes.
+
+ Modes are a mechanism to allow multiple groups of actions for every element
+ in the pad group. The number of groups and available modes in each is
+ persistent across device plugs. The current mode is user-switchable, it
+ will be announced through the wp_tablet_pad_group.mode_switch event both
+ whenever it is switched, and after wp_tablet_pad.enter.
+
+ The current mode logically applies to all elements in the pad group,
+ although it is at clients' discretion whether to actually perform different
+ actions, and/or issue the respective .set_feedback requests to notify the
+ compositor. See the wp_tablet_pad_group.mode_switch event for more details.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the pad object">
+ Destroy the wp_tablet_pad_group object. Objects created from this object
+ are unaffected and should be destroyed separately.
+ </description>
+ </request>
+
+ <event name="buttons">
+ <description summary="buttons announced">
+ Sent on wp_tablet_pad_group initialization to announce the available
+ buttons in the group. Button indices start at 0, a button may only be
+ in one group at a time.
+
+ This event is first sent in the initial burst of events before the
+ wp_tablet_pad_group.done event.
+
+ Some buttons are reserved by the compositor. These buttons may not be
+ assigned to any wp_tablet_pad_group. Compositors may broadcast this
+ event in the case of changes to the mapping of these reserved buttons.
+ If the compositor happens to reserve all buttons in a group, this event
+ will be sent with an empty array.
+ </description>
+ <arg name="buttons" type="array" summary="buttons in this group"/>
+ </event>
+
+ <event name="ring">
+ <description summary="ring announced">
+ Sent on wp_tablet_pad_group initialization to announce available rings.
+ One event is sent for each ring available on this pad group.
+
+ This event is sent in the initial burst of events before the
+ wp_tablet_pad_group.done event.
+ </description>
+ <arg name="ring" type="new_id" interface="zwp_tablet_pad_ring_v2"/>
+ </event>
+
+ <event name="strip">
+ <description summary="strip announced">
+ Sent on wp_tablet_pad initialization to announce available strips.
+ One event is sent for each strip available on this pad group.
+
+ This event is sent in the initial burst of events before the
+ wp_tablet_pad_group.done event.
+ </description>
+ <arg name="strip" type="new_id" interface="zwp_tablet_pad_strip_v2"/>
+ </event>
+
+ <event name="modes">
+ <description summary="mode-switch ability announced">
+ Sent on wp_tablet_pad_group initialization to announce that the pad
+ group may switch between modes. A client may use a mode to store a
+ specific configuration for buttons, rings and strips and use the
+ wl_tablet_pad_group.mode_switch event to toggle between these
+ configurations. Mode indices start at 0.
+
+ Switching modes is compositor-dependent. See the
+ wp_tablet_pad_group.mode_switch event for more details.
+
+ This event is sent in the initial burst of events before the
+ wp_tablet_pad_group.done event. This event is only sent when more than
+ more than one mode is available.
+ </description>
+ <arg name="modes" type="uint" summary="the number of modes"/>
+ </event>
+
+ <event name="done">
+ <description summary="tablet group description events sequence complete">
+ This event is sent immediately to signal the end of the initial
+ burst of descriptive events. A client may consider the static
+ description of the tablet to be complete and finalize initialization
+ of the tablet group.
+ </description>
+ </event>
+
+ <event name="mode_switch">
+ <description summary="mode switch event">
+ Notification that the mode was switched.
+
+ A mode applies to all buttons, rings and strips in a group
+ simultaneously, but a client is not required to assign different actions
+ for each mode. For example, a client may have mode-specific button
+ mappings but map the ring to vertical scrolling in all modes. Mode
+ indices start at 0.
+
+ Switching modes is compositor-dependent. The compositor may provide
+ visual cues to the client about the mode, e.g. by toggling LEDs on
+ the tablet device. Mode-switching may be software-controlled or
+ controlled by one or more physical buttons. For example, on a Wacom
+ Intuos Pro, the button inside the ring may be assigned to switch
+ between modes.
+
+ The compositor will also send this event after wp_tablet_pad.enter on
+ each group in order to notify of the current mode. Groups that only
+ feature one mode will use mode=0 when emitting this event.
+
+ If a button action in the new mode differs from the action in the
+ previous mode, the client should immediately issue a
+ wp_tablet_pad.set_feedback request for each changed button.
+
+ If a ring or strip action in the new mode differs from the action
+ in the previous mode, the client should immediately issue a
+ wp_tablet_ring.set_feedback or wp_tablet_strip.set_feedback request
+ for each changed ring or strip.
+ </description>
+ <arg name="time" type="uint" summary="the time of the event with millisecond granularity"/>
+ <arg name="serial" type="uint"/>
+ <arg name="mode" type="uint" summary="the new mode of the pad"/>
+ </event>
+ </interface>
+
+ <interface name="zwp_tablet_pad_v2" version="1">
+ <description summary="a set of buttons, rings and strips">
+ A pad device is a set of buttons, rings and strips
+ usually physically present on the tablet device itself. Some
+ exceptions exist where the pad device is physically detached, e.g. the
+ Wacom ExpressKey Remote.
+
+ Pad devices have no axes that control the cursor and are generally
+ auxiliary devices to the tool devices used on the tablet surface.
+
+ A pad device has a number of static characteristics, e.g. the number
+ of rings. These capabilities are sent in an event sequence after the
+ wp_tablet_seat.pad_added event before any actual events from this pad.
+ This initial event sequence is terminated by a wp_tablet_pad.done
+ event.
+
+ All pad features (buttons, rings and strips) are logically divided into
+ groups and all pads have at least one group. The available groups are
+ notified through the wp_tablet_pad.group event; the compositor will
+ emit one event per group before emitting wp_tablet_pad.done.
+
+ Groups may have multiple modes. Modes allow clients to map multiple
+ actions to a single pad feature. Only one mode can be active per group,
+ although different groups may have different active modes.
+ </description>
+
+ <request name="set_feedback">
+ <description summary="set compositor feedback">
+ Requests the compositor to use the provided feedback string
+ associated with this button. This request should be issued immediately
+ after a wp_tablet_pad_group.mode_switch event from the corresponding
+ group is received, or whenever a button is mapped to a different
+ action. See wp_tablet_pad_group.mode_switch for more details.
+
+ Clients are encouraged to provide context-aware descriptions for
+ the actions associated with each button, and compositors may use
+ this information to offer visual feedback on the button layout
+ (e.g. on-screen displays).
+
+ Button indices start at 0. Setting the feedback string on a button
+ that is reserved by the compositor (i.e. not belonging to any
+ wp_tablet_pad_group) does not generate an error but the compositor
+ is free to ignore the request.
+
+ The provided string 'description' is a UTF-8 encoded string to be
+ associated with this ring, and is considered user-visible; general
+ internationalization rules apply.
+
+ The serial argument will be that of the last
+ wp_tablet_pad_group.mode_switch event received for the group of this
+ button. Requests providing other serials than the most recent one will
+ be ignored.
+ </description>
+ <arg name="button" type="uint" summary="button index"/>
+ <arg name="description" type="string" summary="button description"/>
+ <arg name="serial" type="uint" summary="serial of the mode switch event"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the pad object">
+ Destroy the wp_tablet_pad object. Objects created from this object
+ are unaffected and should be destroyed separately.
+ </description>
+ </request>
+
+ <event name="group">
+ <description summary="group announced">
+ Sent on wp_tablet_pad initialization to announce available groups.
+ One event is sent for each pad group available.
+
+ This event is sent in the initial burst of events before the
+ wp_tablet_pad.done event. At least one group will be announced.
+ </description>
+ <arg name="pad_group" type="new_id" interface="zwp_tablet_pad_group_v2"/>
+ </event>
+
+ <event name="path">
+ <description summary="path to the device">
+ A system-specific device path that indicates which device is behind
+ this wp_tablet_pad. This information may be used to gather additional
+ information about the device, e.g. through libwacom.
+
+ The format of the path is unspecified, it may be a device node, a
+ sysfs path, or some other identifier. It is up to the client to
+ identify the string provided.
+
+ This event is sent in the initial burst of events before the
+ wp_tablet_pad.done event.
+ </description>
+ <arg name="path" type="string" summary="path to local device"/>
+ </event>
+
+ <event name="buttons">
+ <description summary="buttons announced">
+ Sent on wp_tablet_pad initialization to announce the available
+ buttons.
+
+ This event is sent in the initial burst of events before the
+ wp_tablet_pad.done event. This event is only sent when at least one
+ button is available.
+ </description>
+ <arg name="buttons" type="uint" summary="the number of buttons"/>
+ </event>
+
+ <event name="done">
+ <description summary="pad description event sequence complete">
+ This event signals the end of the initial burst of descriptive
+ events. A client may consider the static description of the pad to
+ be complete and finalize initialization of the pad.
+ </description>
+ </event>
+
+ <enum name="button_state">
+ <description summary="physical button state">
+ Describes the physical state of a button that caused the button
+ event.
+ </description>
+ <entry name="released" value="0" summary="the button is not pressed"/>
+ <entry name="pressed" value="1" summary="the button is pressed"/>
+ </enum>
+
+ <event name="button">
+ <description summary="physical button state">
+ Sent whenever the physical state of a button changes.
+ </description>
+ <arg name="time" type="uint" summary="the time of the event with millisecond granularity"/>
+ <arg name="button" type="uint" summary="the index of the button that changed state"/>
+ <arg name="state" type="uint" enum="button_state"/>
+ </event>
+
+ <event name="enter">
+ <description summary="enter event">
+ Notification that this pad is focused on the specified surface.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the enter event"/>
+ <arg name="tablet" type="object" interface="zwp_tablet_v2" summary="the tablet the pad is attached to"/>
+ <arg name="surface" type="object" interface="wl_surface" summary="surface the pad is focused on"/>
+ </event>
+
+ <event name="leave">
+ <description summary="enter event">
+ Notification that this pad is no longer focused on the specified
+ surface.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the leave event"/>
+ <arg name="surface" type="object" interface="wl_surface" summary="surface the pad is no longer focused on"/>
+ </event>
+
+ <event name="removed">
+ <description summary="pad removed event">
+ Sent when the pad has been removed from the system. When a tablet
+ is removed its pad(s) will be removed too.
+
+ When this event is received, the client must destroy all rings, strips
+ and groups that were offered by this pad, and issue wp_tablet_pad.destroy
+ the pad itself.
+ </description>
+ </event>
+ </interface>
+</protocol>
diff --git a/src/client/client.pro b/src/client/client.pro
index 1b514eee..793a4418 100644
--- a/src/client/client.pro
+++ b/src/client/client.pro
@@ -36,6 +36,7 @@ WAYLANDCLIENTSOURCES += \
../extensions/qt-key-unstable-v1.xml \
../extensions/qt-windowmanager.xml \
../3rdparty/protocol/wp-primary-selection-unstable-v1.xml \
+ ../3rdparty/protocol/tablet-unstable-v2.xml \
../3rdparty/protocol/text-input-unstable-v2.xml \
../3rdparty/protocol/xdg-output-unstable-v1.xml \
../3rdparty/protocol/wayland.xml
@@ -52,6 +53,7 @@ SOURCES += qwaylandintegration.cpp \
qwaylandextendedsurface.cpp \
qwaylandsubsurface.cpp \
qwaylandsurface.cpp \
+ qwaylandtabletv2.cpp \
qwaylandtouch.cpp \
qwaylandqtkey.cpp \
../shared/qwaylandmimehelper.cpp \
@@ -77,6 +79,7 @@ HEADERS += qwaylandintegration_p.h \
qwaylandextendedsurface_p.h \
qwaylandsubsurface_p.h \
qwaylandsurface_p.h \
+ qwaylandtabletv2_p.h \
qwaylandtouch_p.h \
qwaylandqtkey_p.h \
qwaylandabstractdecoration_p.h \
diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp
index a8d19dc6..ffcc72ff 100644
--- a/src/client/qwaylanddisplay.cpp
+++ b/src/client/qwaylanddisplay.cpp
@@ -69,6 +69,7 @@
#include "qwaylandextendedsurface_p.h"
#include "qwaylandsubsurface_p.h"
#include "qwaylandtouch_p.h"
+#include "qwaylandtabletv2_p.h"
#include "qwaylandqtkey_p.h"
#include <QtWaylandClient/private/qwayland-text-input-unstable-v2.h>
@@ -330,6 +331,8 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin
mTouchExtension.reset(new QWaylandTouchExtension(this, id));
} else if (interface == QStringLiteral("zqt_key_v1")) {
mQtKeyExtension.reset(new QWaylandQtKeyExtension(this, id));
+ } else if (interface == QStringLiteral("zwp_tablet_manager_v2")) {
+ mTabletManager.reset(new QWaylandTabletManagerV2(this, id, qMin(1, int(version))));
#if QT_CONFIG(wayland_client_primary_selection)
} else if (interface == QStringLiteral("zwp_primary_selection_device_manager_v1")) {
mPrimarySelectionManager.reset(new QWaylandPrimarySelectionDeviceManagerV1(this, id, 1));
diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h
index cd845b44..e99ec198 100644
--- a/src/client/qwaylanddisplay_p.h
+++ b/src/client/qwaylanddisplay_p.h
@@ -97,6 +97,7 @@ class QWaylandDataDeviceManager;
#if QT_CONFIG(wayland_client_primary_selection)
class QWaylandPrimarySelectionDeviceManagerV1;
#endif
+class QWaylandTabletManagerV2;
class QWaylandTouchExtension;
class QWaylandQtKeyExtension;
class QWaylandWindow;
@@ -160,6 +161,7 @@ public:
QWaylandPrimarySelectionDeviceManagerV1 *primarySelectionManager() const { return mPrimarySelectionManager.data(); }
#endif
QtWayland::qt_surface_extension *windowExtension() const { return mWindowExtension.data(); }
+ QWaylandTabletManagerV2 *tabletManager() const { return mTabletManager.data(); }
QWaylandTouchExtension *touchExtension() const { return mTouchExtension.data(); }
QtWayland::zwp_text_input_manager_v2 *textInputManager() const { return mTextInputManager.data(); }
QWaylandHardwareIntegration *hardwareIntegration() const { return mHardwareIntegration.data(); }
@@ -247,6 +249,7 @@ private:
QScopedPointer<QWaylandTouchExtension> mTouchExtension;
QScopedPointer<QWaylandQtKeyExtension> mQtKeyExtension;
QScopedPointer<QWaylandWindowManagerIntegration> mWindowManagerIntegration;
+ QScopedPointer<QWaylandTabletManagerV2> mTabletManager;
#if QT_CONFIG(wayland_client_primary_selection)
QScopedPointer<QWaylandPrimarySelectionDeviceManagerV1> mPrimarySelectionManager;
#endif
diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp
index ce086ee4..e0f0c6c8 100644
--- a/src/client/qwaylandinputdevice.cpp
+++ b/src/client/qwaylandinputdevice.cpp
@@ -50,6 +50,7 @@
#if QT_CONFIG(wayland_client_primary_selection)
#include "qwaylandprimaryselectionv1_p.h"
#endif
+#include "qwaylandtabletv2_p.h"
#include "qwaylandtouch_p.h"
#include "qwaylandscreen_p.h"
#include "qwaylandcursor_p.h"
@@ -418,6 +419,8 @@ QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version,
if (mQDisplay->textInputManager())
mTextInput.reset(new QWaylandTextInput(mQDisplay, mQDisplay->textInputManager()->get_text_input(wl_seat())));
+ if (auto *tm = mQDisplay->tabletManager())
+ mTabletSeat.reset(new QWaylandTabletSeatV2(tm, this));
}
QWaylandInputDevice::~QWaylandInputDevice()
diff --git a/src/client/qwaylandinputdevice_p.h b/src/client/qwaylandinputdevice_p.h
index 87f8559c..448d0fce 100644
--- a/src/client/qwaylandinputdevice_p.h
+++ b/src/client/qwaylandinputdevice_p.h
@@ -89,6 +89,7 @@ class QWaylandDisplay;
#if QT_CONFIG(wayland_client_primary_selection)
class QWaylandPrimarySelectionDeviceV1;
#endif
+class QWaylandTabletSeatV2;
class QWaylandTextInput;
#if QT_CONFIG(cursor)
class QWaylandCursorTheme;
@@ -127,6 +128,9 @@ public:
QWaylandPrimarySelectionDeviceV1 *primarySelectionDevice() const;
#endif
+ void setTabletSeat(QWaylandTabletSeatV2 *tabletSeat);
+ QWaylandTabletSeatV2* tabletSeat() const;
+
void setTextInput(QWaylandTextInput *textInput);
QWaylandTextInput *textInput() const;
@@ -183,6 +187,7 @@ private:
Touch *mTouch = nullptr;
QScopedPointer<QWaylandTextInput> mTextInput;
+ QScopedPointer<QWaylandTabletSeatV2> mTabletSeat;
uint32_t mTime = 0;
uint32_t mSerial = 0;
diff --git a/src/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp
index 33e64c6e..85c1990b 100644
--- a/src/client/qwaylandintegration.cpp
+++ b/src/client/qwaylandintegration.cpp
@@ -425,6 +425,8 @@ void QWaylandIntegration::initializeShellIntegration()
qCWarning(lcQpaWayland) << "Loading shell integration failed.";
qCWarning(lcQpaWayland) << "Attempted to load the following shells" << preferredShells;
}
+
+ QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
}
QWaylandInputDevice *QWaylandIntegration::createInputDevice(QWaylandDisplay *display, int version, uint32_t id)
diff --git a/src/client/qwaylandtabletv2.cpp b/src/client/qwaylandtabletv2.cpp
new file mode 100644
index 00000000..eb2e865f
--- /dev/null
+++ b/src/client/qwaylandtabletv2.cpp
@@ -0,0 +1,332 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the plugins of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qwaylandtabletv2_p.h"
+#include "qwaylandinputdevice_p.h"
+#include "qwaylanddisplay_p.h"
+#include "qwaylandsurface_p.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace QtWaylandClient {
+
+QWaylandTabletManagerV2::QWaylandTabletManagerV2(QWaylandDisplay *display, uint id, uint version)
+ : zwp_tablet_manager_v2(display->wl_registry(), id, qMin(version, uint(1)))
+{
+ // Create tabletSeats for all seats.
+ // This only works if we get the manager after all seats
+ const auto seats = display->inputDevices();
+ for (auto *seat : seats)
+ createTabletSeat(seat);
+}
+
+QWaylandTabletSeatV2 *QWaylandTabletManagerV2::createTabletSeat(QWaylandInputDevice *seat)
+{
+ return new QWaylandTabletSeatV2(this, seat);
+}
+
+QWaylandTabletSeatV2::QWaylandTabletSeatV2(QWaylandTabletManagerV2 *manager, QWaylandInputDevice *seat)
+ : QtWayland::zwp_tablet_seat_v2(manager->get_tablet_seat(seat->wl_seat()))
+{
+}
+
+QWaylandTabletSeatV2::~QWaylandTabletSeatV2()
+{
+ for (auto *tablet : m_tablets)
+ tablet->destroy();
+ for (auto *tool : m_tools)
+ tool->destroy();
+ for (auto *pad : m_pads)
+ pad->destroy();
+ destroy();
+}
+
+void QWaylandTabletSeatV2::zwp_tablet_seat_v2_tablet_added(zwp_tablet_v2 *id)
+{
+ auto *tablet = new QWaylandTabletV2(id);
+ m_tablets.push_back(tablet);
+ connect(tablet, &QWaylandTabletV2::destroyed, this, [this, tablet] { m_tablets.removeOne(tablet); });
+}
+
+void QWaylandTabletSeatV2::zwp_tablet_seat_v2_tool_added(zwp_tablet_tool_v2 *id)
+{
+ auto *tool = new QWaylandTabletToolV2(id);
+ m_tools.push_back(tool);
+ connect(tool, &QWaylandTabletToolV2::destroyed, this, [this, tool] { m_tools.removeOne(tool); });
+}
+
+void QWaylandTabletSeatV2::zwp_tablet_seat_v2_pad_added(zwp_tablet_pad_v2 *id)
+{
+ auto *pad = new QWaylandTabletPadV2(id);
+ m_pads.push_back(pad);
+ connect(pad, &QWaylandTabletPadV2::destroyed, this, [this, pad] { m_pads.removeOne(pad); });
+}
+
+QWaylandTabletV2::QWaylandTabletV2(::zwp_tablet_v2 *tablet)
+ : QtWayland::zwp_tablet_v2(tablet)
+{
+}
+
+void QWaylandTabletV2::zwp_tablet_v2_removed()
+{
+ destroy();
+ delete this;
+}
+
+QWaylandTabletToolV2::QWaylandTabletToolV2(::zwp_tablet_tool_v2 *tool)
+ : QtWayland::zwp_tablet_tool_v2(tool)
+{
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_type(uint32_t tool_type)
+{
+ m_toolType = type(tool_type);
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_hardware_serial(uint32_t hardware_serial_hi, uint32_t hardware_serial_lo)
+{
+ m_uid = (quint64(hardware_serial_hi) << 32) + hardware_serial_lo;
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_capability(uint32_t capability)
+{
+ if (capability == capability_rotation)
+ m_hasRotation = true;
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_done()
+{
+ switch (m_toolType) {
+ case type::type_airbrush:
+ case type::type_brush:
+ case type::type_pencil:
+ case type::type_pen:
+ m_pointerType = QTabletEvent::PointerType::Pen;
+ break;
+ case type::type_eraser:
+ m_pointerType = QTabletEvent::PointerType::Eraser;
+ break;
+ case type::type_mouse:
+ case type::type_lens:
+ m_pointerType = QTabletEvent::PointerType::Cursor;
+ break;
+ case type::type_finger:
+ m_pointerType = QTabletEvent::PointerType::UnknownPointer;
+ break;
+ }
+ switch (m_toolType) {
+ case type::type_airbrush:
+ m_tabletDevice = QTabletEvent::TabletDevice::Airbrush;
+ break;
+ case type::type_brush:
+ case type::type_pencil:
+ case type::type_pen:
+ case type::type_eraser:
+ m_tabletDevice = m_hasRotation ? QTabletEvent::TabletDevice::RotationStylus : QTabletEvent::TabletDevice::Stylus;
+ break;
+ case type::type_lens:
+ m_tabletDevice = QTabletEvent::TabletDevice::Puck;
+ break;
+ case type::type_mouse:
+ case type::type_finger:
+ m_tabletDevice = QTabletEvent::TabletDevice::NoDevice;
+ break;
+ }
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_removed()
+{
+ destroy();
+ delete this;
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_proximity_in(uint32_t serial, zwp_tablet_v2 *tablet, wl_surface *surface)
+{
+ Q_UNUSED(tablet);
+ Q_UNUSED(serial);
+ if (Q_UNLIKELY(!surface)) {
+ qCDebug(lcQpaWayland) << "Ignoring zwp_tablet_tool_v2_proximity_v2 with no surface";
+ return;
+ }
+ m_pending.enteredSurface = true;
+ m_pending.proximitySurface = QWaylandSurface::fromWlSurface(surface);
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_proximity_out()
+{
+ m_pending.enteredSurface = false;
+ m_pending.proximitySurface = nullptr;
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_down(uint32_t serial)
+{
+ Q_UNUSED(serial);
+ m_pending.down = true;
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_up()
+{
+ m_pending.down = false;
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_motion(wl_fixed_t x, wl_fixed_t y)
+{
+ m_pending.surfacePosition = QPointF(wl_fixed_to_double(x), wl_fixed_to_double(y));
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_pressure(uint32_t pressure)
+{
+ const int maxPressure = 65535;
+ m_pending.pressure = qreal(pressure)/maxPressure;
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_distance(uint32_t distance)
+{
+ m_pending.distance = distance;
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_tilt(wl_fixed_t tilt_x, wl_fixed_t tilt_y)
+{
+ m_pending.xTilt = wl_fixed_to_double(tilt_x);
+ m_pending.yTilt = wl_fixed_to_double(tilt_y);
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_rotation(wl_fixed_t degrees)
+{
+ m_pending.rotation = wl_fixed_to_double(degrees);
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_slider(int32_t position)
+{
+ m_pending.slider = qreal(position) / 65535;
+}
+
+static Qt::MouseButton mouseButtonFromTablet(uint button)
+{
+ switch (button) {
+ case 0x110: return Qt::MouseButton::LeftButton; // BTN_LEFT
+ case 0x14b: return Qt::MouseButton::MiddleButton; // BTN_STYLUS
+ case 0x14c: return Qt::MouseButton::RightButton; // BTN_STYLUS2
+ default:
+ return Qt::NoButton;
+ }
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_button(uint32_t serial, uint32_t button, uint32_t state)
+{
+ Q_UNUSED(serial);
+ Qt::MouseButton mouseButton = mouseButtonFromTablet(button);
+ if (state == button_state_pressed)
+ m_pending.buttons |= mouseButton;
+ else
+ m_pending.buttons &= ~mouseButton;
+}
+
+void QWaylandTabletToolV2::zwp_tablet_tool_v2_frame(uint32_t time)
+{
+ if (m_pending.proximitySurface && !m_applied.proximitySurface) {
+ QWindowSystemInterface::handleTabletEnterProximityEvent(m_tabletDevice, m_pointerType, m_uid);
+ m_applied.proximitySurface = m_pending.proximitySurface;
+ }
+
+ if (!(m_pending == m_applied) && m_pending.proximitySurface) {
+ if (!m_pending.proximitySurface) {
+ qCWarning(lcQpaWayland) << "Can't send tablet event with no proximity surface, ignoring";
+ return;
+ }
+ QWaylandWindow *waylandWindow = QWaylandWindow::fromWlSurface(m_pending.proximitySurface->object());
+ QWindow *window = waylandWindow->window();
+ ulong timestamp = time;
+ const QPointF localPosition = waylandWindow->mapFromWlSurface(m_pending.surfacePosition);
+
+ QPointF delta = localPosition - localPosition.toPoint();
+ QPointF globalPosition = window->mapToGlobal(localPosition.toPoint());
+ globalPosition += delta;
+
+ Qt::MouseButtons buttons = m_pending.down ? Qt::MouseButton::LeftButton : Qt::MouseButton::NoButton;
+ buttons |= m_pending.buttons;
+ qreal pressure = m_pending.pressure;
+ int xTilt = int(m_pending.xTilt);
+ int yTilt = int(m_pending.yTilt);
+ qreal tangentialPressure = m_pending.slider;
+ qreal rotation = m_pending.rotation;
+ int z = int(m_pending.distance);
+ QWindowSystemInterface::handleTabletEvent(window, timestamp, localPosition, globalPosition,
+ m_tabletDevice, m_pointerType, buttons, pressure,
+ xTilt, yTilt, tangentialPressure, rotation, z, m_uid);
+ }
+
+ if (!m_pending.proximitySurface && m_applied.enteredSurface) {
+ QWindowSystemInterface::handleTabletLeaveProximityEvent(m_tabletDevice, m_pointerType, m_uid);
+ m_pending = State(); // Don't leave pressure etc. lying around when we enter the next surface
+ }
+
+ m_applied = m_pending;
+}
+
+// TODO: delete when upgrading to c++20
+bool QWaylandTabletToolV2::State::operator==(const QWaylandTabletToolV2::State &o) const {
+ return
+ down == o.down &&
+ proximitySurface.data() == o.proximitySurface.data() &&
+ enteredSurface == o.enteredSurface &&
+ surfacePosition == o.surfacePosition &&
+ distance == o.distance &&
+ pressure == o.pressure &&
+ rotation == o.rotation &&
+ xTilt == o.xTilt &&
+ yTilt == o.yTilt &&
+ slider == o.slider &&
+ buttons == o.buttons;
+}
+
+QWaylandTabletPadV2::QWaylandTabletPadV2(::zwp_tablet_pad_v2 *pad)
+ : QtWayland::zwp_tablet_pad_v2(pad)
+{
+}
+
+void QWaylandTabletPadV2::zwp_tablet_pad_v2_removed()
+{
+ destroy();
+ delete this;
+}
+
+} // namespace QtWaylandClient
+
+QT_END_NAMESPACE
diff --git a/src/client/qwaylandtabletv2_p.h b/src/client/qwaylandtabletv2_p.h
new file mode 100644
index 00000000..b4daaf5d
--- /dev/null
+++ b/src/client/qwaylandtabletv2_p.h
@@ -0,0 +1,191 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the plugins of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QWAYLANDTABLETV2_P_H
+#define QWAYLANDTABLETV2_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtWaylandClient/private/qwayland-tablet-unstable-v2.h>
+
+#include <QtWaylandClient/private/qtwaylandclientglobal_p.h>
+
+#include <QtGui/QTabletEvent>
+#include <QtCore/QObject>
+#include <QtCore/QPointer>
+#include <QtCore/QPointF>
+
+QT_BEGIN_NAMESPACE
+
+namespace QtWaylandClient {
+
+class QWaylandDisplay;
+class QWaylandInputDevice;
+class QWaylandSurface;
+
+class QWaylandTabletSeatV2;
+class QWaylandTabletV2;
+class QWaylandTabletToolV2;
+class QWaylandTabletPadV2;
+
+class Q_WAYLAND_CLIENT_EXPORT QWaylandTabletManagerV2 : public QtWayland::zwp_tablet_manager_v2
+{
+public:
+ explicit QWaylandTabletManagerV2(QWaylandDisplay *display, uint id, uint version);
+ QWaylandTabletSeatV2 *createTabletSeat(QWaylandInputDevice *seat);
+};
+
+class Q_WAYLAND_CLIENT_EXPORT QWaylandTabletSeatV2 : public QObject, public QtWayland::zwp_tablet_seat_v2
+{
+ Q_OBJECT
+public:
+ explicit QWaylandTabletSeatV2(QWaylandTabletManagerV2 *manager, QWaylandInputDevice *seat);
+ ~QWaylandTabletSeatV2() override;
+
+protected:
+ void zwp_tablet_seat_v2_tablet_added(struct ::zwp_tablet_v2 *id) override;
+ void zwp_tablet_seat_v2_tool_added(struct ::zwp_tablet_tool_v2 *id) override;
+ void zwp_tablet_seat_v2_pad_added(struct ::zwp_tablet_pad_v2 *id) override;
+
+private:
+ QVector<QWaylandTabletV2 *> m_tablets;
+ QVector<QWaylandTabletToolV2 *> m_tools;
+ QVector<QWaylandTabletPadV2 *> m_pads;
+};
+
+class Q_WAYLAND_CLIENT_EXPORT QWaylandTabletV2 : public QObject, public QtWayland::zwp_tablet_v2
+{
+ Q_OBJECT
+public:
+ explicit QWaylandTabletV2(::zwp_tablet_v2 *tablet);
+
+protected:
+// void zwp_tablet_v2_name(const QString &name) override;
+// void zwp_tablet_v2_id(uint32_t vid, uint32_t pid) override;
+// void zwp_tablet_v2_path(const QString &path) override;
+// void zwp_tablet_v2_done() override;
+ void zwp_tablet_v2_removed() override;
+};
+
+class Q_WAYLAND_CLIENT_EXPORT QWaylandTabletToolV2 : public QObject, public QtWayland::zwp_tablet_tool_v2
+{
+ Q_OBJECT
+public:
+ explicit QWaylandTabletToolV2(::zwp_tablet_tool_v2 *tool);
+
+protected:
+ void zwp_tablet_tool_v2_type(uint32_t tool_type) override;
+ void zwp_tablet_tool_v2_hardware_serial(uint32_t hardware_serial_hi, uint32_t hardware_serial_lo) override;
+// void zwp_tablet_tool_v2_hardware_id_wacom(uint32_t hardware_id_hi, uint32_t hardware_id_lo) override;
+ void zwp_tablet_tool_v2_capability(uint32_t capability) override;
+ void zwp_tablet_tool_v2_done() override;
+ void zwp_tablet_tool_v2_removed() override;
+ void zwp_tablet_tool_v2_proximity_in(uint32_t serial, struct ::zwp_tablet_v2 *tablet, struct ::wl_surface *surface) override;
+ void zwp_tablet_tool_v2_proximity_out() override;
+ void zwp_tablet_tool_v2_down(uint32_t serial) override;
+ void zwp_tablet_tool_v2_up() override;
+ void zwp_tablet_tool_v2_motion(wl_fixed_t x, wl_fixed_t y) override;
+ void zwp_tablet_tool_v2_pressure(uint32_t pressure) override;
+ void zwp_tablet_tool_v2_distance(uint32_t distance) override;
+ void zwp_tablet_tool_v2_tilt(wl_fixed_t tilt_x, wl_fixed_t tilt_y) override;
+ void zwp_tablet_tool_v2_rotation(wl_fixed_t degrees) override;
+ void zwp_tablet_tool_v2_slider(int32_t position) override;
+// void zwp_tablet_tool_v2_wheel(wl_fixed_t degrees, int32_t clicks) override;
+ void zwp_tablet_tool_v2_button(uint32_t serial, uint32_t button, uint32_t state) override;
+ void zwp_tablet_tool_v2_frame(uint32_t time) override;
+
+private:
+
+ // Static state (sent before done event)
+ QTabletEvent::PointerType m_pointerType = QTabletEvent::PointerType::UnknownPointer;
+ QTabletEvent::TabletDevice m_tabletDevice = QTabletEvent::TabletDevice::NoDevice;
+ type m_toolType = type_pen;
+ bool m_hasRotation = false;
+ quint64 m_uid = 0;
+
+ // Accumulated state (applied on frame event)
+ struct State {
+ bool down = false;
+ QPointer<QWaylandSurface> proximitySurface;
+ bool enteredSurface = false; // Not enough with just proximitySurface, if the surface is deleted, we still want to send a leave event
+ QPointF surfacePosition;
+ uint distance = 0;
+ qreal pressure = 0;
+ qreal rotation = 0;
+ qreal xTilt = 0;
+ qreal yTilt = 0;
+ qreal slider = 0;
+ Qt::MouseButtons buttons = Qt::MouseButton::NoButton; // Actual buttons, down state -> left mouse is mapped inside the frame handler
+ //auto operator<=>(const Point&) const = default; // TODO: use this when upgrading to C++20
+ bool operator==(const State &o) const;
+ } m_pending, m_applied;
+};
+
+// We don't actually use this, but need to handle the "removed" event to comply with the protocol
+class Q_WAYLAND_CLIENT_EXPORT QWaylandTabletPadV2 : public QObject, public QtWayland::zwp_tablet_pad_v2
+{
+ Q_OBJECT
+public:
+ explicit QWaylandTabletPadV2(::zwp_tablet_pad_v2 *pad);
+
+protected:
+// void zwp_tablet_pad_v2_group(struct ::zwp_tablet_pad_group_v2 *pad_group) override;
+// void zwp_tablet_pad_v2_path(const QString &path) override;
+// void zwp_tablet_pad_v2_buttons(uint32_t buttons) override;
+// void zwp_tablet_pad_v2_done() override;
+// void zwp_tablet_pad_v2_button(uint32_t time, uint32_t button, uint32_t state) override;
+// void zwp_tablet_pad_v2_enter(uint32_t serial, struct ::zwp_tablet_v2 *tablet, struct ::wl_surface *surface) override;
+// void zwp_tablet_pad_v2_leave(uint32_t serial, struct ::wl_surface *surface) override;
+ void zwp_tablet_pad_v2_removed() override;
+};
+
+} // namespace QtWaylandClient
+
+QT_END_NAMESPACE
+
+#endif // QWAYLANDTABLETV2_P_H
diff --git a/sync.profile b/sync.profile
index 57221345..95ddb557 100644
--- a/sync.profile
+++ b/sync.profile
@@ -24,6 +24,7 @@
"^qwayland-qt-key-unstable-v1.h" ,
"^qwayland-server-buffer-extension.h",
"^qwayland-surface-extension.h",
+ "^qwayland-tablet-unstable-v2.h",
"^qwayland-text-input-unstable-v2.h",
"^qwayland-touch-extension.h",
"^qwayland-wayland.h",
@@ -34,6 +35,7 @@
"^wayland-qt-key-unstable-v1-client-protocol.h",
"^wayland-server-buffer-extension-client-protocol.h",
"^wayland-surface-extension-client-protocol.h",
+ "^wayland-tablet-unstable-v2-client-protocol.h",
"^wayland-text-input-unstable-v2-client-protocol.h",
"^wayland-touch-extension-client-protocol.h",
"^wayland-wayland-client-protocol.h",
diff --git a/tests/auto/client/client.pro b/tests/auto/client/client.pro
index cda1765e..46a3aa42 100644
--- a/tests/auto/client/client.pro
+++ b/tests/auto/client/client.pro
@@ -11,6 +11,7 @@ SUBDIRS += \
seatv4 \
seatv5 \
surface \
+ tabletv2 \
wl_connect \
xdgdecorationv1 \
xdgoutput \
diff --git a/tests/auto/client/tabletv2/tabletv2.pro b/tests/auto/client/tabletv2/tabletv2.pro
new file mode 100644
index 00000000..9dc9636e
--- /dev/null
+++ b/tests/auto/client/tabletv2/tabletv2.pro
@@ -0,0 +1,7 @@
+include (../shared/shared.pri)
+
+WAYLANDSERVERSOURCES += \
+ $$PWD/../../../../src/3rdparty/protocol/tablet-unstable-v2.xml
+
+TARGET = tst_tabletv2
+SOURCES += tst_tabletv2.cpp
diff --git a/tests/auto/client/tabletv2/tst_tabletv2.cpp b/tests/auto/client/tabletv2/tst_tabletv2.cpp
new file mode 100644
index 00000000..2fe2ff42
--- /dev/null
+++ b/tests/auto/client/tabletv2/tst_tabletv2.cpp
@@ -0,0 +1,918 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mockcompositor.h"
+
+#include <qwayland-server-tablet-unstable-v2.h>
+
+#include <QtGui/QRasterWindow>
+
+using namespace MockCompositor;
+
+constexpr int tabletVersion = 1; // protocol VERSION, not the name suffix (_v2)
+
+class TabletManagerV2;
+class TabletSeatV2;
+
+class TabletV2 : public QObject, public QtWaylandServer::zwp_tablet_v2
+{
+ Q_OBJECT
+public:
+ explicit TabletV2(TabletSeatV2 *tabletSeat)
+ : m_tabletSeat(tabletSeat)
+ {
+ }
+
+ void send_removed() = delete;
+ void send_removed(struct ::wl_resource *resource) = delete;
+ void sendRemoved();
+
+ QPointer<TabletSeatV2> m_tabletSeat; // destroy order is not guaranteed
+protected:
+ void zwp_tablet_v2_destroy(Resource *resource) override;
+};
+
+class TabletToolV2 : public QObject, public QtWaylandServer::zwp_tablet_tool_v2
+{
+ Q_OBJECT
+public:
+ using ToolType = QtWaylandServer::zwp_tablet_tool_v2::type;
+ explicit TabletToolV2(TabletSeatV2 *tabletSeat, ToolType toolType, quint64 hardwareSerial)
+ : m_tabletSeat(tabletSeat)
+ , m_toolType(toolType)
+ , m_hardwareSerial(hardwareSerial)
+ {
+ }
+
+ wl_resource *toolResource() // for convenience
+ {
+ Q_ASSERT(resourceMap().size() == 1);
+ // Strictly speaking, there may be more than one resource for the tool, for intsance if
+ // if there are multiple clients, or a client has called get_tablet_seat multiple times.
+ // For now we'll pretend there can only be one resource.
+ return resourceMap().first()->handle;
+ }
+
+ void send_removed() = delete;
+ void send_removed(struct ::wl_resource *resource) = delete;
+ void sendRemoved();
+
+ uint sendProximityIn(TabletV2 *tablet, Surface *surface);
+ void sendProximityOut();
+ void sendMotion(QPointF position)
+ {
+ Q_ASSERT(m_proximitySurface);
+ for (auto *resource : resourceMap())
+ send_motion(resource->handle, wl_fixed_from_double(position.x()), wl_fixed_from_double(position.y()));
+ }
+ uint sendDown();
+ void sendUp() { send_up(toolResource()); }
+ void sendPressure(uint pressure);
+ void sendTilt(qreal tiltX, qreal tiltY) { send_tilt(toolResource(), wl_fixed_from_double(tiltX), wl_fixed_from_double(tiltY)); }
+ void sendRotation(qreal rotation) { send_rotation(toolResource(), wl_fixed_from_double(rotation)); }
+ uint sendButton(uint button, bool pressed);
+ uint sendFrame();
+
+ QPointer<TabletSeatV2> m_tabletSeat; // destruction order is not guaranteed
+ ToolType m_toolType = ToolType::type_pen;
+ quint64 m_hardwareSerial = 0;
+ QPointer<Surface> m_proximitySurface;
+protected:
+ void zwp_tablet_tool_v2_destroy(Resource *resource) override;
+};
+
+class TabletPadV2 : public QObject, public QtWaylandServer::zwp_tablet_pad_v2
+{
+ Q_OBJECT
+public:
+ explicit TabletPadV2(TabletSeatV2 *tabletSeat)
+ : m_tabletSeat(tabletSeat)
+ {
+ }
+
+ void send_removed() = delete;
+ void send_removed(struct ::wl_resource *resource) = delete;
+ void sendRemoved();
+
+ QPointer<TabletSeatV2> m_tabletSeat; // destroy order is not guaranteed
+protected:
+ void zwp_tablet_pad_v2_destroy(Resource *resource) override;
+};
+
+class TabletSeatV2 : public QObject, public QtWaylandServer::zwp_tablet_seat_v2
+{
+ Q_OBJECT
+public:
+ explicit TabletSeatV2(TabletManagerV2 *manager, Seat *seat)
+ : m_manager(manager)
+ , m_seat(seat)
+ {}
+ TabletV2 *addTablet()
+ {
+ auto *tablet = new TabletV2(this);
+ m_tablets.append(tablet);
+ for (auto *resource : resourceMap())
+ sendTabletAdded(resource, tablet);
+ return tablet;
+ }
+
+ void sendTabletAdded(Resource *resource, TabletV2 *tablet)
+ {
+ // Although, not necessarily correct, assuming just one tablet_seat per client
+ auto *tabletResource = tablet->add(resource->client(), resource->version());
+ zwp_tablet_seat_v2::send_tablet_added(resource->handle, tabletResource->handle);
+ // TODO: send extra stuff before done?
+ tablet->send_done(tabletResource->handle);
+ }
+
+ using ToolType = QtWaylandServer::zwp_tablet_tool_v2::type;
+ TabletToolV2 *addTool(ToolType toolType = ToolType::type_pen, quint64 hardwareSerial = 0)
+ {
+ auto *tool = new TabletToolV2(this, toolType, hardwareSerial);
+ m_tools.append(tool);
+ for (auto *resource : resourceMap())
+ sendToolAdded(resource, tool);
+ return tool;
+ }
+
+ void sendToolAdded(Resource *resource, TabletToolV2 *tool)
+ {
+ // Although, not necessarily correct, assuming just one tablet_seat per client
+ auto *toolResource = tool->add(resource->client(), resource->version())->handle;
+ zwp_tablet_seat_v2::send_tool_added(resource->handle, toolResource);
+ tool->send_type(toolResource, tool->m_toolType);
+ if (tool->m_hardwareSerial) {
+ const uint hi = tool->m_hardwareSerial >> 32;
+ const uint lo = tool->m_hardwareSerial & 0xffffffff;
+ tool->send_hardware_serial(toolResource, hi, lo);
+ }
+ tool->send_done(toolResource);
+ }
+
+ TabletPadV2 *addPad()
+ {
+ auto *pad = new TabletPadV2(this);
+ m_pads.append(pad);
+ for (auto *resource : resourceMap())
+ sendPadAdded(resource, pad);
+ return pad;
+ }
+
+ void sendPadAdded(Resource *resource, TabletPadV2 *pad)
+ {
+ // Although, not necessarily correct, assuming just one tablet_seat per client
+ auto *padResource = pad->add(resource->client(), resource->version())->handle;
+ zwp_tablet_seat_v2::send_pad_added(resource->handle, padResource);
+ pad->send_done(padResource);
+ }
+
+ void removeAll()
+ {
+ const auto tools = m_tools;
+ for (auto *tool : tools)
+ tool->sendRemoved();
+
+ const auto tablets = m_tablets;
+ for (auto *tablet : tablets)
+ tablet->sendRemoved();
+
+ const auto pads = m_pads;
+ for (auto *pad : pads)
+ pad->sendRemoved();
+ }
+
+ TabletManagerV2 *m_manager = nullptr;
+ Seat *m_seat = nullptr;
+ QVector<TabletV2 *> m_tablets;
+ QVector<TabletV2 *> m_tabletsWaitingForDestroy;
+ QVector<TabletToolV2 *> m_tools;
+ QVector<TabletToolV2 *> m_toolsWaitingForDestroy;
+ QVector<TabletPadV2 *> m_pads;
+ QVector<TabletPadV2 *> m_padsWaitingForDestroy;
+
+protected:
+ void zwp_tablet_seat_v2_bind_resource(Resource *resource)
+ {
+ for (auto *tablet : m_tablets)
+ sendTabletAdded(resource, tablet);
+ for (auto *tool : m_tools)
+ sendToolAdded(resource, tool);
+ for (auto *pad : m_pads)
+ sendPadAdded(resource, pad);
+ }
+};
+
+class TabletManagerV2 : public Global, public QtWaylandServer::zwp_tablet_manager_v2
+{
+ Q_OBJECT
+public:
+ explicit TabletManagerV2(CoreCompositor *compositor, int version = 1)
+ : QtWaylandServer::zwp_tablet_manager_v2(compositor->m_display, version)
+ , m_version(version)
+ {}
+ bool isClean() override
+ {
+ for (auto *seat : m_tabletSeats) {
+ if (!seat->m_tabletsWaitingForDestroy.empty())
+ return false;
+ if (!seat->m_toolsWaitingForDestroy.empty())
+ return false;
+ if (!seat->m_padsWaitingForDestroy.empty())
+ return false;
+ }
+ return true;
+ }
+
+ TabletSeatV2 *tabletSeatFor(Seat *seat)
+ {
+ Q_ASSERT(seat);
+ if (auto *tabletSeat = m_tabletSeats.value(seat, nullptr))
+ return tabletSeat;
+
+ auto *tabletSeat = new TabletSeatV2(this, seat);
+ m_tabletSeats[seat] = tabletSeat;
+ return tabletSeat;
+ }
+
+ int m_version = 1; // TODO: Remove on libwayland upgrade
+ QMap<Seat *, TabletSeatV2 *> m_tabletSeats;
+
+protected:
+ void zwp_tablet_manager_v2_destroy(Resource *resource) override
+ {
+ // tablet_seats created from this object are unaffected and should be destroyed separately.
+ wl_resource_destroy(resource->handle);
+ }
+
+ void zwp_tablet_manager_v2_get_tablet_seat(Resource *resource, uint32_t id, ::wl_resource *seatResource) override
+ {
+ auto *seat = fromResource<Seat>(seatResource);
+ QVERIFY(seat);
+ auto *tabletSeat = tabletSeatFor(seat);
+ tabletSeat->add(resource->client(), id, resource->version());
+ }
+};
+
+void TabletV2::sendRemoved()
+{
+ for (auto *resource : resourceMap())
+ zwp_tablet_v2_send_removed(resource->handle);
+ bool removed = m_tabletSeat->m_tablets.removeOne(this);
+ QVERIFY(removed);
+ m_tabletSeat->m_tabletsWaitingForDestroy.append(this);
+}
+
+void TabletV2::zwp_tablet_v2_destroy(QtWaylandServer::zwp_tablet_v2::Resource *resource)
+{
+ Q_UNUSED(resource)
+ if (m_tabletSeat) {
+ bool removed = m_tabletSeat->m_tabletsWaitingForDestroy.removeOne(this);
+ QVERIFY(removed);
+ }
+ wl_resource_destroy(resource->handle);
+}
+
+void TabletToolV2::sendRemoved()
+{
+ for (auto *resource : resourceMap())
+ zwp_tablet_tool_v2_send_removed(resource->handle);
+ bool removed = m_tabletSeat->m_tools.removeOne(this);
+ QVERIFY(removed);
+ m_tabletSeat->m_toolsWaitingForDestroy.append(this);
+}
+
+uint TabletToolV2::sendProximityIn(TabletV2 *tablet, Surface *surface)
+{
+ Q_ASSERT(!m_proximitySurface);
+ m_proximitySurface = surface;
+ uint serial = m_tabletSeat->m_seat->m_compositor->nextSerial();
+ auto *client = surface->resource()->client();
+ auto tabletResource = tablet->resourceMap().value(client)->handle;
+ send_proximity_in(toolResource(), serial, tabletResource, surface->resource()->handle);
+ return serial;
+}
+
+void TabletToolV2::sendProximityOut()
+{
+ Q_ASSERT(m_proximitySurface);
+ send_proximity_out(toolResource());
+ m_proximitySurface = nullptr;
+}
+
+uint TabletToolV2::sendDown()
+{
+ uint serial = m_tabletSeat->m_seat->m_compositor->nextSerial();
+ send_down(toolResource(), serial);
+ return serial;
+}
+
+void TabletToolV2::sendPressure(uint pressure)
+{
+ Q_ASSERT(m_proximitySurface);
+ auto *client = m_proximitySurface->resource()->client();
+ auto toolResource = resourceMap().value(client)->handle;
+ send_pressure(toolResource, pressure);
+}
+
+uint TabletToolV2::sendButton(uint button, bool pressed)
+{
+ button_state state = pressed ? button_state_pressed : button_state_released;
+ uint serial = m_tabletSeat->m_seat->m_compositor->nextSerial();
+ send_button(toolResource(), serial, button, state);
+ return serial;
+}
+
+uint TabletToolV2::sendFrame()
+{
+ uint time = m_tabletSeat->m_seat->m_compositor->currentTimeMilliseconds();
+ for (auto *resource : resourceMap())
+ send_frame(resource->handle, time);
+ return time;
+}
+
+void TabletToolV2::zwp_tablet_tool_v2_destroy(QtWaylandServer::zwp_tablet_tool_v2::Resource *resource)
+{
+ if (m_tabletSeat) {
+ bool removed = m_tabletSeat->m_toolsWaitingForDestroy.removeOne(this);
+ QVERIFY(removed);
+ }
+ wl_resource_destroy(resource->handle);
+}
+
+void TabletPadV2::sendRemoved()
+{
+ for (auto *resource : resourceMap())
+ zwp_tablet_pad_v2_send_removed(resource->handle);
+ bool removed = m_tabletSeat->m_pads.removeOne(this);
+ QVERIFY(removed);
+ m_tabletSeat->m_padsWaitingForDestroy.append(this);
+}
+
+void TabletPadV2::zwp_tablet_pad_v2_destroy(QtWaylandServer::zwp_tablet_pad_v2::Resource *resource)
+{
+ if (m_tabletSeat) {
+ bool removed = m_tabletSeat->m_padsWaitingForDestroy.removeOne(this);
+ QVERIFY(removed);
+ }
+ wl_resource_destroy(resource->handle);
+}
+
+class TabletCompositor : public DefaultCompositor {
+public:
+ explicit TabletCompositor()
+ {
+ exec([this] {
+ m_config.autoConfigure = true;
+ add<TabletManagerV2>(tabletVersion);
+ });
+ }
+ TabletSeatV2 *tabletSeat(int i = 0)
+ {
+ return get<TabletManagerV2>()->tabletSeatFor(get<Seat>(i));
+ }
+ TabletV2 *tablet(int i = 0, int iSeat = 0)
+ {
+ if (auto *ts = tabletSeat(iSeat))
+ return ts->m_tablets.value(i, nullptr);
+ return nullptr;
+ }
+ TabletToolV2 *tabletTool(int i = 0, int iSeat = 0)
+ {
+ if (auto *ts = tabletSeat(iSeat))
+ return ts->m_tools.value(i, nullptr);
+ return nullptr;
+ }
+ TabletPadV2 *tabletPad(int i = 0, int iSeat = 0)
+ {
+ if (auto *ts = tabletSeat(iSeat))
+ return ts->m_pads.value(i, nullptr);
+ return nullptr;
+ }
+};
+
+Q_DECLARE_METATYPE(QtWaylandServer::zwp_tablet_tool_v2::type);
+Q_DECLARE_METATYPE(QTabletEvent::PointerType);
+Q_DECLARE_METATYPE(Qt::MouseButton);
+
+class tst_tabletv2 : public QObject, private TabletCompositor
+{
+ using ToolType = QtWaylandServer::zwp_tablet_tool_v2::type;
+ Q_OBJECT
+private slots:
+ void cleanup();
+ void bindsToManager();
+ void createsTabletSeat();
+ void destroysTablet();
+ void destroysTool();
+ void destroysPad();
+ void proximityEvents();
+ void moveEvent();
+ void pointerType_data();
+ void pointerType();
+ void hardwareSerial();
+ void buttons_data();
+ void buttons();
+ void tabletEvents();
+};
+
+class ProximityFilter : public QObject {
+ Q_OBJECT
+public:
+ ProximityFilter() { qApp->installEventFilter(this); }
+ ~ProximityFilter() override { qDeleteAll(m_events); }
+ QVector<QTabletEvent *> m_events;
+
+ int nextEventIndex = 0;
+ int numEvents() const { return m_events.size() - nextEventIndex; }
+ QTabletEvent *popEvent()
+ {
+ auto *event = m_events.value(nextEventIndex, nullptr);
+ if (event)
+ ++nextEventIndex;
+ return event;
+ }
+
+protected:
+ bool eventFilter(QObject *object, QEvent *event) override
+ {
+ Q_UNUSED(object);
+ switch (event->type()) {
+ case QEvent::TabletEnterProximity:
+ case QEvent::TabletLeaveProximity: {
+ auto *e = static_cast<QTabletEvent *>(event);
+ auto *ev = new QTabletEvent(e->type(), e->posF(), e->globalPosF(), e->device(),
+ e->pointerType(), e->pressure(), e->xTilt(), e->yTilt(),
+ e->tangentialPressure(), e->rotation(), e->z(),
+ Qt::KeyboardModifier::NoModifier, e->uniqueId(),
+ e->button(), e->buttons());
+ m_events << ev;
+ break;
+ }
+ default:
+ break;
+ }
+ return false;
+ }
+};
+
+void tst_tabletv2::cleanup()
+{
+ exec([&] {
+ tabletSeat()->removeAll();
+ });
+ QCOMPOSITOR_COMPARE(get<TabletManagerV2>()->m_tabletSeats.size(), 1);
+ QCOMPOSITOR_COMPARE(tabletSeat()->m_tablets.size(), 0);
+ QCOMPOSITOR_COMPARE(tabletSeat()->m_tools.size(), 0);
+ QCOMPOSITOR_COMPARE(tabletSeat()->m_pads.size(), 0);
+
+ QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage()));
+}
+
+void tst_tabletv2::bindsToManager()
+{
+ QCOMPOSITOR_TRY_COMPARE(get<TabletManagerV2>()->resourceMap().size(), 1);
+ QCOMPOSITOR_TRY_COMPARE(get<TabletManagerV2>()->resourceMap().first()->version(), tabletVersion);
+}
+
+void tst_tabletv2::createsTabletSeat()
+{
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat()->resourceMap().contains(client()));
+ QCOMPOSITOR_TRY_COMPARE(tabletSeat()->resourceMap().value(client())->version(), tabletVersion);
+ //TODO: Maybe also assert some capability reported though qt APIs?
+}
+
+void tst_tabletv2::destroysTablet()
+{
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addTablet();
+ });
+ QCOMPOSITOR_TRY_VERIFY(tablet());
+
+ exec([&] {
+ tablet()->sendRemoved();
+ });
+
+ QCOMPOSITOR_TRY_VERIFY(!tablet());
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat()->m_tabletsWaitingForDestroy.empty());
+}
+
+void tst_tabletv2::destroysTool()
+{
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addTool();
+ });
+ QCOMPOSITOR_TRY_VERIFY(tabletTool());
+
+ exec([&] {
+ tabletTool()->sendRemoved();
+ });
+
+ QCOMPOSITOR_TRY_VERIFY(!tabletTool());
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat()->m_toolsWaitingForDestroy.empty());
+}
+
+void tst_tabletv2::destroysPad()
+{
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addPad();
+ });
+ QCOMPOSITOR_TRY_VERIFY(tabletPad());
+
+ exec([&] {
+ tabletPad()->sendRemoved();
+ });
+
+ QCOMPOSITOR_TRY_VERIFY(!tabletPad());
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat()->m_padsWaitingForDestroy.empty());
+}
+
+void tst_tabletv2::proximityEvents()
+{
+ ProximityFilter filter;
+
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addTablet();
+ tabletSeat()->addTool();
+ });
+
+ QRasterWindow window;
+ window.resize(64, 64);
+ window.show();
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ QCOMPOSITOR_TRY_VERIFY(tablet());
+ exec([&] {
+ auto *surface = xdgSurface()->m_surface;
+ auto *tool = tabletTool();
+ tool->sendProximityIn(tablet(), surface);
+ tool->sendFrame();
+ });
+
+ QTRY_COMPARE(filter.numEvents(), 1);
+ QTabletEvent *enterEvent = filter.popEvent();
+ QCOMPARE(enterEvent->type(), QEvent::TabletEnterProximity);
+
+ exec([&] {
+ auto *tool = tabletTool();
+ tool->sendProximityOut();
+ tool->sendFrame();
+ });
+
+ QTRY_COMPARE(filter.numEvents(), 1);
+ QTabletEvent *leaveEvent = filter.popEvent();
+ QCOMPARE(leaveEvent->type(), QEvent::TabletLeaveProximity);
+}
+
+class TabletWindow : public QRasterWindow {
+ Q_OBJECT
+public:
+ ~TabletWindow() override { qDeleteAll(m_events); }
+
+ void tabletEvent(QTabletEvent *e) override
+ {
+ m_events << new QTabletEvent(e->type(), e->posF(), e->globalPosF(), e->device(),
+ e->pointerType(), e->pressure(), e->xTilt(), e->yTilt(),
+ e->tangentialPressure(), e->rotation(), e->z(),
+ Qt::KeyboardModifier::NoModifier, e->uniqueId(), e->button(),
+ e->buttons());
+ emit tabletEventReceived(m_events.last());
+ }
+ int nextEventIndex = 0;
+ int numEvents() const { return m_events.size() - nextEventIndex; }
+ QTabletEvent *popEvent()
+ {
+ auto *event = m_events.value(nextEventIndex, nullptr);
+ if (event)
+ ++nextEventIndex;
+ return event;
+ }
+
+signals:
+ void tabletEventReceived(QTabletEvent *event);
+
+private:
+ QVector<QTabletEvent *> m_events;
+};
+
+void tst_tabletv2::moveEvent()
+{
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addTablet();
+ tabletSeat()->addTool();
+ });
+
+ TabletWindow window;
+ window.resize(64, 64);
+ window.show();
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ QCOMPOSITOR_TRY_VERIFY(tablet());
+ exec([&] {
+ auto *surface = xdgSurface()->m_surface;
+ auto *tool = tabletTool();
+ tool->sendProximityIn(tablet(), surface);
+ QMargins margins = window.frameMargins();
+ tool->sendMotion(QPointF(12 + margins.left(), 34 + margins.top()));
+ tool->sendFrame();
+ });
+ QTRY_VERIFY(window.numEvents());
+ QTabletEvent *event = window.popEvent();
+ QCOMPARE(event->type(), QEvent::TabletMove);
+ QCOMPARE(event->pressure(), 0);
+ QCOMPARE(event->posF(), QPointF(12, 34));
+}
+
+void tst_tabletv2::pointerType_data()
+{
+ QTest::addColumn<ToolType>("toolType");
+ QTest::addColumn<QTabletEvent::PointerType>("pointerType");
+ QTest::addColumn<QTabletEvent::TabletDevice>("tabletDevice");
+
+ QTest::newRow("pen") << ToolType::type_pen << QTabletEvent::PointerType::Pen << QTabletEvent::TabletDevice::Stylus;
+ QTest::newRow("eraser") << ToolType::type_eraser << QTabletEvent::PointerType::Eraser << QTabletEvent::TabletDevice::Stylus;
+ QTest::newRow("pencil") << ToolType::type_pencil << QTabletEvent::PointerType::Pen << QTabletEvent::TabletDevice::Stylus;
+ QTest::newRow("airbrush") << ToolType::type_airbrush << QTabletEvent::PointerType::Pen << QTabletEvent::TabletDevice::Airbrush;
+ QTest::newRow("brush") << ToolType::type_brush << QTabletEvent::PointerType::Pen << QTabletEvent::TabletDevice::Stylus; // TODO: is TabletDevice::Stylus the right thing?
+ QTest::newRow("lens") << ToolType::type_lens << QTabletEvent::PointerType::Cursor << QTabletEvent::TabletDevice::Puck;
+ // TODO: also add tests for FourDMouse and RotationStylus (also need to send capabilities)
+
+ // TODO: should these rather be mapped to touch/mouse events?
+ QTest::newRow("finger") << ToolType::type_finger << QTabletEvent::PointerType::UnknownPointer << QTabletEvent::TabletDevice::NoDevice;
+ QTest::newRow("mouse") << ToolType::type_mouse << QTabletEvent::PointerType::Cursor << QTabletEvent::TabletDevice::NoDevice;
+}
+
+void tst_tabletv2::pointerType()
+{
+ using ToolType = QtWaylandServer::zwp_tablet_tool_v2::type;
+ QFETCH(ToolType, toolType);
+ QFETCH(QTabletEvent::PointerType, pointerType);
+ QFETCH(QTabletEvent::TabletDevice, tabletDevice);
+
+ ProximityFilter filter;
+
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addTablet();
+ tabletSeat()->addTool(toolType);
+ });
+
+ TabletWindow window;
+ window.resize(64, 64);
+ window.show();
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ QCOMPOSITOR_TRY_VERIFY(tablet());
+ exec([&] {
+ auto *surface = xdgSurface()->m_surface;
+ auto *tool = tabletTool();
+ tool->sendProximityIn(tablet(), surface);
+ QMargins margins = window.frameMargins();
+ tool->sendMotion(QPointF(12 + margins.left(), 34 + margins.top()));
+ tool->sendFrame();
+ });
+
+ QTRY_COMPARE(filter.numEvents(), 1);
+ QTabletEvent *event = filter.popEvent();
+ QCOMPARE(event->pointerType(), pointerType);
+ QCOMPARE(event->device(), tabletDevice);
+
+ QTRY_VERIFY(window.numEvents());
+ event = window.popEvent();
+ QCOMPARE(event->pointerType(), pointerType);
+ QCOMPARE(event->device(), tabletDevice);
+
+ exec([&] {
+ tabletTool()->sendProximityOut();
+ tabletTool()->sendFrame();
+ });
+
+ QTRY_VERIFY(filter.numEvents());
+ event = filter.popEvent();
+ QCOMPARE(event->pointerType(), pointerType);
+ QCOMPARE(event->device(), tabletDevice);
+}
+
+void tst_tabletv2::hardwareSerial()
+{
+ ProximityFilter filter;
+ const quint64 uid = 0xbaba15dead15f00d;
+
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addTablet();
+ tabletSeat()->addTool(ToolType::type_pen, uid);
+ });
+
+ TabletWindow window;
+ window.resize(64, 64);
+ window.show();
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ QCOMPOSITOR_TRY_VERIFY(tablet());
+ exec([&] {
+ auto *surface = xdgSurface()->m_surface;
+ auto *tool = tabletTool();
+ tool->sendProximityIn(tablet(), surface);
+ QMargins margins = window.frameMargins();
+ tool->sendMotion(QPointF(12 + margins.left(), 34 + margins.top()));
+ tool->sendFrame();
+ });
+
+ QTRY_COMPARE(filter.numEvents(), 1);
+ QTabletEvent *event = filter.popEvent();
+ QCOMPARE(event->uniqueId(), uid);
+
+ QTRY_VERIFY(window.numEvents());
+ event = window.popEvent();
+ QCOMPARE(event->uniqueId(), uid);
+
+ exec([&] {
+ tabletTool()->sendProximityOut();
+ tabletTool()->sendFrame();
+ });
+
+ QTRY_VERIFY(filter.numEvents());
+ event = filter.popEvent();
+ QCOMPARE(event->uniqueId(), uid);
+}
+
+// As defined in linux/input-event-codes.h
+#ifndef BTN_STYLUS
+#define BTN_STYLUS 0x14b
+#endif
+#ifndef BTN_STYLUS2
+#define BTN_STYLUS2 0x14c
+#endif
+
+void tst_tabletv2::buttons_data()
+{
+ QTest::addColumn<uint>("tabletButton");
+ QTest::addColumn<Qt::MouseButton>("mouseButton");
+
+ QTest::newRow("BTN_STYLUS2") << uint(BTN_STYLUS2) << Qt::MouseButton::RightButton;
+ QTest::newRow("BTN_STYLUS") << uint(BTN_STYLUS) << Qt::MouseButton::MiddleButton;
+}
+
+void tst_tabletv2::buttons()
+{
+ QFETCH(uint, tabletButton);
+ QFETCH(Qt::MouseButton, mouseButton);
+
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addTablet();
+ tabletSeat()->addTool();
+ });
+
+ TabletWindow window;
+ window.resize(64, 64);
+ window.show();
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ QCOMPOSITOR_TRY_VERIFY(tablet());
+ exec([&] {
+ tabletTool()->sendProximityIn(tablet(), xdgSurface()->m_surface);
+ QMargins margins = window.frameMargins();
+ tabletTool()->sendMotion(QPointF(12 + margins.left(), 34 + margins.top()));
+ tabletTool()->sendFrame();
+ });
+
+ QTRY_VERIFY(window.numEvents());
+ window.popEvent();
+
+ QCOMPOSITOR_TRY_VERIFY(tablet());
+ exec([&] {
+ tabletTool()->sendButton(tabletButton, true);
+ tabletTool()->sendFrame();
+ tabletTool()->sendButton(tabletButton, false);
+ tabletTool()->sendFrame();
+ });
+
+ QTRY_VERIFY(window.numEvents());
+ QTabletEvent *event = window.popEvent();
+ QCOMPARE(event->buttons(), mouseButton);
+
+ exec([&] {
+ tabletTool()->sendProximityOut();
+ tabletTool()->sendFrame();
+ });
+}
+
+void tst_tabletv2::tabletEvents()
+{
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addTablet();
+ tabletSeat()->addTool();
+ });
+
+ TabletWindow window;
+ window.resize(64, 64);
+ window.show();
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ const QPointF insideDecorations(window.frameMargins().left(), window.frameMargins().top());
+
+ QCOMPOSITOR_TRY_VERIFY(tablet());
+ exec([&] {
+ auto *surface = xdgSurface()->m_surface;
+ auto *tool = tabletTool();
+ // TODO: encapsulate this into a helper function?
+ tool->sendProximityIn(tablet(), surface);
+ tool->sendMotion(QPointF(12, 34) + insideDecorations);
+ tool->sendDown();
+ tool->sendPressure(65535);
+ tool->sendFrame();
+ });
+
+ QTRY_VERIFY(window.numEvents());
+ QTabletEvent *event = window.popEvent();
+ QCOMPARE(event->type(), QEvent::TabletPress);
+ QCOMPARE(event->pressure(), 1.0);
+ QCOMPARE(event->posF(), QPointF(12, 34));
+
+ // Values we didn't send should be 0
+ QCOMPARE(event->rotation(), 0);
+ QCOMPARE(event->xTilt(), 0);
+ QCOMPARE(event->yTilt(), 0);
+
+ exec([&] {
+ tabletTool()->sendMotion(QPointF(45, 56) + insideDecorations);
+ tabletTool()->sendPressure(65535/2);
+ tabletTool()->sendRotation(90);
+ tabletTool()->sendTilt(13, 37);
+ tabletTool()->sendFrame();
+ });
+
+ QTRY_VERIFY(window.numEvents());
+ event = window.popEvent();
+ QCOMPARE(event->type(), QEvent::TabletMove);
+ QVERIFY(qAbs(event->pressure() - 0.5) < 0.01);
+ QVERIFY(qAbs(event->rotation() - 90) < 0.01);
+ QVERIFY(qAbs(event->xTilt() - 13) < 0.01);
+ QVERIFY(qAbs(event->yTilt() - 37) < 0.01);
+ QCOMPARE(event->posF(), QPointF(45, 56));
+
+ // Verify that the values stay the same if we don't update them
+ exec([&] {
+ tabletTool()->sendMotion(QPointF(10, 11) + insideDecorations); // Change position only
+ tabletTool()->sendFrame();
+ });
+ QTRY_VERIFY(window.numEvents());
+ event = window.popEvent();
+ QCOMPARE(event->type(), QEvent::TabletMove);
+ QVERIFY(qAbs(event->pressure() - 0.5) < 0.01);
+ QVERIFY(qAbs(event->rotation() - 90) < 0.01);
+ QVERIFY(qAbs(event->xTilt() - 13) < 0.01);
+ QVERIFY(qAbs(event->yTilt() - 37) < 0.01);
+ QCOMPARE(event->posF(), QPointF(10, 11));
+
+ exec([&] {
+ tabletTool()->sendPressure(0);
+ tabletTool()->sendUp();
+ tabletTool()->sendFrame();
+
+ tabletTool()->sendProximityOut();
+ tabletTool()->sendFrame();
+ });
+
+ QTRY_VERIFY(window.numEvents());
+ event = window.popEvent();
+ QCOMPARE(event->type(), QEvent::TabletRelease);
+ QCOMPARE(event->pressure(), 0);
+ QCOMPARE(event->posF(), QPointF(10, 11));
+}
+
+QCOMPOSITOR_TEST_MAIN(tst_tabletv2)
+#include "tst_tabletv2.moc"