diff options
-rw-r--r-- | include/wlr/types/wlr_action_binder_v1.h | 64 | ||||
-rw-r--r-- | protocol/ext-action-binder-v1.xml | 203 | ||||
-rw-r--r-- | protocol/meson.build | 1 | ||||
-rw-r--r-- | types/meson.build | 1 | ||||
-rw-r--r-- | types/wlr_action_binder_v1.c | 336 |
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); +} |