From 8ff435831f5c79e24ac42b35b141e1f79eb66bc8 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 4 Feb 2021 20:48:05 +0100 Subject: [PATCH] xdg-activation-v1: new protocol implementation This implements the new xdg-activation-v1 protocol [1]. [1]: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/50 --- include/wlr/types/wlr_xdg_activation_v1.h | 61 ++++ meson.build | 1 - protocol/meson.build | 3 +- types/meson.build | 2 +- types/wlr_xdg_activation_v1.c | 327 ++++++++++++++++++++++ 5 files changed, 391 insertions(+), 3 deletions(-) create mode 100644 include/wlr/types/wlr_xdg_activation_v1.h create mode 100644 types/wlr_xdg_activation_v1.c diff --git a/include/wlr/types/wlr_xdg_activation_v1.h b/include/wlr/types/wlr_xdg_activation_v1.h new file mode 100644 index 00000000..121e66a1 --- /dev/null +++ b/include/wlr/types/wlr_xdg_activation_v1.h @@ -0,0 +1,61 @@ +/* + * 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_XDG_ACTIVATION_V1 +#define WLR_TYPES_WLR_XDG_ACTIVATION_V1 + +#include + +struct wlr_xdg_activation_token_v1 { + struct wlr_xdg_activation_v1 *activation; + // The source surface that created the token. + struct wlr_surface *surface; // can be NULL + struct wlr_seat *seat; // can be NULL + // The serial for the input event that created the token. + uint32_t serial; // invalid if seat is NULL + // The application ID to be activated. This is just a hint. + char *app_id; // can be NULL + struct wl_list link; // wlr_xdg_activation_v1.tokens + + // private state + + char *token; + struct wl_resource *resource; // can be NULL + + struct wl_listener seat_destroy; + struct wl_listener surface_destroy; +}; + +struct wlr_xdg_activation_v1 { + + struct wl_list tokens; // wlr_xdg_activation_token_v1.link + + struct { + struct wl_signal destroy; + struct wl_signal request_activate; // wlr_xdg_activation_v1_request_activate_event + } events; + + // private state + + struct wl_global *global; + + struct wl_listener display_destroy; +}; + +struct wlr_xdg_activation_v1_request_activate_event { + struct wlr_xdg_activation_v1 *activation; + // The token used to request activation. + struct wlr_xdg_activation_token_v1 *token; + // The surface requesting for activation. + struct wlr_surface *surface; +}; + +struct wlr_xdg_activation_v1 *wlr_xdg_activation_v1_create( + struct wl_display *display); + +#endif diff --git a/meson.build b/meson.build index 9bd3a16e..3dc05bfb 100644 --- a/meson.build +++ b/meson.build @@ -105,7 +105,6 @@ pixman = dependency('pixman-1') math = cc.find_library('m') rt = cc.find_library('rt') - wlr_files = [] wlr_deps = [ wayland_server, diff --git a/protocol/meson.build b/protocol/meson.build index 87d944a1..6387312c 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -1,4 +1,4 @@ -wayland_protos = dependency('wayland-protocols', version: '>=1.17') +wayland_protos = dependency('wayland-protocols', version: '>=1.21') wl_protocol_dir = wayland_protos.get_variable(pkgconfig: 'pkgdatadir') wayland_scanner_dep = dependency('wayland-scanner', native: true) @@ -23,6 +23,7 @@ protocols = { 'relative-pointer-unstable-v1': wl_protocol_dir / 'unstable/relative-pointer/relative-pointer-unstable-v1.xml', 'tablet-unstable-v2': wl_protocol_dir / 'unstable/tablet/tablet-unstable-v2.xml', 'text-input-unstable-v3': wl_protocol_dir / 'unstable/text-input/text-input-unstable-v3.xml', + 'xdg-activation-v1': wl_protocol_dir / 'staging/xdg-activation/xdg-activation-v1.xml', 'xdg-decoration-unstable-v1': wl_protocol_dir / 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml', 'xdg-foreign-unstable-v1': wl_protocol_dir / 'unstable/xdg-foreign/xdg-foreign-unstable-v1.xml', 'xdg-foreign-unstable-v2': wl_protocol_dir / 'unstable/xdg-foreign/xdg-foreign-unstable-v2.xml', diff --git a/types/meson.build b/types/meson.build index 47431429..18e32738 100644 --- a/types/meson.build +++ b/types/meson.build @@ -62,10 +62,10 @@ wlr_files += files( 'wlr_virtual_keyboard_v1.c', 'wlr_virtual_pointer_v1.c', 'wlr_xcursor_manager.c', + 'wlr_xdg_activation_v1.c', 'wlr_xdg_decoration_v1.c', 'wlr_xdg_foreign_v1.c', 'wlr_xdg_foreign_v2.c', 'wlr_xdg_foreign_registry.c', 'wlr_xdg_output_v1.c', ) - diff --git a/types/wlr_xdg_activation_v1.c b/types/wlr_xdg_activation_v1.c new file mode 100644 index 00000000..28cb8da3 --- /dev/null +++ b/types/wlr_xdg_activation_v1.c @@ -0,0 +1,327 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include "util/signal.h" +#include "util/token.h" +#include "xdg-activation-v1-protocol.h" + +#define XDG_ACTIVATION_V1_VERSION 1 + +static const struct xdg_activation_token_v1_interface token_impl; + +static struct wlr_xdg_activation_token_v1 *token_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &xdg_activation_token_v1_interface, &token_impl)); + return wl_resource_get_user_data(resource); +} + +static void token_destroy(struct wlr_xdg_activation_token_v1 *token) { + if (token == NULL) { + return; + } + if (token->resource != NULL) { + wl_resource_set_user_data(token->resource, NULL); // make inert + } + wl_list_remove(&token->link); + wl_list_remove(&token->seat_destroy.link); + wl_list_remove(&token->surface_destroy.link); + free(token->app_id); + free(token->token); + free(token); +} + +static void token_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_xdg_activation_token_v1 *token = token_from_resource(resource); + token_destroy(token); +} + +static void token_handle_destroy(struct wl_client *client, + struct wl_resource *token_resource) { + wl_resource_destroy(token_resource); +} + +static void token_handle_commit(struct wl_client *client, + struct wl_resource *token_resource) { + struct wlr_xdg_activation_token_v1 *token = + token_from_resource(token_resource); + if (token == NULL) { + wl_resource_post_error(token_resource, + XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED, + "The activation token has already been used"); + return; + } + + // Make the token resource inert + wl_resource_set_user_data(token->resource, NULL); + token->resource = NULL; + + char token_str[TOKEN_STRLEN + 1] = {0}; + if (!generate_token(token_str)) { + wl_client_post_no_memory(client); + return; + } + + if (token->seat != NULL) { + struct wlr_seat_client *seat_client = + wlr_seat_client_for_wl_client(token->seat, client); + if (seat_client == NULL || + !wlr_seat_client_validate_event_serial(seat_client, token->serial)) { + wlr_log(WLR_DEBUG, "Rejecting token commit request: " + "serial %"PRIu32" was never given to client", token->serial); + goto error; + } + + if (token->surface != NULL && + token->surface != token->seat->keyboard_state.focused_surface) { + wlr_log(WLR_DEBUG, "Rejecting token commit request: " + "surface doesn't have keyboard focus"); + goto error; + } + } + + token->token = strdup(token_str); + if (token->token == NULL) { + wl_client_post_no_memory(client); + return; + } + + assert(wl_list_empty(&token->link)); + wl_list_insert(&token->activation->tokens, &token->link); + + xdg_activation_token_v1_send_done(token_resource, token_str); + + // TODO: figure out when to discard the token + // TODO: consider emitting a new_token event + + return; + +error: + // Here we send the generated token, but it's invalid and can't be used to + // request activation. + xdg_activation_token_v1_send_done(token_resource, token_str); + token_destroy(token); +} + +static void token_handle_set_app_id(struct wl_client *client, + struct wl_resource *token_resource, const char *app_id) { + struct wlr_xdg_activation_token_v1 *token = + token_from_resource(token_resource); + if (token == NULL) { + wl_resource_post_error(token_resource, + XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED, + "The activation token has already been used"); + return; + } + + free(token->app_id); + token->app_id = strdup(app_id); +} + +static void token_handle_seat_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_activation_token_v1 *token = + wl_container_of(listener, token, seat_destroy); + wl_list_remove(&token->seat_destroy.link); + wl_list_init(&token->seat_destroy.link); + token->serial = 0; + token->seat = NULL; +} + +static void token_handle_set_serial(struct wl_client *client, + struct wl_resource *token_resource, uint32_t serial, + struct wl_resource *seat_resource) { + struct wlr_xdg_activation_token_v1 *token = + token_from_resource(token_resource); + if (token == NULL) { + wl_resource_post_error(token_resource, + XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED, + "The activation token has already been used"); + return; + } + + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + if (seat_client == NULL) { + wlr_log(WLR_DEBUG, "Rejecting token set_serial request: seat is inert"); + return; + } + + token->seat = seat_client->seat; + token->serial = serial; + + token->seat_destroy.notify = token_handle_seat_destroy; + wl_list_remove(&token->seat_destroy.link); + wl_signal_add(&token->seat->events.destroy, &token->seat_destroy); +} + +static void token_handle_surface_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_activation_token_v1 *token = + wl_container_of(listener, token, surface_destroy); + wl_list_remove(&token->surface_destroy.link); + wl_list_init(&token->surface_destroy.link); + token->surface = NULL; +} + +static void token_handle_set_surface(struct wl_client *client, + struct wl_resource *token_resource, + struct wl_resource *surface_resource) { + struct wlr_xdg_activation_token_v1 *token = + token_from_resource(token_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + if (token == NULL) { + wl_resource_post_error(token_resource, + XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED, + "The activation token has already been used"); + return; + } + + token->surface = surface; + + token->surface_destroy.notify = token_handle_surface_destroy; + wl_list_remove(&token->surface_destroy.link); + wl_signal_add(&surface->events.destroy, &token->surface_destroy); +} + +static const struct xdg_activation_token_v1_interface token_impl = { + .destroy = token_handle_destroy, + .commit = token_handle_commit, + .set_app_id = token_handle_set_app_id, + .set_serial = token_handle_set_serial, + .set_surface = token_handle_set_surface, +}; + +static const struct xdg_activation_v1_interface activation_impl; + +static struct wlr_xdg_activation_v1 *activation_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &xdg_activation_v1_interface, &activation_impl)); + return wl_resource_get_user_data(resource); +} + +static void activation_handle_destroy(struct wl_client *client, + struct wl_resource *activation_resource) { + wl_resource_destroy(activation_resource); +} + +static void activation_handle_get_activation_token(struct wl_client *client, + struct wl_resource *activation_resource, uint32_t id) { + struct wlr_xdg_activation_v1 *activation = + activation_from_resource(activation_resource); + + struct wlr_xdg_activation_token_v1 *token = calloc(1, sizeof(*token)); + if (token == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_list_init(&token->link); + wl_list_init(&token->seat_destroy.link); + wl_list_init(&token->surface_destroy.link); + + token->activation = activation; + + uint32_t version = wl_resource_get_version(activation_resource); + token->resource = wl_resource_create(client, + &xdg_activation_token_v1_interface, version, id); + if (token->resource == NULL) { + free(token); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(token->resource, &token_impl, token, + token_handle_resource_destroy); +} + +static void activation_handle_activate(struct wl_client *client, + struct wl_resource *activation_resource, const char *token_str, + struct wl_resource *surface_resource) { + struct wlr_xdg_activation_v1 *activation = + activation_from_resource(activation_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + struct wlr_xdg_activation_token_v1 *token; + bool found = false; + wl_list_for_each(token, &activation->tokens, link) { + if (strcmp(token_str, token->token) == 0) { + found = true; + break; + } + } + if (!found) { + wlr_log(WLR_DEBUG, "Rejecting activate request: unknown token"); + return; + } + + struct wlr_xdg_activation_v1_request_activate_event event = { + .activation = activation, + .token = token, + .surface = surface, + }; + wlr_signal_emit_safe(&activation->events.request_activate, &event); + + token_destroy(token); +} + +static const struct xdg_activation_v1_interface activation_impl = { + .destroy = activation_handle_destroy, + .get_activation_token = activation_handle_get_activation_token, + .activate = activation_handle_activate, +}; + +static void activation_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_xdg_activation_v1 *activation = data; + + struct wl_resource *resource = wl_resource_create(client, + &xdg_activation_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &activation_impl, activation, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_activation_v1 *activation = + wl_container_of(listener, activation, display_destroy); + wlr_signal_emit_safe(&activation->events.destroy, NULL); + + struct wlr_xdg_activation_token_v1 *token, *token_tmp; + wl_list_for_each_safe(token, token_tmp, &activation->tokens, link) { + token_destroy(token); + } + + wl_list_remove(&activation->display_destroy.link); + wl_global_destroy(activation->global); + free(activation); +} + +struct wlr_xdg_activation_v1 *wlr_xdg_activation_v1_create( + struct wl_display *display) { + struct wlr_xdg_activation_v1 *activation = calloc(1, sizeof(*activation)); + if (activation == NULL) { + return NULL; + } + + wl_list_init(&activation->tokens); + wl_signal_init(&activation->events.destroy); + wl_signal_init(&activation->events.request_activate); + + activation->global = wl_global_create(display, + &xdg_activation_v1_interface, XDG_ACTIVATION_V1_VERSION, activation, + activation_bind); + if (activation->global == NULL) { + free(activation->global); + return NULL; + } + + activation->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &activation->display_destroy); + + return activation; +}