From 9e426e70e68cc292f55dbd2c496bcfd6bea32caf Mon Sep 17 00:00:00 2001 From: columbarius Date: Sat, 10 Feb 2024 23:14:05 +0100 Subject: [PATCH] ext-foreign-toplevel-list-v1: new protocol implementation This implements the new ext-foreign-toplevel-list-v1 protocol [1]. Implemented analog to the zwlr-foreign-toplevel-management-v1 implementation. The additional _ext_ in the names was added to avoid name collisions. [1]: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/187 Co-authored-by: Leon Henrik Plickat --- .../types/wlr_ext_foreign_toplevel_list_v1.h | 67 +++++ protocol/meson.build | 1 + types/meson.build | 1 + types/wlr_ext_foreign_toplevel_list_v1.c | 275 ++++++++++++++++++ 4 files changed, 344 insertions(+) create mode 100644 include/wlr/types/wlr_ext_foreign_toplevel_list_v1.h create mode 100644 types/wlr_ext_foreign_toplevel_list_v1.c diff --git a/include/wlr/types/wlr_ext_foreign_toplevel_list_v1.h b/include/wlr/types/wlr_ext_foreign_toplevel_list_v1.h new file mode 100644 index 00000000..a5ba9d38 --- /dev/null +++ b/include/wlr/types/wlr_ext_foreign_toplevel_list_v1.h @@ -0,0 +1,67 @@ +/* + * 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_FOREIGN_TOPLEVEL_LIST_V1_H +#define WLR_TYPES_WLR_FOREIGN_TOPLEVEL_LIST_V1_H + +#include + +struct wlr_ext_foreign_toplevel_list_v1 { + struct wl_global *global; + struct wl_list resources; // wl_resource_get_link() + struct wl_list toplevels; // ext_foreign_toplevel_handle_v1.link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_ext_foreign_toplevel_handle_v1 { + struct wlr_ext_foreign_toplevel_list_v1 *list; + struct wl_list resources; // wl_resource_get_link() + struct wl_list link; // wlr_ext_foreign_toplevel_list_v1.toplevels + + char *title; + char *app_id; + char *identifier; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_ext_foreign_toplevel_handle_v1_state { + const char *title; + const char *app_id; +}; + +struct wlr_ext_foreign_toplevel_list_v1 *wlr_ext_foreign_toplevel_list_v1_create( + struct wl_display *display, uint32_t version); + +struct wlr_ext_foreign_toplevel_handle_v1 *wlr_ext_foreign_toplevel_handle_v1_create( + struct wlr_ext_foreign_toplevel_list_v1 *list, + const struct wlr_ext_foreign_toplevel_handle_v1_state *state); + +/** + * Destroy the given toplevel handle, sending the closed event to any + * client. + */ +void wlr_ext_foreign_toplevel_handle_v1_destroy( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel); + +void wlr_ext_foreign_toplevel_handle_v1_update_state( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, + const struct wlr_ext_foreign_toplevel_handle_v1_state *state); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index d8e93d62..6a8d82b4 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -22,6 +22,7 @@ protocols = { 'content-type-v1': wl_protocol_dir / 'staging/content-type/content-type-v1.xml', 'cursor-shape-v1': wl_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', 'drm-lease-v1': wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', + 'ext-foreign-toplevel-list-v1': wl_protocol_dir / 'staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml', 'ext-idle-notify-v1': wl_protocol_dir / 'staging/ext-idle-notify/ext-idle-notify-v1.xml', 'ext-session-lock-v1': wl_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml', 'fractional-scale-v1': wl_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml', diff --git a/types/meson.build b/types/meson.build index 688ddece..8962a390 100644 --- a/types/meson.build +++ b/types/meson.build @@ -42,6 +42,7 @@ wlr_files += files( 'wlr_drm.c', 'wlr_export_dmabuf_v1.c', 'wlr_foreign_toplevel_management_v1.c', + 'wlr_ext_foreign_toplevel_list_v1.c', 'wlr_fullscreen_shell_v1.c', 'wlr_gamma_control_v1.c', 'wlr_idle_inhibit_v1.c', diff --git a/types/wlr_ext_foreign_toplevel_list_v1.c b/types/wlr_ext_foreign_toplevel_list_v1.c new file mode 100644 index 00000000..326e4f41 --- /dev/null +++ b/types/wlr_ext_foreign_toplevel_list_v1.c @@ -0,0 +1,275 @@ +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include "ext-foreign-toplevel-list-v1-protocol.h" + +#include "util/token.h" + +#define FOREIGN_TOPLEVEL_LIST_V1_VERSION 1 + +static const struct ext_foreign_toplevel_list_v1_interface toplevel_handle_impl; + +static void foreign_toplevel_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct ext_foreign_toplevel_list_v1_interface toplevel_handle_impl = { + .destroy = foreign_toplevel_handle_destroy, +}; + +void wlr_ext_foreign_toplevel_handle_v1_update_state( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, + const struct wlr_ext_foreign_toplevel_handle_v1_state *state) { + bool changed_app_id = false; + bool changed_title = false; + + if (state->app_id) { + if (strcmp(toplevel->app_id, state->app_id) != 0) { + free(toplevel->app_id); + toplevel->app_id = strdup(state->app_id); + if (toplevel->app_id == NULL) { + wlr_log(WLR_ERROR, "failed to allocate memory for toplevel app_id"); + return; + } + changed_app_id = true; + } + } else { + if (toplevel->app_id) { + free(toplevel->app_id); + toplevel->app_id = NULL; + changed_app_id = true; + } + } + + if (state->title) { + if (strcmp(toplevel->title, state->title) != 0) { + free(toplevel->title); + toplevel->title = strdup(state->title); + if (toplevel->title == NULL) { + wlr_log(WLR_ERROR, "failed to allocate memory for toplevel title"); + return; + } + changed_title = true; + } + } else { + if (toplevel->title) { + free(toplevel->title); + toplevel->title = NULL; + changed_title = true; + } + } + + if (!changed_app_id && !changed_title) { + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + if (changed_app_id) { + ext_foreign_toplevel_handle_v1_send_app_id(resource, state->app_id ? state->app_id : ""); + } + if (changed_title) { + ext_foreign_toplevel_handle_v1_send_title(resource, state->title ? state->title : ""); + } + ext_foreign_toplevel_handle_v1_send_done(resource); + } +} + +void wlr_ext_foreign_toplevel_handle_v1_destroy( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel) { + if (!toplevel) { + return; + } + + wl_signal_emit_mutable(&toplevel->events.destroy, NULL); + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &toplevel->resources) { + ext_foreign_toplevel_handle_v1_send_closed(resource); + wl_resource_set_user_data(resource, NULL); + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + } + + wl_list_remove(&toplevel->link); + + free(toplevel->title); + free(toplevel->app_id); + free(toplevel->identifier); + free(toplevel); +} + +static void foreign_toplevel_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static struct wl_resource *create_toplevel_resource_for_resource( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, + struct wl_resource *list_resource) { + struct wl_client *client = wl_resource_get_client(list_resource); + struct wl_resource *resource = wl_resource_create(client, + &ext_foreign_toplevel_handle_v1_interface, + wl_resource_get_version(list_resource), 0); + if (!resource) { + wl_client_post_no_memory(client); + return NULL; + } + + wl_resource_set_implementation(resource, &toplevel_handle_impl, toplevel, + foreign_toplevel_resource_destroy); + + wl_list_insert(&toplevel->resources, wl_resource_get_link(resource)); + ext_foreign_toplevel_list_v1_send_toplevel(list_resource, resource); + return resource; +} + +static void toplevel_send_details_to_toplevel_resource( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, + struct wl_resource *resource) { + if (toplevel->title) { + ext_foreign_toplevel_handle_v1_send_title(resource, toplevel->title); + } + if (toplevel->app_id) { + ext_foreign_toplevel_handle_v1_send_app_id(resource, toplevel->app_id); + } + assert(toplevel->identifier); // Required to exist by protocol. + ext_foreign_toplevel_handle_v1_send_identifier(resource, toplevel->identifier); + ext_foreign_toplevel_handle_v1_send_done(resource); +} + +struct wlr_ext_foreign_toplevel_handle_v1 * +wlr_ext_foreign_toplevel_handle_v1_create(struct wlr_ext_foreign_toplevel_list_v1 *list, + const struct wlr_ext_foreign_toplevel_handle_v1_state *state) { + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel = calloc(1, sizeof(*toplevel)); + if (!toplevel) { + wlr_log(WLR_ERROR, "failed to allocate memory for toplevel handle"); + return NULL; + } + + toplevel->identifier = calloc(TOKEN_SIZE, sizeof(char)); + if (toplevel->identifier == NULL) { + wlr_log(WLR_ERROR, "failed to allocate memory for toplevel identifier"); + free(toplevel); + return NULL; + } + + if (!generate_token(toplevel->identifier)) { + free(toplevel->identifier); + free(toplevel); + return NULL; + } + + wl_list_insert(&list->toplevels, &toplevel->link); + toplevel->list = list; + if (state->app_id) { + toplevel->app_id = strdup(state->app_id); + } + if (state->title) { + toplevel->title = strdup(state->title); + } + + wl_list_init(&toplevel->resources); + + wl_signal_init(&toplevel->events.destroy); + + struct wl_resource *list_resource, *toplevel_resource; + wl_resource_for_each(list_resource, &list->resources) { + toplevel_resource = create_toplevel_resource_for_resource(toplevel, list_resource); + if (!toplevel_resource) { + continue; + } + toplevel_send_details_to_toplevel_resource(toplevel, toplevel_resource); + } + + return toplevel; +} + +static const struct ext_foreign_toplevel_list_v1_interface + foreign_toplevel_list_impl; + +static void foreign_toplevel_list_handle_stop(struct wl_client *client, + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_foreign_toplevel_list_v1_interface, + &foreign_toplevel_list_impl)); + + ext_foreign_toplevel_list_v1_send_finished(resource); + wl_resource_destroy(resource); +} + +static const struct ext_foreign_toplevel_list_v1_interface + foreign_toplevel_list_impl = { + .stop = foreign_toplevel_list_handle_stop +}; + +static void foreign_toplevel_list_resource_destroy( + struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void foreign_toplevel_list_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_ext_foreign_toplevel_list_v1 *list = data; + struct wl_resource *resource = wl_resource_create(client, + &ext_foreign_toplevel_list_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &foreign_toplevel_list_impl, + list, foreign_toplevel_list_resource_destroy); + + wl_list_insert(&list->resources, wl_resource_get_link(resource)); + + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel; + wl_list_for_each(toplevel, &list->toplevels, link) { + struct wl_resource *toplevel_resource = + create_toplevel_resource_for_resource(toplevel, resource); + toplevel_send_details_to_toplevel_resource(toplevel, + toplevel_resource); + } +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_foreign_toplevel_list_v1 *list = + wl_container_of(listener, list, display_destroy); + wl_signal_emit_mutable(&list->events.destroy, NULL); + wl_list_remove(&list->display_destroy.link); + wl_global_destroy(list->global); + free(list); +} + +struct wlr_ext_foreign_toplevel_list_v1 *wlr_ext_foreign_toplevel_list_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= FOREIGN_TOPLEVEL_LIST_V1_VERSION); + + struct wlr_ext_foreign_toplevel_list_v1 *list = calloc(1, sizeof(*list)); + if (!list) { + return NULL; + } + + list->global = wl_global_create(display, + &ext_foreign_toplevel_list_v1_interface, + version, list, + foreign_toplevel_list_bind); + if (!list->global) { + free(list); + return NULL; + } + + wl_signal_init(&list->events.destroy); + wl_list_init(&list->resources); + wl_list_init(&list->toplevels); + + list->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &list->display_destroy); + + return list; +}