From 84e8d1810d57b65a0bc3b5ac2998bc023df880f4 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Mon, 6 May 2024 02:15:26 +0100 Subject: [PATCH] Tablet: move to new impl Ring and strip are not implemented. Will I implement this? God fucking knows. Nobody seems to have that anyways. --- CMakeLists.txt | 2 +- protocols/meson.build | 2 +- protocols/tablet-unstable-v2.xml | 1178 --------------------------- src/Compositor.cpp | 2 - src/Compositor.hpp | 1 - src/debug/HyprCtl.cpp | 25 +- src/devices/IHID.hpp | 3 + src/devices/Tablet.cpp | 326 ++++++++ src/devices/Tablet.hpp | 228 ++++++ src/events/Devices.cpp | 4 +- src/helpers/Monitor.cpp | 14 - src/helpers/WLClasses.hpp | 66 -- src/includes.hpp | 1 - src/managers/PointerManager.cpp | 96 ++- src/managers/PointerManager.hpp | 18 +- src/managers/ProtocolManager.cpp | 2 + src/managers/input/InputManager.cpp | 127 ++- src/managers/input/InputManager.hpp | 44 +- src/managers/input/Tablets.cpp | 501 ++++++------ src/protocols/Tablet.cpp | 656 +++++++++++++++ src/protocols/Tablet.hpp | 236 ++++++ 21 files changed, 1926 insertions(+), 1606 deletions(-) delete mode 100644 protocols/tablet-unstable-v2.xml create mode 100644 src/devices/Tablet.cpp create mode 100644 src/devices/Tablet.hpp create mode 100644 src/protocols/Tablet.cpp create mode 100644 src/protocols/Tablet.hpp 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; +};