aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/wlr/types/wlr_action_binder_v1.h64
-rw-r--r--protocol/ext-action-binder-v1.xml203
-rw-r--r--protocol/meson.build1
-rw-r--r--types/meson.build1
-rw-r--r--types/wlr_action_binder_v1.c336
5 files changed, 605 insertions, 0 deletions
diff --git a/include/wlr/types/wlr_action_binder_v1.h b/include/wlr/types/wlr_action_binder_v1.h
new file mode 100644
index 00000000..d93f6dc4
--- /dev/null
+++ b/include/wlr/types/wlr_action_binder_v1.h
@@ -0,0 +1,64 @@
+/*
+ * This an unstable interface of wlroots. No guarantees are made regarding the
+ * future consistency of this API.
+ */
+#ifndef WLR_USE_UNSTABLE
+#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features"
+#endif
+
+#ifndef WLR_TYPES_WLR_ACTION_BINDER_V1_H
+#define WLR_TYPES_WLR_ACTION_BINDER_V1_H
+
+#include <wayland-server-core.h>
+#include <wayland-server-protocol.h>
+#include "ext-action-binder-v1-protocol.h"
+
+struct wlr_action_binder_v1 {
+ struct wl_global *global;
+ struct wl_list states; // wlr_action_binder_v1_state.link
+ struct wl_listener display_destroy;
+
+ struct {
+ struct wl_signal bind;
+ struct wl_signal destroy;
+ } events;
+
+ void *data;
+};
+
+struct wlr_action_binder_v1_state {
+ struct wl_list binds; // wlr_action_binding_v1.link
+ struct wl_list bind_queue; // wlr_action_binding_v1.link
+ struct wlr_action_binder_v1 *binder;
+ struct wl_resource *resource;
+
+ struct wl_list link;
+};
+
+struct wlr_action_binding_v1 {
+ struct wl_resource *resource;
+ struct wlr_action_binder_v1_state *state;
+
+ char *namespace, *name;
+
+ char *description; // may be NULL when the client doesn't set a description
+ char *trigger_kind, *trigger; // may be NULL when the client doesn't set a trigger hint
+ char *app_id; // may be NULL when the client doesn't set an app_id
+ struct wlr_seat *seat; // may be NULL when the client doesn't set a seat
+ struct wl_listener seat_destroy;
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+
+ bool bound;
+ struct wl_list link;
+};
+
+struct wlr_action_binder_v1 *wlr_action_binder_v1_create(struct wl_display *display);
+void wlr_action_binding_v1_bind(struct wlr_action_binding_v1 *bind, const char *trigger);
+void wlr_action_binding_v1_reject(struct wlr_action_binding_v1 *bind);
+void wlr_action_binding_v1_trigger(struct wlr_action_binding_v1 *binding, uint32_t trigger_type, uint32_t time_msec);
+void wlr_action_binding_v1_trigger_now(struct wlr_action_binding_v1 *binding, uint32_t trigger_type);
+
+#endif
diff --git a/protocol/ext-action-binder-v1.xml b/protocol/ext-action-binder-v1.xml
new file mode 100644
index 00000000..b477c137
--- /dev/null
+++ b/protocol/ext-action-binder-v1.xml
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="action_binder_v1">
+ <copyright>
+ Copyright © 2015-2017 Quentin “Sardem FF7” Glidic, 2023 Anna "navi" Figueiredo Gomes
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, 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>
+
+ <interface name="ext_action_binder_v1" version="1">
+ <description summary="action binder">
+ This interface is designed to allow any application to bind
+ an action.
+
+ An action is an arbitrary couple of a namespace and a name describing the
+ wanted behaviour. These two strings are not meant to be user-visible.
+ Some namespaces are well-known and shared by applications while each
+ application can have its own namespaces for internal actions.
+ It is possible to have the same action in several namespaces, e.g. to
+ allow application-specific bindings in addition to global actions.
+
+ It is left to the compositor to determine which client will get events.
+ The choice can be based on policy, heuristic, user configuration, or any
+ other mechanism that may be relevant.
+ Here are some examples of dispatching choice: all applications, last
+ focused, user-defined preference order, latest fullscreened application.
+
+ This interface is exposed as global
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="unbind the actions">
+ The client no longer wants to receive events for any action.
+ </description>
+ </request>
+
+ <request name="create_binding">
+ <description summary="create a binding"/>
+ <arg name="binding" type="new_id" interface="ext_action_binding_v1" summary="the new binding" />
+ </request>
+
+ <request name="bind">
+ <description summary="binds all created bindings">
+ Binds all bindings created from this interface.
+ This request may be called again if new bindings are created,
+ already bound bindings are unaffected.
+
+ After calling bind, either the "bound" or "rejected" event is sent
+ for each binding created.
+
+ If no action has been set for any binding, the error "invalid_binding" is raised.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="invalid_binding" value="0" summary="the binding has no action set"/>
+ </enum>
+ </interface>
+
+ <interface name="ext_action_binding_v1" version="1">
+ <request name="destroy" type="destructor">
+ <description summary="unbind the actions">
+ The client no longer wants to receive events for this binding.
+ </description>
+ </request>
+
+ <request name="set_name">
+ <description summary="sets the namespace:name of a binding">
+ This an action.
+ Sets the namespace:name of the binding.
+
+ Attempting to send this request twice raises an already set error
+ </description>
+ <arg name="namespace" type="string" summary="the action namespace" />
+ <arg name="name" type="string" summary="the action name" />
+ </request>
+
+ <request name="set_description">
+ <description summary="sets the human-readable description of a binding">
+ This setting is optional.
+ This description may be used by the compositor to render a ui for bindings.
+
+ Attempting to send this request twice raises an already_set error
+ </description>
+ <arg name="description" type="string" summary="a human-readable description of what the binding does" />
+ </request>
+
+ <request name="set_app_id">
+ <description summary="sets an app_id for this binding">
+ This setting is optional.
+
+ Attempting to send this request twice raises an already_set error
+ </description>
+ <arg name="app_id" type="string" summary="app_id of the application requesting this bind"/>
+ </request>
+
+ <request name="set_seat">
+ <description summary="sets a target seat">
+ This setting is optional.
+
+ Attempting to send this request twice raises an already_set error
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="target seat"/>
+ </request>
+
+ <request name="set_trigger_hint">
+ <description summary="sets the machine-readable trigger of a binding">
+ This setting is optional.
+ The trigger is a suggestion to the compositor, and the action should not rely
+ to being set to that specific trigger.
+
+ The client does not know which trigger was actually set, but when a binding is
+ bound, it recieves from the compositor a human readable string describing the trigger,
+ if any, so it could show it in a ui.
+
+ The trigger format is split into two fields, what kind of device triggers it, and a
+ general trigger string.
+
+ as of version 1 of this protocol, the following kinds are defined:
+ "sym": trigger is a combo of XKB key names
+ "mouse": trigger is button[1-9], mapped to their x11 values, (1=left, 2=middle, 3=right,
+ 4=scroll up, 5=scroll down, 6=scroll left, 7=scroll right, 8=back, 9=forward)
+ "switch": trigger on the format switch:state, where "lid" and "tablet" are valid switches
+ "gesture": trigger on the format gesture[:fingers][:direction] with the following gestures:
+ hold: 1-5 fingers, no direction
+ swipe: 3-5 fingers, up, down, left or right
+ pinch: 2-5 fingers, all above, inward, outward, clockwise, counterclockwise
+
+ Attempting to send this request twice raises an already_set error
+ </description>
+ <arg name="kind" type="string" summary="what category of trigger it fits in"/>
+ <arg name="trigger" type="string" summary="a trigger that the client would like to trigger the action"/>
+ </request>
+
+ <event name="bound">
+ <description summary="the compositor bound the binding to an action">
+ Sent after the event was processed, and was bound to one or more of the actions set.
+ </description>
+ <arg name="trigger" type="string" summary="human-readable string describing the trigger for the action" />
+ </event>
+
+ <event name="rejected">
+ <description summary="the compositor rejected the binding">
+ Sent after the event was processed, and got rejected.
+ or at any time should the compositor want to remove the binding.
+ The compositor will send no further events after this event.
+ The client should destroy the resource at this point.
+ </description>
+ </event>
+
+ <enum name="trigger_type">
+ <description summary="type of binding triggered">
+ Depending on the user configuration, an action can be either one_shot or
+ sustained. The client must handle all the three event types and either make
+ sense of them or ignore them properly.
+
+ one_shot actions are for events that don't have a defined "end", like a laptop
+ lid closing, or a gesture. The client should not expect to recieve a released or
+ ending event for that action.
+
+ sustained actions have a start and an end. after a 'pressed' event is sent, a
+ 'released' event should eventually be sent as well.
+ </description>
+ <entry name="one_shot" value="0"
+ summary="a one shot action was triggered" />
+ <entry name="pressed" value="1"
+ summary="a sustained action was started" />
+ <entry name="released" value="2"
+ summary="a sustained action ended" />
+ </enum>
+
+ <event name="triggered">
+ <description summary="the action triggered">
+ This event is sent when actions are triggered.
+ If a binding would trigger both triggered and started events, the
+ started event must be sent first.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="type" type="uint" enum="trigger_type" summary="the type of trigger that was sent"/>
+ </event>
+ <enum name="error">
+ <entry name="already_set" value="0" summary="property was already set"/>
+ </enum>
+ </interface>
+</protocol>
diff --git a/protocol/meson.build b/protocol/meson.build
index 6a8d82b4..f411a6ff 100644
--- a/protocol/meson.build
+++ b/protocol/meson.build
@@ -52,6 +52,7 @@ protocols = {
'drm': 'drm.xml',
'input-method-unstable-v2': 'input-method-unstable-v2.xml',
'kde-server-decoration': 'server-decoration.xml',
+ 'ext-action-binder-v1': 'ext-action-binder-v1.xml',
'virtual-keyboard-unstable-v1': 'virtual-keyboard-unstable-v1.xml',
'wlr-data-control-unstable-v1': 'wlr-data-control-unstable-v1.xml',
'wlr-export-dmabuf-unstable-v1': 'wlr-export-dmabuf-unstable-v1.xml',
diff --git a/types/meson.build b/types/meson.build
index 8962a390..6d78d500 100644
--- a/types/meson.build
+++ b/types/meson.build
@@ -33,6 +33,7 @@ wlr_files += files(
'buffer/dmabuf.c',
'buffer/readonly_data.c',
'buffer/resource.c',
+ 'wlr_action_binder_v1.c',
'wlr_compositor.c',
'wlr_content_type_v1.c',
'wlr_cursor_shape_v1.c',
diff --git a/types/wlr_action_binder_v1.c b/types/wlr_action_binder_v1.c
new file mode 100644
index 00000000..f5eebacb
--- /dev/null
+++ b/types/wlr_action_binder_v1.c
@@ -0,0 +1,336 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-server-core.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_action_binder_v1.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_xdg_shell.h>
+#include <wlr/util/log.h>
+#include <util/time.h>
+#include "ext-action-binder-v1-protocol.h"
+
+static void resource_handle_destroy(struct wl_client *client, struct wl_resource *resource) {
+ wl_resource_destroy(resource);
+}
+
+static const struct ext_action_binder_v1_interface ext_action_binder_v1_implementation;
+static struct wlr_action_binder_v1_state *wlr_action_binder_v1_state_from_resource(struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &ext_action_binder_v1_interface, &ext_action_binder_v1_implementation));
+ return wl_resource_get_user_data(resource);
+}
+
+static const struct ext_action_binding_v1_interface ext_action_binding_v1_implementation;
+static struct wlr_action_binding_v1 *wlr_action_binding_v1_from_resource(struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &ext_action_binding_v1_interface, &ext_action_binding_v1_implementation));
+ return wl_resource_get_user_data(resource);
+}
+
+static void destroy_binding(struct wlr_action_binding_v1 *binding) {
+ if (!binding) {
+ return;
+ }
+
+ free(binding->namespace);
+ free(binding->name);
+
+ free(binding->trigger);
+ free(binding->trigger_kind);
+
+ free(binding->description);
+
+ free(binding->app_id);
+
+ wl_list_remove(&binding->link);
+ wl_list_remove(&binding->seat_destroy.link);
+
+ wl_resource_set_user_data(binding->resource, NULL); // make resource inert
+
+ free(binding);
+}
+
+static void handle_seat_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_action_binding_v1 *binding = wl_container_of(listener, binding, seat_destroy);
+ wl_list_remove(&binding->seat_destroy.link);
+ binding->seat = NULL;
+ wlr_action_binding_v1_reject(binding);
+}
+
+static void action_binding_destroy(struct wl_resource *resource) {
+ struct wlr_action_binding_v1 *binding = wlr_action_binding_v1_from_resource(resource);
+ destroy_binding(binding);
+}
+
+static void action_binding_set_name(struct wl_client *client,
+ struct wl_resource *resource, const char *namespace, const char *name) {
+ struct wlr_action_binding_v1 *binding = wlr_action_binding_v1_from_resource(resource);
+ if (!binding) {
+ return;
+ }
+
+ if (binding->bound || binding->name || binding->namespace) {
+ wl_resource_post_error(binding->resource,
+ EXT_ACTION_BINDING_V1_ERROR_ALREADY_SET,
+ "attempted to set a binding property twice");
+ return;
+ }
+
+ binding->name = strdup(name);
+ if (binding->name == NULL) {
+ wl_client_post_no_memory(client);
+ }
+
+ binding->namespace = strdup(namespace);
+ if (binding->namespace == NULL) {
+ wl_client_post_no_memory(client);
+ }
+}
+
+static void action_binding_set_trigger(struct wl_client *client,
+ struct wl_resource *resource, const char *trigger_kind, const char *trigger) {
+ struct wlr_action_binding_v1 *binding = wlr_action_binding_v1_from_resource(resource);
+ if (!binding) {
+ return;
+ }
+ if (binding->bound || binding->trigger || binding->trigger_kind) {
+ wl_resource_post_error(binding->resource,
+ EXT_ACTION_BINDING_V1_ERROR_ALREADY_SET,
+ "attempted to set a binding property twice");
+ return;
+ }
+
+ binding->trigger_kind = strdup(trigger_kind);
+ if (binding->trigger_kind == NULL) {
+ wl_client_post_no_memory(client);
+ }
+ binding->trigger = strdup(trigger);
+ if (binding->trigger == NULL) {
+ wl_client_post_no_memory(client);
+ }
+}
+
+static void action_binding_set_desc(struct wl_client *client,
+ struct wl_resource *resource, const char *description) {
+ struct wlr_action_binding_v1 *binding = wlr_action_binding_v1_from_resource(resource);
+ if (!binding) {
+ return;
+ }
+ if (binding->bound || binding->description) {
+ wl_resource_post_error(binding->resource,
+ EXT_ACTION_BINDING_V1_ERROR_ALREADY_SET,
+ "attempted to set a binding property twice");
+ return;
+ }
+
+ binding->description = strdup(description);
+ if (binding->description == NULL) {
+ wl_client_post_no_memory(client);
+ }
+}
+
+static void action_binding_set_app_id(struct wl_client *client,
+ struct wl_resource *resource, const char *app_id) {
+ struct wlr_action_binding_v1 *binding = wlr_action_binding_v1_from_resource(resource);
+ if (!binding) {
+ return;
+ }
+ if (binding->bound || binding->app_id) {
+ wl_resource_post_error(binding->resource,
+ EXT_ACTION_BINDING_V1_ERROR_ALREADY_SET,
+ "attempted to set a binding property twice");
+ return;
+ }
+
+ binding->app_id = strdup(app_id);
+ if (binding->app_id == NULL) {
+ wl_client_post_no_memory(client);
+ }
+}
+
+static void action_binding_set_seat(struct wl_client *client,
+ struct wl_resource *resource, struct wl_resource *seat_resource) {
+ struct wlr_action_binding_v1 *binding = wlr_action_binding_v1_from_resource(resource);
+ if (!binding) {
+ return;
+ }
+
+ if (binding->bound || binding->seat) {
+ wl_resource_post_error(binding->resource,
+ EXT_ACTION_BINDING_V1_ERROR_ALREADY_SET,
+ "attempted to set a binding property twice");
+ return;
+ }
+
+ struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat_resource);
+ binding->seat = seat_client ? seat_client->seat : NULL;
+ if (binding->seat) {
+ wl_signal_add(&binding->seat->events.destroy, &binding->seat_destroy);
+ }
+}
+
+static const struct ext_action_binding_v1_interface ext_action_binding_v1_implementation = {
+ .destroy = resource_handle_destroy,
+ .set_trigger_hint = action_binding_set_trigger,
+ .set_description = action_binding_set_desc,
+ .set_name = action_binding_set_name,
+ .set_app_id = action_binding_set_app_id,
+ .set_seat = action_binding_set_seat,
+};
+
+static void action_binder_create_binding(struct wl_client *client,
+ struct wl_resource *resource, uint32_t binding) {
+ struct wlr_action_binder_v1_state *state = wlr_action_binder_v1_state_from_resource(resource);
+
+ struct wl_resource *bind_resource = wl_resource_create(client,
+ &ext_action_binding_v1_interface, ext_action_binding_v1_interface.version, binding);
+ if (bind_resource == NULL) {
+ wl_client_post_no_memory(client);
+ return;
+ }
+
+ struct wlr_action_binding_v1 *bind = calloc(1, sizeof(*bind));
+ if (bind == NULL) {
+ wl_client_post_no_memory(client);
+ wl_resource_destroy(bind_resource);
+ return;
+ }
+ bind->resource = bind_resource;
+ bind->state = state;
+ bind->bound = false;
+
+ wl_list_init(&bind->link);
+ wl_signal_init(&bind->events.destroy);
+ wl_list_insert(&state->bind_queue, &bind->link);
+
+ wl_list_init(&bind->seat_destroy.link);
+ bind->seat_destroy.notify = handle_seat_destroy;
+
+ wl_resource_set_implementation(bind_resource,
+ &ext_action_binding_v1_implementation, bind, action_binding_destroy);
+}
+
+static void action_binder_bind_actions(struct wl_client *client, struct wl_resource *resource) {
+ struct wlr_action_binder_v1_state *state = wlr_action_binder_v1_state_from_resource(resource);
+ struct wlr_action_binding_v1 *binding = NULL;
+
+ wl_list_for_each(binding, &state->bind_queue, link) {
+ if (!binding->namespace || !binding->name) {
+ wl_resource_post_error(binding->resource,
+ EXT_ACTION_BINDER_V1_ERROR_INVALID_BINDING,
+ "attempted to bind a unactionable binding");
+ return;
+ }
+ }
+
+ wl_signal_emit(&state->binder->events.bind, NULL);
+}
+
+static const struct ext_action_binder_v1_interface ext_action_binder_v1_implementation = {
+ .create_binding = action_binder_create_binding,
+ .bind = action_binder_bind_actions,
+ .destroy = resource_handle_destroy,
+};
+
+static void action_binder_destroy(struct wl_resource *resource) {
+ struct wlr_action_binder_v1_state *state = wlr_action_binder_v1_state_from_resource(resource);
+ struct wlr_action_binding_v1 *binding = NULL, *tmp = NULL;
+
+ wl_list_for_each_safe(binding, tmp, &state->binds, link) {
+ wl_signal_emit(&binding->events.destroy, NULL);
+ destroy_binding(binding);
+ }
+
+ wl_list_for_each_safe(binding, tmp, &state->bind_queue, link) {
+ destroy_binding(binding);
+ }
+
+ wl_list_remove(&state->link);
+
+ free(state);
+}
+
+static void action_binder_bind(struct wl_client *wl_client,
+ void *data, uint32_t version, uint32_t id) {
+ struct wlr_action_binder_v1 *binder = data;
+
+ struct wl_resource *resource = wl_resource_create(wl_client,
+ &ext_action_binder_v1_interface, version, id);
+ if (resource == NULL) {
+ wl_client_post_no_memory(wl_client);
+ return;
+ }
+
+ struct wlr_action_binder_v1_state *state = calloc(1, sizeof(*state));
+ if (state == NULL) {
+ wl_client_post_no_memory(wl_client);
+ wl_resource_destroy(resource);
+ return;
+ }
+
+ wl_list_init(&state->binds);
+ wl_list_init(&state->bind_queue);
+ state->binder = binder;
+ state->resource = resource;
+
+ wl_list_insert(&binder->states, &state->link);
+
+ wl_resource_set_implementation(resource,
+ &ext_action_binder_v1_implementation, state, action_binder_destroy);
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_action_binder_v1 *binder = wl_container_of(listener, binder, display_destroy);
+ wl_signal_emit(&binder->events.destroy, NULL);
+ wl_list_remove(&binder->display_destroy.link);
+ wl_global_destroy(binder->global);
+ free(binder);
+}
+
+struct wlr_action_binder_v1 *wlr_action_binder_v1_create(struct wl_display *display) {
+ struct wlr_action_binder_v1 *action_binder = calloc(1, sizeof(*action_binder));
+ if (!action_binder) {
+ return NULL;
+ }
+
+ struct wl_global *global = wl_global_create(display,
+ &ext_action_binder_v1_interface, 1, action_binder, action_binder_bind);
+ if (!global) {
+ free(action_binder);
+ return NULL;
+ }
+ action_binder->global = global;
+
+ wl_signal_init(&action_binder->events.bind);
+ wl_signal_init(&action_binder->events.destroy);
+ wl_list_init(&action_binder->states);
+
+ action_binder->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &action_binder->display_destroy);
+
+ return action_binder;
+}
+
+void wlr_action_binding_v1_bind(struct wlr_action_binding_v1 *binding, const char *trigger) {
+ assert(!binding->bound);
+ binding->bound = true;
+ wl_list_remove(&binding->link);
+ wl_list_insert(&binding->state->binds, &binding->link);
+
+ ext_action_binding_v1_send_bound(binding->resource, trigger);
+}
+
+void wlr_action_binding_v1_reject(struct wlr_action_binding_v1 *binding) {
+ ext_action_binding_v1_send_rejected(binding->resource);
+ wl_signal_emit(&binding->events.destroy, NULL);
+ destroy_binding(binding);
+}
+
+void wlr_action_binding_v1_trigger(struct wlr_action_binding_v1 *binding, uint32_t trigger_type, uint32_t time_msec) {
+ ext_action_binding_v1_send_triggered(binding->resource, time_msec, trigger_type);
+}
+
+void wlr_action_binding_v1_trigger_now(struct wlr_action_binding_v1 *binding, uint32_t trigger_type) {
+ ext_action_binding_v1_send_triggered(binding->resource, get_current_time_msec(), trigger_type);
+}