Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
protocol: implement river-libinput-config-v1
build.zig | 2 +
build.zig.zon | 4 +-
protocol/river-libinput-config-v1.xml | 885 ++++++++++++++++++++++++++++++++++
river/InputDevice.zig | 13 +-
river/LibinputAccelConfig.zig | 124 +++++
river/LibinputConfig.zig | 118 +++++
river/LibinputDevice.zig | 561 +++++++++++++++++++++
river/Server.zig | 5 +
8 files changed, 1709 insertions(+), 3 deletions(-)
diff --git a/build.zig b/build.zig
index a689d0d..1bb009a 100644
--- a/build.zig
+++ b/build.zig
@@ -80,6 +80,7 @@ pub fn build(b: *Build) !void {
scanner.addCustomProtocol(b.path("protocol/river-xkb-bindings-v1.xml"));
scanner.addCustomProtocol(b.path("protocol/river-layer-shell-v1.xml"));
scanner.addCustomProtocol(b.path("protocol/river-input-management-v1.xml"));
+ scanner.addCustomProtocol(b.path("protocol/river-libinput-config-v1.xml"));
scanner.addCustomProtocol(b.path("protocol/wlr-layer-shell-unstable-v1.xml"));
scanner.addCustomProtocol(b.path("protocol/wlr-output-power-management-unstable-v1.xml"));
@@ -110,6 +111,7 @@ pub fn build(b: *Build) !void {
scanner.generate("river_xkb_bindings_v1", 1);
scanner.generate("river_layer_shell_v1", 1);
scanner.generate("river_input_manager_v1", 1);
+ scanner.generate("river_libinput_config_v1", 1);
scanner.generate("zwlr_output_power_manager_v1", 1);
scanner.generate("zwlr_layer_shell_v1", 4);
diff --git a/build.zig.zon b/build.zig.zon
index a5a1fd2..65cf0e5 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -14,8 +14,8 @@
.hash = "pixman-0.3.0-LClMnz2VAAAs7QSCGwLimV5VUYx0JFnX5xWU6HwtMuDX",
},
.wayland = .{
- .url = "git+https://codeberg.org/ifreund/zig-wayland?ref=main#52b54b71f1665b1b7ba61bf71a9319726e618fb6",
- .hash = "wayland-0.5.0-dev-lQa1ki3YAQCEdP63PCLFnVQzfXAixkrXsPavlEl8UkPe",
+ .url = "git+https://codeberg.org/ifreund/zig-wayland?ref=main#f2480d25764a50ed2fe29f49e4209c074a557f46",
+ .hash = "wayland-0.5.0-dev-lQa1kvTUAQCsD8AobfOXJA_-TVG-WTYXju68OZ8L85RM",
},
.wlroots = .{
.url = "https://codeberg.org/ifreund/zig-wlroots/archive/v0.19.3.tar.gz",
diff --git a/protocol/river-libinput-config-v1.xml b/protocol/river-libinput-config-v1.xml
new file mode 100644
index 0000000..869878a
--- /dev/null
+++ b/protocol/river-libinput-config-v1.xml
@@ -0,0 +1,885 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="river_libinput_config_v1">
+ <copyright>
+ Copyright 2025 The River Developers
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ </copyright>
+
+ <description summary="configure libinput devices">
+ This protocol exposes libinput device configuration APIs. The libinput
+ documentation should be referred to for detailed information on libinput's
+ behavior.
+
+ This protocol is designed so that (hopefully) any backwards compatible
+ change to libinput's API can be matched with a backwards compatible change
+ to this protocol.
+
+ Note: the libinput API uses floating point types (float and double in C)
+ which are not (yet?) natively supported by the Wayland protocol. However,
+ the Wayland protocol does support sending arbitrary bytes through the array
+ argument type. This protocol uses e.g. type="array" summary="double" to
+ indicate a native-endian IEEE-754 64-bit double value.
+
+ The key words "must", "must not", "required", "shall", "shall not",
+ "should", "should not", "recommended", "may", and "optional" in this
+ document are to be interpreted as described in IETF RFC 2119.
+
+ Warning! The protocol described in this file is currently in the testing
+ phase. Backward compatible changes may be added together with the
+ corresponding interface version bump. Backward incompatible changes can only
+ be done by creating a new major version of the extension.
+ </description>
+
+ <interface name="river_libinput_config_v1" version="1">
+ <description summary="libinput config global interface">
+ Global interface for configuring libinput devices. This global should
+ only be advertised if river_input_manager_v1 is advertised as well.
+ </description>
+
+ <enum name="error">
+ <entry name="invalid_arg" value="0"
+ summary="invalid enum value or similar"/>
+ <entry name="invalid_destroy" value="1"/>
+ </enum>
+
+ <request name="stop">
+ <description summary="stop sending events">
+ This request indicates that the client no longer wishes to receive
+ events on this object.
+
+ The Wayland protocol is asynchronous, which means the server may send
+ further events until the stop request is processed. The client must wait
+ for a river_libinput_config_v1.finished event before destroying this
+ object.
+ </description>
+ </request>
+
+ <event name="finished">
+ <description summary="the server has finished with the object">
+ This event indicates that the server will send no further events on this
+ object. The client should destroy the object. See
+ river_libinput_config_v1.destroy for more information.
+ </description>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the river_libinput_config_v1 object">
+ This request should be called after the finished event has been received
+ to complete destruction of the object.
+
+ It is a protocol error to make this request before the finished event
+ has been received.
+
+ If a client wishes to destroy this object it should send a
+ river_libinput_config_v1.stop request and wait for a
+ river_libinput_config_v1.finished event. Once the finished event is
+ received it is safe to destroy this object and any other objects created
+ through this interface.
+ </description>
+ </request>
+
+ <event name="libinput_device">
+ <description summary="new libinput device">
+ A new libinput device has been created. Not every river_input_device_v1
+ is necessarily a libinput device as well.
+ </description>
+ <arg name="id" type="new_id" interface="river_libinput_device_v1"/>
+ </event>
+
+ <request name="create_accel_config">
+ <description summary="create a acceleration config">
+ Create a acceleration config which can be applied
+ with river_libinput_device_v1.apply_accel_config.
+ </description>
+ <arg name="id" type="new_id"
+ interface="river_libinput_accel_config_v1"/>
+ <arg name="profile" type="uint"
+ enum="river_libinput_device_v1.accel_profile"/>
+ </request>
+ </interface>
+
+ <interface name="river_libinput_device_v1" version="1">
+ <description summary="a libinput device">
+ In general, *_support events will be sent exactly once directly after the
+ river_libinput_device_v1 is created. *_default events will be sent after
+ *_support events if the config option is supported, and *_current events
+ willl be sent after the *_default events and again whenever the config
+ option is changed.
+ </description>
+
+ <enum name="error">
+ <entry name="invalid_arg" value="0"
+ summary="invalid enum value or similar"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the libinput device object">
+ This request indicates that the client will no longer use the input
+ device object and that it may be safely destroyed.
+ </description>
+ </request>
+
+ <event name="removed">
+ <description summary="the libinput device is removed">
+ This event indicates that the libinput device has been removed.
+
+ The server will send no further events on this object and ignore any
+ request (other than river_libinput_device_v1.destroy) made after this
+ event is sent. The client should destroy this object with the
+ river_libinput_device_v1.destroy request to free up resources.
+ </description>
+ </event>
+
+ <event name="input_device">
+ <description summary="corresponding river input device">
+ The river_input_device_v1 corresponding to this libinput device.
+ This event will always be the first event sent on the
+ river_libinput_device_v1 object, and it will be sent exactly once.
+ </description>
+ <arg name="device" type="object" interface="river_input_device_v1"/>
+ </event>
+
+ <enum name="send_events_modes" bitfield="true">
+ <entry name="enabled" value="0"/>
+ <entry name="disabled" value="1"/>
+ <entry name="disabled_on_external_mouse" value="2"/>
+ </enum>
+
+ <event name="send_events_support">
+ <description summary="supported send events modes">
+ Supported send events modes.
+ </description>
+ <arg name="modes" type="uint" enum="send_events_modes"/>
+ </event>
+
+ <event name="send_events_default">
+ <description summary="default send events mode">
+ Default send events mode.
+ </description>
+ <arg name="mode" type="uint" enum="send_events_modes"/>
+ </event>
+
+ <event name="send_events_current">
+ <description summary="current send events mode">
+ Current send events mode.
+ </description>
+ <arg name="mode" type="uint" enum="send_events_modes"/>
+ </event>
+
+ <request name="set_send_events">
+ <description summary="set send events mode">
+ Set the send events mode for the device.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="mode" type="uint" enum="send_events_modes"/>
+ </request>
+
+ <enum name="tap_state">
+ <entry name="disabled" value="0"/>
+ <entry name="enabled" value="1"/>
+ </enum>
+
+ <event name="tap_support">
+ <description summary="tap-to-click/drag support">
+ The number of fingers supported for tap-to-click/drag.
+ If finger_count is 0, tap-to-click and drag are unsupported.
+ </description>
+ <arg name="finger_count" type="int"/>
+ </event>
+
+ <event name="tap_default">
+ <description summary="default tap-to-click state">
+ Default tap-to-click state.
+ </description>
+ <arg name="state" type="uint" enum="tap_state"/>
+ </event>
+
+ <event name="tap_current">
+ <description summary="current tap-to-click state">
+ Current tap-to-click state.
+ </description>
+ <arg name="state" type="uint" enum="tap_state"/>
+ </event>
+
+ <request name="set_tap">
+ <description summary="enable/disable tap-to-click">
+ Configure tap-to-click on this device, with a default mapping of
+ 1, 2, 3 finger tap mapping to left, right, middle click, respectively.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="state" type="uint" enum="tap_state"/>
+ </request>
+
+ <enum name="tap_button_map">
+ <entry name="lrm" value="0"
+ summary="1/2/3 finger tap maps to left/right/middle"/>
+ <entry name="lmr" value="1"
+ summary="1/2/3 finger tap maps to left/middle/right"/>
+ </enum>
+
+ <event name="tap_button_map_default">
+ <description summary="default tap-to-click button map">
+ Default tap-to-click button map.
+ </description>
+ <arg name="button_map" type="uint" enum="tap_button_map"/>
+ </event>
+
+ <event name="tap_button_map_current">
+ <description summary="current tap-to-click button map">
+ Current tap-to-click button map.
+ </description>
+ <arg name="button_map" type="uint" enum="tap_button_map"/>
+ </event>
+
+ <request name="set_tap_button_map">
+ <description summary="set tap-to-click button map">
+ Set the finger number to button number mapping for tap-to-click. The
+ default mapping on most devices is to have a 1, 2 and 3 finger tap to
+ map to the left, right and middle button, respectively.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="button_map" type="uint" enum="tap_button_map"/>
+ </request>
+
+ <enum name="drag_state">
+ <entry name="disabled" value="0"/>
+ <entry name="enabled" value="1"/>
+ </enum>
+
+ <event name="drag_default">
+ <description summary="default tap-and-drag state">
+ Default tap-and-drag state.
+ </description>
+ <arg name="state" type="uint" enum="drag_state"/>
+ </event>
+
+ <event name="drag_current">
+ <description summary="current tap-and-drag state">
+ Current tap-and-drag state.
+ </description>
+ <arg name="state" type="uint" enum="drag_state"/>
+ </event>
+
+ <request name="set_drag">
+ <description summary="set tap-and-drag state">
+ Configure tap-and-drag functionality on the device.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="state" type="uint" enum="drag_state"/>
+ </request>
+
+ <enum name="drag_lock_state">
+ <entry name="disabled" value="0"/>
+ <entry name="enabled_timeout" value="1"/>
+ <entry name="enabled_sticky" value="2"/>
+ </enum>
+
+ <event name="drag_lock_default">
+ <description summary="default drag lock state">
+ Default drag lock state.
+ </description>
+ <arg name="state" type="uint" enum="drag_lock_state"/>
+ </event>
+
+ <event name="drag_lock_current">
+ <description summary="current drag lock state">
+ Current drag lock state.
+ </description>
+ <arg name="state" type="uint" enum="drag_lock_state"/>
+ </event>
+
+ <request name="set_drag_lock">
+ <description summary="set drag lock state">
+ Configure drag-lock during tapping on this device. When enabled, a
+ finger may be lifted and put back on the touchpad and the drag process
+ continues. A timeout for lifting the finger is optional. When disabled,
+ lifting the finger during a tap-and-drag will immediately stop the drag.
+ See the libinput documentation for more details.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="state" type="uint" enum="drag_lock_state"/>
+ </request>
+
+ <event name="three_finger_drag_support">
+ <description summary="three finger drag support">
+ The number of fingers supported for three/four finger drag.
+ If finger_count is less than 3, three finger drag is unsupported.
+ </description>
+ <arg name="finger_count" type="int"/>
+ </event>
+
+ <enum name="three_finger_drag_state">
+ <entry name="disabled" value="0"/>
+ <entry name="enabled_3fg" value="1"/>
+ <entry name="enabled_4fg" value="2"/>
+ </enum>
+
+ <event name="three_finger_drag_default">
+ <description summary="default three finger drag state">
+ Default three finger drag state.
+ </description>
+ <arg name="state" type="uint" enum="three_finger_drag_state"/>
+ </event>
+
+ <event name="three_finger_drag_current">
+ <description summary="current three finger drag state">
+ Current three finger drag state.
+ </description>
+ <arg name="state" type="uint" enum="three_finger_drag_state"/>
+ </event>
+
+ <request name="set_three_finger_drag">
+ <description summary="set three finger drag state">
+ Configure three finger drag functionality for the device.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="state" type="uint" enum="three_finger_drag_state"/>
+ </request>
+
+ <event name="calibration_matrix_support">
+ <description summary="support for a calibration matrix">
+ A calibration matrix is supported if the supported argument is non-zero.
+ </description>
+ <arg name="supported" type="int" summary="boolean"/>
+ </event>
+
+ <event name="calibration_matrix_default">
+ <description summary="default calibration matrix">
+ Default calibration matrix.
+ </description>
+ <arg name="matrix" type="array" summary="array of 6 floats"/>
+ </event>
+
+ <event name="calibration_matrix_current">
+ <description summary="current calibration matrix">
+ Current calibration matrix.
+ </description>
+ <arg name="matrix" type="array" summary="array of 6 floats"/>
+ </event>
+
+ <request name="set_calibration_matrix">
+ <description summary="set calibration matrix">
+ Set calibration matrix.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="matrix" type="array" summary="array of 6 floats"/>
+ </request>
+
+ <enum name="accel_profile">
+ <entry name="none" value="0"/>
+ <entry name="flat" value="1"/>
+ <entry name="adaptive" value="2"/>
+ <entry name="custom" value="4"/>
+ </enum>
+
+ <enum name="accel_profiles" bitfield="true">
+ <entry name="none" value="0"/>
+ <entry name="flat" value="1"/>
+ <entry name="adaptive" value="2"/>
+ <entry name="custom" value="4"/>
+ </enum>
+
+ <event name="accel_profiles_support">
+ <description summary="supported acceleration profiles">
+ Supported acceleration profiles.
+ </description>
+ <arg name="profiles" type="uint" enum="accel_profiles"/>
+ </event>
+
+ <event name="accel_profile_default">
+ <description summary="default acceleration profile">
+ Default acceleration profile.
+ </description>
+ <arg name="profile" type="uint" enum="accel_profile"/>
+ </event>
+
+ <event name="accel_profile_current">
+ <description summary="current send events mode">
+ Current acceleration profile.
+ </description>
+ <arg name="profile" type="uint" enum="accel_profile"/>
+ </event>
+
+ <request name="set_accel_profile">
+ <description summary="set send events mode">
+ Set the acceleration profile.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="profile" type="uint" enum="accel_profile"/>
+ </request>
+
+ <event name="accel_speed_default">
+ <description summary="default acceleration speed">
+ Default acceleration speed.
+ </description>
+ <arg name="speed" type="array" summary="double"/>
+ </event>
+
+ <event name="accel_speed_current">
+ <description summary="current acceleration speed">
+ Current acceleration speed.
+ </description>
+ <arg name="speed" type="array" summary="double"/>
+ </event>
+
+ <request name="set_accel_speed">
+ <description summary="set acceleration speed">
+ Set the acceleration speed within a range of [-1, 1], where 0 is
+ the default acceleration for this device, -1 is the slowest acceleration
+ and 1 is the maximum acceleration available on this device.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="speed" type="array" summary="double"/>
+ </request>
+
+ <request name="apply_accel_config">
+ <description summary="apply acceleration config">
+ Apply a pointer accleration config.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="config" type="object" interface="river_libinput_accel_config_v1"/>
+ </request>
+
+ <event name="natural_scroll_support">
+ <description summary="support for natural scroll">
+ Natural scroll is supported if the supported argument is non-zero.
+ </description>
+ <arg name="supported" type="int" summary="boolean"/>
+ </event>
+
+ <enum name="natural_scroll_state">
+ <entry name="disabled" value="0"/>
+ <entry name="enabled" value="1"/>
+ </enum>
+
+ <event name="natural_scroll_default">
+ <description summary="default natural scroll">
+ Default natural scroll.
+ </description>
+ <arg name="state" type="uint" enum="natural_scroll_state"/>
+ </event>
+
+ <event name="natural_scroll_current">
+ <description summary="current natural scroll state">
+ Current natural scroll.
+ </description>
+ <arg name="state" type="uint" enum="natural_scroll_state"/>
+ </event>
+
+ <request name="set_natural_scroll">
+ <description summary="set natural scroll state">
+ Set natural scroll state.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="state" type="uint" enum="natural_scroll_state"/>
+ </request>
+
+ <event name="left_handed_support">
+ <description summary="support for left-handed mode">
+ Left-handed mode is supported if the supported argument is non-zero.
+ </description>
+ <arg name="supported" type="int" summary="boolean"/>
+ </event>
+
+ <enum name="left_handed_state">
+ <entry name="disabled" value="0"/>
+ <entry name="enabled" value="1"/>
+ </enum>
+
+ <event name="left_handed_default">
+ <description summary="default left-handed mode">
+ Default left-handed mode.
+ </description>
+ <arg name="state" type="uint" enum="left_handed_state"/>
+ </event>
+
+ <event name="left_handed_current">
+ <description summary="current left-handed mode state">
+ Current left-handed mode.
+ </description>
+ <arg name="state" type="uint" enum="left_handed_state"/>
+ </event>
+
+ <request name="set_left_handed">
+ <description summary="set left-handed mode state">
+ Set left-handed mode state.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="state" type="uint" enum="left_handed_state"/>
+ </request>
+
+ <enum name="click_method">
+ <entry name="none" value="0"/>
+ <entry name="button_areas" value="1"/>
+ <entry name="clickfinger" value="2"/>
+ </enum>
+
+ <enum name="click_methods" bitfield="true">
+ <entry name="none" value="0"/>
+ <entry name="button_areas" value="1"/>
+ <entry name="clickfinger" value="2"/>
+ </enum>
+
+ <event name="click_method_support">
+ <description summary="supported click methods">
+ The click methods suppported by the device.
+ </description>
+ <arg name="methods" type="uint" enum="click_methods"/>
+ </event>
+
+ <event name="click_method_default">
+ <description summary="default click method">
+ Default click method.
+ </description>
+ <arg name="method" type="uint" enum="click_method"/>
+ </event>
+
+ <event name="click_method_current">
+ <description summary="current click method">
+ Current click method.
+ </description>
+ <arg name="method" type="uint" enum="click_method"/>
+ </event>
+
+ <request name="set_click_method">
+ <description summary="set click method">
+ Set click method.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="method" type="uint" enum="click_method"/>
+ </request>
+
+ <enum name="clickfinger_button_map">
+ <entry name="lrm" value="0"/>
+ <entry name="lmr" value="1"/>
+ </enum>
+
+ <event name="clickfinger_button_map_default">
+ <description summary="default clickfinger button map">
+ Default clickfinger button map.
+ Supported if click_methods.clickfinger is supported.
+ </description>
+ <arg name="button_map" type="uint" enum="clickfinger_button_map"/>
+ </event>
+
+ <event name="clickfinger_button_map_current">
+ <description summary="current clickfinger button map">
+ Current clickfinger button map.
+ Supported if click_methods.clickfinger is supported.
+ </description>
+ <arg name="button_map" type="uint" enum="clickfinger_button_map"/>
+ </event>
+
+ <request name="set_clickfinger_button_map">
+ <description summary="set clickfinger button map">
+ Set clickfinger button map.
+ Supported if click_methods.clickfinger is supported.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="button_map" type="uint" enum="clickfinger_button_map"/>
+ </request>
+
+ <event name="middle_emulation_support">
+ <description summary="support for middle mouse button emulation">
+ Middle mouse button emulation is supported if the supported argument is
+ non-zero.
+ </description>
+ <arg name="supported" type="int" summary="boolean"/>
+ </event>
+
+ <enum name="middle_emulation_state">
+ <entry name="disabled" value="0"/>
+ <entry name="enabled" value="1"/>
+ </enum>
+
+ <event name="middle_emulation_default">
+ <description summary="default middle mouse button emulation">
+ Default middle mouse button emulation.
+ </description>
+ <arg name="state" type="uint" enum="middle_emulation_state"/>
+ </event>
+
+ <event name="middle_emulation_current">
+ <description summary="current middle mouse button emulation state">
+ Current middle mouse button emulation.
+ </description>
+ <arg name="state" type="uint" enum="middle_emulation_state"/>
+ </event>
+
+ <request name="set_middle_emulation">
+ <description summary="set middle mouse button emulation state">
+ Set middle mouse button emulation state.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="state" type="uint" enum="middle_emulation_state"/>
+ </request>
+
+ <enum name="scroll_method">
+ <entry name="no_scroll" value="0"/>
+ <entry name="two_finger" value="1"/>
+ <entry name="edge" value="2"/>
+ <entry name="on_button_down" value="4"/>
+ </enum>
+
+ <enum name="scroll_methods" bitfield="true">
+ <entry name="no_scroll" value="0"/>
+ <entry name="two_finger" value="1"/>
+ <entry name="edge" value="2"/>
+ <entry name="on_button_down" value="4"/>
+ </enum>
+
+ <event name="scroll_method_support">
+ <description summary="supported scroll methods">
+ The scroll methods suppported by the device.
+ </description>
+ <arg name="methods" type="uint" enum="scroll_methods"/>
+ </event>
+
+ <event name="scroll_method_default">
+ <description summary="default scroll method">
+ Default scroll method.
+ </description>
+ <arg name="method" type="uint" enum="scroll_method"/>
+ </event>
+
+ <event name="scroll_method_current">
+ <description summary="current scroll method">
+ Current scroll method.
+ </description>
+ <arg name="method" type="uint" enum="scroll_method"/>
+ </event>
+
+ <request name="set_scroll_method">
+ <description summary="set scroll method">
+ Set scroll method.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="method" type="uint" enum="scroll_method"/>
+ </request>
+
+ <event name="scroll_button_default">
+ <description summary="default scroll button">
+ Default scroll button.
+ Supported if scroll_methods.on_button_down is supported.
+ </description>
+ <arg name="button" type="uint"/>
+ </event>
+
+ <event name="scroll_button_current">
+ <description summary="current scroll button">
+ Current scroll button.
+ Supported if scroll_methods.on_button_down is supported.
+ </description>
+ <arg name="button" type="uint"/>
+ </event>
+
+ <request name="set_scroll_button">
+ <description summary="set scroll button">
+ Set scroll button.
+ Supported if scroll_methods.on_button_down is supported.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="button" type="uint"/>
+ </request>
+
+ <enum name="scroll_button_lock_state">
+ <entry name="disabled" value="0"/>
+ <entry name="enabled" value="1"/>
+ </enum>
+
+ <event name="scroll_button_lock_default">
+ <description summary="default scroll button lock state">
+ Default scroll button lock state.
+ Supported if scroll_methods.on_button_down is supported.
+ </description>
+ <arg name="state" type="uint" enum="scroll_button_lock_state"/>
+ </event>
+
+ <event name="scroll_button_lock_current">
+ <description summary="current scroll button lock state">
+ Current scroll button lock state.
+ Supported if scroll_methods.on_button_down is supported.
+ </description>
+ <arg name="state" type="uint" enum="scroll_button_lock_state"/>
+ </event>
+
+ <request name="set_scroll_button_lock">
+ <description summary="set scroll button lock state">
+ Set scroll button lock state.
+ Supported if scroll_methods.on_button_down is supported.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="state" type="uint" enum="scroll_button_lock_state"/>
+ </request>
+
+ <event name="dwt_support">
+ <description summary="support for disable-while-typing">
+ Disable-while-typing is supported if the supported argument is
+ non-zero.
+ </description>
+ <arg name="supported" type="int" summary="boolean"/>
+ </event>
+
+ <enum name="dwt_state">
+ <entry name="disabled" value="0"/>
+ <entry name="enabled" value="1"/>
+ </enum>
+
+ <event name="dwt_default">
+ <description summary="default disable-while-typing state">
+ Default disable-while-typing state.
+ </description>
+ <arg name="state" type="uint" enum="dwt_state"/>
+ </event>
+
+ <event name="dwt_current">
+ <description summary="current disable-while-typing state">
+ Current disable-while-typing state.
+ </description>
+ <arg name="state" type="uint" enum="dwt_state"/>
+ </event>
+
+ <request name="set_dwt">
+ <description summary="set disable-while-typing state">
+ Set disable-while-typing state.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="state" type="uint" enum="dwt_state"/>
+ </request>
+
+ <event name="dwtp_support">
+ <description summary="support for disable-while-trackpointing">
+ Disable-while-trackpointing is supported if the supported argument is
+ non-zero.
+ </description>
+ <arg name="supported" type="int" summary="boolean"/>
+ </event>
+
+ <enum name="dwtp_state">
+ <entry name="disabled" value="0"/>
+ <entry name="enabled" value="1"/>
+ </enum>
+
+ <event name="dwtp_default">
+ <description summary="default disable-while-trackpointing state">
+ Default disable-while-trackpointing state.
+ </description>
+ <arg name="state" type="uint" enum="dwtp_state"/>
+ </event>
+
+ <event name="dwtp_current">
+ <description summary="current disable-while-trackpointing state">
+ Current disable-while-trackpointing state.
+ </description>
+ <arg name="state" type="uint" enum="dwtp_state"/>
+ </event>
+
+ <request name="set_dwtp">
+ <description summary="set disable-while-trackpointing state">
+ Set disable-while-trackpointing state.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="state" type="uint" enum="dwtp_state"/>
+ </request>
+
+ <event name="rotation_support">
+ <description summary="support for rotation">
+ Rotation is supported if the supported argument is non-zero.
+ </description>
+ <arg name="supported" type="int" summary="boolean"/>
+ </event>
+
+ <event name="rotation_default">
+ <description summary="default rotation angle">
+ Default rotation angle.
+ </description>
+ <arg name="angle" type="uint"/>
+ </event>
+
+ <event name="rotation_current">
+ <description summary="current rotation angle">
+ Current rotation angle.
+ </description>
+ <arg name="angle" type="uint"/>
+ </event>
+
+ <request name="set_rotation">
+ <description summary="set rotation angle">
+ Set rotation angle in degrees clockwise off the logical neutral
+ position. Angle must be in the range [0-360).
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="angle" type="uint"/>
+ </request>
+ </interface>
+
+ <interface name="river_libinput_accel_config_v1" version="1">
+ <description summary="acceleration config">
+ The result returned by libinput on setting configuration for a device.
+ </description>
+
+ <enum name="error">
+ <entry name="invalid_arg" value="0"
+ summary="invalid enum value or similar"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the accel object">
+ This request indicates that the client will no longer use the accel
+ config object and that it may be safely destroyed.
+ </description>
+ </request>
+
+ <enum name="accel_type">
+ <entry name="fallback" value="0"/>
+ <entry name="motion" value="1"/>
+ <entry name="scroll" value="2"/>
+ </enum>
+
+ <request name="set_points">
+ <description summary="define custom acceleration function">
+ Defines the acceleration function for a given movement type
+ in an acceleration configuration with custom accel profile.
+ </description>
+ <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
+ <arg name="type" type="uint" enum="accel_type"/>
+ <arg name="step" type="array" summary="double"/>
+ <arg name="points" type="array" summary="array of doubles"/>
+ </request>
+ </interface>
+
+ <interface name="river_libinput_result_v1" version="1">
+ <description summary="config application result">
+ The result returned by libinput on setting configuration for a device.
+ </description>
+
+ <event name="success" type="destructor">
+ <description summary="config success">
+ The configuration was successfully applied to the device.
+ </description>
+ </event>
+
+ <event name="unsupported" type="destructor">
+ <description summary="config unsupported">
+ The configuration is unsupported by the device and was ignored.
+ </description>
+ </event>
+
+ <event name="invalid" type="destructor">
+ <description summary="config invalid">
+ The configuration is invalid and was ignored.
+ </description>
+ </event>
+ </interface>
+</protocol>
diff --git a/river/InputDevice.zig b/river/InputDevice.zig
index 0a5c66f..e92570f 100644
--- a/river/InputDevice.zig
+++ b/river/InputDevice.zig
@@ -27,8 +27,9 @@ const c = @import("c.zig").c;
const server = &@import("main.zig").server;
const util = @import("util.zig");
-const Seat = @import("Seat.zig");
const Keyboard = @import("Keyboard.zig");
+const LibinputDevice = @import("LibinputDevice.zig");
+const Seat = @import("Seat.zig");
const Tablet = @import("Tablet.zig");
const log = std.log.scoped(.input);
@@ -37,6 +38,8 @@ seat: *Seat,
wlr_device: *wlr.InputDevice,
objects: wl.list.Head(river.InputDeviceV1, null),
+libinput: LibinputDevice,
+
remove: wl.Listener(*wlr.InputDevice) = .init(handleRemove),
config: struct {
@@ -57,6 +60,7 @@ pub fn init(
device.* = .{
.seat = seat,
.wlr_device = wlr_device,
+ .libinput = undefined,
.objects = undefined,
.link = undefined,
};
@@ -75,6 +79,9 @@ pub fn init(
var it = server.input_manager.objects.safeIterator(.forward);
while (it.next()) |im_v1| device.createObject(im_v1);
}
+ if (wlr_device.getLibinputDevice()) |handle| {
+ device.libinput.init(@ptrCast(handle));
+ }
}
pub fn createObject(device: *InputDevice, im_v1: *river.InputManagerV1) void {
@@ -101,6 +108,10 @@ pub fn deinit(device: *InputDevice) void {
assert(device.objects.empty());
device.remove.link.remove();
+ if (device.wlr_device.getLibinputDevice() != null) {
+ device.libinput.deinit();
+ }
+
device.link.remove();
device.seat.updateCapabilities();
diff --git a/river/LibinputAccelConfig.zig b/river/LibinputAccelConfig.zig
new file mode 100644
index 0000000..ee49319
--- /dev/null
+++ b/river/LibinputAccelConfig.zig
@@ -0,0 +1,124 @@
+// This file is part of river, a dynamic tiling wayland compositor.
+//
+// Copyright 2025 The River Developers
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+const LibinputAccelConfig = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const wayland = @import("wayland");
+const wl = wayland.server.wl;
+const river = wayland.server.river;
+
+const c = @import("c.zig").c;
+const server = &@import("main.zig").server;
+const util = @import("util.zig");
+
+const Keyboard = @import("Keyboard.zig");
+const Seat = @import("Seat.zig");
+
+const log = std.log.scoped(.input);
+
+object: *river.LibinputAccelConfigV1,
+libinput: ?*c.libinput_config_accel,
+
+pub fn create(
+ client: *wl.Client,
+ version: u32,
+ id: u32,
+ profile: c.enum_libinput_config_accel_profile,
+) !void {
+ const accel_config = try util.gpa.create(LibinputAccelConfig);
+ errdefer util.gpa.destroy(accel_config);
+ const object = try river.LibinputAccelConfigV1.create(client, version, id);
+ errdefer comptime unreachable;
+ accel_config.* = .{
+ .object = object,
+ .libinput = c.libinput_config_accel_create(profile),
+ };
+ object.setHandler(*LibinputAccelConfig, handleRequest, handleDestroy, accel_config);
+}
+
+fn handleDestroy(_: *river.LibinputAccelConfigV1, accel_config: *LibinputAccelConfig) void {
+ if (accel_config.libinput) |libinput| {
+ c.libinput_config_accel_destroy(libinput);
+ }
+ util.gpa.destroy(accel_config);
+}
+
+fn handleRequest(
+ object: *river.LibinputAccelConfigV1,
+ request: river.LibinputAccelConfigV1.Request,
+ accel_config: *LibinputAccelConfig,
+) void {
+ assert(accel_config.object == object);
+ switch (request) {
+ .destroy => object.destroy(),
+ .set_points => |args| {
+ const accel_type: c.enum_libinput_config_accel_type = switch (args.type) {
+ .fallback => c.LIBINPUT_ACCEL_TYPE_FALLBACK,
+ .motion => c.LIBINPUT_ACCEL_TYPE_MOTION,
+ .scroll => c.LIBINPUT_ACCEL_TYPE_SCROLL,
+ _ => {
+ object.postError(.invalid_arg, "invalid accel_type enum value");
+ return;
+ },
+ };
+ if (args.step.size != @sizeOf(f64)) {
+ object.postError(.invalid_arg, "invalid step argument");
+ return;
+ }
+ const step: f64 = args.step.slice(f64)[0];
+ if (args.points.size % @sizeOf(f64) != 0) {
+ object.postError(.invalid_arg, "invalid points argument");
+ return;
+ }
+ const points = util.gpa.alloc(f64, @divExact(args.points.size, @sizeOf(f64))) catch {
+ log.err("out of memory", .{});
+ object.postNoMemory();
+ return;
+ };
+ defer util.gpa.free(points);
+ @memcpy(
+ @as([]u8, @ptrCast(points)),
+ @as([*]u8, @ptrCast(args.points.data))[0..args.points.size],
+ );
+ const result = river.LibinputResultV1.create(
+ object.getClient(),
+ object.getVersion(),
+ args.result,
+ ) catch {
+ log.err("out of memory", .{});
+ object.postNoMemory();
+ return;
+ };
+ const libinput = accel_config.libinput orelse {
+ result.destroySendInvalid();
+ return;
+ };
+ switch (c.libinput_config_accel_set_points(
+ libinput,
+ accel_type,
+ step,
+ points.len,
+ points.ptr,
+ )) {
+ c.LIBINPUT_CONFIG_STATUS_SUCCESS => result.destroySendSuccess(),
+ c.LIBINPUT_CONFIG_STATUS_UNSUPPORTED => result.destroySendUnsupported(),
+ else => result.destroySendInvalid(),
+ }
+ },
+ }
+}
diff --git a/river/LibinputConfig.zig b/river/LibinputConfig.zig
new file mode 100644
index 0000000..a529db8
--- /dev/null
+++ b/river/LibinputConfig.zig
@@ -0,0 +1,118 @@
+// This file is part of river, a dynamic tiling wayland compositor.
+//
+// Copyright 2025 The River Developers
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+const LibinputConfig = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const wl = @import("wayland").server.wl;
+const river = @import("wayland").server.river;
+
+const c = @import("c.zig").c;
+const server = &@import("main.zig").server;
+
+const LibinputAccelConfig = @import("LibinputAccelConfig.zig");
+const LibinputDevice = @import("LibinputDevice.zig");
+
+const log = std.log.scoped(.input);
+
+global: *wl.Global,
+objects: wl.list.Head(river.LibinputConfigV1, null),
+devices: wl.list.Head(LibinputDevice, .link),
+
+server_destroy: wl.Listener(*wl.Server) = .init(handleServerDestroy),
+
+pub fn init(config: *LibinputConfig) !void {
+ config.* = .{
+ .global = try wl.Global.create(server.wl_server, river.LibinputConfigV1, 1, *LibinputConfig, config, bind),
+ .objects = undefined,
+ .devices = undefined,
+ };
+ config.objects.init();
+ config.devices.init();
+ server.wl_server.addDestroyListener(&config.server_destroy);
+}
+
+fn handleServerDestroy(listener: *wl.Listener(*wl.Server), _: *wl.Server) void {
+ const config: *LibinputConfig = @fieldParentPtr("server_destroy", listener);
+
+ config.global.destroy();
+}
+
+fn bind(client: *wl.Client, config: *LibinputConfig, version: u32, id: u32) void {
+ const object = river.LibinputConfigV1.create(client, version, id) catch {
+ client.postNoMemory();
+ log.err("out of memory", .{});
+ return;
+ };
+ object.setHandler(*LibinputConfig, handleRequest, handleDestroy, config);
+ config.objects.append(object);
+ {
+ var it = config.devices.iterator(.forward);
+ while (it.next()) |device| device.createObject(object);
+ }
+}
+
+fn handleRequestInert(
+ object: *river.LibinputConfigV1,
+ request: river.LibinputConfigV1.Request,
+ _: ?*anyopaque,
+) void {
+ if (request == .destroy) object.destroy();
+}
+
+fn handleDestroy(object: *river.LibinputConfigV1, _: *LibinputConfig) void {
+ object.getLink().remove();
+}
+
+fn handleRequest(
+ object: *river.LibinputConfigV1,
+ request: river.LibinputConfigV1.Request,
+ _: *LibinputConfig,
+) void {
+ switch (request) {
+ .stop => {
+ object.getLink().remove();
+ object.sendFinished();
+ object.setHandler(?*anyopaque, handleRequestInert, null, null);
+ },
+ .destroy => {
+ object.postError(.invalid_destroy, "destroy before finished event sent");
+ },
+ .create_accel_config => |args| {
+ const profile: c.enum_libinput_config_accel_profile = switch (args.profile) {
+ .none => c.LIBINPUT_CONFIG_ACCEL_PROFILE_NONE,
+ .flat => c.LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT,
+ .adaptive => c.LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
+ .custom => c.LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM,
+ _ => {
+ object.postError(.invalid_arg, "invalid accel_profile enum value");
+ return;
+ },
+ };
+ LibinputAccelConfig.create(
+ object.getClient(),
+ object.getVersion(),
+ args.id,
+ profile,
+ ) catch {
+ log.err("out of memory", .{});
+ object.postNoMemory();
+ return;
+ };
+ },
+ }
+}
diff --git a/river/LibinputDevice.zig b/river/LibinputDevice.zig
new file mode 100644
index 0000000..53dd4d7
--- /dev/null
+++ b/river/LibinputDevice.zig
@@ -0,0 +1,561 @@
+// This file is part of river, a dynamic tiling wayland compositor.
+//
+// Copyright 2025 The River Developers
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+const LibinputDevice = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const mem = std.mem;
+const wlr = @import("wlroots");
+const wl = @import("wayland").server.wl;
+const river = @import("wayland").server.river;
+
+const c = @import("c.zig").c;
+const server = &@import("main.zig").server;
+const util = @import("util.zig");
+
+const InputDevice = @import("InputDevice.zig");
+const LibinputAccelConfig = @import("LibinputAccelConfig.zig");
+
+const log = std.log.scoped(.input);
+
+libinput: *c.libinput_device,
+objects: wl.list.Head(river.LibinputDeviceV1, null),
+
+/// LibinputConfig.devices
+link: wl.list.Link,
+
+pub fn init(device: *LibinputDevice, handle: *c.libinput_device) void {
+ device.* = .{
+ .libinput = handle,
+ .objects = undefined,
+ .link = undefined,
+ };
+ device.objects.init();
+ server.libinput_config.devices.append(device);
+ {
+ var it = server.libinput_config.objects.iterator(.forward);
+ while (it.next()) |config_v1| device.createObject(config_v1);
+ }
+}
+
+pub fn createObject(device: *LibinputDevice, config_v1: *river.LibinputConfigV1) void {
+ const object = river.LibinputDeviceV1.create(config_v1.getClient(), config_v1.getVersion(), 0) catch {
+ log.err("out of memory", .{});
+ config_v1.postNoMemory();
+ return;
+ };
+ device.objects.append(object);
+ object.setHandler(*LibinputDevice, handleRequest, handleDestroy, device);
+ config_v1.sendLibinputDevice(object);
+ {
+ const base: *InputDevice = @fieldParentPtr("libinput", device);
+ var it = base.objects.iterator(.forward);
+ while (it.next()) |input_device_v1| {
+ if (object.getClient() == input_device_v1.getClient()) {
+ object.sendInputDevice(input_device_v1);
+ }
+ }
+ }
+ object.sendSendEventsSupport(@bitCast(c.libinput_device_config_send_events_get_modes(device.libinput)));
+ object.sendSendEventsDefault(@bitCast(c.libinput_device_config_send_events_get_default_mode(device.libinput)));
+ object.sendSendEventsCurrent(@bitCast(c.libinput_device_config_send_events_get_mode(device.libinput)));
+ const tap_finger_count = c.libinput_device_config_tap_get_finger_count(device.libinput);
+ object.sendTapSupport(tap_finger_count);
+ if (tap_finger_count > 0) {
+ object.sendTapDefault(@enumFromInt(c.libinput_device_config_tap_get_default_enabled(device.libinput)));
+ object.sendTapCurrent(@enumFromInt(c.libinput_device_config_tap_get_enabled(device.libinput)));
+ object.sendTapButtonMapDefault(@enumFromInt(c.libinput_device_config_tap_get_default_button_map(device.libinput)));
+ object.sendTapButtonMapCurrent(@enumFromInt(c.libinput_device_config_tap_get_button_map(device.libinput)));
+ object.sendDragDefault(@enumFromInt(c.libinput_device_config_tap_get_default_drag_enabled(device.libinput)));
+ object.sendDragCurrent(@enumFromInt(c.libinput_device_config_tap_get_drag_enabled(device.libinput)));
+ object.sendDragLockDefault(@enumFromInt(c.libinput_device_config_tap_get_default_drag_lock_enabled(device.libinput)));
+ object.sendDragLockCurrent(@enumFromInt(c.libinput_device_config_tap_get_drag_lock_enabled(device.libinput)));
+ }
+ const three_finger_drag_finger_count = c.libinput_device_config_3fg_drag_get_finger_count(device.libinput);
+ object.sendThreeFingerDragSupport(three_finger_drag_finger_count);
+ if (three_finger_drag_finger_count >= 3) {
+ object.sendThreeFingerDragDefault(@enumFromInt(c.libinput_device_config_3fg_drag_get_default_enabled(device.libinput)));
+ object.sendThreeFingerDragCurrent(@enumFromInt(c.libinput_device_config_3fg_drag_get_enabled(device.libinput)));
+ }
+ const has_matrix = c.libinput_device_config_calibration_has_matrix(device.libinput);
+ object.sendCalibrationMatrixSupport(has_matrix);
+ if (has_matrix != 0) {
+ var matrix: [6]f32 = undefined;
+ const bytes: []u8 = @ptrCast(&matrix);
+ var array: wl.Array = .{ .size = bytes.len, .alloc = bytes.len, .data = bytes.ptr };
+ _ = c.libinput_device_config_calibration_get_default_matrix(device.libinput, &matrix);
+ object.sendCalibrationMatrixDefault(&array);
+ _ = c.libinput_device_config_calibration_get_matrix(device.libinput, &matrix);
+ object.sendCalibrationMatrixCurrent(&array);
+ }
+ const profiles = c.libinput_device_config_accel_get_profiles(device.libinput);
+ object.sendAccelProfilesSupport(@bitCast(profiles));
+ if (profiles != 0) {
+ object.sendAccelProfileDefault(@enumFromInt(c.libinput_device_config_accel_get_default_profile(device.libinput)));
+ object.sendAccelProfileCurrent(@enumFromInt(c.libinput_device_config_accel_get_profile(device.libinput)));
+ var speed: [1]f64 = .{c.libinput_device_config_accel_get_default_speed(device.libinput)};
+ const bytes: []u8 = @ptrCast(&speed);
+ var array: wl.Array = .{ .size = bytes.len, .alloc = bytes.len, .data = bytes.ptr };
+ object.sendAccelSpeedDefault(&array);
+ speed = .{c.libinput_device_config_accel_get_speed(device.libinput)};
+ object.sendAccelSpeedCurrent(&array);
+ }
+ const natural_scroll = c.libinput_device_config_scroll_has_natural_scroll(device.libinput);
+ object.sendNaturalScrollSupport(natural_scroll);
+ if (natural_scroll != 0) {
+ const default = c.libinput_device_config_scroll_get_default_natural_scroll_enabled(device.libinput);
+ object.sendNaturalScrollDefault(if (default != 0) .enabled else .disabled);
+ const current = c.libinput_device_config_scroll_get_natural_scroll_enabled(device.libinput);
+ object.sendNaturalScrollCurrent(if (current != 0) .enabled else .disabled);
+ }
+ const left_handed = c.libinput_device_config_left_handed_is_available(device.libinput);
+ object.sendLeftHandedSupport(left_handed);
+ if (left_handed != 0) {
+ const default = c.libinput_device_config_left_handed_get_default(device.libinput);
+ object.sendLeftHandedDefault(if (default != 0) .enabled else .disabled);
+ const current = c.libinput_device_config_left_handed_get(device.libinput);
+ object.sendLeftHandedCurrent(if (current != 0) .enabled else .disabled);
+ }
+ const click_methods = c.libinput_device_config_click_get_methods(device.libinput);
+ object.sendClickMethodSupport(@bitCast(click_methods));
+ if (click_methods != 0) {
+ object.sendClickMethodDefault(@enumFromInt(c.libinput_device_config_click_get_default_method(device.libinput)));
+ object.sendClickMethodCurrent(@enumFromInt(c.libinput_device_config_click_get_method(device.libinput)));
+ if (click_methods & c.LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER != 0) {
+ object.sendClickfingerButtonMapDefault(@enumFromInt(c.libinput_device_config_click_get_default_clickfinger_button_map(device.libinput)));
+ object.sendClickfingerButtonMapCurrent(@enumFromInt(c.libinput_device_config_click_get_clickfinger_button_map(device.libinput)));
+ }
+ }
+ const middle_emulation = c.libinput_device_config_middle_emulation_is_available(device.libinput);
+ object.sendMiddleEmulationSupport(middle_emulation);
+ if (middle_emulation != 0) {
+ object.sendMiddleEmulationDefault(@enumFromInt(c.libinput_device_config_middle_emulation_get_default_enabled(device.libinput)));
+ object.sendMiddleEmulationCurrent(@enumFromInt(c.libinput_device_config_middle_emulation_get_enabled(device.libinput)));
+ }
+ const scroll_methods = c.libinput_device_config_scroll_get_methods(device.libinput);
+ object.sendScrollMethodSupport(@bitCast(scroll_methods));
+ if (scroll_methods != 0) {
+ object.sendScrollMethodDefault(@enumFromInt(c.libinput_device_config_scroll_get_default_method(device.libinput)));
+ object.sendScrollMethodCurrent(@enumFromInt(c.libinput_device_config_scroll_get_method(device.libinput)));
+ if (scroll_methods & c.LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN != 0) {
+ object.sendScrollButtonDefault(c.libinput_device_config_scroll_get_default_button(device.libinput));
+ object.sendScrollButtonCurrent(c.libinput_device_config_scroll_get_button(device.libinput));
+ object.sendScrollButtonLockDefault(@enumFromInt(c.libinput_device_config_scroll_get_default_button_lock(device.libinput)));
+ object.sendScrollButtonLockCurrent(@enumFromInt(c.libinput_device_config_scroll_get_button_lock(device.libinput)));
+ }
+ }
+ const dwt = c.libinput_device_config_dwt_is_available(device.libinput);
+ object.sendDwtSupport(dwt);
+ if (dwt != 0) {
+ const default = c.libinput_device_config_dwt_get_default_enabled(device.libinput);
+ object.sendDwtDefault(if (default != 0) .enabled else .disabled);
+ const current = c.libinput_device_config_dwt_get_enabled(device.libinput);
+ object.sendDwtCurrent(if (current != 0) .enabled else .disabled);
+ }
+ const dwtp = c.libinput_device_config_dwtp_is_available(device.libinput);
+ object.sendDwtpSupport(dwtp);
+ if (dwtp != 0) {
+ const default = c.libinput_device_config_dwtp_get_default_enabled(device.libinput);
+ object.sendDwtpDefault(if (default != 0) .enabled else .disabled);
+ const current = c.libinput_device_config_dwtp_get_enabled(device.libinput);
+ object.sendDwtpCurrent(if (current != 0) .enabled else .disabled);
+ }
+ const rotation = c.libinput_device_config_rotation_is_available(device.libinput);
+ object.sendRotationSupport(rotation);
+ if (rotation != 0) {
+ object.sendRotationDefault(c.libinput_device_config_rotation_get_default_angle(device.libinput));
+ object.sendRotationCurrent(c.libinput_device_config_rotation_get_angle(device.libinput));
+ }
+}
+
+pub fn deinit(device: *LibinputDevice) void {
+ {
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |object| {
+ object.getLink().remove();
+ object.sendRemoved();
+ object.setHandler(?*anyopaque, handleRequestInert, null, null);
+ }
+ }
+ assert(device.objects.empty());
+ device.link.remove();
+}
+
+fn handleRequestInert(
+ object: *river.LibinputDeviceV1,
+ request: river.LibinputDeviceV1.Request,
+ _: ?*anyopaque,
+) void {
+ if (request == .destroy) object.destroy();
+}
+
+fn handleDestroy(object: *river.LibinputDeviceV1, _: *LibinputDevice) void {
+ object.getLink().remove();
+}
+
+fn handleRequest(
+ object: *river.LibinputDeviceV1,
+ request: river.LibinputDeviceV1.Request,
+ device: *LibinputDevice,
+) void {
+ switch (request) {
+ .destroy => object.destroy(),
+ .set_send_events => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_send_events_set_mode(device.libinput, @bitCast(args.mode));
+ if (result.send(status)) {
+ const current = c.libinput_device_config_send_events_get_mode(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendSendEventsCurrent(@bitCast(current));
+ }
+ },
+ .set_tap => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_tap_set_enabled(device.libinput, switch (args.state) {
+ .disabled => c.LIBINPUT_CONFIG_TAP_DISABLED,
+ .enabled => c.LIBINPUT_CONFIG_TAP_ENABLED,
+ _ => {
+ object.postError(.invalid_arg, "invalid tap_state enum value");
+ return;
+ },
+ });
+ if (result.send(status)) {
+ const current = c.libinput_device_config_tap_get_enabled(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendTapCurrent(@enumFromInt(current));
+ }
+ },
+ .set_tap_button_map => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_tap_set_button_map(device.libinput, switch (args.button_map) {
+ .lrm => c.LIBINPUT_CONFIG_TAP_MAP_LRM,
+ .lmr => c.LIBINPUT_CONFIG_TAP_MAP_LMR,
+ _ => {
+ object.postError(.invalid_arg, "invalid tap_button_map enum value");
+ return;
+ },
+ });
+ if (result.send(status)) {
+ const current = c.libinput_device_config_tap_get_button_map(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendTapButtonMapCurrent(@enumFromInt(current));
+ }
+ },
+ .set_drag => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_tap_set_drag_enabled(device.libinput, switch (args.state) {
+ .disabled => c.LIBINPUT_CONFIG_DRAG_DISABLED,
+ .enabled => c.LIBINPUT_CONFIG_DRAG_ENABLED,
+ _ => {
+ object.postError(.invalid_arg, "invalid drag_state enum value");
+ return;
+ },
+ });
+ if (result.send(status)) {
+ const current = c.libinput_device_config_tap_get_drag_enabled(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendDragCurrent(@enumFromInt(current));
+ }
+ },
+ .set_drag_lock => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_tap_set_drag_lock_enabled(device.libinput, switch (args.state) {
+ .disabled => c.LIBINPUT_CONFIG_DRAG_LOCK_DISABLED,
+ .enabled_timeout => c.LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT,
+ .enabled_sticky => c.LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY,
+ _ => {
+ object.postError(.invalid_arg, "invalid drag_lock_state enum value");
+ return;
+ },
+ });
+ if (result.send(status)) {
+ const current = c.libinput_device_config_tap_get_drag_lock_enabled(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendDragLockCurrent(@enumFromInt(current));
+ }
+ },
+ .set_three_finger_drag => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_3fg_drag_set_enabled(device.libinput, switch (args.state) {
+ .disabled => c.LIBINPUT_CONFIG_3FG_DRAG_DISABLED,
+ .enabled_3fg => c.LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG,
+ .enabled_4fg => c.LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG,
+ _ => {
+ object.postError(.invalid_arg, "invalid three_finger_drag_state enum value");
+ return;
+ },
+ });
+ if (result.send(status)) {
+ const current = c.libinput_device_config_3fg_drag_get_enabled(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendThreeFingerDragCurrent(@enumFromInt(current));
+ }
+ },
+ .set_calibration_matrix => |args| {
+ if (args.matrix.size != @sizeOf([6]f32)) {
+ object.postError(.invalid_arg, "invalid calibration matrix");
+ return;
+ }
+ const matrix = args.matrix.slice(f32)[0..6];
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_calibration_set_matrix(device.libinput, matrix);
+ if (result.send(status)) {
+ var current: [6]f32 = undefined;
+ const bytes: []u8 = @ptrCast(¤t);
+ var array: wl.Array = .{ .size = bytes.len, .alloc = bytes.len, .data = bytes.ptr };
+ _ = c.libinput_device_config_calibration_get_matrix(device.libinput, ¤t);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendCalibrationMatrixCurrent(&array);
+ }
+ },
+ .set_accel_profile => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_accel_set_profile(device.libinput, switch (args.profile) {
+ .none => c.LIBINPUT_CONFIG_ACCEL_PROFILE_NONE,
+ .flat => c.LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT,
+ .adaptive => c.LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
+ .custom => c.LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM,
+ _ => {
+ object.postError(.invalid_arg, "invalid accel_profile enum value");
+ return;
+ },
+ });
+ if (result.send(status)) {
+ const current = c.libinput_device_config_accel_get_profile(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendAccelProfileCurrent(@enumFromInt(current));
+ }
+ },
+ .set_accel_speed => |args| {
+ if (args.speed.size != @sizeOf(f32)) {
+ object.postError(.invalid_arg, "invalid accel speed");
+ return;
+ }
+ const speed = args.speed.slice(f32)[0];
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_accel_set_speed(device.libinput, speed);
+ if (result.send(status)) {
+ var current: [1]f64 = .{c.libinput_device_config_accel_get_speed(device.libinput)};
+ const bytes: []u8 = @ptrCast(¤t);
+ var array: wl.Array = .{ .size = bytes.len, .alloc = bytes.len, .data = bytes.ptr };
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendAccelSpeedCurrent(&array);
+ }
+ },
+ .apply_accel_config => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const accel_config: *LibinputAccelConfig = @ptrCast(@alignCast(args.config.getUserData()));
+ const config = accel_config.libinput orelse {
+ _ = result.send(c.LIBINPUT_CONFIG_STATUS_INVALID);
+ return;
+ };
+ const status = c.libinput_device_config_accel_apply(device.libinput, config);
+ if (result.send(status)) {
+ const current = c.libinput_device_config_accel_get_profile(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendAccelProfileCurrent(@enumFromInt(current));
+ }
+ },
+ .set_natural_scroll => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_scroll_set_natural_scroll_enabled(device.libinput, switch (args.state) {
+ .disabled => 0,
+ .enabled => 1,
+ _ => {
+ object.postError(.invalid_arg, "invalid natural_scroll_state enum value");
+ return;
+ },
+ });
+ if (result.send(status)) {
+ const current = c.libinput_device_config_scroll_get_natural_scroll_enabled(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendNaturalScrollCurrent(if (current != 0) .enabled else .disabled);
+ }
+ },
+ .set_left_handed => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_left_handed_set(device.libinput, switch (args.state) {
+ .disabled => 0,
+ .enabled => 1,
+ _ => {
+ object.postError(.invalid_arg, "invalid left_handed_state enum value");
+ return;
+ },
+ });
+ if (result.send(status)) {
+ const current = c.libinput_device_config_left_handed_get(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendLeftHandedCurrent(if (current != 0) .enabled else .disabled);
+ }
+ },
+ .set_click_method => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_click_set_method(device.libinput, switch (args.method) {
+ .none => c.LIBINPUT_CONFIG_CLICK_METHOD_NONE,
+ .button_areas => c.LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS,
+ .clickfinger => c.LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER,
+ _ => {
+ object.postError(.invalid_arg, "invalid click_method enum value");
+ return;
+ },
+ });
+ if (result.send(status)) {
+ const current = c.libinput_device_config_click_get_method(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendClickMethodCurrent(@enumFromInt(current));
+ }
+ },
+ .set_clickfinger_button_map => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_click_set_clickfinger_button_map(device.libinput, switch (args.button_map) {
+ .lrm => c.LIBINPUT_CONFIG_CLICKFINGER_MAP_LRM,
+ .lmr => c.LIBINPUT_CONFIG_CLICKFINGER_MAP_LMR,
+ _ => {
+ object.postError(.invalid_arg, "invalid clickfinger_button_map enum value");
+ return;
+ },
+ });
+ if (result.send(status)) {
+ const current = c.libinput_device_config_click_get_clickfinger_button_map(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendClickfingerButtonMapCurrent(@enumFromInt(current));
+ }
+ },
+ .set_middle_emulation => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_middle_emulation_set_enabled(device.libinput, switch (args.state) {
+ .disabled => c.LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED,
+ .enabled => c.LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED,
+ _ => {
+ object.postError(.invalid_arg, "invalid middle_emulation_state enum value");
+ return;
+ },
+ });
+ if (result.send(status)) {
+ const current = c.libinput_device_config_middle_emulation_get_enabled(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendMiddleEmulationCurrent(@enumFromInt(current));
+ }
+ },
+ .set_scroll_method => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_scroll_set_method(device.libinput, switch (args.method) {
+ .no_scroll => c.LIBINPUT_CONFIG_SCROLL_NO_SCROLL,
+ .two_finger => c.LIBINPUT_CONFIG_SCROLL_2FG,
+ .edge => c.LIBINPUT_CONFIG_SCROLL_EDGE,
+ .on_button_down => c.LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN,
+ _ => {
+ object.postError(.invalid_arg, "invalid scroll_method enum value");
+ return;
+ },
+ });
+ if (result.send(status)) {
+ const current = c.libinput_device_config_scroll_get_method(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendScrollMethodCurrent(@enumFromInt(current));
+ }
+ },
+ .set_scroll_button => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_scroll_set_button(device.libinput, args.button);
+ if (result.send(status)) {
+ const current = c.libinput_device_config_scroll_get_button(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendScrollButtonCurrent(current);
+ }
+ },
+ .set_scroll_button_lock => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_scroll_set_button_lock(device.libinput, switch (args.state) {
+ .disabled => c.LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED,
+ .enabled => c.LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED,
+ _ => {
+ object.postError(.invalid_arg, "invalid scroll_button_lock_state enum value");
+ return;
+ },
+ });
+ if (result.send(status)) {
+ const current = c.libinput_device_config_scroll_get_button_lock(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendScrollButtonLockCurrent(@enumFromInt(current));
+ }
+ },
+ .set_dwt => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_dwt_set_enabled(device.libinput, switch (args.state) {
+ .disabled => c.LIBINPUT_CONFIG_DWT_DISABLED,
+ .enabled => c.LIBINPUT_CONFIG_DWT_ENABLED,
+ _ => {
+ object.postError(.invalid_arg, "invalid dwt_state enum value");
+ return;
+ },
+ });
+ if (result.send(status)) {
+ const current = c.libinput_device_config_dwt_get_enabled(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendDwtCurrent(if (current != 0) .enabled else .disabled);
+ }
+ },
+ .set_dwtp => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_dwtp_set_enabled(device.libinput, switch (args.state) {
+ .disabled => c.LIBINPUT_CONFIG_DWTP_DISABLED,
+ .enabled => c.LIBINPUT_CONFIG_DWTP_ENABLED,
+ _ => {
+ object.postError(.invalid_arg, "invalid dwtp_state enum value");
+ return;
+ },
+ });
+ if (result.send(status)) {
+ const current = c.libinput_device_config_dwtp_get_enabled(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendDwtpCurrent(if (current != 0) .enabled else .disabled);
+ }
+ },
+ .set_rotation => |args| {
+ const result = Result.create(object, args.result) orelse return;
+ const status = c.libinput_device_config_rotation_set_angle(device.libinput, args.angle);
+ if (result.send(status)) {
+ const current = c.libinput_device_config_rotation_get_angle(device.libinput);
+ var it = device.objects.iterator(.forward);
+ while (it.next()) |o| o.sendRotationCurrent(current);
+ }
+ },
+ }
+}
+
+const Result = struct {
+ object: *river.LibinputResultV1,
+
+ pub fn create(object: *river.LibinputDeviceV1, id: u32) ?Result {
+ const result = river.LibinputResultV1.create(object.getClient(), object.getVersion(), id) catch {
+ log.err("out of memory", .{});
+ object.postNoMemory();
+ return null;
+ };
+ return .{ .object = result };
+ }
+
+ pub fn send(result: *const Result, status: u32) bool {
+ switch (status) {
+ c.LIBINPUT_CONFIG_STATUS_SUCCESS => result.object.destroySendSuccess(),
+ c.LIBINPUT_CONFIG_STATUS_UNSUPPORTED => result.object.destroySendUnsupported(),
+ else => result.object.destroySendInvalid(),
+ }
+ return status == c.LIBINPUT_CONFIG_STATUS_SUCCESS;
+ }
+};
diff --git a/river/Server.zig b/river/Server.zig
index 8b65066..d9396da 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -40,6 +40,7 @@ const TabletTool = @import("TabletTool.zig");
const WindowManager = @import("WindowManager.zig");
const XkbBindings = @import("XkbBindings.zig");
const LayerShell = @import("LayerShell.zig");
+const LibinputConfig = @import("LibinputConfig.zig");
const XdgDecoration = @import("XdgDecoration.zig");
const XdgToplevel = @import("XdgToplevel.zig");
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
@@ -86,6 +87,7 @@ foreign_toplevel_list: *wlr.ExtForeignToplevelListV1,
scene: Scene,
input_manager: InputManager,
+libinput_config: LibinputConfig,
om: OutputManager,
config: Config,
idle_inhibit_manager: IdleInhibitManager,
@@ -156,6 +158,7 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
.scene = undefined,
.om = undefined,
.input_manager = undefined,
+ .libinput_config = undefined,
.idle_inhibit_manager = undefined,
.lock_manager = undefined,
.wm = undefined,
@@ -184,6 +187,7 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
try server.scene.init();
try server.om.init();
try server.input_manager.init();
+ try server.libinput_config.init();
try server.idle_inhibit_manager.init();
try server.lock_manager.init();
@@ -335,6 +339,7 @@ fn blocklist(server: *Server, global: *const wl.Global) bool {
global == server.om.wlr_output_manager.global or
global == server.om.power_manager.global or
global == server.om.gamma_control_manager.global or
+ global == server.libinput_config.global or
global == server.input_manager.global or
global == server.input_manager.idle_notifier.global or
global == server.input_manager.virtual_pointer_manager.global or