From 0a895fdeeac64acb5427861c99c3d19bef62e226 Mon Sep 17 00:00:00 2001 From: Lukas Straub Date: Sun, 30 Apr 2023 11:18:20 +0000 Subject: [PATCH] vm_tools: sommelier: Proxy stylus-unstable-v2 to tablet-unstable-v2 Stylus input for linux apps has been a long requested feature. This patch implements this feature, by proxying google's custom stylus-unstable-v2 to tablet-unstable-v2, which all major toolkits support. BUG=https://bugs.chromium.org/p/chromium/issues/detail?id=932049 TEST=Tested stylus input with various apps: xournal, xournalpp, gimp, krita and mypaint Cq-Depend: chromium:4497905 Change-Id: I09dc9ca9b70ff5e90c53846a7ce67651b57a5256 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/4497906 Reviewed-by: Nic Hollingum Commit-Queue: Lukas Straub Tested-by: Lukas Straub --- BUILD.gn | 3 + meson.build | 3 + protocol/stylus-unstable-v2.xml | 200 ++++++ protocol/tablet-unstable-v2.xml | 1178 +++++++++++++++++++++++++++++++ sommelier-ctx.h | 1 + sommelier-seat.cc | 13 +- sommelier-stylus-tablet.cc | 529 ++++++++++++++ sommelier-stylus-tablet.h | 19 + sommelier.cc | 25 + sommelier.h | 18 +- 10 files changed, 1983 insertions(+), 6 deletions(-) create mode 100644 protocol/stylus-unstable-v2.xml create mode 100644 protocol/tablet-unstable-v2.xml create mode 100644 sommelier-stylus-tablet.cc create mode 100644 sommelier-stylus-tablet.h diff --git a/BUILD.gn b/BUILD.gn index ad780f9..6750644 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -47,6 +47,8 @@ wayland_protocols = [ "protocol/linux-explicit-synchronization-unstable-v1.xml", "protocol/pointer-constraints-unstable-v1.xml", "protocol/relative-pointer-unstable-v1.xml", + "protocol/stylus-unstable-v2.xml", + "protocol/tablet-unstable-v2.xml", "protocol/text-input-extension-unstable-v1.xml", "protocol/text-input-unstable-v1.xml", "protocol/text-input-x11-unstable-v1.xml", @@ -112,6 +114,7 @@ static_library("libsommelier") { "sommelier-relative-pointer-manager.cc", "sommelier-seat.cc", "sommelier-shell.cc", + "sommelier-stylus-tablet.cc", "sommelier-subcompositor.cc", "sommelier-text-input.cc", "sommelier-timing.cc", diff --git a/meson.build b/meson.build index 81531f5..08142ec 100644 --- a/meson.build +++ b/meson.build @@ -46,6 +46,8 @@ wl_protocols = [ 'protocol/linux-explicit-synchronization-unstable-v1.xml', 'protocol/pointer-constraints-unstable-v1.xml', 'protocol/relative-pointer-unstable-v1.xml', + 'protocol/stylus-unstable-v2.xml', + 'protocol/tablet-unstable-v2.xml', 'protocol/text-input-unstable-v1.xml', 'protocol/text-input-extension-unstable-v1.xml', 'protocol/text-input-x11-unstable-v1.xml', @@ -156,6 +158,7 @@ libsommelier = static_library('sommelier', 'sommelier-relative-pointer-manager.cc', 'sommelier-seat.cc', 'sommelier-shell.cc', + 'sommelier-stylus-tablet.cc', 'sommelier-subcompositor.cc', 'sommelier-text-input.cc', 'sommelier-timing.cc', diff --git a/protocol/stylus-unstable-v2.xml b/protocol/stylus-unstable-v2.xml new file mode 100644 index 0000000..6cb3742 --- /dev/null +++ b/protocol/stylus-unstable-v2.xml @@ -0,0 +1,200 @@ + + + + + Copyright 2016 The Chromium Authors + + 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. + + + + + Allows a wl_touch or a wl_pointer to report stylus specific information. + The client can use this protocol to obtain detail information about the + type of stylus, as well as the force and tilt of the tool. + + 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 uinterface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and uinterface 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. + + + + + + + + + + + + Create touch_stylus object. See zcr_touch_stylus_v2 interface for + details. If the given wl_touch already has a touch_stylus object + associated, the touch_stylus_exists protocol error is raised. + + + + + + + + + Create pointer_stylus object. See zcr_pointer_stylus_v2 interface for + details. If the given wl_pointer already has a pointer_stylus object + associated, the pointer_stylus_exists protocol error is raised. + + + + + + + + + The zcr_touch_stylus_v2 interface extends the wl_touch interface with + events to describe details about a stylus. A stylus that reports events + through this interface is likely an on-screen stylus, where the user + interacts with the stylus directly on a display. + + These events are to be fired by the server within the same frame as other + wl_touch events. + + + + + + + + + + + + + + + + Notification that the user is using a tool type other than touch. There + can only be one tool in use at a time. + This event is sent in the same frame as the wl_touch.down event. The + tool type cannot change while a touch is being reported. + + + + + + + + Notification of a change in physical force on the surface of the screen. + The force is calibrated and normalized to the 0 to 1 range. + + + + + + + + + Notification of a change in tilt of a stylus. + + Measured from surface normal as plane angle in degrees, values lie in + [-90,90]. A positive x is to the right and a positive y is towards the + user. + + + + + + + + + + + + + The zcr_pointer_stylus_v2 interface extends the wl_pointer interface with + events to describe details about a stylus. A stylus that reports events + through this interface also moves the mouse cursor. The type of the + device reporting values through this interface is described by the + tool_type. When the tool changes, the values previously reported through + this interface are assumed to be reset. + + These events are to be fired by the server within the same frame as other + wl_pointer events. + + + + + + + + + + + + + + + + + Notification that the user is using a tool type other than touch. There + can only be one tool in use at a time. + + + + + + + Notification of the physical force of the stylus on the surface. + The force is calibrated and normalized to the 0 to 1 range. + + The client should assume that the force value is reset when the + tool changes, and that the tool does not support force detection + until the first force event is sent. That force value will persist + until the next force update or tool change. + + + + + + + + Notification of a change in tilt of a stylus. + + Measured from surface normal as plane angle in degrees, values lie in + [-90,90]. A positive x is to the right and a positive y is towards the + user. + + The client should assume that the tilt value is reset when the + tool changes, and that the tool does not support tilt detection + until the first tilt event is sent. That value will persist + until the next tilt update or tool change. + + + + + + + + diff --git a/protocol/tablet-unstable-v2.xml b/protocol/tablet-unstable-v2.xml new file mode 100644 index 0000000..6e4d9a3 --- /dev/null +++ b/protocol/tablet-unstable-v2.xml @@ -0,0 +1,1178 @@ + + + + + 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. + + + + 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. + + + + + 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. + + + + + Get the wp_tablet_seat object for the given seat. This object + provides access to all graphics tablets in this seat. + + + + + + + + Destroy the wp_tablet_manager object. Objects created from this + object are unaffected and should be destroyed separately. + + + + + + + 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. + + + + + Destroy the wp_tablet_seat object. Objects created from this + object are unaffected and should be destroyed separately. + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + + 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. + + + + + 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. + + + + + + + + + + This destroys the client's resource for this tool object. + + + + + + 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. + + + + + + + + + + + + + + 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. + + + + + + + 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. + + + + + + + + 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. + + + + + + + + Describes extra capabilities on a tablet. + + Any tool must provide x and y values, extra axes are + device-specific. + + + + + + + + + + + + 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. + + + + + + + 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. + + + + + + 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. + + + + + + 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. + + + + + + + + + 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. + + + + + + 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. + + + + + + + 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. + + + + + + Sent whenever a tablet tool moves. + + + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + + Sent whenever the z-rotation axis on the tool changes. The + rotation value is in degrees clockwise from the tool's + logical neutral position. + + + + + + + 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. + + + + + + + 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. + + + + + + + + Describes the physical state of a button that produced the 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. + + + + + + + + + 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. + + + + + + + + + + + + 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. + + + + + This destroys the client's resource for this tablet object. + + + + + + This event is sent in the initial burst of events before the + wp_tablet.done event. + + + + + + + This event is sent in the initial burst of events before the + wp_tablet.done event. + + + + + + + + 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. + + + + + + + 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. + + + + + + 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. + + + + + + + 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. + + + + + 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. + + + + + + + + This destroys the client's resource for this ring object. + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + 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. + + + + + + + + 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. + + + + + 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. + + + + + + + + This destroys the client's resource for this strip object. + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + 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. + + + + + + + + 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. + + + + + Destroy the wp_tablet_pad_group object. Objects created from this object + are unaffected and should be destroyed separately. + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + 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. + + + + + + + + + + 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. + + + + + 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. + + + + + + + + + Destroy the wp_tablet_pad object. Objects created from this object + are unaffected and should be destroyed separately. + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + Describes the physical state of a button that caused the button + event. + + + + + + + + Sent whenever the physical state of a button changes. + + + + + + + + + Notification that this pad is focused on the specified surface. + + + + + + + + + Notification that this pad is no longer focused on the specified + surface. + + + + + + + + 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. + + + + diff --git a/sommelier-ctx.h b/sommelier-ctx.h index cd6db84..ba3d325 100644 --- a/sommelier-ctx.h +++ b/sommelier-ctx.h @@ -90,6 +90,7 @@ struct sl_context { struct sl_gaming_input_manager* gaming_input_manager; struct zcr_gaming_seat_v2* gaming_seat; #endif + struct sl_stylus_input_manager* stylus_input_manager; struct sl_relative_pointer_manager* relative_pointer_manager; struct sl_pointer_constraints* pointer_constraints; struct wl_list outputs; diff --git a/sommelier-seat.cc b/sommelier-seat.cc index 0515856..a7f9660 100644 --- a/sommelier-seat.cc +++ b/sommelier-seat.cc @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "sommelier.h" // NOLINT(build/include_directory) -#include "sommelier-transform.h" // NOLINT(build/include_directory) -#include "sommelier-inpututils.h" // NOLINT(build/include_directory) +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-transform.h" // NOLINT(build/include_directory) +#include "sommelier-inpututils.h" // NOLINT(build/include_directory) +#include "sommelier-stylus-tablet.h" // NOLINT(build/include_directory) #include #include @@ -632,6 +633,12 @@ static const struct wl_touch_listener sl_touch_listener = { static void sl_host_touch_recorder_frame(void* data, struct sl_touchrecorder* recorder) { + struct sl_host_touch* host = static_cast(data); + + if (host->seat->stylus_tablet) { + sl_host_stylus_tablet_handle_touch(host->seat->stylus_tablet, recorder); + } + sl_touchrecorder_replay_to_listener(recorder, &sl_touch_listener, data); } diff --git a/sommelier-stylus-tablet.cc b/sommelier-stylus-tablet.cc new file mode 100644 index 0000000..8541c11 --- /dev/null +++ b/sommelier-stylus-tablet.cc @@ -0,0 +1,529 @@ +// Copyright 2023 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Unlike the other protocol handlers, this one talks the stylus-unstable-v2 +// protocol to to the host, but exposes the tablet-unstable-v2 protocol +// to the clients. + +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-transform.h" // NOLINT(build/include_directory) +#include "sommelier-stylus-tablet.h" // NOLINT(build/include_directory) +#include "sommelier-inpututils.h" // NOLINT(build/include_directory) + +#include +#include +#include +#include + +#include "tablet-unstable-v2-server-protocol.h" // NOLINT(build/include_directory) +#include "stylus-unstable-v2-client-protocol.h" // NOLINT(build/include_directory) + +namespace { + +// Versions supported by sommelier. +constexpr uint32_t kTabletManagerVersion = 1; + +} // namespace + +struct sl_host_tablet { + struct wl_resource* tablet; + struct wl_resource* pen_tool; + struct wl_resource* eraser_tool; + + bool valid; + uint32_t tool_type; + int32_t touch_id; + double force; + wl_fixed_t tilt_x; + wl_fixed_t tilt_y; + uint32_t last_time; + + void (*destroy_cb)(void* data); + void* data; +}; + +struct sl_host_stylus_tablet { + struct sl_seat* seat; + struct wl_touch* touch_proxy; + struct zcr_touch_stylus_v2* stylus_proxy; + struct sl_touchrecorder* recorder; + + struct sl_host_surface* focus_surface; + struct wl_listener focus_resource_listener; + struct wl_resource* focus_resource; + struct sl_host_tablet* tablet; +}; + +static void sl_host_tablet_destroy(struct sl_host_tablet* tablet); + +static void sl_host_tablet_tool_set_cursor(struct wl_client* client, + struct wl_resource* resource, + uint32_t serial, + struct wl_resource* surface, + int32_t hotspot_x, + int32_t hotspot_y) { + // NOOP +} + +static void sl_host_tablet_tool_destroy(struct wl_client* client, + struct wl_resource* tool) { + wl_resource_destroy(tool); +} + +static const struct zwp_tablet_tool_v2_interface tablet_tool_interface { + sl_host_tablet_tool_set_cursor, sl_host_tablet_tool_destroy, +}; + +static void sl_host_tablet_tool_destroy(struct wl_resource* tool) { + struct sl_host_tablet* tablet = + static_cast(wl_resource_get_user_data(tool)); + + if (tool == tablet->pen_tool) { + tablet->pen_tool = nullptr; + } else { + tablet->eraser_tool = nullptr; + } + + sl_host_tablet_destroy(tablet); +} + +static struct wl_resource* sl_host_tablet_tool_create( + struct wl_client* client, + struct wl_resource* tablet_seat, + struct sl_host_tablet* resources, + zwp_tablet_tool_v2_type type) { + struct wl_resource* tool = + wl_resource_create(client, &zwp_tablet_tool_v2_interface, + wl_resource_get_version(tablet_seat), 0); + wl_resource_set_implementation(tool, &tablet_tool_interface, resources, + sl_host_tablet_tool_destroy); + zwp_tablet_seat_v2_send_tool_added(tablet_seat, tool); + zwp_tablet_tool_v2_send_type(tool, type); + zwp_tablet_tool_v2_send_capability(tool, + ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE); + zwp_tablet_tool_v2_send_capability(tool, ZWP_TABLET_TOOL_V2_CAPABILITY_TILT); + zwp_tablet_tool_v2_send_done(tool); + + return tool; +} + +static void sl_host_tablet_tablet_destroy(struct wl_client* client, + struct wl_resource* tablet) { + wl_resource_destroy(tablet); +} + +static const struct zwp_tablet_v2_interface tablet_interface { + sl_host_tablet_tablet_destroy, +}; + +static void sl_host_tablet_tablet_destroy(struct wl_resource* resource) { + struct sl_host_tablet* tablet = + static_cast(wl_resource_get_user_data(resource)); + + tablet->tablet = nullptr; + + sl_host_tablet_destroy(tablet); +} + +static struct wl_resource* sl_host_tablet_tablet_create( + struct wl_client* client, + struct wl_resource* tablet_seat, + struct sl_host_tablet* tablet) { + struct wl_resource* resource = + wl_resource_create(client, &zwp_tablet_v2_interface, + wl_resource_get_version(tablet_seat), 0); + wl_resource_set_implementation(resource, &tablet_interface, tablet, + sl_host_tablet_tablet_destroy); + zwp_tablet_seat_v2_send_tablet_added(tablet_seat, resource); + zwp_tablet_v2_send_name(resource, "Touchscreen"); + zwp_tablet_v2_send_done(resource); + + return resource; +} + +static void sl_host_tablet_destroy(struct sl_host_tablet* tablet) { + void (*destroy_cb)(void* data) = tablet->destroy_cb; + void* data = tablet->data; + + if (tablet->pen_tool) { + zwp_tablet_tool_v2_send_removed(tablet->pen_tool); + wl_resource_set_destructor(tablet->pen_tool, nullptr); + wl_resource_destroy(tablet->pen_tool); + tablet->pen_tool = nullptr; + } + + if (tablet->eraser_tool) { + zwp_tablet_tool_v2_send_removed(tablet->eraser_tool); + wl_resource_set_destructor(tablet->eraser_tool, nullptr); + wl_resource_destroy(tablet->eraser_tool); + tablet->eraser_tool = nullptr; + } + + if (tablet->tablet) { + zwp_tablet_v2_send_removed(tablet->tablet); + sl_host_tablet_tablet_destroy(tablet->tablet); + wl_resource_set_destructor(tablet->tablet, nullptr); + wl_resource_destroy(tablet->tablet); + tablet->tablet = nullptr; + } + + delete tablet; + + destroy_cb(data); +} + +static struct sl_host_tablet* sl_host_tablet_create( + struct wl_client* client, + struct wl_resource* tablet_seat, + void (*destroy_cb)(void* data), + void* data) { + struct sl_host_tablet* tablet = new sl_host_tablet(); + tablet->tablet = sl_host_tablet_tablet_create(client, tablet_seat, tablet); + tablet->pen_tool = sl_host_tablet_tool_create(client, tablet_seat, tablet, + ZWP_TABLET_TOOL_V2_TYPE_PEN); + tablet->eraser_tool = sl_host_tablet_tool_create( + client, tablet_seat, tablet, ZWP_TABLET_TOOL_V2_TYPE_ERASER); + + tablet->destroy_cb = destroy_cb; + tablet->data = data; + + return tablet; +} + +static void sl_host_tablet_send_pressure_and_tilt(struct sl_host_tablet* data, + struct wl_resource* tool) { + if (data->force) { + uint32_t pressure = data->force * 65535.0; + zwp_tablet_tool_v2_send_pressure(tool, pressure); + data->force = 0.0; + } + + if (data->tilt_x || data->tilt_y) { + zwp_tablet_tool_v2_send_tilt(tool, data->tilt_x, data->tilt_y); + data->tilt_x = 0; + data->tilt_y = 0; + } +} + +static void sl_set_last_event_serial(struct wl_resource* surface_resource, + uint32_t serial) { + struct sl_host_surface* host_surface = static_cast( + wl_resource_get_user_data(surface_resource)); + + host_surface->last_event_serial = serial; +} + +static wl_resource* sl_host_tablet_active_tool(struct sl_host_tablet* tablet) { + if (tablet->tool_type == ZCR_TOUCH_STYLUS_V2_TOOL_TYPE_PEN) { + return tablet->pen_tool; + } else if (tablet->tool_type == ZCR_TOUCH_STYLUS_V2_TOOL_TYPE_ERASER) { + return tablet->eraser_tool; + } else { + abort(); + } +} + +static void sl_touch_stylus_touch_down(void* data, + struct wl_touch* wl_touch, + uint32_t serial, + uint32_t time, + struct wl_surface* surface, + int32_t id, + wl_fixed_t x, + wl_fixed_t y) { + struct sl_host_surface* host_surface = + surface ? static_cast(wl_surface_get_user_data(surface)) + : nullptr; + struct sl_host_stylus_tablet* stylus_tablet = + static_cast(data); + struct wl_resource* tool = sl_host_tablet_active_tool(stylus_tablet->tablet); + + wl_fixed_t ix = x; + wl_fixed_t iy = y; + + if (!host_surface) + return; + + if (id != stylus_tablet->tablet->touch_id) + return; + + if (host_surface->resource != stylus_tablet->focus_resource) { + wl_list_remove(&stylus_tablet->focus_resource_listener.link); + wl_list_init(&stylus_tablet->focus_resource_listener.link); + stylus_tablet->focus_resource = host_surface->resource; + stylus_tablet->focus_surface = host_surface; + wl_resource_add_destroy_listener(host_surface->resource, + &stylus_tablet->focus_resource_listener); + } + + if (stylus_tablet->seat->ctx->xwayland) { + // Make sure focus surface is on top before sending down event. + sl_restack_windows(stylus_tablet->seat->ctx, + wl_resource_get_id(host_surface->resource)); + sl_roundtrip(stylus_tablet->seat->ctx); + } + + sl_transform_host_to_guest_fixed(stylus_tablet->seat->ctx, host_surface, &ix, + &iy); + zwp_tablet_tool_v2_send_proximity_in( + tool, serial, stylus_tablet->tablet->tablet, host_surface->resource); + zwp_tablet_tool_v2_send_motion(tool, ix, iy); + zwp_tablet_tool_v2_send_frame(tool, time); + zwp_tablet_tool_v2_send_down(tool, serial); + sl_host_tablet_send_pressure_and_tilt(stylus_tablet->tablet, tool); + + if (stylus_tablet->focus_resource) + sl_set_last_event_serial(stylus_tablet->focus_resource, serial); + stylus_tablet->seat->last_serial = serial; + stylus_tablet->tablet->last_time = time; +} + +static void sl_touch_stylus_touch_up(void* data, + struct wl_touch* wl_touch, + uint32_t serial, + uint32_t time, + int32_t id) { + struct sl_host_stylus_tablet* stylus_tablet = + static_cast(data); + struct wl_resource* tool = sl_host_tablet_active_tool(stylus_tablet->tablet); + + if (id != stylus_tablet->tablet->touch_id) + return; + + wl_list_remove(&stylus_tablet->focus_resource_listener.link); + wl_list_init(&stylus_tablet->focus_resource_listener.link); + stylus_tablet->focus_resource = nullptr; + stylus_tablet->focus_surface = nullptr; + + sl_host_tablet_send_pressure_and_tilt(stylus_tablet->tablet, tool); + zwp_tablet_tool_v2_send_up(tool); + zwp_tablet_tool_v2_send_proximity_out(tool); + stylus_tablet->tablet->valid = false; + + if (stylus_tablet->focus_resource) + sl_set_last_event_serial(stylus_tablet->focus_resource, serial); + stylus_tablet->seat->last_serial = serial; + stylus_tablet->tablet->last_time = time; +} + +static void sl_touch_stylus_touch_motion(void* data, + struct wl_touch* wl_touch, + uint32_t time, + int32_t id, + wl_fixed_t x, + wl_fixed_t y) { + struct sl_host_stylus_tablet* stylus_tablet = + static_cast(data); + struct wl_resource* tool = sl_host_tablet_active_tool(stylus_tablet->tablet); + + wl_fixed_t ix = x; + wl_fixed_t iy = y; + + if (id != stylus_tablet->tablet->touch_id) + return; + + if (!stylus_tablet->focus_surface) + return; + + sl_transform_host_to_guest_fixed(stylus_tablet->seat->ctx, + stylus_tablet->focus_surface, &ix, &iy); + sl_host_tablet_send_pressure_and_tilt(stylus_tablet->tablet, tool); + zwp_tablet_tool_v2_send_motion(tool, ix, iy); + + stylus_tablet->tablet->last_time = time; +} + +static void sl_touch_stylus_touch_frame(void* data, struct wl_touch* wl_touch) { + struct sl_host_stylus_tablet* stylus_tablet = + static_cast(data); + struct wl_resource* tool = sl_host_tablet_active_tool(stylus_tablet->tablet); + + sl_host_tablet_send_pressure_and_tilt(stylus_tablet->tablet, tool); + zwp_tablet_tool_v2_send_frame(tool, stylus_tablet->tablet->last_time); +} + +static void sl_touch_stylus_touch_cancel(void* data, + struct wl_touch* wl_touch) { + struct sl_host_stylus_tablet* stylus_tablet = + static_cast(data); + struct wl_resource* tool = sl_host_tablet_active_tool(stylus_tablet->tablet); + + zwp_tablet_tool_v2_send_frame(tool, stylus_tablet->tablet->last_time); +} + +static const struct wl_touch_listener sl_touch_stylus_touch_listener = { + sl_touch_stylus_touch_down, sl_touch_stylus_touch_up, + sl_touch_stylus_touch_motion, sl_touch_stylus_touch_frame, + sl_touch_stylus_touch_cancel, +}; + +void sl_host_stylus_tablet_handle_touch( + struct sl_host_stylus_tablet* stylus_tablet, + struct sl_touchrecorder* recorder) { + if (stylus_tablet->tablet->valid) { + sl_touchrecorder_purge_id(recorder, stylus_tablet->tablet->touch_id); + } +} + +static void sl_host_tablet_recorder_frame(void* data, + struct sl_touchrecorder* recorder) { + struct sl_host_stylus_tablet* stylus_tablet = + static_cast(data); + if (stylus_tablet->tablet->valid) { + sl_touchrecorder_replay_to_listener( + recorder, &sl_touch_stylus_touch_listener, stylus_tablet); + } +} + +static void sl_touch_stylus_tool(void* data, + struct zcr_touch_stylus_v2* stylus, + uint32_t id, + uint32_t type) { + struct sl_host_stylus_tablet* stylus_tablet = + static_cast(data); + + if (type == ZCR_TOUCH_STYLUS_V2_TOOL_TYPE_TOUCH) { + return; + } + + stylus_tablet->tablet->tool_type = type; + stylus_tablet->tablet->touch_id = id; + stylus_tablet->tablet->valid = true; +} + +static void sl_touch_stylus_force(void* data, + struct zcr_touch_stylus_v2* stylus, + uint32_t time, + uint32_t id, + wl_fixed_t force) { + struct sl_host_stylus_tablet* stylus_tablet = + static_cast(data); + + stylus_tablet->tablet->force = wl_fixed_to_double(force); +} + +static void sl_touch_stylus_tilt(void* data, + struct zcr_touch_stylus_v2* stylus, + uint32_t time, + uint32_t id, + wl_fixed_t tilt_x, + wl_fixed_t tilt_y) { + struct sl_host_stylus_tablet* stylus_tablet = + static_cast(data); + + stylus_tablet->tablet->tilt_x = tilt_x; + stylus_tablet->tablet->tilt_y = tilt_y; +} + +static const struct zcr_touch_stylus_v2_listener + sl_internal_touch_stylus_listener = { + sl_touch_stylus_tool, + sl_touch_stylus_force, + sl_touch_stylus_tilt, +}; + +static void sl_tablet_focus_resource_destroyed(struct wl_listener* listener, + void* data) { + struct sl_host_stylus_tablet* host; + + host = wl_container_of(listener, host, focus_resource_listener); + wl_list_remove(&host->focus_resource_listener.link); + wl_list_init(&host->focus_resource_listener.link); + host->focus_resource = nullptr; + host->focus_surface = nullptr; +} + +static void sl_host_touch_tablet_destroy(void* data) { + struct sl_host_stylus_tablet* stylus_tablet = + static_cast(data); + + zcr_touch_stylus_v2_destroy(stylus_tablet->stylus_proxy); + wl_touch_destroy(stylus_tablet->touch_proxy); + sl_touchrecorder_destroy(stylus_tablet->recorder); + stylus_tablet->seat->stylus_tablet = nullptr; + + delete stylus_tablet; +} + +static struct sl_host_stylus_tablet* sl_host_touch_tablet_create( + struct sl_context* ctx, + struct wl_client* client, + struct sl_host_seat* seat, + struct wl_resource* tablet_seat) { + struct sl_host_stylus_tablet* stylus_tablet = new sl_host_stylus_tablet(); + + stylus_tablet->seat = seat->seat; + stylus_tablet->touch_proxy = wl_seat_get_touch(seat->proxy); + stylus_tablet->recorder = sl_touchrecorder_attach( + stylus_tablet->touch_proxy, sl_host_tablet_recorder_frame, nullptr, + stylus_tablet); + + stylus_tablet->stylus_proxy = zcr_stylus_v2_get_touch_stylus( + ctx->stylus_input_manager->internal, stylus_tablet->touch_proxy); + zcr_touch_stylus_v2_add_listener(stylus_tablet->stylus_proxy, + &sl_internal_touch_stylus_listener, + stylus_tablet); + + wl_list_init(&stylus_tablet->focus_resource_listener.link); + stylus_tablet->focus_resource_listener.notify = + sl_tablet_focus_resource_destroyed; + stylus_tablet->focus_resource = nullptr; + stylus_tablet->focus_surface = nullptr; + + stylus_tablet->tablet = sl_host_tablet_create( + client, tablet_seat, sl_host_touch_tablet_destroy, stylus_tablet); + + return stylus_tablet; +} + +static void sl_host_tablet_manager_get_tablet_seat( + struct wl_client* client, + struct wl_resource* tablet_manager, + uint32_t id, + struct wl_resource* seat) { + struct sl_context* ctx = + static_cast(wl_resource_get_user_data(tablet_manager)); + struct sl_host_seat* host_seat = + static_cast(wl_resource_get_user_data(seat)); + + struct wl_resource* tablet_seat = + wl_resource_create(client, &zwp_tablet_seat_v2_interface, + wl_resource_get_version(tablet_manager), id); + + if (host_seat->seat->stylus_tablet) + return; + + if (ctx->stylus_input_manager && ctx->stylus_input_manager->internal) { + host_seat->seat->stylus_tablet = + sl_host_touch_tablet_create(ctx, client, host_seat, tablet_seat); + } +} + +static void sl_host_tablet_manager_destroy(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_tablet_manager_v2_interface + sl_tablet_manager_implementation = {sl_host_tablet_manager_get_tablet_seat, + sl_host_tablet_manager_destroy}; + +static void sl_bind_host_tablet_manager(struct wl_client* client, + void* data, + uint32_t app_version, + uint32_t id) { + struct sl_context* ctx = static_cast(data); + struct wl_resource* resource = + wl_resource_create(client, &zwp_tablet_manager_v2_interface, + MIN(app_version, kTabletManagerVersion), id); + wl_resource_set_implementation(resource, &sl_tablet_manager_implementation, + ctx, nullptr); +} + +struct sl_global* sl_stylus_to_tablet_manager_global_create( + struct sl_context* ctx) { + return sl_global_create(ctx, &zwp_tablet_manager_v2_interface, + kTabletManagerVersion, ctx, + sl_bind_host_tablet_manager); +} diff --git a/sommelier-stylus-tablet.h b/sommelier-stylus-tablet.h new file mode 100644 index 0000000..8894bc3 --- /dev/null +++ b/sommelier-stylus-tablet.h @@ -0,0 +1,19 @@ +// Copyright 2023 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Unlike the other protocol handlers, this one talks the stylus-unstable-v2 +// protocol to to the host, but exposes the tablet-unstable-v2 protocol +// to the clients. + +#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_STYLUS_TABLET_H_ +#define VM_TOOLS_SOMMELIER_SOMMELIER_STYLUS_TABLET_H_ + +#include "sommelier-inpututils.h" // NOLINT(build/include_directory) + +struct sl_host_stylus_tablet; + +void sl_host_stylus_tablet_handle_touch(struct sl_host_stylus_tablet* tablet, + struct sl_touchrecorder* recorder); + +#endif // VM_TOOLS_SOMMELIER_SOMMELIER_STYLUS_TABLET_H_ diff --git a/sommelier.cc b/sommelier.cc index 6eb0b15..4ceb14d 100644 --- a/sommelier.cc +++ b/sommelier.cc @@ -43,6 +43,7 @@ #ifdef GAMEPAD_SUPPORT #include "gaming-input-unstable-v2-client-protocol.h" // NOLINT(build/include_directory) #endif +#include "stylus-unstable-v2-client-protocol.h" // NOLINT(build/include_directory) #include "keyboard-extension-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) #include "linux-dmabuf-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) #include "linux-explicit-synchronization-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) @@ -588,6 +589,7 @@ void sl_registry_handler(void* data, seat->id = id; seat->version = MIN(5, version); seat->last_serial = 0; + seat->stylus_tablet = nullptr; seat->host_global = sl_seat_global_create(seat); wl_list_insert(&ctx->seats, &seat->link); } else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0) { @@ -751,6 +753,24 @@ void sl_registry_handler(void* data, assert(!ctx->gaming_input_manager); ctx->gaming_input_manager = gaming_input_manager; #endif + } else if (strcmp(interface, "zcr_stylus_v2") == 0) { + struct sl_stylus_input_manager* stylus_input_manager = + static_cast( + malloc(sizeof(struct sl_stylus_input_manager))); + assert(stylus_input_manager); + stylus_input_manager->ctx = ctx; + stylus_input_manager->id = id; + stylus_input_manager->internal = static_cast( + wl_registry_bind(registry, id, &zcr_stylus_v2_interface, 1)); + + // Note: This does not forward the stylus-unstable-v2 protcol to the + // clients. Instead, it exposes tablet-unstable-v2 protocol to the clients. + // Note: This is the only user of ctx->stylus_input_manager + stylus_input_manager->tablet_host_global = + sl_stylus_to_tablet_manager_global_create(ctx); + + assert(!ctx->stylus_input_manager); + ctx->stylus_input_manager = stylus_input_manager; } else if (strcmp(interface, "zxdg_output_manager_v1") == 0 && ctx->use_direct_scale) { // This protocol cannot be bound unconditionally as doing so @@ -881,6 +901,11 @@ void sl_registry_remover(void* data, return; } #endif + if (ctx->stylus_input_manager && ctx->stylus_input_manager->id == id) { + sl_global_destroy(ctx->stylus_input_manager->tablet_host_global); + free(ctx->stylus_input_manager); + ctx->stylus_input_manager = NULL; + } if (ctx->relative_pointer_manager && ctx->relative_pointer_manager->id == id) { sl_global_destroy(ctx->relative_pointer_manager->host_global); diff --git a/sommelier.h b/sommelier.h index 2e8b4ad..04cb077 100644 --- a/sommelier.h +++ b/sommelier.h @@ -33,7 +33,6 @@ #define ALT_MASK (1 << 1) #define SHIFT_MASK (1 << 2) - struct sl_global; struct sl_compositor; struct sl_shm; @@ -100,6 +99,11 @@ struct sl_seat { uint32_t version; struct sl_global* host_global; uint32_t last_serial; + // Stylus events are received on both wl_touch and stylus interfaces. + // This is is used to coordinate between the two. + // + // TODO(b/281760854): exo should provide this protocol natively + struct sl_host_stylus_tablet* stylus_tablet; struct wl_list link; }; @@ -347,6 +351,13 @@ struct sl_gaming_input_manager { }; #endif +struct sl_stylus_input_manager { + struct sl_context* ctx; + uint32_t id; + struct zcr_stylus_v2* internal; + struct sl_global* tablet_host_global; +}; + struct sl_pointer_constraints { struct sl_context* ctx; uint32_t id; @@ -405,7 +416,6 @@ struct sl_host_registry { struct wl_list link; }; - typedef void (*sl_sync_func_t)(struct sl_context* ctx, struct sl_sync_point* sync_point); @@ -455,6 +465,9 @@ struct sl_global* sl_subcompositor_global_create(struct sl_context* ctx); struct sl_global* sl_shell_global_create(struct sl_context* ctx); +struct sl_global* sl_stylus_to_tablet_manager_global_create( + struct sl_context* ctx); + double sl_output_aura_scale_factor_to_double(int scale_factor); // Given a position in global host logical space (see sommelier-transform.h), @@ -510,7 +523,6 @@ void sl_restack_windows(struct sl_context* ctx, uint32_t focus_resource_id); void sl_roundtrip(struct sl_context* ctx); - struct sl_window* sl_lookup_window(struct sl_context* ctx, xcb_window_t id); int sl_is_our_window(struct sl_context* ctx, xcb_window_t id);