Merge branch 'stylus-protocol' into 'main'

Draft: proto: Define new ei_stylus protocol

See merge request libinput/libei!341
This commit is contained in:
Jason Gerecke 2025-12-25 05:02:12 -08:00
commit 107e896aea

View file

@ -6,6 +6,7 @@
Copyright © 2010-2011 Intel Corporation
Copyright © 2012-2013 Collabora, Ltd.
Copyright © 2023 Red Hat, Inc.
Copyright © 2025 Wacom Co., Ltd.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
@ -755,8 +756,9 @@
any relative movement on this region for that movement to match the same
*physical* movement on another region.
It is an EIS implementation bug to advertise the touch and/or absolute pointer capability
on a device_type.virtual device without advertising an ei_region for this device.
It is an EIS implementation bug to advertise the touch, absolute pointer, and/or
stylus capabilities on a device_type.virtual device without advertising an ei_region
for this device.
This event is optional and sent immediately after object creation. Where a device
has multiple regions, this event is sent once for each region.
@ -784,6 +786,7 @@
- "ei_button"
- "ei_keyboard"
- "ei_touchscreen"
- "ei_stylus"
The interface version is equal or less to the client-supported
version in ei_handshake.interface_version for the respective interface.
@ -1587,4 +1590,529 @@
<arg name="touchid" type="uint32"/>
</event>
</interface>
<interface name="ei_stylus" version="1">
<description summary="stylus object">
Interface for stylus requests and events.
A stylus is an absolute pointing tool commonly used for writing, drawing,
and other tasks traditionally accomplished with a pen or pencil. This
interface represents the various semantics and properties common to these
kinds of tools. An ei_device may use this interface alone or in
combination with other interfaces to emulate tools with additional
features. For example, a tool with physical buttons may be emulated by
creating an ei_device that has both this and an ei_button interface.
When adding this interface to an ei_device, the server and client are
required to negotiate tool capabilities. Negotiation is performed by the
server first sending ei_stylus.tool_capabilities prior to ei_device.done
and then the client responding with ei_stylus.bind_tool_capabilities
prior to starting emulation for the first time.
This interface may be used on both ei_device.device_type.virtual and
ei_device.device_type.physical devices. As an interface with absolute
motion, virtual devices must declare an arbitrary set of regions that
are be valid for motion events. For physical devices, the server must
send an ei_device.dimensions that reflects the size of the active area
of the stylus' digitizer. This size **must not** include any
"[out-of-bounds][OOB]" margin that may exist (e.g. a digitizer with a
404 x 229 mm active area and 2mm margin on all sides should have a
declared size of only 400 x 225 mm).
Implementations are free to decide how they handle multiple styli. For
example, a sender that is capable of distinguishing two styli (e.g. by
serial number) may choose to use a unique ei_device object for each
stylus, making it possible for the receiver to keep track of which tool
is in use. Senders are not obligated to do this; it is equally valid for
them to discard any distinguishing information that they may (or may not)
have and use a single ei_device for all styli. Similarly, receivers are
free to choose whether to use or discard information about the source
ei_device after processing.
This interface is only provided once per device and where a client
requests ei_stylus.release the interface does not get re-initialized. An
EIS implementation may adjust the behavior of the device (including
removing the device) if the interface is released.
Note that for a client to receive objects of this type, it must announce
support for the interface in ei_handshake.interface_version.
### Protocol States
This protocol is stateful and relies on ei_device.frame to delimit state
changes. Only those properties, axes, and other values that have changed
from one frame to the next are required to be sent.
This documentation may use various phrases to describe the major
proximity states of the protocol. For clarity, these phrases are defined
below:
- **Out of proximity**: Initial state of an ei_stylus object. This state
indicates that a stylus tool is unable to be sensed by a digitizer.
Its location and other properties are unknown an invalid in this
state. This state can only be transitioned out of by sending a frame
containing ei_stylus.proximity_in.
- **Entering proximity**: State of the ei_stylus object for the duration
of the frame containing ei_stylus.proximity_in. This state indicates
that a stylus has just come near enough to a digitizer for its
location and other properties to become known and valid. This is a
transient state that automatically advances to "in proximity" once the
frame has finished processing.
- **In proximity**: State of the ei_stylus object during normal
operation. This state indicates that the stylus is remaining near
enough to its digitizer for location and other properties to remain
valid. This state is transitioned out of by sending a frame containing
ei_stylus.proximity_out.
- **Leaving proximity**: State of the ei_stylus for the duration of the
frame containing ei_stylus.proximity_out. This state indicates that
the stylus has just moved far enough from its digitizer for its
location and other properties to no longer be known or valid. This is
a transient state that automatically advances to "out of proximity"
once the frame has finished processing. Some stylus hardware/drivers
may provide a final update of the last-known valid location and other
properties as a stylus leaves proximity. This protocol allows such a
final update to be sent in this state.
### Logical Contact
This protocol uses the concept of "logical contact" to represent when a
stylus is making intentional contact with its digitizer. Logical
contact is a heuristic and often flagged by hardware drivers based on
things like the state of a hardware "tip switch", the level of pressure
compared to a threshold, etc. It is important to note that physical
contact does *not* necessarily imply logical contact; some physical
contacts are unintentional and some imperfect tools may indicate
physical contact even while hovering (e.g. by always sending some small
non-zero pressure value).
### Stylus Buttons
This protocol does not define notifications or events for the state of
buttons that exist on it. Instead, it is expected that implementations
ensure that the parent ei_device declares both this and an ei_button
interface. Button requests should be routed through the button interface
and be part of the same ei_device.frame that contains the coincident
stylus requests.
[OOB]: https://wayland.freedesktop.org/libinput/doc/latest/tablet-support.html#out-of-bounds-motion-events
</description>
<!-- ei_stylus client requests version 1 -->
<enum name="capability" since="1">
<description summary="capability type">
This enum denotes capability types for the ei_stylus.
A capability describes a particular type of data that may be reported
by a device or tool. Each capability is a bit flag that may be set in
a mask describing a full set of capabilities.
</description>
<entry name="erase" value="1" summary="the tool may act as an eraser"/>
<entry name="pressure" value="2" summary="pressure data may be reported"/>
<entry name="distance" value="4" summary="distance data may be reported"/>
<entry name="tilt" value="8" summary="tilt data may be reported"/>
<entry name="rotation" value="16" summary="rotation data may be reported"/>
<entry name="airbrush_flow" value="32" summary="airbrush_flow data may be reported"/>
</enum>
<request name="release" since="1">
<description summary="stylus removal request">
Notification that the client is no longer interested in this stylus
object. The EIS implementation will release any resources related to
this object and send the ei_stylus.destroyed event once complete.
</description>
</request>
<request name="bind_tool_capabilities" since="1" context-type="sender">
<description summary="stylus tool capability bind request">
Request to bind to a given set of tool capabilities. This is used
by clients to describe which subset of server-supported capabilities
may be sent by this particular stylus.
Clients are required to send this in response to the
ei_stylus.tool_capabilities event. It is required to be sent prior to
the first ei_device.start_emulating request. It is a protocol violation
to not provide this feedback back to the server. It is a protocol
violation to include capabilities that were not present in the server
event.
It is a protocol violation to send requests or events for unbound
capabilities.
</description>
<arg name="capabilities" type="uint32" summary="A mask of 'capability' flags that the client wishes to handle"/>
</request>
<request name="proximity_in" since="1" context-type="sender">
<description summary="sylus proximity in request">
Notification that a stylus is entering proximity of its digitizer. The
stylus object transitions from an "out of proximity" state to
"entering proximity" for the duration of the ei_device.frame that
contains this request. After the frame has been processed, the
object automatically transitions to an "in proximity" state.
The frame containing this request is required to also contain
ei_stylus.motion.
It is a protocol violation to send this request while the stylus object
is already in or entering proximity. It is a protocol violation to send
ei_stylus.proximity_in and ei_device.proximity_out in the same
frame.
</description>
</request>
<request name="proximity_out" since="1" context-type="sender">
<description summary="stylus proximity out request">
Notification that the the stylus is leaving proximity of its
digitizer. The stylus object transitions from an "in proximity" state
to "leaving proximity" for the duration of the ei_device.frame that
contains this request. After the frame has been processed, the object
automatically transitions to an "out of proximity" state.
Once a stylus is "out of proximity", all other state information
(e.g. tool type, up/down/erase state, axis values) is considered
invalid and must be cleared on both the sending and receiving side.
The frame containing this request may contain updates of the stylus
state (location, tilt, etc.) that represents its final known valid
properties. It should not contain events that attempt to explicitly
reset state.
It is a protocol violation to send this request while the stylus object
is already leaving or out of proximity. It is a protocol violation to
send ei_stylus.proximity_in and ei_device.proximity_out in the same
frame.
</description>
</request>
<request name="erase_start" since="1" context-type="sender">
<description summary="erase start request">
Notification that the stylus's eraser feature has been activated.
Physical styli often include some kind of eraser feature, activated by
flipping the tool over or holding a button down. These button-activated
tools in particular may start or stop erasing without ever leaving
proximity.
The default state for tools is "not erasing".
This request may only be sent while the stylus is entering or in
proximity. It is a protocol violation to send this request at any
other time. It is a protocol violation to for ei_stylus.erase_start and
ei_stylus.erase_stop to share the same ei_device.frame. It is a client
bug to send this request when the ei_stylus.capability.erase capability
is not bound. The EIS implementation may ignore unbound requests and/or
disconnect the client.
</description>
</request>
<request name="erase_stop" since="1" context-type="sender">
<description summary="erase stop request">
Notification that the stylus's eraser feature has been deactivated.
Physical styli often include some kind of eraser feature, activated by
flipping the tool over or holding a button down. These button-activated
tools in particular may start or stop erasing without ever leaving
proximity.
The default state for tools is "not erasing".
This request may only be sent while the stylus is in or leaving
proximity. It is a protocol violation to send this request at any
other time. It is a protocol violation to for ei_stylus.erase_start and
ei_stylus.erase_stop to share the same ei_device.frame. It is a client
bug to send this request when the ei_stylus.capability.erase capability
is not bound. The EIS implementation may ignore unbound requests and/or
disconnect the client.
</description>
</request>
<request name="down" since="1" context-type="sender">
<description summary="stylus down request">
Notification that the stylus has come into logical contact with its
digitizer.
Senders are required to use this request to signal logical contact.
Receivers are free to treat the stylus as "up" in the absence of this
event, regardless of other axis values that may be present (e.g.
pressure, distance).
This request may only be sent while the stylus is entering or in
proximity. It is a protocol violation to send this request at any other
time. It is a protocol violation to for ei_stylus.down and ei_stylus.up
to share the same ei_device.frame.
</description>
</request>
<request name="up" since="1" context-type="sender">
<description summary="stylus up request">
Notification that the stylus has left logical contact with its
digitizer.
Senders are required to generate this request to signal the loss of
logical contact. Receivers are free to treat the stylus as "down" in
the absence of this event, regardless of other axis values that may be
present (e.g. pressure, distance).
This request may only be sent while the stylus is in or leaving
proximity. It is a protocol violation to send this request at any other
time. It is a protocol violation to for ei_stylus.down and ei_stylus.up
to share the same ei_device.frame.
</description>
</request>
<request name="motion" since="1" context-type="sender">
<description summary="stylus motion request">
Notification that the stylus has been moved to the given absolute
coordinates. The interpretation of values depends on if the device is
physical (mm) or virtual (pixels). Fractional pixels are allowed.
Valid `(x, y)` locations are those that exist inside of the
ei_device.dimension (for physical devices) or one of the device's
ei_device.region (for virtual devices). It is a client bug to send a
location that exists outside of these locations. The EIS implementation
may clamp out-of-range values and/or disconnect the client.
This request is required to be in the same frame as
ei_stylus.proximity_in. It may also be sent while the stylus is in or
leaving proximity. It is a protocol violation to send this request at
any other time.
</description>
<arg name="x" type="float" summary="The x position of the stylus in mm or logical pixels."/>
<arg name="y" type="float" summary="The y position of the stylus in mm or logical pixels."/>
</request>
<request name="pressure" since="1" context-type="sender">
<description summary="stylus pressure request">
Notification of the relative amount of pressure exerted on the stylus.
The valid range for pressure is `0.0 &lt;= pressure &lt;= 1.0`. It is a
client bug to send values outside of this range. The EIS implementation
may clamp out-of-range values and/or disconnect the client.
The value `pressure = 0.0` indicates the stylus is experiencing
"minimum" or "no" pressure, and `pressure = 1.0` indicates the stylus
is measuring some arbitrary "maximum pressure".
This request may only be sent while the stylus is entering, in, or
leaving proximity. It is a protocol violation to send this request at
any other time.
> [!IMPORTANT]
> Non-zero pressure does not imply logical contact, but logical
> contact _does_ imply a non-zero pressure. If a client has bound the
> pressure capability, it is a bug to not ensure `pressure &gt; 0.0`
> when sending ei_stylus.down. The EIS implementation may correct
> illegal values and/or disconnect the client.
</description>
<arg name="pressure" type="float" summary="The tip pressure as a normalized value."/>
</request>
<request name="distance" since="1" context-type="sender">
<description summary="stylus distance request">
Notification of the relative distance between the stylus and its
digitizer.
The valid range for distance is `0.0 &lt;= distance &lt;= 1.0`. It is a
client bug to send values outside of this range. The EIS implementation
may clamp out-of-range values and/or disconnect the client.
The value `distance = 0.0` indicates the stylus is in physical contact
with the digitizer and `distance = 1.0` indicates the stylus is at some
arbitrary "maximum distance".
This request may only be sent while the stylus is entering, in, or
leaving proximity. It is a protocol violation to send this request at
any other time.
> [!IMPORTANT]
> Physical contact does not imply logical contact, but logical
> contact _does_ imply physical contact. If a client has bound the
> distance capability, it is a bug to not ensure that `d = 0.0` when
> sending ei_stylus.down. The EIS implementation may correct illegal
> values and/or disconnect the client.
</description>
<arg name="distance" type="float" summary="The tip-to-digitizer distance as a normalized value."/>
</request>
<request name="tilt" since="1" context-type="sender">
<description summary="stylus tilt request">
Notification of the stylus's tilt angles in degrees.
The valid range for each tilt angle is `-90.0 &lt;= tilt_[xy] &lt;=
+90.0`. It is a client bug to send values outside of this range. The
EIS implementation may clamp out-of-range values and/or disconnect
the client.
Each tilt angle is measured relative to the digitizer's Z axis. The pair
`(tilt_x = 0, tilt_y = 0)` indicates the stylus is being held parallel
to the Z axis, while `(tilt_x = 90, tilt_y = 0)` and `(tilt_x = 0,
tilt_y = 90)` indicate the stylus parallel to the +X and +Y axes,
respectively.
This request may only be sent while the stylus is entering, in, or
leaving proximity. It is a protocol violation to send this request at
any other time.
</description>
<arg name="tilt_x" type="float" summary="The angle in degrees of the stylus along the digitizer X axis."/>
<arg name="tilt_y" type="float" summary="The angle in degrees of the stylus along the digitizer Y axis."/>
</request>
<request name="rotation" since="1" context-type="sender">
<description summary="stylus rotation request">
Notification of the stylus's barrel rotation angle in degrees.
The valid range for barrel rotation is `0.0 &lt;= rotation &lt; 360.0`.
It is a client bug to send values outside of this range. The EIS
implementation may reduce out-of-range values modulo 360 and/or
disconnect the client.
The value `rotation = 0.0` indicates the stylus is being held at its
neutral rotation angle. Angles increase in value as the stylus is
rotated clockwise while held.
This request may only be sent while the stylus is entering, in, or
leaving proximity. It is a protocol violation to send this request at
any other time.
</description>
<arg name="rotation" type="float" summary="The angle in degrees of the stylus about its own Z axis."/>
</request>
<request name="airbrush_flow" since="1" context-type="sender">
<description summary="stylus airbrush flow request">
Notification of the relative position of an airbrush stylus's flow
control.
The valid range for airbrush flow is `0.0 &lt;= flow &lt;= 1.0`. It is
a client bug to send values outside of this range. The EIS
implementation may clamp out-of-range values and/or disconnect the
client.
The value `flow = 0.0` indicates the airbrush flow control is fully
"off" and `flow = 1.0` indicates the control is fully "on".
This request may only be sent while the stylus is entering, in, or
leaving proximity. It is a protocol violation to send this request at
any other time.
> [!IMPORTANT]
> Many platforms report the airbrush flow control through a generic
> "tangential pressure" or "slider" property. These generic properties
> may contain other types of data when non-airbrush tools are in use.
> Developers should take care to not report other types of data with
> this request.
</description>
<arg name="flow" type="float" summary="The position of an airbrush flow control present on the tool, as a normalized value."/>
</request>
<!-- ei_stylus events version 1 -->
<event name="destroyed" type="destructor" since="1">
<description summary="stylus removal notification">
This object has been removed and a client should release all associated
resources.
This object will be destroyed by the EIS implementation immediately
after this event is sent and as such the client must not attempt to use
it after that point.
</description>
<arg name="serial" type="uint32" summary="This event's serial number."/>
</event>
<event name="tool_capabilities" since="1">
<description summary="stylus tool capabilities event">
See the ei_stylus.bind_tool_capabilities request for more details.
The server sends this event after attaching the ei_stylus interface
to an ei_device. This event describes the set of capabilities that
the tool supports. For sender contexts, the client must confirm
(and optionally narrow down) the capabilities with the
ei_stylus.bind_tool_capabilities request before the device will
send events.
</description>
<arg name="capabilities" type="uint32" summary="A mask of 'capability' flags for the events available through this interface"/>
</event>
<event name="proximity_in" since="1" context-type="receiver">
<description summary="sylus proximity in event">
See the ei_stylus.proximity_in request for more details.
</description>
</event>
<event name="proximity_out" since="1" context-type="receiver">
<description summary="stylus proximity out event">
See the ei_stylus.proximity_out request for more details.
</description>
</event>
<event name="erase_start" since="1" context-type="receiver">
<description summary="erase start event">
See the ei_stylus.erase_start request for more details.
</description>
</event>
<event name="erase_stop" since="1" context-type="receiver">
<description summary="erase stop event">
See the ei_stylus.erase_stop request for more details.
</description>
</event>
<event name="down" since="1" context-type="receiver">
<description summary="stylus down event">
See the ei_stylus.down request for more details.
</description>
</event>
<event name="up" since="1" context-type="receiver">
<description summary="stylus up event">
See the ei_stylus.up request for more details.
</description>
</event>
<event name="motion" since="1" context-type="receiver">
<description summary="stylus motion event">
See the ei_stylus.motion request for more details.
</description>
<arg name="x" type="float" summary="The x position of the stylus in mm or logical pixels."/>
<arg name="y" type="float" summary="The y position of the stylus in mm or logical pixels."/>
</event>
<event name="pressure" since="1" context-type="receiver">
<description summary="stylus pressure event">
See the ei_stylus.pressure request for more details.
</description>
<arg name="pressure" type="float" summary="The tip pressure as a normalized value."/>
</event>
<event name="distance" since="1" context-type="receiver">
<description summary="stylus distance event">
See the ei_stylus.distance request for more details.
</description>
<arg name="distance" type="float" summary="The tip-to-digitizer distance as a normalized value."/>
</event>
<event name="tilt" since="1" context-type="receiver">
<description summary="stylus tilt event">
See the ei_stylus.tilt request for more details.
</description>
<arg name="tilt_x" type="float" summary="The angle in degrees of the stylus along the digitizer X axis."/>
<arg name="tilt_y" type="float" summary="The angle in degrees of the stylus along the digitizer Y axis."/>
</event>
<event name="rotation" since="1" context-type="receiver">
<description summary="stylus rotation event">
See the ei_stylus.rotation request for more details.
</description>
<arg name="rotation" type="float" summary="The angle in degrees of the stylus about its own Z axis."/>
</event>
<event name="airbrush_flow" since="1" context-type="receiver">
<description summary="stylus slider event">
See the ei_stylus.airbrush_flow request for more details.
</description>
<arg name="flow" type="float" summary="The position of an airbrush flow control present on the tool, as a normalized value."/>
</event>
</interface>
</protocol>