Add simple test to check `ext-input-trigger-v1` initialization Unfortunately, WLCS doesn't actually have a way to simulate keyboard input yet, so further testing will have to wait.
diff --git a/CMakeLists.txt b/CMakeLists.txt index bca1a7e..e19a009 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -155,6 +155,8 @@ GENERATE_PROTOCOL(viewporter) GENERATE_PROTOCOL(fractional-scale-v1) GENERATE_PROTOCOL(linux-dmabuf-stable-v1) +GENERATE_PROTOCOL(ext-input-trigger-action-v1) +GENERATE_PROTOCOL(ext-input-trigger-registration-v1) option(WLCS_BUILD_ASAN "Build a test runner with AddressSanitizer annotations" ON) option(WLCS_BUILD_TSAN "Build a test runner with ThreadSanitizer annotations" ON) @@ -189,6 +191,7 @@ tests/wp_viewporter.cpp tests/fractional_scale_v1.cpp tests/linux_dmabuf_v1.cpp + tests/input_trigger_v1.cpp ) set( @@ -338,3 +341,4 @@ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig/ ) +
diff --git a/src/protocol/ext-input-trigger-action-v1.xml b/src/protocol/ext-input-trigger-action-v1.xml new file mode 100644 index 0000000..b0cc3c2 --- /dev/null +++ b/src/protocol/ext-input-trigger-action-v1.xml
@@ -0,0 +1,119 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="ext_input_trigger_action_v1"> + <copyright> + Copyright 2025 Canonical Limited + + 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 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. + </copyright> + + <description summary="Registration for global input actions"> + This protocol allows for a Wayland clients to subscribe to input trigger + actions for "begin", "update, and "end" notifications. + + ext_input_trigger_action_v1 and the associated ext_input_trigger_registration_v1 + are based on the following discussion document: + https://docs.google.com/document/d/1Hj3BXkmW1quIEWqmN7xeJY29nSPfXP2bmoZvpOsEeJQ/ + </description> + + <interface name="ext_input_trigger_action_manager_v1" version="1"> + <description summary="used to register interest in input actions"> + This interface allows a Wayland client to register interest in + input trigger actions. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy input trigger activation manager object"> + This informs the compositor that the input trigger activation manager + object will no longer be used. Existing objects created through this + interface remain valid. + </description> + </request> + + <request name="get_input_trigger_action"> + <description summary="register interest in input trigger action"> + This request creates an input trigger action object. + + The token should be one issued by the compositor, if not then the + compositor can raise a protocol error. + + If the token has been issued by the compositor, but is no longer + available, then an ext_input_trigger_action_v1.unavailable event + should be sent immediately. + </description> + <arg name="token" type="string" summary="the exported action token"/> + <arg name="id" type="new_id" interface="ext_input_trigger_action_v1"/> + </request> + </interface> + + <interface name="ext_input_trigger_action_v1" version="1"> + <description summary="receives notifications of input trigger actions"> + The compositor sends notification of the triggering of the associated + action. + </description> + + <event name="begin"> + <description summary="begin the input action"> + One of the input triggers associated with the action has occurred. + + The app may begin the activity it associates with the action. + </description> + <arg name="time" type="uint" summary="timestamp with millisecond granularity"/> + <arg name="activation_token" type="string" summary="an xdg_activation token"/> + </event> + + <event name="update"> + <description summary="progress an the input action"> + The input trigger that led to the "begin" event has changed. + + This will be sent between the "begin" and "end" events when there is a + meaningful update to the input trigger. (E.g. a multi-touch drag) + + The app may update the activity it associates with the action. + </description> + <arg name="time" type="uint" summary="timestamp with millisecond granularity"/> + <arg name="activation_token" type="string" summary="an xdg_activation token"/> + <arg name="progress" type="fixed" summary="progress normalized to [0.0, 1.0]"/> + </event> + + <event name="end"> + <description summary="end the input action"> + The input trigger that led to the "begin" event has stopped. + + The app may end (or begin!) the activity it associates with the action. + </description> + <arg name="time" type="uint" summary="timestamp with millisecond granularity"/> + <arg name="activation_token" type="string" summary="an xdg_activation token"/> + </event> + + <event name="unavailable"> + <description summary="the input action is (or has become) unavailable"> + Sent if the token is or becomes unavailable. + + This means the object is of no further use and should be destroyed. + </description> + </event> + + <request name="destroy" type="destructor"> + <description summary="destroy the input trigger activation"> + This informs the compositor that the input trigger activation will no + longer be used. + </description> + </request> + </interface> +</protocol>
diff --git a/src/protocol/ext-input-trigger-registration-v1.xml b/src/protocol/ext-input-trigger-registration-v1.xml new file mode 100644 index 0000000..5617ea4 --- /dev/null +++ b/src/protocol/ext-input-trigger-registration-v1.xml
@@ -0,0 +1,352 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="ext_input_trigger_registration_v1"> + <copyright> + Copyright 2025 Canonical Limited + + 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 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. + </copyright> + + <description summary="Registration of global input triggers and associated actions"> + This protocol allows for a privileged Wayland client to register ownership + of input trigger events and associate them with actions. + + ext_input_trigger_registration_v1 and the associated ext_input_trigger_action_v1 + are based on the following discussion document: + https://docs.google.com/document/d/1Hj3BXkmW1quIEWqmN7xeJY29nSPfXP2bmoZvpOsEeJQ/ + </description> + + <interface name="ext_input_trigger_registration_manager_v1" version="1"> + <description summary="used to register input triggers and actions"> + This interface allows a Wayland client to register ownership of input + trigger events and associate them with actions. + </description> + + <enum name="capability" bitfield="true"> + <description summary="the types of inputs that support triggers"> + This is a bitmask of capabilities this system has; if a member is + set, then triggers can be set. + </description> + <entry name="keyboard" value="0x01" summary="key combinations can be used as triggers"/> + <entry name="modifiers" value="0x02" summary="keyboard modifier taps and/or holds can be used as triggers"/> + <entry name="pointer" value="0x04" summary="pointer gestures can be used as triggers"/> + <entry name="touch_drag" value="0x08" summary="touch drag gestures can be used as triggers"/> + <entry name="touch_tap" value="0x10" summary="touch taps can be used as triggers"/> + </enum> + + <event name="capabilities"> + <description summary="input trigger capabilities"> + This is emitted when ext_input_trigger_registration_manager_v1 is bound + and whenever a system gains or loses the capabilities. The argument is + a capability enum containing the complete set of capabilities. + </description> + <arg name="capabilities" type="uint" enum="capability" summary="types of inputs that support triggers"/> + </event> + + <request name="destroy" type="destructor"> + <description summary="destroy input trigger registration manager object"> + This informs the compositor that the input trigger registration manager + object will no longer be used. Existing objects created through this + interface remain valid. + </description> + </request> + + <enum name="modifiers" bitfield="true"> + <description summary="modifier keys"> + The various shift modifiers on the keyboard + </description> + <entry name="alt" value="0x01" summary="any Alt key"/> + <entry name="alt_left" value="0x02" summary="left Alt key"/> + <entry name="alt_right" value="0x04" summary="Right Alt key"/> + <entry name="shift" value="0x08" summary="any Shift key"/> + <entry name="shift_left" value="0x10" summary="left Shift key"/> + <entry name="shift_right" value="0x20" summary="right Shift key"/> + <entry name="sym" value="0x40" summary="Sym key"/> + <entry name="function" value="0x80" summary="Function key"/> + <entry name="ctrl" value="0x100" summary="any Ctrl key"/> + <entry name="ctrl_left" value="0x200" summary="left Ctrl key"/> + <entry name="ctrl_right" value="0x400" summary="right Ctrl key"/> + <entry name="meta" value="0x800" summary="any Meta key"/> + <entry name="meta_left" value="0x1000" summary="left Meta key"/> + <entry name="meta_right" value="0x2000" summary="right Meta key"/> + <!-- no need for caps/num/scroll lock? --> + </enum> + + <request name="register_keyboard_sym_trigger"> + <description summary="register input trigger"> + This request creates an input trigger and asks the compositor to reserve + it to the manager. This allows triggering on a character key combination + such as "Meta+a". + + The trigger begins when the combination of modifiers and keysym are + pressed and ends when that changes. The keysym parameter is interpreted + according to xkbcommon-keysyms.h. + + The compositor will send either the ext_input_trigger_v1.done or + ext_input_trigger_v1.failed event on the created object in + response to this request. + + Input actions triggered by this request will result in an + ext_input_trigger_action_v1.begin event when the key combination is + depressed and ext_input_trigger_action_v1.end when the key combination + changes. + </description> + <arg name="modifiers" type="uint" enum="modifiers"/> + <arg name="keysym" type="uint" summary="the key to trigger the action"/> + <arg name="id" type="new_id" interface="ext_input_trigger_v1"/> + </request> + + <request name="register_keyboard_code_trigger"> + <description summary="register input trigger"> + This request creates an input trigger and asks the compositor to reserve + it to the manager. This allows triggering on a specific key combination + such "Alt+the-key-above-tab". + + The trigger begins when the combination of modifiers and keycode are + pressed and ends when that changes. + + The compositor will send either the ext_input_trigger_v1.done or + ext_input_trigger_v1.failed event on the created object in + response to this request. + + Input actions triggered by this request will result in an + ext_input_trigger_action_v1.begin event when the key combination is + depressed and ext_input_trigger_action_v1.end when the key combination + changes. + </description> + <arg name="modifiers" type="uint" enum="modifiers"/> + <arg name="keycode" type="uint" summary="the key to trigger the action"/> + <arg name="id" type="new_id" interface="ext_input_trigger_v1"/> + </request> + + <request name="register_modifier_trigger"> + <description summary="register input trigger"> + This request creates an input trigger and asks the compositor to reserve + it to the manager. + + This describes a trigger based on holding or tapping one or more + modifier keys. As modifiers are used normally in combination with other + keys, the hold_delay and tap_count should not both be zero. + + The compositor will send either the ext_input_trigger_v1.done or + ext_input_trigger_v1.failed event on the created object in + response to this request. + + Input actions triggered by this request will result in an + ext_input_trigger_action_v1.begin event when triggered + target edge(s) and ext_input_trigger_action_v1.end when the trigger ends. + </description> + <arg name="modifiers" type="uint" enum="modifiers"/> + <arg name="hold_delay" type="uint" summary="minimum milliseconds pressed before triggering"/> + <arg name="tap_count" type="uint" summary="number of consecutive taps before triggering"/> + <arg name="id" type="new_id" interface="ext_input_trigger_v1"/> + </request> + + <enum name="screen_edges" bitfield="true"> + <description summary="edge values for pointer_trigger"> + These values are used to indicate which edge of the screen + triggers a global input action. + </description> + <entry name="top" value="1" summary="top edge"/> + <entry name="bottom" value="2" summary="bottom edge"/> + <entry name="left" value="4" summary="left edge"/> + <entry name="top_left" value="5" summary="top left corner"/> + <entry name="bottom_left" value="6" summary="bottom left corner"/> + <entry name="right" value="8" summary="right edge"/> + <entry name="top_right" value="9" summary="top right corner"/> + <entry name="bottom_right" value="10" summary="bottom right corner"/> + </enum> + + <request name="register_pointer_trigger"> + <description summary="register input trigger"> + This request creates an input trigger and asks the compositor to reserve + it to the manager. + + The compositor will send either the ext_input_trigger_v1.done or + ext_input_trigger_v1.failed event on the created object in + response to this request. + + Input actions triggered by this request will result in an + ext_input_trigger_action_v1.begin event when the pointer is pushed to the + target edge(s) and ext_input_trigger_action_v1.end when pointer moves away. + </description> + <arg name="edges" type="uint" enum="screen_edges"/> + <arg name="id" type="new_id" interface="ext_input_trigger_v1"/> + </request> + + <enum name="drag_direction" bitfield="false"> + <description summary="directions for drag"> + These values are used to indicate which touch drag direction triggers + a global input action. + </description> + <entry name="up" value="1" summary="upwards"/> + <entry name="down" value="2" summary="downwards"/> + <entry name="left" value="3" summary="leftwards"/> + <entry name="up_left" value="4" summary="up to left"/> + <entry name="down_left" value="5" summary="down to left"/> + <entry name="right" value="6" summary="rightwards"/> + <entry name="up_right" value="7" summary="up to right"/> + <entry name="down_right" value="8" summary="down to right"/> + </enum> + + <request name="register_touch_drag_trigger"> + <description summary="register input trigger"> + This request creates an input trigger and asks the compositor to reserve + it to the manager. + + This describes a trigger based on dragging three (or more) touchpoints. + If advertising the touch_drag capability the compositor must support + three touchpoints. Fewer touchpoints are typically reserved for apps, + requesting more than three touchpoints may result in a failed event. + + The compositor will send either the ext_input_trigger_v1.done or + ext_input_trigger_v1.failed event on the created object in + response to this request. + + Input actions triggered by this request will result in an + ext_input_trigger_action_v1.begin event when the mult-touch moves in the + drag direction, ext_input_trigger_action_v1.update as move continues and + ext_input_trigger_action_v1.end when touches change. + </description> + <arg name="touches" type="uint" summary="number of touches"/> + <arg name="direction" type="uint" enum="drag_direction"/> + <arg name="id" type="new_id" interface="ext_input_trigger_v1"/> + </request> + + <request name="register_touch_tap_trigger"> + <description summary="register input trigger"> + This request creates an input trigger and asks the compositor to reserve + it to the manager. + + This describes a trigger based on tapping three (or more) touchpoints. + If advertising the touch_tap capability the compositor must support + three touchpoints. Fewer touchpoints are typically reserved for apps, + requesting more than three touchpoints may result in a failed event. + + The compositor will send either the ext_input_trigger_v1.done or + ext_input_trigger_v1.failed event on the created object in + response to this request. + + Input actions triggered by this request will result in an + ext_input_trigger_action_v1.begin and end events when the mult-touch is + held for at least hold_delay and released without a touch drag. + </description> + <arg name="touches" type="uint" summary="number of touches"/> + <arg name="hold_delay" type="uint" summary="minimum milliseconds pressed before triggering"/> + <arg name="id" type="new_id" interface="ext_input_trigger_v1"/> + </request> + + <request name="get_action_control"> + <description summary="get input trigger action control object"> + This request creates an action control object. + + The name parameter describes the action (it has no semantic content and + may be duplicated across actions). + + The compositor will send the ext_input_trigger_action_control_v1.done event in + response to this request. + </description> + <arg name="name" type="string" summary="a description of the action"/> + <arg name="id" type="new_id" interface="ext_input_trigger_action_control_v1"/> + </request> + </interface> + + <interface name="ext_input_trigger_v1" version="1"> + <description summary="represents the (attempted) registration of an input trigger"> + In response to the creation of this object the compositor must send + either the done or failed event. + + The done event indicates that the trigger is "owned" by the manager. + This means the compositor will not allow this, or any other manager to + register the trigger event. + + The failed event indicates that the trigger is not "owned" by the manager. + This the object is of no further use and should be destroyed. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the input trigger"> + This informs the compositor that the input trigger will no longer be + used. + </description> + </request> + + <event name="done"> + <description summary="input trigger successfully registered"> + This client is now responsible for handling this input trigger. + </description> + </event> + + <event name="failed"> + <description summary="input trigger failed to register"> + The failed event indicates that the trigger is not "owned" by the manager. + This means the object is of no further use and should be destroyed. + </description> + </event> + </interface> + + <interface name="ext_input_trigger_action_control_v1" version="1"> + <description summary="Allows configuration of an input trigger action"> + In response to the creation of this object the compositor must send + the done event. + </description> + + <request name="add_input_trigger_event"> + <description summary="add an input trigger to the action"> + This request adds an input trigger to those that activate the action. + </description> + <arg name="trigger" type="object" interface="ext_input_trigger_v1" summary="the trigger being added"/> + </request> + + <request name="drop_input_trigger_event"> + <description summary="drop an input trigger from the action"> + This request drops an input trigger from those that activate the action. + </description> + <arg name="trigger" type="object" interface="ext_input_trigger_v1" summary="the trigger being dropped"/> + </request> + + <event name="done"> + <description summary="the exported action token"> + The 'done' event contains the unique token of this action and notifies + that the provider is done. The action token is usable by the + ext_input_trigger_action_v1 protocol. The token can be used by this + or another client (or clients). + </description> + <arg name="token" type="string" summary="the exported action token"/> + </event> + + <request name="cancel" type="destructor"> + <description summary="purge the input trigger action"> + This informs the compositor that the input trigger action will no + longer be used. + + Any ext_input_trigger_action_v1 created from the associated token + by this or other clients will become unavailable. + </description> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the input trigger action"> + This informs the compositor that the input trigger action will no + longer be used. + Any ext_input_trigger_action_v1 created from the associated token + by this or other clients will be unaffected. + </description> + </request> + </interface> +</protocol>
diff --git a/tests/input_trigger_v1.cpp b/tests/input_trigger_v1.cpp new file mode 100644 index 0000000..ff2759b --- /dev/null +++ b/tests/input_trigger_v1.cpp
@@ -0,0 +1,97 @@ +/* + * Copyright © 2025 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, + * as published by the Free Software Foundation. + * + * 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 <http://www.gnu.org/licenses/>. + * + * Authored by: Tarek Ismail <tarek.ismail@canonical.com> + */ + + +#include "generated/ext-input-trigger-registration-v1-client.h" +#include "generated/ext-input-trigger-action-v1-client.h" +#include "in_process_server.h" +#include "version_specifier.h" + +#include "gmock/gmock.h" +#include <xkbcommon/xkbcommon.h> + +using namespace testing; +using namespace wlcs; + +namespace wlcs +{ +WLCS_CREATE_INTERFACE_DESCRIPTOR(ext_input_trigger_registration_manager_v1); +WLCS_CREATE_INTERFACE_DESCRIPTOR(ext_input_trigger_action_manager_v1); +WLCS_CREATE_INTERFACE_DESCRIPTOR(ext_input_trigger_action_v1); +WLCS_CREATE_INTERFACE_DESCRIPTOR(ext_input_trigger_action_control_v1); +} + +class TestInputTrigger: public StartedInProcessServer +{ + +}; + +TEST_F(TestInputTrigger, foo) +{ + Client client{the_server()}; + + auto const registration_manager = + client.bind_if_supported<ext_input_trigger_registration_manager_v1>(AtLeastVersion{1}); + auto const action_manager = client.bind_if_supported<ext_input_trigger_action_manager_v1>(AtLeastVersion{1}); + + auto const trigger = ext_input_trigger_registration_manager_v1_register_keyboard_sym_trigger( + registration_manager, + EXT_INPUT_TRIGGER_REGISTRATION_MANAGER_V1_MODIFIERS_SHIFT | + EXT_INPUT_TRIGGER_REGISTRATION_MANAGER_V1_MODIFIERS_CTRL, + XKB_KEY_C); + ext_input_trigger_v1_listener const trigger_listener = { + .done = + [](void* data, auto* trigger) + { + (void)data; + (void)trigger; + }, + .failed = + [](auto...) + { + FAIL() << "Unexpected call to ext_input_trigger_v1.failed"; + }, + }; + ext_input_trigger_v1_add_listener(trigger, &trigger_listener, nullptr); + + std::string action_control_token{}; + auto const action_control = + ext_input_trigger_registration_manager_v1_get_action_control(registration_manager, "ctrl_alt_c"); + ext_input_trigger_action_control_v1_listener const action_control_listener = { + .done = [](void* data, auto* action_control, char const* token) + { + (void)action_control; + auto* action_control_token = static_cast<std::string*>(data); + *action_control_token = std::string{token}; + }}; + ext_input_trigger_action_control_v1_add_listener(action_control, &action_control_listener, &action_control_token); + + ext_input_trigger_action_control_v1_add_input_trigger_event(action_control, trigger); + + // wait for done event with token + client.roundtrip(); + + // Make sure that we get a token + EXPECT_THAT(action_control_token, Not(StrEq(""))); + + // get corresponding action + auto const action = + ext_input_trigger_action_manager_v1_get_input_trigger_action(action_manager, action_control_token.c_str()); + + EXPECT_THAT(action, Ne(nullptr)); +}