diff --git a/CMakeLists.txt b/CMakeLists.txt
index f491e100..513f06bb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -251,7 +251,6 @@ target_link_libraries(Hyprland
uuid
)
-protocol("protocols/tablet-unstable-v2.xml" "tablet-unstable-v2" true)
protocol("protocols/wlr-layer-shell-unstable-v1.xml" "wlr-layer-shell-unstable-v1" true)
protocol("protocols/wlr-screencopy-unstable-v1.xml" "wlr-screencopy-unstable-v1" true)
protocol("subprojects/hyprland-protocols/protocols/hyprland-global-shortcuts-v1.xml" "hyprland-global-shortcuts-v1" true)
@@ -285,6 +284,7 @@ protocolNew("unstable/pointer-constraints/pointer-constraints-unstable-v1.xml" "
protocolNew("staging/xdg-activation/xdg-activation-v1.xml" "xdg-activation-v1" false)
protocolNew("staging/ext-idle-notify/ext-idle-notify-v1.xml" "ext-idle-notify-v1" false)
protocolNew("staging/ext-session-lock/ext-session-lock-v1.xml" "ext-session-lock-v1" false)
+protocolNew("stable/tablet/tablet-v2.xml" "tablet-v2" false)
# tools
add_subdirectory(hyprctl)
diff --git a/protocols/meson.build b/protocols/meson.build
index 4c315797..345b47ee 100644
--- a/protocols/meson.build
+++ b/protocols/meson.build
@@ -29,7 +29,6 @@ protocols = [
[wl_protocol_dir, 'unstable/text-input/text-input-unstable-v1.xml'],
['wlr-layer-shell-unstable-v1.xml'],
['wlr-screencopy-unstable-v1.xml'],
- ['tablet-unstable-v2.xml'],
[hl_protocol_dir, 'protocols/hyprland-toplevel-export-v1.xml'],
[hl_protocol_dir, 'protocols/hyprland-global-shortcuts-v1.xml']
]
@@ -60,6 +59,7 @@ new_protocols = [
[wl_protocol_dir, 'staging/xdg-activation/xdg-activation-v1.xml'],
[wl_protocol_dir, 'staging/ext-idle-notify/ext-idle-notify-v1.xml'],
[wl_protocol_dir, 'staging/ext-session-lock/ext-session-lock-v1.xml'],
+ [wl_protocol_dir, 'stable/tablet/tablet-v2.xml'],
]
wl_protos_src = []
diff --git a/protocols/tablet-unstable-v2.xml b/protocols/tablet-unstable-v2.xml
deleted file mode 100644
index b286d964..00000000
--- a/protocols/tablet-unstable-v2.xml
+++ /dev/null
@@ -1,1178 +0,0 @@
-
-
-
-
- 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/src/Compositor.cpp b/src/Compositor.cpp
index caf042bc..a31b1a57 100644
--- a/src/Compositor.cpp
+++ b/src/Compositor.cpp
@@ -242,8 +242,6 @@ void CCompositor::initServer() {
Debug::log(INFO, "VR will not be available");
}
- // m_sWLRTabletManager = wlr_tablet_v2_create(m_sWLDisplay);
-
m_sWLRForeignRegistry = wlr_xdg_foreign_registry_create(m_sWLDisplay);
wlr_xdg_foreign_v1_create(m_sWLDisplay, m_sWLRForeignRegistry);
diff --git a/src/Compositor.hpp b/src/Compositor.hpp
index d017949b..96557b8d 100644
--- a/src/Compositor.hpp
+++ b/src/Compositor.hpp
@@ -56,7 +56,6 @@ class CCompositor {
wlr_presentation* m_sWLRPresentation;
wlr_egl* m_sWLREGL;
int m_iDRMFD;
- wlr_tablet_manager_v2* m_sWLRTabletManager;
wlr_xdg_foreign_registry* m_sWLRForeignRegistry;
wlr_linux_dmabuf_v1* m_sWLRLinuxDMABuf;
wlr_backend* m_sWLRHeadlessBackend;
diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp
index c0dc8d8e..56b7bd2b 100644
--- a/src/debug/HyprCtl.cpp
+++ b/src/debug/HyprCtl.cpp
@@ -22,6 +22,7 @@
#include "../devices/IPointer.hpp"
#include "../devices/IKeyboard.hpp"
#include "../devices/ITouch.hpp"
+#include "../devices/Tablet.hpp"
static void trimTrailingComma(std::string& str) {
if (!str.empty() && str.back() == ',')
@@ -557,7 +558,7 @@ std::string devicesRequest(eHyprCtlOutputFormat format, std::string request) {
result += "\"tablets\": [\n";
- for (auto& d : g_pInputManager->m_lTabletPads) {
+ for (auto& d : g_pInputManager->m_vTabletPads) {
result += std::format(
R"#( {{
"address": "0x{:x}",
@@ -567,26 +568,26 @@ std::string devicesRequest(eHyprCtlOutputFormat format, std::string request) {
"name": "{}"
}}
}},)#",
- (uintptr_t)&d, (uintptr_t)d.pTabletParent, escapeJSONStrings(d.pTabletParent ? d.pTabletParent->name : ""));
+ (uintptr_t)d.get(), (uintptr_t)d->parent.get(), escapeJSONStrings(d->parent ? d->parent->hlName : ""));
}
- for (auto& d : g_pInputManager->m_lTablets) {
+ for (auto& d : g_pInputManager->m_vTablets) {
result += std::format(
R"#( {{
"address": "0x{:x}",
"name": "{}"
}},)#",
- (uintptr_t)&d, escapeJSONStrings(d.name));
+ (uintptr_t)d.get(), escapeJSONStrings(d->hlName));
}
- for (auto& d : g_pInputManager->m_lTabletTools) {
+ for (auto& d : g_pInputManager->m_vTabletTools) {
result += std::format(
R"#( {{
"address": "0x{:x}",
"type": "tabletTool",
"belongsTo": "0x{:x}"
}},)#",
- (uintptr_t)&d, d.wlrTabletTool ? (uintptr_t)d.wlrTabletTool->data : 0);
+ (uintptr_t)d.get(), d->wlr() ? (uintptr_t)d->wlr()->data : 0);
}
trimTrailingComma(result);
@@ -643,16 +644,16 @@ std::string devicesRequest(eHyprCtlOutputFormat format, std::string request) {
result += "\n\nTablets:\n";
- for (auto& d : g_pInputManager->m_lTabletPads) {
- result += std::format("\tTablet Pad at {:x} (belongs to {:x} -> {})\n", (uintptr_t)&d, (uintptr_t)d.pTabletParent, d.pTabletParent ? d.pTabletParent->name : "");
+ for (auto& d : g_pInputManager->m_vTabletPads) {
+ result += std::format("\tTablet Pad at {:x} (belongs to {:x} -> {})\n", (uintptr_t)d.get(), (uintptr_t)d->parent.get(), d->parent ? d->parent->hlName : "");
}
- for (auto& d : g_pInputManager->m_lTablets) {
- result += std::format("\tTablet at {:x}:\n\t\t{}\n\t\t\tsize: {}x{}mm\n", (uintptr_t)&d, d.name, d.wlrTablet->width_mm, d.wlrTablet->height_mm);
+ for (auto& d : g_pInputManager->m_vTablets) {
+ result += std::format("\tTablet at {:x}:\n\t\t{}\n\t\t\tsize: {}x{}mm\n", (uintptr_t)d.get(), d->hlName, d->wlr()->width_mm, d->wlr()->height_mm);
}
- for (auto& d : g_pInputManager->m_lTabletTools) {
- result += std::format("\tTablet Tool at {:x} (belongs to {:x})\n", (uintptr_t)&d, d.wlrTabletTool ? (uintptr_t)d.wlrTabletTool->data : 0);
+ for (auto& d : g_pInputManager->m_vTabletTools) {
+ result += std::format("\tTablet Tool at {:x} (belongs to {:x})\n", (uintptr_t)d.get(), d->wlr() ? (uintptr_t)d->wlr()->data : 0);
}
result += "\n\nTouch:\n";
diff --git a/src/devices/IHID.hpp b/src/devices/IHID.hpp
index 2830864b..cac25112 100644
--- a/src/devices/IHID.hpp
+++ b/src/devices/IHID.hpp
@@ -8,6 +8,7 @@ enum eHIDCapabilityType : uint32_t {
HID_INPUT_CAPABILITY_KEYBOARD = (1 << 0),
HID_INPUT_CAPABILITY_POINTER = (1 << 1),
HID_INPUT_CAPABILITY_TOUCH = (1 << 2),
+ HID_INPUT_CAPABILITY_TABLET = (1 << 3),
};
enum eHIDType {
@@ -16,6 +17,8 @@ enum eHIDType {
HID_TYPE_KEYBOARD,
HID_TYPE_TOUCH,
HID_TYPE_TABLET,
+ HID_TYPE_TABLET_TOOL,
+ HID_TYPE_TABLET_PAD,
};
/*
diff --git a/src/devices/Tablet.cpp b/src/devices/Tablet.cpp
new file mode 100644
index 00000000..f5b610a9
--- /dev/null
+++ b/src/devices/Tablet.cpp
@@ -0,0 +1,326 @@
+#include "Tablet.hpp"
+#include "../defines.hpp"
+#include "../protocols/Tablet.hpp"
+
+SP CTablet::create(wlr_tablet* tablet) {
+ SP pTab = SP(new CTablet(tablet));
+
+ pTab->self = pTab;
+
+ PROTO::tablet->registerDevice(pTab);
+
+ return pTab;
+}
+
+SP CTabletTool::create(wlr_tablet_tool* tablet) {
+ SP pTab = SP(new CTabletTool(tablet));
+
+ pTab->self = pTab;
+
+ PROTO::tablet->registerDevice(pTab);
+
+ return pTab;
+}
+
+SP CTabletPad::create(wlr_tablet_pad* tablet) {
+ SP pTab = SP(new CTabletPad(tablet));
+
+ pTab->self = pTab;
+
+ PROTO::tablet->registerDevice(pTab);
+
+ return pTab;
+}
+
+SP CTabletTool::fromWlr(wlr_tablet_tool* tool) {
+ return ((CTabletTool*)tool->data)->self.lock();
+}
+
+SP CTablet::fromWlr(wlr_tablet* tablet) {
+ return ((CTablet*)tablet->data)->self.lock();
+}
+
+static uint32_t wlrUpdateToHl(uint32_t wlr) {
+ uint32_t result = 0;
+ if (wlr & WLR_TABLET_TOOL_AXIS_X)
+ result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_X;
+ if (wlr & WLR_TABLET_TOOL_AXIS_Y)
+ result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_Y;
+ if (wlr & WLR_TABLET_TOOL_AXIS_DISTANCE)
+ result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_DISTANCE;
+ if (wlr & WLR_TABLET_TOOL_AXIS_PRESSURE)
+ result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_PRESSURE;
+ if (wlr & WLR_TABLET_TOOL_AXIS_TILT_X)
+ result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_TILT_X;
+ if (wlr & WLR_TABLET_TOOL_AXIS_TILT_Y)
+ result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_TILT_Y;
+ if (wlr & WLR_TABLET_TOOL_AXIS_ROTATION)
+ result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_ROTATION;
+ if (wlr & WLR_TABLET_TOOL_AXIS_SLIDER)
+ result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_SLIDER;
+ if (wlr & WLR_TABLET_TOOL_AXIS_WHEEL)
+ result |= CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_WHEEL;
+ return result;
+}
+
+uint32_t CTablet::getCapabilities() {
+ return HID_INPUT_CAPABILITY_POINTER | HID_INPUT_CAPABILITY_TABLET;
+}
+
+wlr_tablet* CTablet::wlr() {
+ return tablet;
+}
+
+CTablet::CTablet(wlr_tablet* tablet_) : tablet(tablet_) {
+ if (!tablet)
+ return;
+
+ tablet->data = this;
+
+ // clang-format off
+ hyprListener_destroy.initCallback(&tablet->base.events.destroy, [this] (void* owner, void* data) {
+ tablet = nullptr;
+ disconnectCallbacks();
+ events.destroy.emit();
+ }, this, "CTablet");
+
+ hyprListener_axis.initCallback(&tablet->events.axis, [this] (void* owner, void* data) {
+ auto E = (wlr_tablet_tool_axis_event*)data;
+
+ tabletEvents.axis.emit(SAxisEvent{
+ .tool = E->tool,
+ .tablet = self.lock(),
+ .timeMs = E->time_msec,
+ .updatedAxes = wlrUpdateToHl(E->updated_axes),
+ .axis = {E->x, E->y},
+ .axisDelta = {E->dx, E->dy},
+ .tilt = {E->tilt_x, E->tilt_y},
+ .pressure = E->pressure,
+ .distance = E->distance,
+ .rotation = E->rotation,
+ .slider = E->slider,
+ .wheelDelta = E->wheel_delta,
+ });
+ }, this, "CTablet");
+
+ hyprListener_proximity.initCallback(&tablet->events.proximity, [this] (void* owner, void* data) {
+ auto E = (wlr_tablet_tool_proximity_event*)data;
+
+ tabletEvents.proximity.emit(SProximityEvent{
+ .tool = E->tool,
+ .tablet = self.lock(),
+ .timeMs = E->time_msec,
+ .proximity = {E->x, E->y},
+ .in = E->state == WLR_TABLET_TOOL_PROXIMITY_IN,
+ });
+ }, this, "CTablet");
+
+ hyprListener_tip.initCallback(&tablet->events.tip, [this] (void* owner, void* data) {
+ auto E = (wlr_tablet_tool_tip_event*)data;
+
+ tabletEvents.tip.emit(STipEvent{
+ .tool = E->tool,
+ .tablet = self.lock(),
+ .timeMs = E->time_msec,
+ .tip = {E->x, E->y},
+ .in = E->state == WLR_TABLET_TOOL_TIP_DOWN,
+ });
+ }, this, "CTablet");
+
+ hyprListener_button.initCallback(&tablet->events.button, [this] (void* owner, void* data) {
+ auto E = (wlr_tablet_tool_button_event*)data;
+
+ tabletEvents.button.emit(SButtonEvent{
+ .tool = E->tool,
+ .tablet = self.lock(),
+ .timeMs = E->time_msec,
+ .button = E->button,
+ .down = E->state == WLR_BUTTON_PRESSED,
+ });
+ }, this, "CTablet");
+ // clang-format on
+
+ deviceName = tablet->base.name ? tablet->base.name : "UNKNOWN";
+}
+
+CTablet::~CTablet() {
+ if (tablet)
+ tablet->data = nullptr;
+
+ PROTO::tablet->recheckRegisteredDevices();
+}
+
+void CTablet::disconnectCallbacks() {
+ hyprListener_axis.removeCallback();
+ hyprListener_button.removeCallback();
+ hyprListener_destroy.removeCallback();
+ hyprListener_proximity.removeCallback();
+ hyprListener_tip.removeCallback();
+}
+
+eHIDType CTablet::getType() {
+ return HID_TYPE_TABLET;
+}
+
+uint32_t CTabletPad::getCapabilities() {
+ return HID_INPUT_CAPABILITY_TABLET;
+}
+
+wlr_tablet_pad* CTabletPad::wlr() {
+ return pad;
+}
+
+eHIDType CTabletPad::getType() {
+ return HID_TYPE_TABLET_PAD;
+}
+
+CTabletPad::CTabletPad(wlr_tablet_pad* pad_) : pad(pad_) {
+ if (!pad)
+ return;
+
+ // clang-format off
+ hyprListener_destroy.initCallback(&pad->base.events.destroy, [this] (void* owner, void* data) {
+ pad = nullptr;
+ disconnectCallbacks();
+ events.destroy.emit();
+ }, this, "CTabletPad");
+
+ hyprListener_button.initCallback(&pad->events.button, [this] (void* owner, void* data) {
+ auto E = (wlr_tablet_pad_button_event*)data;
+
+ padEvents.button.emit(SButtonEvent{
+ .timeMs = E->time_msec,
+ .button = E->button,
+ .down = E->state == WLR_BUTTON_PRESSED,
+ .mode = E->mode,
+ .group = E->group,
+ });
+ }, this, "CTabletPad");
+
+ hyprListener_ring.initCallback(&pad->events.ring, [this] (void* owner, void* data) {
+ auto E = (wlr_tablet_pad_ring_event*)data;
+
+ padEvents.ring.emit(SRingEvent{
+ .timeMs = E->time_msec,
+ .finger = E->source == WLR_TABLET_PAD_RING_SOURCE_FINGER,
+ .ring = E->ring,
+ .position = E->position,
+ .mode = E->mode,
+ });
+ }, this, "CTabletPad");
+
+ hyprListener_strip.initCallback(&pad->events.strip, [this] (void* owner, void* data) {
+ auto E = (wlr_tablet_pad_strip_event*)data;
+
+ padEvents.strip.emit(SStripEvent{
+ .timeMs = E->time_msec,
+ .finger = E->source == WLR_TABLET_PAD_STRIP_SOURCE_FINGER,
+ .strip = E->strip,
+ .position = E->position,
+ .mode = E->mode,
+ });
+ }, this, "CTabletPad");
+
+ hyprListener_attach.initCallback(&pad->events.attach_tablet, [this] (void* owner, void* data) {
+ if (!data)
+ return;
+
+ padEvents.attach.emit(CTabletTool::fromWlr((wlr_tablet_tool*)data));
+ }, this, "CTabletPad");
+ // clang-format on
+
+ deviceName = pad->base.name ? pad->base.name : "UNKNOWN";
+}
+
+CTabletPad::~CTabletPad() {
+ PROTO::tablet->recheckRegisteredDevices();
+}
+
+void CTabletPad::disconnectCallbacks() {
+ hyprListener_ring.removeCallback();
+ hyprListener_button.removeCallback();
+ hyprListener_destroy.removeCallback();
+ hyprListener_strip.removeCallback();
+ hyprListener_attach.removeCallback();
+}
+
+uint32_t CTabletTool::getCapabilities() {
+ return HID_INPUT_CAPABILITY_POINTER | HID_INPUT_CAPABILITY_TABLET;
+}
+
+wlr_tablet_tool* CTabletTool::wlr() {
+ return tool;
+}
+
+eHIDType CTabletTool::getType() {
+ return HID_TYPE_TABLET_TOOL;
+}
+
+CTabletTool::CTabletTool(wlr_tablet_tool* tool_) : tool(tool_) {
+ if (!tool)
+ return;
+
+ // clang-format off
+ hyprListener_destroy.initCallback(&tool->events.destroy, [this] (void* owner, void* data) {
+ tool = nullptr;
+ disconnectCallbacks();
+ events.destroy.emit();
+ }, this, "CTabletTool");
+ // clang-format on
+
+ if (tool->tilt)
+ toolCapabilities |= HID_TABLET_TOOL_CAPABILITY_TILT;
+ if (tool->pressure)
+ toolCapabilities |= HID_TABLET_TOOL_CAPABILITY_PRESSURE;
+ if (tool->distance)
+ toolCapabilities |= HID_TABLET_TOOL_CAPABILITY_DISTANCE;
+ if (tool->rotation)
+ toolCapabilities |= HID_TABLET_TOOL_CAPABILITY_ROTATION;
+ if (tool->slider)
+ toolCapabilities |= HID_TABLET_TOOL_CAPABILITY_SLIDER;
+ if (tool->wheel)
+ toolCapabilities |= HID_TABLET_TOOL_CAPABILITY_WHEEL;
+
+ tool->data = this;
+
+ deviceName = std::to_string(tool->hardware_serial) + std::to_string(tool->hardware_wacom);
+}
+
+CTabletTool::~CTabletTool() {
+ if (tool)
+ tool->data = nullptr;
+
+ PROTO::tablet->recheckRegisteredDevices();
+}
+
+void CTabletTool::disconnectCallbacks() {
+ hyprListener_destroy.removeCallback();
+ hyprListener_destroySurface.removeCallback();
+}
+
+wlr_surface* CTabletTool::getSurface() {
+ return pSurface;
+}
+
+void CTabletTool::setSurface(wlr_surface* surf) {
+ if (surf == pSurface)
+ return;
+
+ if (pSurface) {
+ hyprListener_destroySurface.removeCallback();
+ pSurface = nullptr;
+ }
+
+ pSurface = surf;
+
+ if (surf) {
+ hyprListener_destroySurface.initCallback(
+ &surf->events.destroy,
+ [this](void* owner, void* data) {
+ PROTO::tablet->proximityOut(self.lock());
+ pSurface = nullptr;
+ hyprListener_destroySurface.removeCallback();
+ },
+ this, "CTabletTool");
+ }
+}
diff --git a/src/devices/Tablet.hpp b/src/devices/Tablet.hpp
new file mode 100644
index 00000000..f2444972
--- /dev/null
+++ b/src/devices/Tablet.hpp
@@ -0,0 +1,228 @@
+#pragma once
+
+#include "IHID.hpp"
+#include "../helpers/WLListener.hpp"
+#include "../macros.hpp"
+#include "../helpers/Vector2D.hpp"
+#include "../helpers/Box.hpp"
+
+struct wlr_tablet;
+struct wlr_tablet_tool;
+struct wlr_tablet_pad;
+
+class CTabletTool;
+class CTabletPad;
+
+/*
+ A tablet device
+ Tablets don't have an interface for now,
+ if there will be a need it's trivial to do.
+*/
+class CTablet : public IHID {
+ public:
+ static SP create(wlr_tablet* tablet);
+ static SP fromWlr(wlr_tablet* tablet);
+ ~CTablet();
+
+ virtual uint32_t getCapabilities();
+ virtual eHIDType getType();
+ wlr_tablet* wlr();
+
+ enum eTabletToolAxes {
+ HID_TABLET_TOOL_AXIS_X = (1 << 0),
+ HID_TABLET_TOOL_AXIS_Y = (1 << 1),
+ HID_TABLET_TOOL_AXIS_TILT_X = (1 << 2),
+ HID_TABLET_TOOL_AXIS_TILT_Y = (1 << 3),
+ HID_TABLET_TOOL_AXIS_DISTANCE = (1 << 4),
+ HID_TABLET_TOOL_AXIS_PRESSURE = (1 << 5),
+ HID_TABLET_TOOL_AXIS_ROTATION = (1 << 6),
+ HID_TABLET_TOOL_AXIS_SLIDER = (1 << 7),
+ HID_TABLET_TOOL_AXIS_WHEEL = (1 << 8),
+ };
+
+ struct SAxisEvent {
+ wlr_tablet_tool* tool;
+ SP tablet;
+
+ uint32_t timeMs = 0;
+ uint32_t updatedAxes = 0; // eTabletToolAxes
+ Vector2D axis;
+ Vector2D axisDelta;
+ Vector2D tilt;
+ double pressure = 0;
+ double distance = 0;
+ double rotation = 0;
+ double slider = 0;
+ double wheelDelta = 0;
+ };
+
+ struct SProximityEvent {
+ wlr_tablet_tool* tool;
+ SP tablet;
+
+ uint32_t timeMs = 0;
+ Vector2D proximity;
+ bool in = false;
+ };
+
+ struct STipEvent {
+ wlr_tablet_tool* tool;
+ SP tablet;
+
+ uint32_t timeMs = 0;
+ Vector2D tip;
+ bool in = false;
+ };
+
+ struct SButtonEvent {
+ wlr_tablet_tool* tool;
+ SP tablet;
+
+ uint32_t timeMs = 0;
+ uint32_t button;
+ bool down = false;
+ };
+
+ struct {
+ CSignal axis;
+ CSignal proximity;
+ CSignal tip;
+ CSignal button;
+ } tabletEvents;
+
+ WP self;
+
+ bool relativeInput = false;
+ std::string hlName = "";
+ std::string boundOutput = "";
+ CBox activeArea;
+ CBox boundBox; // output-local
+
+ private:
+ CTablet(wlr_tablet* tablet);
+
+ void disconnectCallbacks();
+
+ wlr_tablet* tablet = nullptr;
+
+ DYNLISTENER(destroy);
+ DYNLISTENER(axis);
+ DYNLISTENER(proximity);
+ DYNLISTENER(tip);
+ DYNLISTENER(button);
+};
+
+class CTabletPad : public IHID {
+ public:
+ static SP create(wlr_tablet_pad* pad);
+ ~CTabletPad();
+
+ virtual uint32_t getCapabilities();
+ virtual eHIDType getType();
+ wlr_tablet_pad* wlr();
+
+ struct SButtonEvent {
+ uint32_t timeMs = 0;
+ uint32_t button = 0;
+ bool down = false;
+ uint32_t mode = 0;
+ uint32_t group = 0;
+ };
+
+ struct SRingEvent {
+ uint32_t timeMs = 0;
+ bool finger = false;
+ uint32_t ring = 0;
+ double position = 0;
+ uint32_t mode = 0;
+ };
+
+ struct SStripEvent {
+ uint32_t timeMs = 0;
+ bool finger = false;
+ uint32_t strip = 0;
+ double position = 0;
+ uint32_t mode = 0;
+ };
+
+ struct {
+ CSignal button;
+ CSignal ring;
+ CSignal strip;
+ CSignal attach;
+ } padEvents;
+
+ WP self;
+ WP parent;
+
+ std::string hlName;
+
+ private:
+ CTabletPad(wlr_tablet_pad* pad);
+
+ void disconnectCallbacks();
+
+ wlr_tablet_pad* pad = nullptr;
+
+ DYNLISTENER(destroy);
+ DYNLISTENER(ring);
+ DYNLISTENER(strip);
+ DYNLISTENER(button);
+ DYNLISTENER(attach);
+};
+
+class CTabletTool : public IHID {
+ public:
+ static SP create(wlr_tablet_tool* tool);
+ static SP fromWlr(wlr_tablet_tool* tool);
+ ~CTabletTool();
+
+ enum eTabletToolType {
+ HID_TABLET_TOOL_TYPE_PEN = 1,
+ HID_TABLET_TOOL_TYPE_ERASER,
+ HID_TABLET_TOOL_TYPE_BRUSH,
+ HID_TABLET_TOOL_TYPE_PENCIL,
+ HID_TABLET_TOOL_TYPE_AIRBRUSH,
+ HID_TABLET_TOOL_TYPE_MOUSE,
+ HID_TABLET_TOOL_TYPE_LENS,
+ HID_TABLET_TOOL_TYPE_TOTEM,
+ };
+
+ enum eTabletToolCapabilities {
+ HID_TABLET_TOOL_CAPABILITY_TILT = (1 << 0),
+ HID_TABLET_TOOL_CAPABILITY_PRESSURE = (1 << 1),
+ HID_TABLET_TOOL_CAPABILITY_DISTANCE = (1 << 2),
+ HID_TABLET_TOOL_CAPABILITY_ROTATION = (1 << 3),
+ HID_TABLET_TOOL_CAPABILITY_SLIDER = (1 << 4),
+ HID_TABLET_TOOL_CAPABILITY_WHEEL = (1 << 5),
+ };
+
+ virtual uint32_t getCapabilities();
+ wlr_tablet_tool* wlr();
+ virtual eHIDType getType();
+ wlr_surface* getSurface();
+ void setSurface(wlr_surface*);
+
+ WP self;
+ Vector2D tilt;
+ bool active = false; // true if in proximity
+ uint32_t toolCapabilities = 0;
+
+ bool isDown = false;
+ std::vector buttonsDown;
+ Vector2D absolutePos; // last known absolute position.
+
+ std::string hlName;
+
+ private:
+ CTabletTool(wlr_tablet_tool* tool);
+
+ void disconnectCallbacks();
+
+ wlr_surface* pSurface = nullptr;
+
+ wlr_tablet_tool* tool = nullptr;
+
+ DYNLISTENER(destroy);
+ DYNLISTENER(destroySurface);
+};
\ No newline at end of file
diff --git a/src/events/Devices.cpp b/src/events/Devices.cpp
index f36a95de..16a64559 100644
--- a/src/events/Devices.cpp
+++ b/src/events/Devices.cpp
@@ -38,8 +38,8 @@ void Events::listener_newInput(wl_listener* listener, void* data) {
g_pInputManager->newTouchDevice(DEVICE);
break;
case WLR_INPUT_DEVICE_TABLET:
- Debug::log(LOG, "Attached a tablet tool with name {}", DEVICE->name);
- g_pInputManager->newTabletTool(DEVICE);
+ Debug::log(LOG, "Attached a tablet with name {}", DEVICE->name);
+ g_pInputManager->newTablet(DEVICE);
break;
case WLR_INPUT_DEVICE_TABLET_PAD:
Debug::log(LOG, "Attached a tablet pad with name {}", DEVICE->name);
diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp
index d0e777ed..fa68655f 100644
--- a/src/helpers/Monitor.cpp
+++ b/src/helpers/Monitor.cpp
@@ -146,20 +146,6 @@ void CMonitor::onConnect(bool noRule) {
if (!noRule)
g_pHyprRenderer->applyMonitorRule(this, &monitorRule, true);
- for (const auto& PTOUCHDEV : g_pInputManager->m_vTouches) {
- if (matchesStaticSelector(PTOUCHDEV->boundOutput)) {
- Debug::log(LOG, "Binding touch device {} to output {}", PTOUCHDEV->hlName, szName);
- // wlr_cursor_map_input_to_output(g_pCompositor->m_sWLRCursor, &PTOUCHDEV->wlr()->base, output);
- }
- }
-
- for (const auto& PTABLET : g_pInputManager->m_lTablets) {
- if (matchesStaticSelector(PTABLET.boundOutput)) {
- Debug::log(LOG, "Binding tablet {} to output {}", PTABLET.name, szName);
- // wlr_cursor_map_input_to_output(g_pCompositor->m_sWLRCursor, PTABLET.wlrDevice, output);
- }
- }
-
if (!state.commit())
Debug::log(WARN, "wlr_output_commit_state failed in CMonitor::onCommit");
diff --git a/src/helpers/WLClasses.hpp b/src/helpers/WLClasses.hpp
index bed2b915..bc07f6ba 100644
--- a/src/helpers/WLClasses.hpp
+++ b/src/helpers/WLClasses.hpp
@@ -85,72 +85,6 @@ struct SDrag {
DYNLISTENER(commitIcon);
};
-struct STablet {
- DYNLISTENER(Tip);
- DYNLISTENER(Axis);
- DYNLISTENER(Button);
- DYNLISTENER(Proximity);
- DYNLISTENER(Destroy);
-
- wlr_tablet* wlrTablet = nullptr;
- wlr_tablet_v2_tablet* wlrTabletV2 = nullptr;
- wlr_input_device* wlrDevice = nullptr;
-
- bool relativeInput = false;
-
- std::string name = "";
-
- std::string boundOutput = "";
-
- CBox activeArea;
-
- //
- bool operator==(const STablet& b) const {
- return wlrDevice == b.wlrDevice;
- }
-};
-
-struct STabletTool {
- wlr_tablet_tool* wlrTabletTool = nullptr;
- wlr_tablet_v2_tablet_tool* wlrTabletToolV2 = nullptr;
-
- wlr_tablet_v2_tablet* wlrTabletOwnerV2 = nullptr;
-
- wlr_surface* pSurface = nullptr;
-
- double tiltX = 0;
- double tiltY = 0;
-
- bool active = true;
-
- std::string name = "";
-
- DYNLISTENER(TabletToolDestroy);
- DYNLISTENER(TabletToolSetCursor);
-
- bool operator==(const STabletTool& b) const {
- return wlrTabletTool == b.wlrTabletTool;
- }
-};
-
-struct STabletPad {
- wlr_tablet_v2_tablet_pad* wlrTabletPadV2 = nullptr;
- STablet* pTabletParent = nullptr;
- wlr_input_device* pWlrDevice = nullptr;
-
- std::string name = "";
-
- DYNLISTENER(Attach);
- DYNLISTENER(Button);
- DYNLISTENER(Strip);
- DYNLISTENER(Ring);
- DYNLISTENER(Destroy);
-
- bool operator==(const STabletPad& b) const {
- return wlrTabletPadV2 == b.wlrTabletPadV2;
- }
-};
-
struct SSwipeGesture {
PHLWORKSPACE pWorkspaceBegin = nullptr;
diff --git a/src/includes.hpp b/src/includes.hpp
index 57ea5d54..080dbd4e 100644
--- a/src/includes.hpp
+++ b/src/includes.hpp
@@ -69,7 +69,6 @@ extern "C" {
#include
#include
#include
-#include
#include
#include
#include
diff --git a/src/managers/PointerManager.cpp b/src/managers/PointerManager.cpp
index caff8d22..c6478b9a 100644
--- a/src/managers/PointerManager.cpp
+++ b/src/managers/PointerManager.cpp
@@ -635,37 +635,60 @@ void CPointerManager::warpTo(const Vector2D& logical) {
void CPointerManager::move(const Vector2D& deltaLogical) {
const auto oldPos = pointerPos;
- auto newPos = oldPos + deltaLogical;
+ auto newPos = oldPos + Vector2D{std::isnan(deltaLogical.x) ? 0.0 : deltaLogical.x, std::isnan(deltaLogical.y) ? 0.0 : deltaLogical.y};
warpTo(newPos);
}
-void CPointerManager::warpAbsolute(const Vector2D& abs, SP dev) {
+void CPointerManager::warpAbsolute(Vector2D abs, SP dev) {
SP currentMonitor = g_pCompositor->m_pLastMonitor.lock();
+ if (!currentMonitor)
+ return;
+
+ if (!std::isnan(abs.x))
+ abs.x = std::clamp(abs.x, 0.0, 1.0);
+ if (!std::isnan(abs.y))
+ abs.y = std::clamp(abs.y, 0.0, 1.0);
+
+ // in logical global
+ CBox mappedArea = currentMonitor->logicalBox();
switch (dev->getType()) {
- case HID_TYPE_TABLET:
- //TODO:
- break;
- case HID_TYPE_TOUCH: {
- auto TOUCH = SP(dev);
- if (!TOUCH->boundOutput.empty()) {
- const auto PMONITOR = g_pCompositor->getMonitorFromString(TOUCH->boundOutput);
- if (PMONITOR)
+ case HID_TYPE_TABLET: {
+ CTablet* TAB = reinterpret_cast(dev.get());
+ if (!TAB->boundOutput.empty()) {
+ if (const auto PMONITOR = g_pCompositor->getMonitorFromString(TAB->boundOutput); PMONITOR) {
currentMonitor = PMONITOR->self.lock();
+ mappedArea = currentMonitor->logicalBox();
+ }
+ }
+
+ if (!TAB->boundBox.empty())
+ mappedArea = TAB->boundBox.translate(currentMonitor->vecPosition);
+ break;
+ }
+ case HID_TYPE_TOUCH: {
+ ITouch* TOUCH = reinterpret_cast(dev.get());
+ if (!TOUCH->boundOutput.empty()) {
+ if (const auto PMONITOR = g_pCompositor->getMonitorFromString(TOUCH->boundOutput); PMONITOR) {
+ currentMonitor = PMONITOR->self.lock();
+ mappedArea = currentMonitor->logicalBox();
+ }
}
break;
}
default: break;
}
- if (!currentMonitor)
- return;
-
damageIfSoftware();
- pointerPos = currentMonitor->vecPosition + currentMonitor->vecSize * abs;
+ if (std::isnan(abs.x) || std::isnan(abs.y)) {
+ pointerPos.x = std::isnan(abs.x) ? pointerPos.x : mappedArea.x + mappedArea.w * abs.x;
+ pointerPos.y = std::isnan(abs.y) ? pointerPos.y : mappedArea.y + mappedArea.h * abs.y;
+ } else
+ pointerPos = mappedArea.pos() + mappedArea.size() * abs;
+
onCursorMoved();
recheckEnteredOutputs();
@@ -839,6 +862,47 @@ void CPointerManager::attachTouch(SP touch) {
Debug::log(LOG, "Attached touch {} to global", touch->hlName);
}
+void CPointerManager::attachTablet(SP tablet) {
+ if (!tablet)
+ return;
+
+ auto listener = tabletListeners.emplace_back(makeShared());
+
+ listener->tablet = tablet;
+
+ // clang-format off
+ listener->destroy = tablet->events.destroy.registerListener([this] (std::any d) {
+ detachTablet(nullptr);
+ });
+
+ listener->axis = tablet->tabletEvents.axis.registerListener([this] (std::any e) {
+ auto E = std::any_cast(e);
+
+ g_pInputManager->onTabletAxis(E);
+ });
+
+ listener->proximity = tablet->tabletEvents.proximity.registerListener([this] (std::any e) {
+ auto E = std::any_cast(e);
+
+ g_pInputManager->onTabletProximity(E);
+ });
+
+ listener->tip = tablet->tabletEvents.tip.registerListener([this] (std::any e) {
+ auto E = std::any_cast(e);
+
+ g_pInputManager->onTabletTip(E);
+ });
+
+ listener->button = tablet->tabletEvents.button.registerListener([this] (std::any e) {
+ auto E = std::any_cast(e);
+
+ g_pInputManager->onTabletButton(E);
+ });
+ // clang-format on
+
+ Debug::log(LOG, "Attached tablet {} to global", tablet->hlName);
+}
+
void CPointerManager::detachPointer(SP pointer) {
std::erase_if(pointerListeners, [pointer](const auto& e) { return e->pointer.expired() || e->pointer == pointer; });
}
@@ -847,6 +911,10 @@ void CPointerManager::detachTouch(SP touch) {
std::erase_if(touchListeners, [touch](const auto& e) { return e->touch.expired() || e->touch == touch; });
}
+void CPointerManager::detachTablet(SP tablet) {
+ std::erase_if(tabletListeners, [tablet](const auto& e) { return e->tablet.expired() || e->tablet == tablet; });
+}
+
void CPointerManager::damageCursor(SP pMonitor) {
for (auto& mw : monitorStates) {
if (mw->monitor != pMonitor)
diff --git a/src/managers/PointerManager.hpp b/src/managers/PointerManager.hpp
index 2446cde7..71ab0fc2 100644
--- a/src/managers/PointerManager.hpp
+++ b/src/managers/PointerManager.hpp
@@ -2,6 +2,7 @@
#include "../devices/IPointer.hpp"
#include "../devices/ITouch.hpp"
+#include "../devices/Tablet.hpp"
#include "../helpers/Box.hpp"
#include "../helpers/Region.hpp"
#include "../desktop/WLSurface.hpp"
@@ -22,18 +23,18 @@ class CPointerManager {
public:
CPointerManager();
- // pointers will move the cursor on their respective events
void attachPointer(SP pointer);
- // touch inputs won't move the cursor, it needs to be done manually
void attachTouch(SP touch);
+ void attachTablet(SP tablet);
void detachPointer(SP pointer);
void detachTouch(SP touch);
+ void detachTablet(SP tablet);
// only clamps to the layout.
void warpTo(const Vector2D& logical);
void move(const Vector2D& deltaLogical);
- void warpAbsolute(const Vector2D& abs, SP dev);
+ void warpAbsolute(Vector2D abs, SP dev);
void setCursorBuffer(wlr_buffer* buf, const Vector2D& hotspot, const float& scale);
void setCursorSurface(CWLSurface* buf, const Vector2D& hotspot);
@@ -111,6 +112,17 @@ class CPointerManager {
};
std::vector> touchListeners;
+ struct STabletListener {
+ CHyprSignalListener destroy;
+ CHyprSignalListener axis;
+ CHyprSignalListener proximity;
+ CHyprSignalListener tip;
+ CHyprSignalListener button;
+
+ WP tablet;
+ };
+ std::vector> tabletListeners;
+
struct {
std::vector monitorBoxes;
} currentMonitorLayout;
diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp
index ee27d35d..60fde1aa 100644
--- a/src/managers/ProtocolManager.cpp
+++ b/src/managers/ProtocolManager.cpp
@@ -25,6 +25,7 @@
#include "../protocols/OutputManagement.hpp"
#include "../protocols/ServerDecorationKDE.hpp"
#include "../protocols/FocusGrab.hpp"
+#include "../protocols/Tablet.hpp"
CProtocolManager::CProtocolManager() {
@@ -53,6 +54,7 @@ CProtocolManager::CProtocolManager() {
PROTO::outputManagement = std::make_unique(&zwlr_output_manager_v1_interface, 4, "OutputManagement");
PROTO::serverDecorationKDE = std::make_unique(&org_kde_kwin_server_decoration_manager_interface, 1, "ServerDecorationKDE");
PROTO::focusGrab = std::make_unique(&hyprland_focus_grab_manager_v1_interface, 1, "FocusGrab");
+ PROTO::tablet = std::make_unique(&zwp_tablet_manager_v2_interface, 1, "TabletV2");
// Old protocol implementations.
// TODO: rewrite them to use hyprwayland-scanner.
diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp
index 22fc24d4..7e746cfc 100644
--- a/src/managers/input/InputManager.cpp
+++ b/src/managers/input/InputManager.cpp
@@ -58,9 +58,9 @@ CInputManager::~CInputManager() {
m_vKeyboards.clear();
m_vPointers.clear();
m_vTouches.clear();
- m_lTablets.clear();
- m_lTabletTools.clear();
- m_lTabletPads.clear();
+ m_vTablets.clear();
+ m_vTabletTools.clear();
+ m_vTabletPads.clear();
m_vIdleInhibitors.clear();
m_lSwitches.clear();
}
@@ -777,6 +777,8 @@ void CInputManager::newVirtualKeyboard(SP keyboard)
}
void CInputManager::setupKeyboard(SP keeb) {
+ m_vHIDs.push_back(keeb);
+
try {
keeb->hlName = getNameForNewDevice(keeb->wlr()->base.name);
} catch (std::exception& e) {
@@ -977,6 +979,8 @@ void CInputManager::newMouse(wlr_input_device* mouse) {
}
void CInputManager::setupMouse(SP mauz) {
+ m_vHIDs.push_back(mauz);
+
try {
mauz->hlName = getNameForNewDevice(mauz->wlr()->base.name);
} catch (std::exception& e) {
@@ -1165,6 +1169,11 @@ void CInputManager::setPointerConfigs() {
}
}
+static void removeFromHIDs(WP hid) {
+ std::erase_if(g_pInputManager->m_vHIDs, [hid](const auto& e) { return e.expired() || e == hid; });
+ g_pInputManager->updateCapabilities();
+}
+
void CInputManager::destroyKeyboard(SP pKeyboard) {
if (pKeyboard->xkbTranslationState)
xkb_state_unref(pKeyboard->xkbTranslationState);
@@ -1180,6 +1189,8 @@ void CInputManager::destroyKeyboard(SP pKeyboard) {
g_pCompositor->m_sSeat.keyboard.reset();
wlr_seat_set_keyboard(g_pCompositor->m_sSeat.seat, nullptr);
}
+
+ removeFromHIDs(pKeyboard);
}
void CInputManager::destroyPointer(SP mouse) {
@@ -1189,12 +1200,40 @@ void CInputManager::destroyPointer(SP mouse) {
if (!g_pCompositor->m_sSeat.mouse.expired())
unconstrainMouse();
+
+ removeFromHIDs(mouse);
}
void CInputManager::destroyTouchDevice(SP touch) {
Debug::log(LOG, "Touch device at {:x} removed", (uintptr_t)touch.get());
std::erase_if(m_vTouches, [touch](const auto& other) { return other == touch; });
+
+ removeFromHIDs(touch);
+}
+
+void CInputManager::destroyTablet(SP tablet) {
+ Debug::log(LOG, "Tablet device at {:x} removed", (uintptr_t)tablet.get());
+
+ std::erase_if(m_vTablets, [tablet](const auto& other) { return other == tablet; });
+
+ removeFromHIDs(tablet);
+}
+
+void CInputManager::destroyTabletTool(SP tool) {
+ Debug::log(LOG, "Tablet tool at {:x} removed", (uintptr_t)tool.get());
+
+ std::erase_if(m_vTabletTools, [tool](const auto& other) { return other == tool; });
+
+ removeFromHIDs(tool);
+}
+
+void CInputManager::destroyTabletPad(SP pad) {
+ Debug::log(LOG, "Tablet pad at {:x} removed", (uintptr_t)pad.get());
+
+ std::erase_if(m_vTabletPads, [pad](const auto& other) { return other == pad; });
+
+ removeFromHIDs(pad);
}
void CInputManager::onKeyboardKey(std::any event, SP pKeyboard) {
@@ -1338,14 +1377,19 @@ bool CInputManager::isConstrained() {
void CInputManager::updateCapabilities() {
uint32_t caps = 0;
- if (!m_vKeyboards.empty())
- caps |= WL_SEAT_CAPABILITY_KEYBOARD;
- if (!m_vPointers.empty())
- caps |= WL_SEAT_CAPABILITY_POINTER;
- if (!m_vTouches.empty())
- caps |= WL_SEAT_CAPABILITY_TOUCH;
- if (!m_lTabletTools.empty())
- caps |= WL_SEAT_CAPABILITY_POINTER;
+ for (auto& h : m_vHIDs) {
+ if (h.expired())
+ continue;
+
+ auto cap = h->getCapabilities();
+
+ if (cap & HID_INPUT_CAPABILITY_KEYBOARD)
+ caps |= WL_SEAT_CAPABILITY_KEYBOARD;
+ if (cap & HID_INPUT_CAPABILITY_POINTER)
+ caps |= WL_SEAT_CAPABILITY_POINTER;
+ if (cap & HID_INPUT_CAPABILITY_TOUCH)
+ caps |= WL_SEAT_CAPABILITY_TOUCH;
+ }
wlr_seat_set_capabilities(g_pCompositor->m_sSeat.seat, caps);
m_uiCapabilities = caps;
@@ -1380,6 +1424,7 @@ void CInputManager::disableAllKeyboards(bool virt) {
void CInputManager::newTouchDevice(wlr_input_device* pDevice) {
const auto PNEWDEV = m_vTouches.emplace_back(CTouchDevice::create(wlr_touch_from_input_device(pDevice)));
+ m_vHIDs.push_back(PNEWDEV);
try {
PNEWDEV->hlName = getNameForNewDevice(pDevice->name);
@@ -1450,43 +1495,39 @@ void CInputManager::setTouchDeviceConfigs(SP dev) {
}
void CInputManager::setTabletConfigs() {
- for (auto& t : m_lTablets) {
- if (wlr_input_device_is_libinput(t.wlrDevice)) {
- const auto LIBINPUTDEV = (libinput_device*)wlr_libinput_get_device_handle(t.wlrDevice);
+ for (auto& t : m_vTablets) {
+ if (wlr_input_device_is_libinput(&t->wlr()->base)) {
+ const auto NAME = t->hlName;
+ const auto LIBINPUTDEV = (libinput_device*)wlr_libinput_get_device_handle(&t->wlr()->base);
- const auto RELINPUT = g_pConfigManager->getDeviceInt(t.name, "relative_input", "input:tablet:relative_input");
- t.relativeInput = RELINPUT;
+ const auto RELINPUT = g_pConfigManager->getDeviceInt(NAME, "relative_input", "input:tablet:relative_input");
+ t->relativeInput = RELINPUT;
- const int ROTATION = std::clamp(g_pConfigManager->getDeviceInt(t.name, "transform", "input:tablet:transform"), 0, 7);
- Debug::log(LOG, "Setting calibration matrix for device {}", t.name);
+ const int ROTATION = std::clamp(g_pConfigManager->getDeviceInt(NAME, "transform", "input:tablet:transform"), 0, 7);
+ Debug::log(LOG, "Setting calibration matrix for device {}", NAME);
libinput_device_config_calibration_set_matrix(LIBINPUTDEV, MATRICES[ROTATION]);
- if (g_pConfigManager->getDeviceInt(t.name, "left_handed", "input:tablet:left_handed") == 0)
+ if (g_pConfigManager->getDeviceInt(NAME, "left_handed", "input:tablet:left_handed") == 0)
libinput_device_config_left_handed_set(LIBINPUTDEV, 0);
else
libinput_device_config_left_handed_set(LIBINPUTDEV, 1);
- const auto OUTPUT = g_pConfigManager->getDeviceString(t.name, "output", "input:tablet:output");
- const auto PMONITOR = g_pCompositor->getMonitorFromString(OUTPUT);
- if (!OUTPUT.empty() && OUTPUT != STRVAL_EMPTY && PMONITOR) {
- Debug::log(LOG, "Binding tablet {} to output {}", t.name, PMONITOR->szName);
- // wlr_cursor_map_input_to_output(g_pCompositor->m_sWLRCursor, t.wlrDevice, PMONITOR->output);
- // wlr_cursor_map_input_to_region(g_pCompositor->m_sWLRCursor, t.wlrDevice, nullptr);
- t.boundOutput = OUTPUT;
- } else if (!PMONITOR)
- Debug::log(ERR, "Failed to bind tablet {} to output '{}': monitor not found", t.name, OUTPUT);
+ const auto OUTPUT = g_pConfigManager->getDeviceString(NAME, "output", "input:tablet:output");
+ if (OUTPUT != STRVAL_EMPTY) {
+ Debug::log(LOG, "Binding tablet {} to output {}", NAME, OUTPUT);
+ t->boundOutput = OUTPUT;
+ } else
+ t->boundOutput = "";
- const auto REGION_POS = g_pConfigManager->getDeviceVec(t.name, "region_position", "input:tablet:region_position");
- const auto REGION_SIZE = g_pConfigManager->getDeviceVec(t.name, "region_size", "input:tablet:region_size");
- //auto regionBox = CBox{REGION_POS.x, REGION_POS.y, REGION_SIZE.x, REGION_SIZE.y};
- // if (!regionBox.empty())
- // wlr_cursor_map_input_to_region(g_pCompositor->m_sWLRCursor, t.wlrDevice, regionBox.pWlr());
+ const auto REGION_POS = g_pConfigManager->getDeviceVec(NAME, "region_position", "input:tablet:region_position");
+ const auto REGION_SIZE = g_pConfigManager->getDeviceVec(NAME, "region_size", "input:tablet:region_size");
+ t->boundBox = {REGION_POS, REGION_SIZE};
- const auto ACTIVE_AREA_SIZE = g_pConfigManager->getDeviceVec(t.name, "active_area_size", "input:tablet:active_area_size");
- const auto ACTIVE_AREA_POS = g_pConfigManager->getDeviceVec(t.name, "active_area_position", "input:tablet:active_area_position");
+ const auto ACTIVE_AREA_SIZE = g_pConfigManager->getDeviceVec(NAME, "active_area_size", "input:tablet:active_area_size");
+ const auto ACTIVE_AREA_POS = g_pConfigManager->getDeviceVec(NAME, "active_area_position", "input:tablet:active_area_position");
if (ACTIVE_AREA_SIZE.x != 0 || ACTIVE_AREA_SIZE.y != 0) {
- t.activeArea = CBox{ACTIVE_AREA_POS.x / t.wlrTablet->width_mm, ACTIVE_AREA_POS.y / t.wlrTablet->height_mm,
- (ACTIVE_AREA_POS.x + ACTIVE_AREA_SIZE.x) / t.wlrTablet->width_mm, (ACTIVE_AREA_POS.y + ACTIVE_AREA_SIZE.y) / t.wlrTablet->height_mm};
+ t->activeArea = CBox{ACTIVE_AREA_POS.x / t->wlr()->width_mm, ACTIVE_AREA_POS.y / t->wlr()->height_mm, (ACTIVE_AREA_POS.x + ACTIVE_AREA_SIZE.x) / t->wlr()->width_mm,
+ (ACTIVE_AREA_POS.y + ACTIVE_AREA_SIZE.y) / t->wlr()->height_mm};
}
}
}
@@ -1575,16 +1616,16 @@ std::string CInputManager::getNameForNewDevice(std::string internalName) {
[&](const auto& other) { return other->hlName == proposedNewName + (dupeno == 0 ? "" : ("-" + std::to_string(dupeno))); }) != m_vTouches.end())
dupeno++;
- while (std::find_if(m_lTabletPads.begin(), m_lTabletPads.end(),
- [&](const STabletPad& other) { return other.name == proposedNewName + (dupeno == 0 ? "" : ("-" + std::to_string(dupeno))); }) != m_lTabletPads.end())
+ while (std::find_if(m_vTabletPads.begin(), m_vTabletPads.end(),
+ [&](const auto& other) { return other->hlName == proposedNewName + (dupeno == 0 ? "" : ("-" + std::to_string(dupeno))); }) != m_vTabletPads.end())
dupeno++;
- while (std::find_if(m_lTablets.begin(), m_lTablets.end(),
- [&](const STablet& other) { return other.name == proposedNewName + (dupeno == 0 ? "" : ("-" + std::to_string(dupeno))); }) != m_lTablets.end())
+ while (std::find_if(m_vTablets.begin(), m_vTablets.end(),
+ [&](const auto& other) { return other->hlName == proposedNewName + (dupeno == 0 ? "" : ("-" + std::to_string(dupeno))); }) != m_vTablets.end())
dupeno++;
- while (std::find_if(m_lTabletTools.begin(), m_lTabletTools.end(),
- [&](const STabletTool& other) { return other.name == proposedNewName + (dupeno == 0 ? "" : ("-" + std::to_string(dupeno))); }) != m_lTabletTools.end())
+ while (std::find_if(m_vTabletTools.begin(), m_vTabletTools.end(),
+ [&](const auto& other) { return other->hlName == proposedNewName + (dupeno == 0 ? "" : ("-" + std::to_string(dupeno))); }) != m_vTabletTools.end())
dupeno++;
return proposedNewName + (dupeno == 0 ? "" : ("-" + std::to_string(dupeno)));
diff --git a/src/managers/input/InputManager.hpp b/src/managers/input/InputManager.hpp
index a2caed46..970d53c8 100644
--- a/src/managers/input/InputManager.hpp
+++ b/src/managers/input/InputManager.hpp
@@ -9,6 +9,7 @@
#include "../../helpers/signal/Listener.hpp"
#include "../../devices/IPointer.hpp"
#include "../../devices/ITouch.hpp"
+#include "../../devices/Tablet.hpp"
class CPointerConstraint;
class CWindow;
@@ -87,9 +88,15 @@ class CInputManager {
void newVirtualMouse(SP);
void newTouchDevice(wlr_input_device*);
void newSwitch(wlr_input_device*);
+ void newTabletTool(wlr_tablet_tool*);
+ void newTabletPad(wlr_input_device*);
+ void newTablet(wlr_input_device*);
void destroyTouchDevice(SP);
void destroyKeyboard(SP);
void destroyPointer(SP);
+ void destroyTablet(SP);
+ void destroyTabletTool(SP);
+ void destroyTabletPad(SP);
void destroySwitch(SSwitchDevice*);
void unconstrainMouse();
@@ -116,6 +123,15 @@ class CInputManager {
void onTouchUp(ITouch::SUpEvent);
void onTouchMove(ITouch::SMotionEvent);
+ void onSwipeBegin(IPointer::SSwipeBeginEvent);
+ void onSwipeEnd(IPointer::SSwipeEndEvent);
+ void onSwipeUpdate(IPointer::SSwipeUpdateEvent);
+
+ void onTabletAxis(CTablet::SAxisEvent);
+ void onTabletProximity(CTablet::SProximityEvent);
+ void onTabletTip(CTablet::STipEvent);
+ void onTabletButton(CTablet::SButtonEvent);
+
STouchData m_sTouchData;
// for dragging floating windows
@@ -124,18 +140,17 @@ class CInputManager {
bool m_bWasDraggingWindow = false;
// for refocus to be forced
- PHLWINDOWREF m_pForcedFocus;
+ PHLWINDOWREF m_pForcedFocus;
- SDrag m_sDrag;
+ SDrag m_sDrag;
- std::vector> m_vKeyboards;
- std::vector> m_vPointers;
- std::vector> m_vTouches;
-
- // tablets
- std::list m_lTablets;
- std::list m_lTabletTools;
- std::list m_lTabletPads;
+ std::vector> m_vKeyboards;
+ std::vector> m_vPointers;
+ std::vector> m_vTouches;
+ std::vector> m_vTablets;
+ std::vector> m_vTabletTools;
+ std::vector> m_vTabletPads;
+ std::vector> m_vHIDs; // general container for all HID devices connected to the input manager.
// Switches
std::list m_lSwitches;
@@ -147,16 +162,9 @@ class CInputManager {
std::vector> m_vConstraints;
//
- void newTabletTool(wlr_input_device*);
- void newTabletPad(wlr_input_device*);
- void focusTablet(STablet*, wlr_tablet_tool*, bool motion = false);
void newIdleInhibitor(std::any);
void recheckIdleInhibitorStatus();
- void onSwipeBegin(IPointer::SSwipeBeginEvent);
- void onSwipeEnd(IPointer::SSwipeEndEvent);
- void onSwipeUpdate(IPointer::SSwipeUpdateEvent);
-
SSwipeGesture m_sActiveSwipe;
CTimer m_tmrLastCursorMovement;
@@ -223,7 +231,7 @@ class CInputManager {
void mouseMoveUnified(uint32_t, bool refocus = false);
- STabletTool* ensureTabletToolPresent(wlr_tablet_tool*);
+ SP ensureTabletToolPresent(wlr_tablet_tool*);
void applyConfigToKeyboard(SP);
diff --git a/src/managers/input/Tablets.cpp b/src/managers/input/Tablets.cpp
index a0bd81ce..cf666ec6 100644
--- a/src/managers/input/Tablets.cpp
+++ b/src/managers/input/Tablets.cpp
@@ -1,290 +1,291 @@
#include "InputManager.hpp"
#include "../../Compositor.hpp"
#include "../../protocols/IdleNotify.hpp"
+#include "../../protocols/Tablet.hpp"
+#include "../../devices/Tablet.hpp"
+#include "../../managers/PointerManager.hpp"
+#include "../../protocols/PointerConstraints.hpp"
-void CInputManager::newTabletTool(wlr_input_device* pDevice) {
- const auto PNEWTABLET = &m_lTablets.emplace_back();
+static void unfocusTool(SP tool) {
+ if (!tool->getSurface())
+ return;
+
+ tool->setSurface(nullptr);
+ if (tool->isDown)
+ PROTO::tablet->up(tool);
+ for (auto& b : tool->buttonsDown) {
+ PROTO::tablet->buttonTool(tool, b, false);
+ }
+ PROTO::tablet->proximityOut(tool);
+}
+
+static void focusTool(SP tool, SP tablet, wlr_surface* surf) {
+ if (tool->getSurface() == surf || !surf)
+ return;
+
+ if (tool->getSurface() && tool->getSurface() != surf)
+ unfocusTool(tool);
+
+ tool->setSurface(surf);
+ PROTO::tablet->proximityIn(tool, tablet, surf);
+ if (tool->isDown)
+ PROTO::tablet->down(tool);
+ for (auto& b : tool->buttonsDown) {
+ PROTO::tablet->buttonTool(tool, b, true);
+ }
+}
+
+static void refocusTablet(SP tab, SP tool, bool motion = false) {
+ const auto LASTHLSURFACE = CWLSurface::surfaceFromWlr(g_pInputManager->m_pLastMouseSurface);
+
+ if (!LASTHLSURFACE || !tool->active) {
+ if (tool->getSurface())
+ unfocusTool(tool);
+
+ return;
+ }
+
+ const auto BOX = LASTHLSURFACE->getSurfaceBoxGlobal();
+
+ if (!BOX.has_value()) {
+ if (tool->getSurface())
+ unfocusTool(tool);
+
+ return;
+ }
+
+ const auto CURSORPOS = g_pInputManager->getMouseCoordsInternal();
+
+ focusTool(tool, tab, g_pInputManager->m_pLastMouseSurface);
+
+ if (!motion)
+ return;
+
+ if (LASTHLSURFACE->constraint() && tool->wlr()->type != WLR_TABLET_TOOL_TYPE_MOUSE) {
+ // cursor logic will completely break here as the cursor will be locked.
+ // let's just "map" the desired position to the constraint area.
+
+ Vector2D local;
+
+ // yes, this technically ignores any regions set by the app. Too bad!
+ if (LASTHLSURFACE->getWindow())
+ local = tool->absolutePos * LASTHLSURFACE->getWindow()->m_vRealSize.goal();
+ else
+ local = tool->absolutePos * BOX->size();
+
+ if (LASTHLSURFACE->getWindow() && LASTHLSURFACE->getWindow()->m_bIsX11)
+ local = local * LASTHLSURFACE->getWindow()->m_fX11SurfaceScaledBy;
+
+ PROTO::tablet->motion(tool, local);
+ return;
+ }
+
+ auto local = CURSORPOS - BOX->pos();
+
+ if (LASTHLSURFACE->getWindow() && LASTHLSURFACE->getWindow()->m_bIsX11)
+ local = local * LASTHLSURFACE->getWindow()->m_fX11SurfaceScaledBy;
+
+ PROTO::tablet->motion(tool, local);
+}
+
+void CInputManager::onTabletAxis(CTablet::SAxisEvent e) {
+ const auto PTAB = e.tablet;
+ const auto PTOOL = ensureTabletToolPresent(e.tool);
+
+ if (PTOOL->active && (e.updatedAxes & (CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_X | CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_Y))) {
+ double x = (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_X) ? e.axis.x : NAN;
+ double dx = (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_X) ? e.axisDelta.x : NAN;
+ double y = (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_Y) ? e.axis.y : NAN;
+ double dy = (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_Y) ? e.axisDelta.y : NAN;
+
+ Vector2D delta = {std::isnan(dx) ? 0.0 : dx, std::isnan(dy) ? 0.0 : dy};
+
+ switch (e.tool->type) {
+ case WLR_TABLET_TOOL_TYPE_MOUSE: {
+ g_pPointerManager->move(delta);
+ break;
+ }
+ default: {
+ if (!std::isnan(x))
+ PTOOL->absolutePos.x = x;
+ if (!std::isnan(y))
+ PTOOL->absolutePos.y = y;
+
+ if (PTAB->relativeInput)
+ g_pPointerManager->move(delta);
+ else {
+ //Calculate transformations if active area is set
+ if (!PTAB->activeArea.empty()) {
+ if (!std::isnan(x))
+ x = (x - PTAB->activeArea.x) / (PTAB->activeArea.w - PTAB->activeArea.x);
+ if (!std::isnan(y))
+ y = (y - PTAB->activeArea.y) / (PTAB->activeArea.h - PTAB->activeArea.y);
+ }
+ g_pPointerManager->warpAbsolute({x, y}, PTAB);
+ }
+ break;
+ }
+ }
+
+ refocusTablet(PTAB, PTOOL, true);
+ m_tmrLastCursorMovement.reset();
+ }
+
+ if (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_PRESSURE)
+ PROTO::tablet->pressure(PTOOL, e.pressure);
+
+ if (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_DISTANCE)
+ PROTO::tablet->distance(PTOOL, e.distance);
+
+ if (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_ROTATION)
+ PROTO::tablet->rotation(PTOOL, e.rotation);
+
+ if (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_SLIDER)
+ PROTO::tablet->slider(PTOOL, e.slider);
+
+ if (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_WHEEL)
+ PROTO::tablet->wheel(PTOOL, e.wheelDelta);
+
+ if (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_TILT_X)
+ PTOOL->tilt.x = e.tilt.x;
+
+ if (e.updatedAxes & CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_TILT_Y)
+ PTOOL->tilt.y = e.tilt.y;
+
+ if (e.updatedAxes & (CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_TILT_X | CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_TILT_Y))
+ PROTO::tablet->tilt(PTOOL, PTOOL->tilt);
+
+ PROTO::idle->onActivity();
+}
+
+void CInputManager::onTabletTip(CTablet::STipEvent e) {
+ const auto PTAB = e.tablet;
+ const auto PTOOL = ensureTabletToolPresent(e.tool);
+
+ if (e.in) {
+ simulateMouseMovement();
+ refocusTablet(PTAB, PTOOL);
+ PROTO::tablet->down(PTOOL);
+ } else
+ PROTO::tablet->up(PTOOL);
+
+ PTOOL->isDown = e.in;
+
+ PROTO::idle->onActivity();
+}
+
+void CInputManager::onTabletButton(CTablet::SButtonEvent e) {
+ const auto PTOOL = ensureTabletToolPresent(e.tool);
+
+ PROTO::tablet->buttonTool(PTOOL, e.button, e.down);
+
+ if (e.down)
+ PTOOL->buttonsDown.push_back(e.button);
+ else
+ std::erase(PTOOL->buttonsDown, e.button);
+
+ PROTO::idle->onActivity();
+}
+
+void CInputManager::onTabletProximity(CTablet::SProximityEvent e) {
+ const auto PTAB = e.tablet;
+ const auto PTOOL = ensureTabletToolPresent(e.tool);
+
+ PTOOL->active = e.in;
+
+ if (!e.in) {
+ if (PTOOL->getSurface())
+ unfocusTool(PTOOL);
+ } else {
+ simulateMouseMovement();
+ refocusTablet(PTAB, PTOOL);
+ }
+
+ PROTO::idle->onActivity();
+}
+
+void CInputManager::newTablet(wlr_input_device* pDevice) {
+ const auto PNEWTABLET = m_vTablets.emplace_back(CTablet::create(wlr_tablet_from_input_device(pDevice)));
+ m_vHIDs.push_back(PNEWTABLET);
try {
- PNEWTABLET->name = deviceNameToInternalString(pDevice->name);
+ PNEWTABLET->hlName = deviceNameToInternalString(pDevice->name);
} catch (std::exception& e) {
Debug::log(ERR, "Tablet had no name???"); // logic error
}
- PNEWTABLET->wlrTablet = wlr_tablet_from_input_device(pDevice);
- PNEWTABLET->wlrDevice = pDevice;
- PNEWTABLET->wlrTabletV2 = wlr_tablet_create(g_pCompositor->m_sWLRTabletManager, g_pCompositor->m_sSeat.seat, pDevice);
- PNEWTABLET->wlrTablet->data = PNEWTABLET;
+ g_pPointerManager->attachTablet(PNEWTABLET);
- Debug::log(LOG, "Attaching tablet to cursor!");
-
- // wlr_cursor_attach_input_device(g_pCompositor->m_sWLRCursor, pDevice);
-
- PNEWTABLET->hyprListener_Destroy.initCallback(
- &pDevice->events.destroy,
- [](void* owner, void* data) {
- const auto PTAB = (STablet*)owner;
-
- g_pInputManager->m_lTablets.remove(*PTAB);
-
- Debug::log(LOG, "Removed a tablet");
+ PNEWTABLET->events.destroy.registerStaticListener(
+ [this](void* owner, std::any d) {
+ auto TABLET = ((CTablet*)owner)->self;
+ destroyTablet(TABLET.lock());
},
- PNEWTABLET, "Tablet");
-
- PNEWTABLET->hyprListener_Axis.initCallback(
- &wlr_tablet_from_input_device(pDevice)->events.axis,
- [](void* owner, void* data) {
- const auto EVENT = (wlr_tablet_tool_axis_event*)data;
- const auto PTAB = (STablet*)owner;
-
- switch (EVENT->tool->type) {
- case WLR_TABLET_TOOL_TYPE_MOUSE:
- // wlr_cursor_move(g_pCompositor->m_sWLRCursor, PTAB->wlrDevice, EVENT->dx, EVENT->dy);
- g_pInputManager->simulateMouseMovement();
- g_pInputManager->focusTablet(PTAB, EVENT->tool, true);
- g_pInputManager->m_tmrLastCursorMovement.reset();
- break;
- default:
- double x = (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_X) ? EVENT->x : NAN;
- double dx = (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_X) ? EVENT->dx : NAN;
- double y = (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_Y) ? EVENT->y : NAN;
- double dy = (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_Y) ? EVENT->dy : NAN;
-
- // if (PTAB->relativeInput)
- // wlr_cursor_move(g_pCompositor->m_sWLRCursor, PTAB->wlrDevice, dx, dy);
- // else {
- // Calculate transformations if active area is set
- // if (!PTAB->activeArea.empty()) {
- // x = (x - PTAB->activeArea.x) / (PTAB->activeArea.w - PTAB->activeArea.x);
- // y = (y - PTAB->activeArea.y) / (PTAB->activeArea.h - PTAB->activeArea.y);
- // }
- // wlr_cursor_warp_absolute(g_pCompositor->m_sWLRCursor, PTAB->wlrDevice, x, y);
- // }
-
- g_pInputManager->simulateMouseMovement();
- g_pInputManager->focusTablet(PTAB, EVENT->tool, true);
- g_pInputManager->m_tmrLastCursorMovement.reset();
- break;
- }
-
- const auto PTOOL = g_pInputManager->ensureTabletToolPresent(EVENT->tool);
-
- // TODO: this might be wrong
- if (PTOOL->active) {
- g_pInputManager->simulateMouseMovement();
-
- g_pInputManager->focusTablet(PTAB, EVENT->tool, true);
- }
-
- if (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE)
- wlr_tablet_v2_tablet_tool_notify_pressure(PTOOL->wlrTabletToolV2, EVENT->pressure);
-
- if (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE)
- wlr_tablet_v2_tablet_tool_notify_distance(PTOOL->wlrTabletToolV2, EVENT->distance);
-
- if (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION)
- wlr_tablet_v2_tablet_tool_notify_rotation(PTOOL->wlrTabletToolV2, EVENT->rotation);
-
- if (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER)
- wlr_tablet_v2_tablet_tool_notify_slider(PTOOL->wlrTabletToolV2, EVENT->slider);
-
- if (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL)
- wlr_tablet_v2_tablet_tool_notify_wheel(PTOOL->wlrTabletToolV2, EVENT->wheel_delta, 0);
-
- if (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X)
- PTOOL->tiltX = EVENT->tilt_x;
-
- if (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y)
- PTOOL->tiltY = EVENT->tilt_y;
-
- if (EVENT->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y))
- wlr_tablet_v2_tablet_tool_notify_tilt(PTOOL->wlrTabletToolV2, PTOOL->tiltX, PTOOL->tiltY);
-
- PROTO::idle->onActivity();
- },
- PNEWTABLET, "Tablet");
-
- PNEWTABLET->hyprListener_Tip.initCallback(
- &wlr_tablet_from_input_device(pDevice)->events.tip,
- [](void* owner, void* data) {
- const auto EVENT = (wlr_tablet_tool_tip_event*)data;
- const auto PTAB = (STablet*)owner;
-
- const auto PTOOL = g_pInputManager->ensureTabletToolPresent(EVENT->tool);
-
- // TODO: this might be wrong
- if (EVENT->state == WLR_TABLET_TOOL_TIP_DOWN) {
- g_pInputManager->simulateMouseMovement();
- g_pInputManager->focusTablet(PTAB, EVENT->tool);
- wlr_send_tablet_v2_tablet_tool_down(PTOOL->wlrTabletToolV2);
- } else {
- wlr_send_tablet_v2_tablet_tool_up(PTOOL->wlrTabletToolV2);
- }
-
- PROTO::idle->onActivity();
- },
- PNEWTABLET, "Tablet");
-
- PNEWTABLET->hyprListener_Button.initCallback(
- &wlr_tablet_from_input_device(pDevice)->events.button,
- [](void* owner, void* data) {
- const auto EVENT = (wlr_tablet_tool_button_event*)data;
-
- const auto PTOOL = g_pInputManager->ensureTabletToolPresent(EVENT->tool);
-
- wlr_tablet_v2_tablet_tool_notify_button(PTOOL->wlrTabletToolV2, (zwp_tablet_pad_v2_button_state)EVENT->button, (zwp_tablet_pad_v2_button_state)EVENT->state);
- PROTO::idle->onActivity();
- },
- PNEWTABLET, "Tablet");
-
- PNEWTABLET->hyprListener_Proximity.initCallback(
- &wlr_tablet_from_input_device(pDevice)->events.proximity,
- [](void* owner, void* data) {
- const auto EVENT = (wlr_tablet_tool_proximity_event*)data;
- const auto PTAB = (STablet*)owner;
-
- const auto PTOOL = g_pInputManager->ensureTabletToolPresent(EVENT->tool);
-
- if (EVENT->state == WLR_TABLET_TOOL_PROXIMITY_OUT) {
- PTOOL->active = false;
-
- if (PTOOL->pSurface) {
- wlr_tablet_v2_tablet_tool_notify_proximity_out(PTOOL->wlrTabletToolV2);
- PTOOL->pSurface = nullptr;
- }
-
- } else {
- PTOOL->active = true;
- g_pInputManager->simulateMouseMovement();
- g_pInputManager->focusTablet(PTAB, EVENT->tool);
- }
-
- PROTO::idle->onActivity();
- },
- PNEWTABLET, "Tablet");
+ PNEWTABLET.get());
setTabletConfigs();
}
-STabletTool* CInputManager::ensureTabletToolPresent(wlr_tablet_tool* pTool) {
+SP CInputManager::ensureTabletToolPresent(wlr_tablet_tool* pTool) {
if (pTool->data == nullptr) {
- const auto PTOOL = &m_lTabletTools.emplace_back();
+ const auto PTOOL = m_vTabletTools.emplace_back(CTabletTool::create(pTool));
+ m_vHIDs.push_back(PTOOL);
- Debug::log(LOG, "Creating tablet tool v2 for {:x}", (uintptr_t)pTool);
-
- PTOOL->wlrTabletTool = pTool;
- pTool->data = PTOOL;
-
- PTOOL->wlrTabletToolV2 = wlr_tablet_tool_create(g_pCompositor->m_sWLRTabletManager, g_pCompositor->m_sSeat.seat, pTool);
-
- PTOOL->hyprListener_TabletToolDestroy.initCallback(
- &pTool->events.destroy,
- [](void* owner, void* data) {
- const auto PTOOL = (STabletTool*)owner;
-
- PTOOL->wlrTabletTool->data = nullptr;
- g_pInputManager->m_lTabletTools.remove(*PTOOL);
+ PTOOL->events.destroy.registerStaticListener(
+ [this](void* owner, std::any d) {
+ auto TOOL = ((CTabletTool*)owner)->self;
+ destroyTabletTool(TOOL.lock());
},
- PTOOL, "Tablet Tool V1");
-
- //TODO: set cursor request
+ PTOOL.get());
}
- return (STabletTool*)pTool->data;
+ return CTabletTool::fromWlr(pTool);
}
void CInputManager::newTabletPad(wlr_input_device* pDevice) {
- const auto PNEWPAD = &m_lTabletPads.emplace_back();
+ const auto PNEWPAD = m_vTabletPads.emplace_back(CTabletPad::create(wlr_tablet_pad_from_input_device(pDevice)));
+ m_vHIDs.push_back(PNEWPAD);
try {
- PNEWPAD->name = deviceNameToInternalString(pDevice->name);
+ PNEWPAD->hlName = deviceNameToInternalString(pDevice->name);
} catch (std::exception& e) {
Debug::log(ERR, "Pad had no name???"); // logic error
}
- PNEWPAD->wlrTabletPadV2 = wlr_tablet_pad_create(g_pCompositor->m_sWLRTabletManager, g_pCompositor->m_sSeat.seat, pDevice);
- PNEWPAD->pWlrDevice = pDevice;
+ // clang-format off
+ PNEWPAD->events.destroy.registerStaticListener([this](void* owner, std::any d) {
+ auto PAD = ((CTabletPad*)owner)->self;
+ destroyTabletPad(PAD.lock());
+ }, PNEWPAD.get());
- PNEWPAD->hyprListener_Button.initCallback(
- &wlr_tablet_pad_from_input_device(pDevice)->events.button,
- [](void* owner, void* data) {
- const auto EVENT = (wlr_tablet_pad_button_event*)data;
- const auto PPAD = (STabletPad*)owner;
+ PNEWPAD->padEvents.button.registerStaticListener([this](void* owner, std::any e) {
+ const auto E = std::any_cast(e);
+ const auto PPAD = ((CTabletPad*)owner)->self.lock();
- wlr_tablet_v2_tablet_pad_notify_mode(PPAD->wlrTabletPadV2, EVENT->group, EVENT->mode, EVENT->time_msec);
- wlr_tablet_v2_tablet_pad_notify_button(PPAD->wlrTabletPadV2, EVENT->button, EVENT->time_msec, (zwp_tablet_pad_v2_button_state)EVENT->state);
- },
- PNEWPAD, "Tablet Pad");
+ PROTO::tablet->mode(PPAD, 0, E.mode, E.timeMs);
+ PROTO::tablet->buttonPad(PPAD, E.button, E.timeMs, E.down);
+ }, PNEWPAD.get());
- PNEWPAD->hyprListener_Strip.initCallback(
- &wlr_tablet_pad_from_input_device(pDevice)->events.strip,
- [](void* owner, void* data) {
- const auto EVENT = (wlr_tablet_pad_strip_event*)data;
- const auto PPAD = (STabletPad*)owner;
+ PNEWPAD->padEvents.strip.registerStaticListener([this](void* owner, std::any e) {
+ const auto E = std::any_cast(e);
+ const auto PPAD = ((CTabletPad*)owner)->self.lock();
- wlr_tablet_v2_tablet_pad_notify_strip(PPAD->wlrTabletPadV2, EVENT->strip, EVENT->position, EVENT->source == WLR_TABLET_PAD_STRIP_SOURCE_FINGER, EVENT->time_msec);
- },
- PNEWPAD, "Tablet Pad");
+ PROTO::tablet->strip(PPAD, E.strip, E.position, E.finger, E.timeMs);
+ }, PNEWPAD.get());
- PNEWPAD->hyprListener_Ring.initCallback(
- &wlr_tablet_pad_from_input_device(pDevice)->events.strip,
- [](void* owner, void* data) {
- const auto EVENT = (wlr_tablet_pad_ring_event*)data;
- const auto PPAD = (STabletPad*)owner;
+ PNEWPAD->padEvents.ring.registerStaticListener([this](void* owner, std::any e) {
+ const auto E = std::any_cast(e);
+ const auto PPAD = ((CTabletPad*)owner)->self.lock();
- wlr_tablet_v2_tablet_pad_notify_ring(PPAD->wlrTabletPadV2, EVENT->ring, EVENT->position, EVENT->source == WLR_TABLET_PAD_RING_SOURCE_FINGER, EVENT->time_msec);
- },
- PNEWPAD, "Tablet Pad");
+ PROTO::tablet->ring(PPAD, E.ring, E.position, E.finger, E.timeMs);
+ }, PNEWPAD.get());
- PNEWPAD->hyprListener_Attach.initCallback(
- &wlr_tablet_pad_from_input_device(pDevice)->events.strip,
- [](void* owner, void* data) {
- const auto TABLET = (wlr_tablet_tool*)data;
- const auto PPAD = (STabletPad*)owner;
+ PNEWPAD->padEvents.attach.registerStaticListener([this](void* owner, std::any e) {
+ const auto PPAD = ((CTabletPad*)owner)->self.lock();
+ const auto TOOL = std::any_cast>(e);
- PPAD->pTabletParent = (STablet*)TABLET->data;
+ PPAD->parent = TOOL;
+ }, PNEWPAD.get());
- if (!PPAD->pTabletParent)
- Debug::log(ERR, "tabletpad got attached to a nullptr tablet!! this might be bad.");
- },
- PNEWPAD, "Tablet Pad");
-
- PNEWPAD->hyprListener_Destroy.initCallback(
- &pDevice->events.destroy,
- [](void* owner, void* data) {
- const auto PPAD = (STabletPad*)owner;
-
- g_pInputManager->m_lTabletPads.remove(*PPAD);
-
- Debug::log(LOG, "Removed a tablet pad");
- },
- PNEWPAD, "Tablet Pad");
-}
-
-void CInputManager::focusTablet(STablet* pTab, wlr_tablet_tool* pTool, bool motion) {
- const auto PTOOL = g_pInputManager->ensureTabletToolPresent(pTool);
-
- if (const auto PWINDOW = g_pCompositor->m_pLastWindow.lock(); PWINDOW) {
- const auto CURSORPOS = g_pInputManager->getMouseCoordsInternal();
-
- if (PTOOL->pSurface != g_pInputManager->m_pLastMouseSurface)
- wlr_tablet_v2_tablet_tool_notify_proximity_out(PTOOL->wlrTabletToolV2);
-
- if (g_pInputManager->m_pLastMouseSurface) {
- PTOOL->pSurface = g_pCompositor->m_pLastFocus;
- wlr_tablet_v2_tablet_tool_notify_proximity_in(PTOOL->wlrTabletToolV2, pTab->wlrTabletV2, g_pInputManager->m_pLastMouseSurface);
- }
-
- if (motion) {
- auto local = CURSORPOS - PWINDOW->m_vRealPosition.goal();
-
- if (PWINDOW->m_bIsX11)
- local = local * PWINDOW->m_fX11SurfaceScaledBy;
-
- wlr_tablet_v2_tablet_tool_notify_motion(PTOOL->wlrTabletToolV2, local.x, local.y);
- }
- } else {
- if (PTOOL->pSurface)
- wlr_tablet_v2_tablet_tool_notify_proximity_out(PTOOL->wlrTabletToolV2);
- }
+ // clang-format on
}
diff --git a/src/protocols/Tablet.cpp b/src/protocols/Tablet.cpp
new file mode 100644
index 00000000..b3ca76f5
--- /dev/null
+++ b/src/protocols/Tablet.cpp
@@ -0,0 +1,656 @@
+#include "Tablet.hpp"
+#include "../devices/Tablet.hpp"
+#include "../Compositor.hpp"
+#include
+
+#define LOGM PROTO::tablet->protoLog
+
+CTabletPadStripV2Resource::CTabletPadStripV2Resource(SP resource_, uint32_t id_) : id(id_), resource(resource_) {
+ if (!good())
+ return;
+
+ resource->setDestroy([this](CZwpTabletPadStripV2* r) { PROTO::tablet->destroyResource(this); });
+ resource->setOnDestroy([this](CZwpTabletPadStripV2* r) { PROTO::tablet->destroyResource(this); });
+}
+
+bool CTabletPadStripV2Resource::good() {
+ return resource->resource();
+}
+
+CTabletPadRingV2Resource::CTabletPadRingV2Resource(SP resource_, uint32_t id_) : id(id_), resource(resource_) {
+ if (!good())
+ return;
+
+ resource->setDestroy([this](CZwpTabletPadRingV2* r) { PROTO::tablet->destroyResource(this); });
+ resource->setOnDestroy([this](CZwpTabletPadRingV2* r) { PROTO::tablet->destroyResource(this); });
+}
+
+bool CTabletPadRingV2Resource::good() {
+ return resource->resource();
+}
+
+CTabletPadGroupV2Resource::CTabletPadGroupV2Resource(SP resource_, size_t idx_) : idx(idx_), resource(resource_) {
+ if (!good())
+ return;
+
+ resource->setDestroy([this](CZwpTabletPadGroupV2* r) { PROTO::tablet->destroyResource(this); });
+ resource->setOnDestroy([this](CZwpTabletPadGroupV2* r) { PROTO::tablet->destroyResource(this); });
+}
+
+bool CTabletPadGroupV2Resource::good() {
+ return resource->resource();
+}
+
+void CTabletPadGroupV2Resource::sendData(SP pad, wlr_tablet_pad_group* group) {
+ resource->sendModes(group->mode_count);
+
+ wl_array buttonArr;
+ wl_array_init(&buttonArr);
+ wl_array_add(&buttonArr, group->button_count * sizeof(int));
+ memcpy(buttonArr.data, group->buttons, group->button_count * sizeof(int));
+ resource->sendButtons(&buttonArr);
+ wl_array_release(&buttonArr);
+
+ for (size_t i = 0; i < group->strip_count; ++i) {
+ const auto RESOURCE =
+ PROTO::tablet->m_vStrips.emplace_back(makeShared(makeShared(resource->client(), resource->version(), 0), i));
+
+ if (!RESOURCE->good()) {
+ resource->noMemory();
+ PROTO::tablet->m_vStrips.pop_back();
+ return;
+ }
+
+ resource->sendStrip(RESOURCE->resource.get());
+ }
+
+ for (size_t i = 0; i < group->ring_count; ++i) {
+ const auto RESOURCE =
+ PROTO::tablet->m_vRings.emplace_back(makeShared(makeShared(resource->client(), resource->version(), 0), i));
+
+ if (!RESOURCE->good()) {
+ resource->noMemory();
+ PROTO::tablet->m_vRings.pop_back();
+ return;
+ }
+
+ resource->sendRing(RESOURCE->resource.get());
+ }
+
+ resource->sendDone();
+}
+
+CTabletPadV2Resource::CTabletPadV2Resource(SP resource_, SP pad_, SP seat_) : pad(pad_), seat(seat_), resource(resource_) {
+ if (!good())
+ return;
+
+ resource->setDestroy([this](CZwpTabletPadV2* r) { PROTO::tablet->destroyResource(this); });
+ resource->setOnDestroy([this](CZwpTabletPadV2* r) { PROTO::tablet->destroyResource(this); });
+}
+
+bool CTabletPadV2Resource::good() {
+ return resource->resource();
+}
+
+void CTabletPadV2Resource::sendData() {
+ // this is dodgy as fuck. I hate wl_array. it's expanded wl_array_for_each because C++ would complain about the implicit casts
+ const char** path_ptr;
+ for (path_ptr = (const char**)(&pad->wlr()->paths)->data; (const char*)path_ptr < ((const char*)(&pad->wlr()->paths)->data + (&pad->wlr()->paths)->size); (path_ptr)++) {
+ resource->sendPath(*path_ptr);
+ }
+
+ resource->sendButtons(pad->wlr()->button_count);
+
+ wlr_tablet_pad_group* group;
+ size_t i = 0;
+ wl_list_for_each(group, &pad->wlr()->groups, link) {
+ createGroup(group, i++);
+ }
+
+ resource->sendDone();
+}
+
+void CTabletPadV2Resource::createGroup(wlr_tablet_pad_group* group, size_t idx) {
+ const auto RESOURCE =
+ PROTO::tablet->m_vGroups.emplace_back(makeShared(makeShared(resource->client(), resource->version(), 0), idx));
+
+ if (!RESOURCE->good()) {
+ resource->noMemory();
+ PROTO::tablet->m_vGroups.pop_back();
+ return;
+ }
+
+ resource->sendGroup(RESOURCE->resource.get());
+
+ RESOURCE->sendData(pad.lock(), group);
+}
+
+CTabletV2Resource::CTabletV2Resource(SP resource_, SP tablet_, SP seat_) : tablet(tablet_), seat(seat_), resource(resource_) {
+ if (!good())
+ return;
+
+ resource->setDestroy([this](CZwpTabletV2* r) { PROTO::tablet->destroyResource(this); });
+ resource->setOnDestroy([this](CZwpTabletV2* r) { PROTO::tablet->destroyResource(this); });
+}
+
+bool CTabletV2Resource::good() {
+ return resource->resource();
+}
+
+void CTabletV2Resource::sendData() {
+ resource->sendName(tablet->deviceName.c_str());
+ resource->sendId(tablet->wlr()->usb_vendor_id, tablet->wlr()->usb_product_id);
+
+ // this is dodgy as fuck. I hate wl_array. it's expanded wl_array_for_each because C++ would complain about the implicit casts
+ const char** path_ptr;
+ for (path_ptr = (const char**)(&tablet->wlr()->paths)->data; (const char*)path_ptr < ((const char*)(&tablet->wlr()->paths)->data + (&tablet->wlr()->paths)->size);
+ (path_ptr)++) {
+ resource->sendPath(*path_ptr);
+ }
+
+ resource->sendDone();
+}
+
+CTabletToolV2Resource::CTabletToolV2Resource(SP resource_, SP tool_, SP seat_) : tool(tool_), seat(seat_), resource(resource_) {
+ if (!good())
+ return;
+
+ resource->setDestroy([this](CZwpTabletToolV2* r) { PROTO::tablet->destroyResource(this); });
+ resource->setOnDestroy([this](CZwpTabletToolV2* r) { PROTO::tablet->destroyResource(this); });
+
+ resource->setSetCursor([this](CZwpTabletToolV2* r, uint32_t serial, wl_resource* surf, int32_t hot_x, int32_t hot_y) {
+ wlr_seat_pointer_request_set_cursor_event e;
+ e.hotspot_x = hot_x;
+ e.hotspot_y = hot_y;
+ e.surface = surf ? wlr_surface_from_resource(surf) : nullptr;
+ e.serial = serial;
+ g_pInputManager->processMouseRequest(&e);
+ });
+}
+
+CTabletToolV2Resource::~CTabletToolV2Resource() {
+ if (frameSource)
+ wl_event_source_remove(frameSource);
+}
+
+bool CTabletToolV2Resource::good() {
+ return resource->resource();
+}
+
+void CTabletToolV2Resource::sendData() {
+ static auto WLR_TYPE_TO_PROTO = [](uint32_t wlr) -> zwpTabletToolV2Type {
+ switch (wlr) {
+ case WLR_TABLET_TOOL_TYPE_PEN: return ZWP_TABLET_TOOL_V2_TYPE_PEN;
+ case WLR_TABLET_TOOL_TYPE_ERASER: return ZWP_TABLET_TOOL_V2_TYPE_ERASER;
+ case WLR_TABLET_TOOL_TYPE_BRUSH: return ZWP_TABLET_TOOL_V2_TYPE_BRUSH;
+ case WLR_TABLET_TOOL_TYPE_PENCIL: return ZWP_TABLET_TOOL_V2_TYPE_PENCIL;
+ case WLR_TABLET_TOOL_TYPE_AIRBRUSH: return ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH;
+ case WLR_TABLET_TOOL_TYPE_MOUSE: return ZWP_TABLET_TOOL_V2_TYPE_MOUSE;
+ case WLR_TABLET_TOOL_TYPE_LENS: return ZWP_TABLET_TOOL_V2_TYPE_LENS;
+ default: ASSERT(false);
+ }
+ UNREACHABLE();
+ };
+
+ resource->sendType(WLR_TYPE_TO_PROTO(tool->wlr()->type));
+ resource->sendHardwareSerial(tool->wlr()->hardware_serial >> 32, tool->wlr()->hardware_serial & 0xFFFFFFFF);
+ resource->sendHardwareIdWacom(tool->wlr()->hardware_wacom >> 32, tool->wlr()->hardware_wacom & 0xFFFFFFFF);
+ if (tool->toolCapabilities & CTabletTool::eTabletToolCapabilities::HID_TABLET_TOOL_CAPABILITY_DISTANCE)
+ resource->sendCapability(zwpTabletToolV2Capability::ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE);
+ if (tool->toolCapabilities & CTabletTool::eTabletToolCapabilities::HID_TABLET_TOOL_CAPABILITY_PRESSURE)
+ resource->sendCapability(zwpTabletToolV2Capability::ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE);
+ if (tool->toolCapabilities & CTabletTool::eTabletToolCapabilities::HID_TABLET_TOOL_CAPABILITY_ROTATION)
+ resource->sendCapability(zwpTabletToolV2Capability::ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION);
+ if (tool->toolCapabilities & CTabletTool::eTabletToolCapabilities::HID_TABLET_TOOL_CAPABILITY_SLIDER)
+ resource->sendCapability(zwpTabletToolV2Capability::ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER);
+ if (tool->toolCapabilities & CTabletTool::eTabletToolCapabilities::HID_TABLET_TOOL_CAPABILITY_TILT)
+ resource->sendCapability(zwpTabletToolV2Capability::ZWP_TABLET_TOOL_V2_CAPABILITY_TILT);
+ if (tool->toolCapabilities & CTabletTool::eTabletToolCapabilities::HID_TABLET_TOOL_CAPABILITY_WHEEL)
+ resource->sendCapability(zwpTabletToolV2Capability::ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL);
+ resource->sendDone();
+}
+
+void CTabletToolV2Resource::queueFrame() {
+ if (frameSource)
+ return;
+
+ frameSource = wl_event_loop_add_idle(
+ g_pCompositor->m_sWLEventLoop, [](void* data) { ((CTabletToolV2Resource*)data)->sendFrame(false); }, this);
+}
+
+void CTabletToolV2Resource::sendFrame(bool removeSource) {
+ if (frameSource) {
+ if (removeSource)
+ wl_event_source_remove(frameSource);
+ frameSource = nullptr;
+ }
+
+ if (!current)
+ return;
+
+ timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ resource->sendFrame(now.tv_sec * 1000 + now.tv_nsec / 1000000);
+}
+
+CTabletSeat::CTabletSeat(SP resource_) : resource(resource_) {
+ if (!good())
+ return;
+
+ resource->setDestroy([this](CZwpTabletSeatV2* r) { PROTO::tablet->destroyResource(this); });
+ resource->setOnDestroy([this](CZwpTabletSeatV2* r) { PROTO::tablet->destroyResource(this); });
+}
+
+bool CTabletSeat::good() {
+ return resource->resource();
+}
+
+void CTabletSeat::sendTool(SP tool) {
+ const auto RESOURCE =
+ PROTO::tablet->m_vTools.emplace_back(makeShared(makeShared(resource->client(), resource->version(), 0), tool, self.lock()));
+
+ if (!RESOURCE->good()) {
+ resource->noMemory();
+ PROTO::tablet->m_vTools.pop_back();
+ return;
+ }
+
+ resource->sendToolAdded(RESOURCE->resource.get());
+
+ RESOURCE->sendData();
+ tools.push_back(RESOURCE);
+}
+
+void CTabletSeat::sendPad(SP pad) {
+ const auto RESOURCE =
+ PROTO::tablet->m_vPads.emplace_back(makeShared(makeShared(resource->client(), resource->version(), 0), pad, self.lock()));
+
+ if (!RESOURCE->good()) {
+ resource->noMemory();
+ PROTO::tablet->m_vPads.pop_back();
+ return;
+ }
+
+ resource->sendPadAdded(RESOURCE->resource.get());
+
+ RESOURCE->sendData();
+ pads.push_back(RESOURCE);
+}
+
+void CTabletSeat::sendTablet(SP tablet) {
+ const auto RESOURCE =
+ PROTO::tablet->m_vTablets.emplace_back(makeShared(makeShared(resource->client(), resource->version(), 0), tablet, self.lock()));
+
+ if (!RESOURCE->good()) {
+ resource->noMemory();
+ PROTO::tablet->m_vTablets.pop_back();
+ return;
+ }
+
+ resource->sendTabletAdded(RESOURCE->resource.get());
+
+ RESOURCE->sendData();
+ tablets.push_back(RESOURCE);
+}
+
+void CTabletSeat::sendData() {
+ for (auto& tw : PROTO::tablet->tablets) {
+ if (tw.expired())
+ continue;
+
+ sendTablet(tw.lock());
+ }
+
+ for (auto& tw : PROTO::tablet->tools) {
+ if (tw.expired())
+ continue;
+
+ sendTool(tw.lock());
+ }
+
+ for (auto& tw : PROTO::tablet->pads) {
+ if (tw.expired())
+ continue;
+
+ sendPad(tw.lock());
+ }
+}
+
+CTabletV2Protocol::CTabletV2Protocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {
+ ;
+}
+
+void CTabletV2Protocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {
+ const auto RESOURCE = m_vManagers.emplace_back(std::make_unique(client, ver, id)).get();
+ RESOURCE->setOnDestroy([this](CZwpTabletManagerV2* p) { this->onManagerResourceDestroy(p->resource()); });
+
+ RESOURCE->setDestroy([this](CZwpTabletManagerV2* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); });
+ RESOURCE->setGetTabletSeat([this](CZwpTabletManagerV2* pMgr, uint32_t id, wl_resource* seat) { this->onGetSeat(pMgr, id, seat); });
+}
+
+void CTabletV2Protocol::onManagerResourceDestroy(wl_resource* res) {
+ std::erase_if(m_vManagers, [&](const auto& other) { return other->resource() == res; });
+}
+
+void CTabletV2Protocol::destroyResource(CTabletSeat* resource) {
+ std::erase_if(m_vSeats, [&](const auto& other) { return other.get() == resource; });
+}
+
+void CTabletV2Protocol::destroyResource(CTabletToolV2Resource* resource) {
+ std::erase_if(m_vTools, [&](const auto& other) { return other.get() == resource; });
+}
+
+void CTabletV2Protocol::destroyResource(CTabletV2Resource* resource) {
+ std::erase_if(m_vTablets, [&](const auto& other) { return other.get() == resource; });
+}
+
+void CTabletV2Protocol::destroyResource(CTabletPadV2Resource* resource) {
+ std::erase_if(m_vPads, [&](const auto& other) { return other.get() == resource; });
+}
+
+void CTabletV2Protocol::destroyResource(CTabletPadGroupV2Resource* resource) {
+ std::erase_if(m_vGroups, [&](const auto& other) { return other.get() == resource; });
+}
+
+void CTabletV2Protocol::destroyResource(CTabletPadRingV2Resource* resource) {
+ std::erase_if(m_vRings, [&](const auto& other) { return other.get() == resource; });
+}
+
+void CTabletV2Protocol::destroyResource(CTabletPadStripV2Resource* resource) {
+ std::erase_if(m_vStrips, [&](const auto& other) { return other.get() == resource; });
+}
+
+void CTabletV2Protocol::onGetSeat(CZwpTabletManagerV2* pMgr, uint32_t id, wl_resource* seat) {
+ const auto RESOURCE = m_vSeats.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id)));
+
+ if (!RESOURCE->good()) {
+ pMgr->noMemory();
+ m_vSeats.pop_back();
+ return;
+ }
+
+ RESOURCE->self = RESOURCE;
+ RESOURCE->sendData();
+}
+
+void CTabletV2Protocol::registerDevice(SP tablet) {
+ for (auto& s : m_vSeats) {
+ s->sendTablet(tablet);
+ }
+
+ tablets.push_back(tablet);
+}
+
+void CTabletV2Protocol::registerDevice(SP tool) {
+ for (auto& s : m_vSeats) {
+ s->sendTool(tool);
+ }
+
+ tools.push_back(tool);
+}
+
+void CTabletV2Protocol::registerDevice(SP pad) {
+ for (auto& s : m_vSeats) {
+ s->sendPad(pad);
+ }
+
+ pads.push_back(pad);
+}
+
+void CTabletV2Protocol::unregisterDevice(SP tablet) {
+ for (auto& t : m_vTablets) {
+ if (t->tablet == tablet) {
+ t->resource->sendRemoved();
+ t->inert = true;
+ }
+ }
+ std::erase_if(tablets, [tablet](const auto& e) { return e.expired() || e == tablet; });
+}
+
+void CTabletV2Protocol::unregisterDevice(SP tool) {
+ for (auto& t : m_vTools) {
+ if (t->tool == tool) {
+ t->resource->sendRemoved();
+ t->inert = true;
+ }
+ }
+ std::erase_if(tools, [tool](const auto& e) { return e.expired() || e == tool; });
+}
+
+void CTabletV2Protocol::unregisterDevice(SP pad) {
+ for (auto& t : m_vPads) {
+ if (t->pad == pad) {
+ t->resource->sendRemoved();
+ t->inert = true;
+ }
+ }
+ std::erase_if(pads, [pad](const auto& e) { return e.expired() || e == pad; });
+}
+
+void CTabletV2Protocol::recheckRegisteredDevices() {
+ std::erase_if(tablets, [](const auto& e) { return e.expired(); });
+ std::erase_if(tools, [](const auto& e) { return e.expired(); });
+ std::erase_if(pads, [](const auto& e) { return e.expired(); });
+
+ // now we need to send removed events
+ for (auto& t : m_vTablets) {
+ if (!t->tablet.expired() || t->inert)
+ continue;
+
+ t->resource->sendRemoved();
+ t->inert = true;
+ }
+
+ for (auto& t : m_vTools) {
+ if (!t->tool.expired() || t->inert)
+ continue;
+
+ if (t->current) {
+ t->resource->sendProximityOut();
+ t->sendFrame();
+ t->lastSurf = nullptr;
+ }
+
+ t->resource->sendRemoved();
+ t->inert = true;
+ }
+
+ for (auto& t : m_vPads) {
+ if (!t->pad.expired() || t->inert)
+ continue;
+
+ t->resource->sendRemoved();
+ t->inert = true;
+ }
+}
+
+void CTabletV2Protocol::pressure(SP tool, double value) {
+ for (auto& t : m_vTools) {
+ if (t->tool != tool || !t->current)
+ continue;
+
+ t->resource->sendPressure(std::clamp(value * 65535, 0.0, 65535.0));
+ t->queueFrame();
+ }
+}
+
+void CTabletV2Protocol::distance(SP tool, double value) {
+ for (auto& t : m_vTools) {
+ if (t->tool != tool || !t->current)
+ continue;
+
+ t->resource->sendDistance(std::clamp(value * 65535, 0.0, 65535.0));
+ t->queueFrame();
+ }
+}
+
+void CTabletV2Protocol::rotation(SP tool, double value) {
+ for (auto& t : m_vTools) {
+ if (t->tool != tool || !t->current)
+ continue;
+
+ t->resource->sendRotation(wl_fixed_from_double(value));
+ t->queueFrame();
+ }
+}
+
+void CTabletV2Protocol::slider(SP tool, double value) {
+ for (auto& t : m_vTools) {
+ if (t->tool != tool || !t->current)
+ continue;
+
+ t->resource->sendSlider(std::clamp(value * 65535, -65535.0, 65535.0));
+ t->queueFrame();
+ }
+}
+
+void CTabletV2Protocol::wheel(SP tool, double value) {
+ for (auto& t : m_vTools) {
+ if (t->tool != tool || !t->current)
+ continue;
+
+ t->resource->sendWheel(wl_fixed_from_double(value), 0);
+ t->queueFrame();
+ }
+}
+
+void CTabletV2Protocol::tilt(SP tool, const Vector2D& value) {
+ for (auto& t : m_vTools) {
+ if (t->tool != tool || !t->current)
+ continue;
+
+ t->resource->sendTilt(wl_fixed_from_double(value.x), wl_fixed_from_double(value.y));
+ t->queueFrame();
+ }
+}
+
+void CTabletV2Protocol::up(SP tool) {
+ for (auto& t : m_vTools) {
+ if (t->tool != tool || !t->current)
+ continue;
+
+ t->resource->sendUp();
+ t->queueFrame();
+ }
+}
+
+void CTabletV2Protocol::down(SP tool) {
+ for (auto& t : m_vTools) {
+ if (t->tool != tool || !t->current)
+ continue;
+
+ auto serial = wlr_seat_client_next_serial(wlr_seat_client_for_wl_client(g_pCompositor->m_sSeat.seat, t->resource->client()));
+ t->resource->sendDown(serial);
+ t->queueFrame();
+ }
+}
+
+void CTabletV2Protocol::proximityIn(SP tool, SP tablet, wlr_surface* surf) {
+ proximityOut(tool);
+ const auto CLIENT = wl_resource_get_client(surf->resource);
+
+ SP toolResource;
+ SP tabletResource;
+
+ for (auto& t : m_vTools) {
+ if (t->tool != tool || t->resource->client() != CLIENT)
+ continue;
+
+ if (t->seat.expired()) {
+ LOGM(ERR, "proximityIn on a tool without a seat parent");
+ return;
+ }
+
+ if (t->lastSurf == surf)
+ return;
+
+ toolResource = t;
+
+ for (auto& tab : m_vTablets) {
+ if (tab->tablet != tablet)
+ continue;
+
+ if (tab->seat != t->seat || !tab->seat)
+ continue;
+
+ tabletResource = tab;
+ break;
+ }
+ }
+
+ if (!tabletResource || !toolResource) {
+ LOGM(ERR, "proximityIn on a tool and tablet without valid resource(s)??");
+ return;
+ }
+
+ toolResource->current = true;
+ toolResource->lastSurf = surf;
+
+ auto serial = wlr_seat_client_next_serial(wlr_seat_client_for_wl_client(g_pCompositor->m_sSeat.seat, toolResource->resource->client()));
+ toolResource->resource->sendProximityIn(serial, tabletResource->resource.get(), surf->resource);
+ toolResource->queueFrame();
+
+ LOGM(ERR, "proximityIn: found no resource to send enter");
+}
+
+void CTabletV2Protocol::proximityOut(SP tool) {
+ for (auto& t : m_vTools) {
+ if (t->tool != tool || !t->current)
+ continue;
+
+ t->current = false;
+ t->lastSurf = nullptr;
+ t->resource->sendProximityOut();
+ t->sendFrame();
+ }
+}
+
+void CTabletV2Protocol::buttonTool(SP tool, uint32_t button, uint32_t state) {
+ for (auto& t : m_vTools) {
+ if (t->tool != tool || !t->current)
+ continue;
+
+ auto serial = wlr_seat_client_next_serial(wlr_seat_client_for_wl_client(g_pCompositor->m_sSeat.seat, t->resource->client()));
+ t->resource->sendButton(serial, button, (zwpTabletToolV2ButtonState)state);
+ t->queueFrame();
+ }
+}
+
+void CTabletV2Protocol::motion(SP tool, const Vector2D& value) {
+ for (auto& t : m_vTools) {
+ if (t->tool != tool || !t->current)
+ continue;
+
+ t->resource->sendMotion(wl_fixed_from_double(value.x), wl_fixed_from_double(value.y));
+ t->queueFrame();
+ }
+}
+
+void CTabletV2Protocol::mode(SP pad, uint32_t group, uint32_t mode, uint32_t timeMs) {
+ for (auto& t : m_vPads) {
+ if (t->pad != pad)
+ continue;
+ if (t->groups.size() <= group) {
+ LOGM(ERR, "BUG THIS: group >= t->groups.size()");
+ return;
+ }
+ auto serial = wlr_seat_client_next_serial(wlr_seat_client_for_wl_client(g_pCompositor->m_sSeat.seat, t->resource->client()));
+ t->groups.at(group)->resource->sendModeSwitch(timeMs, serial, mode);
+ }
+}
+
+void CTabletV2Protocol::buttonPad(SP pad, uint32_t button, uint32_t timeMs, uint32_t state) {
+ for (auto& t : m_vPads) {
+ if (t->pad != pad)
+ continue;
+ t->resource->sendButton(timeMs, button, zwpTabletToolV2ButtonState{state});
+ }
+}
+
+void CTabletV2Protocol::strip(SP pad, uint32_t strip, double position, bool finger, uint32_t timeMs) {
+ LOGM(ERR, "FIXME: STUB: CTabletV2Protocol::strip not implemented");
+}
+
+void CTabletV2Protocol::ring(SP pad, uint32_t ring, double position, bool finger, uint32_t timeMs) {
+ LOGM(ERR, "FIXME: STUB: CTabletV2Protocol::ring not implemented");
+}
diff --git a/src/protocols/Tablet.hpp b/src/protocols/Tablet.hpp
new file mode 100644
index 00000000..74a45c63
--- /dev/null
+++ b/src/protocols/Tablet.hpp
@@ -0,0 +1,236 @@
+#pragma once
+
+#include
+#include
+#include
+#include "WaylandProtocol.hpp"
+#include "tablet-v2.hpp"
+#include "../helpers/Vector2D.hpp"
+
+class CTablet;
+class CTabletTool;
+class CTabletPad;
+class CEventLoopTimer;
+class CTabletSeat;
+
+class CTabletPadStripV2Resource {
+ public:
+ CTabletPadStripV2Resource(SP resource_, uint32_t id);
+
+ bool good();
+
+ uint32_t id = 0;
+
+ private:
+ SP resource;
+
+ friend class CTabletSeat;
+ friend class CTabletPadGroupV2Resource;
+ friend class CTabletV2Protocol;
+};
+
+class CTabletPadRingV2Resource {
+ public:
+ CTabletPadRingV2Resource(SP resource_, uint32_t id);
+
+ bool good();
+
+ uint32_t id = 0;
+
+ private:
+ SP resource;
+
+ friend class CTabletSeat;
+ friend class CTabletPadGroupV2Resource;
+ friend class CTabletV2Protocol;
+};
+
+class CTabletPadGroupV2Resource {
+ public:
+ CTabletPadGroupV2Resource(SP resource_, size_t idx);
+
+ bool good();
+ void sendData(SP pad, wlr_tablet_pad_group* group);
+
+ std::vector> rings;
+ std::vector> strips;
+
+ size_t idx = 0;
+
+ private:
+ SP resource;
+
+ friend class CTabletSeat;
+ friend class CTabletPadV2Resource;
+ friend class CTabletV2Protocol;
+};
+
+class CTabletPadV2Resource {
+ public:
+ CTabletPadV2Resource(SP resource_, SP pad_, SP seat_);
+
+ bool good();
+ void sendData();
+
+ std::vector> groups;
+
+ WP pad;
+ WP seat;
+
+ bool inert = false; // removed was sent
+
+ private:
+ SP resource;
+
+ void createGroup(wlr_tablet_pad_group* group, size_t idx);
+
+ friend class CTabletSeat;
+ friend class CTabletV2Protocol;
+};
+
+class CTabletV2Resource {
+ public:
+ CTabletV2Resource(SP resource_, SP tablet_, SP seat_);
+
+ bool good();
+ void sendData();
+
+ WP tablet;
+ WP seat;
+
+ bool inert = false; // removed was sent
+
+ private:
+ SP resource;
+
+ friend class CTabletSeat;
+ friend class CTabletV2Protocol;
+};
+
+class CTabletToolV2Resource {
+ public:
+ CTabletToolV2Resource(SP resource_, SP tool_, SP seat_);
+ ~CTabletToolV2Resource();
+
+ bool good();
+ void sendData();
+ void queueFrame();
+ void sendFrame(bool removeSource = true);
+
+ bool current = false;
+ wlr_surface* lastSurf = nullptr; // READ-ONLY
+
+ WP tool;
+ WP seat;
+ wl_event_source* frameSource = nullptr;
+
+ bool inert = false; // removed was sent
+
+ private:
+ SP resource;
+
+ friend class CTabletSeat;
+ friend class CTabletV2Protocol;
+};
+
+class CTabletSeat {
+ public:
+ CTabletSeat(SP resource_);
+
+ bool good();
+ void sendData();
+
+ std::vector> tools;
+ std::vector> pads;
+ std::vector> tablets;
+
+ void sendTool(SP tool);
+ void sendPad(SP pad);
+ void sendTablet(SP tablet);
+
+ private:
+ SP resource;
+ WP self;
+
+ friend class CTabletV2Protocol;
+};
+
+class CTabletV2Protocol : public IWaylandProtocol {
+ public:
+ CTabletV2Protocol(const wl_interface* iface, const int& ver, const std::string& name);
+
+ virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);
+
+ void registerDevice(SP tablet);
+ void registerDevice(SP tool);
+ void registerDevice(SP pad);
+
+ void unregisterDevice(SP tablet);
+ void unregisterDevice(SP tool);
+ void unregisterDevice(SP pad);
+
+ void recheckRegisteredDevices();
+
+ // Tablet tool events
+ void pressure(SP tool, double value);
+ void distance(SP tool, double value);
+ void rotation(SP tool, double value);
+ void slider(SP tool, double value);
+ void wheel(SP tool, double value);
+ void tilt(SP tool, const Vector2D& value);
+ void up(SP tool);
+ void down(SP tool);
+ void proximityIn(SP tool, SP tablet, wlr_surface* surf);
+ void proximityOut(SP tool);
+ void buttonTool(SP tool, uint32_t button, uint32_t state);
+ void motion(SP tool, const Vector2D& value);
+
+ // Tablet pad events
+ void mode(SP pad, uint32_t group, uint32_t mode, uint32_t timeMs);
+ void buttonPad(SP pad, uint32_t button, uint32_t timeMs, uint32_t state);
+ void strip(SP pad, uint32_t strip, double position, bool finger, uint32_t timeMs);
+ void ring(SP pad, uint32_t ring, double position, bool finger, uint32_t timeMs);
+
+ private:
+ void onManagerResourceDestroy(wl_resource* res);
+ void destroyResource(CTabletSeat* resource);
+ void destroyResource(CTabletToolV2Resource* resource);
+ void destroyResource(CTabletV2Resource* resource);
+ void destroyResource(CTabletPadV2Resource* resource);
+ void destroyResource(CTabletPadGroupV2Resource* resource);
+ void destroyResource(CTabletPadRingV2Resource* resource);
+ void destroyResource(CTabletPadStripV2Resource* resource);
+ void onGetSeat(CZwpTabletManagerV2* pMgr, uint32_t id, wl_resource* seat);
+
+ //
+ std::vector> m_vManagers;
+ std::vector> m_vSeats;
+ std::vector> m_vTools;
+ std::vector> m_vTablets;
+ std::vector> m_vPads;
+ std::vector> m_vGroups;
+ std::vector> m_vRings;
+ std::vector> m_vStrips;
+
+ // registered
+ std::vector> tablets;
+ std::vector> tools;
+ std::vector> pads;
+
+ // FIXME: rings and strips are broken, I don't understand how this shit works.
+ // It's 2am.
+ SP ringForID(SP pad, uint32_t id);
+ SP stripForID(SP pad, uint32_t id);
+
+ friend class CTabletSeat;
+ friend class CTabletToolV2Resource;
+ friend class CTabletV2Resource;
+ friend class CTabletPadV2Resource;
+ friend class CTabletPadGroupV2Resource;
+ friend class CTabletPadRingV2Resource;
+ friend class CTabletPadStripV2Resource;
+};
+
+namespace PROTO {
+ inline UP tablet;
+};