diff options
author | emersion <contact@emersion.fr> | 2018-12-09 00:36:00 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-12-09 00:36:00 +0100 |
commit | 3699496256cd7a7c381dbc2cd6afe39cda9390dc (patch) | |
tree | 57c21c576d286c6330e991caf6980171a8d10783 | |
parent | e75075dfa20250e80a41402ec91d648516087f39 (diff) | |
parent | 16175751d64c7f471a46239f02576a3e367afc7b (diff) |
Merge pull request #1201 from ammen99/master
Implement wlr-foreign-toplevel-management-unstable-v1
-rw-r--r-- | examples/foreign-toplevel.c | 358 | ||||
-rw-r--r-- | examples/meson.build | 4 | ||||
-rw-r--r-- | include/rootston/desktop.h | 2 | ||||
-rw-r--r-- | include/rootston/view.h | 19 | ||||
-rw-r--r-- | include/wlr/types/meson.build | 1 | ||||
-rw-r--r-- | include/wlr/types/wlr_foreign_toplevel_management_v1.h | 120 | ||||
-rw-r--r-- | protocol/meson.build | 2 | ||||
-rw-r--r-- | protocol/wlr-foreign-toplevel-management-unstable-v1.xml | 235 | ||||
-rw-r--r-- | rootston/desktop.c | 86 | ||||
-rw-r--r-- | rootston/wl_shell.c | 26 | ||||
-rw-r--r-- | rootston/xdg_shell.c | 28 | ||||
-rw-r--r-- | rootston/xdg_shell_v6.c | 28 | ||||
-rw-r--r-- | rootston/xwayland.c | 29 | ||||
-rw-r--r-- | types/meson.build | 1 | ||||
-rw-r--r-- | types/wlr_foreign_toplevel_management_v1.c | 578 |
15 files changed, 1516 insertions, 1 deletions
diff --git a/examples/foreign-toplevel.c b/examples/foreign-toplevel.c new file mode 100644 index 00000000..40ddbb09 --- /dev/null +++ b/examples/foreign-toplevel.c @@ -0,0 +1,358 @@ +#define _POSIX_C_SOURCE 200809L +#include <getopt.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <wayland-client.h> +#include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" + +/** + * Usage: + * 1. foreign-toplevel + * Prints a list of opened toplevels + * 2. foreign-toplevel -f <id> + * Focus the toplevel with the given id + * 3. foreign-toplevel -a <id> + * Maximize the toplevel with the given id + * 4. foreign-toplevel -u <id> + * Unmaximize the toplevel with the given id + * 5. foreign-toplevel -i <id> + * Minimize the toplevel with the given id + * 6. foreign-toplevel -r <id> + * Restore(unminimize) the toplevel with the given id + * 7. foreign-toplevel -c <id> + * Close the toplevel with the given id + * 8. foreign-toplevel -m + * Continuously print changes to the list of opened toplevels. + * Can be used together with some of the previous options. + */ + +enum toplevel_state_field { + TOPLEVEL_STATE_MAXIMIZED = 1, + TOPLEVEL_STATE_MINIMIZED = 2, + TOPLEVEL_STATE_ACTIVATED = 4, + TOPLEVEL_STATE_INVALID = 8, +}; + +struct toplevel_state { + char *title; + char *app_id; + + uint32_t state; +}; + +static void copy_state(struct toplevel_state *current, + struct toplevel_state *pending) { + if (current->title && pending->title) { + free(current->title); + } + if (current->app_id && pending->app_id) { + free(current->app_id); + } + + if (pending->title) { + current->title = pending->title; + pending->title = NULL; + } + if (pending->app_id) { + current->app_id = pending->app_id; + pending->app_id = NULL; + } + + if (!(pending->state & TOPLEVEL_STATE_INVALID)) { + current->state = pending->state; + } + + pending->state = TOPLEVEL_STATE_INVALID; +} + +static uint32_t global_id = 0; +struct toplevel_v1 { + struct wl_list link; + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel; + + uint32_t id; + struct toplevel_state current, pending; +}; + +static void print_toplevel(struct toplevel_v1 *toplevel, bool print_endl) { + printf("-> %d. title=%s app_id=%s", toplevel->id, + toplevel->current.title ?: "(nil)", + toplevel->current.app_id ?: "(nil)"); + + if (print_endl) { + printf("\n"); + } +} + +static void print_toplevel_state(struct toplevel_v1 *toplevel, bool print_endl) { + if (toplevel->current.state & TOPLEVEL_STATE_MAXIMIZED) { + printf(" maximized"); + } else { + printf(" unmaximized"); + } + if (toplevel->current.state & TOPLEVEL_STATE_MINIMIZED) { + printf(" minimized"); + } else { + printf(" unminimized"); + } + if (toplevel->current.state & TOPLEVEL_STATE_ACTIVATED) { + printf(" active"); + } else { + printf(" inactive"); + } + + if (print_endl) { + printf("\n"); + } +} + +static void toplevel_handle_title(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel, + const char *title) { + struct toplevel_v1 *toplevel = data; + free(toplevel->pending.title); + toplevel->pending.title = strdup(title); +} + +static void toplevel_handle_app_id(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel, + const char *app_id) { + struct toplevel_v1 *toplevel = data; + free(toplevel->pending.app_id); + toplevel->pending.app_id = strdup(app_id); +} + +static void toplevel_handle_output_enter(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel, + struct wl_output *output) { + struct toplevel_v1 *toplevel = data; + print_toplevel(toplevel, false); + printf(" enter output %u\n", + (uint32_t)(size_t)wl_output_get_user_data(output)); +} + +static void toplevel_handle_output_leave(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel, + struct wl_output *output) { + struct toplevel_v1 *toplevel = data; + print_toplevel(toplevel, false); + printf(" leave output %u\n", + (uint32_t)(size_t)wl_output_get_user_data(output)); +} + +static uint32_t array_to_state(struct wl_array *array) { + uint32_t state = 0; + uint32_t *entry; + wl_array_for_each(entry, array) { + if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED) + state |= TOPLEVEL_STATE_MAXIMIZED; + if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED) + state |= TOPLEVEL_STATE_MINIMIZED; + if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) + state |= TOPLEVEL_STATE_ACTIVATED; + } + + return state; +} + +static void toplevel_handle_state(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel, + struct wl_array *state) { + struct toplevel_v1 *toplevel = data; + toplevel->pending.state = array_to_state(state); +} + +static void toplevel_handle_done(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel) { + struct toplevel_v1 *toplevel = data; + bool state_changed = toplevel->current.state != toplevel->pending.state; + + copy_state(&toplevel->current, &toplevel->pending); + + print_toplevel(toplevel, !state_changed); + if (state_changed) { + print_toplevel_state(toplevel, true); + } +} + +static void toplevel_handle_closed(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel) { + struct toplevel_v1 *toplevel = data; + print_toplevel(toplevel, false); + printf(" closed\n"); + + zwlr_foreign_toplevel_handle_v1_destroy(zwlr_toplevel); +} + +static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_impl = { + .title = toplevel_handle_title, + .app_id = toplevel_handle_app_id, + .output_enter = toplevel_handle_output_enter, + .output_leave = toplevel_handle_output_leave, + .state = toplevel_handle_state, + .done = toplevel_handle_done, + .closed = toplevel_handle_closed, +}; + +static struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager = NULL; +static struct wl_list toplevel_list; + +static void toplevel_manager_handle_toplevel(void *data, + struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel) { + struct toplevel_v1 *toplevel = calloc(1, sizeof(struct toplevel_v1)); + if (!toplevel) { + fprintf(stderr, "Failed to allocate memory for toplevel\n"); + return; + } + + toplevel->id = global_id++; + toplevel->zwlr_toplevel = zwlr_toplevel; + wl_list_insert(&toplevel_list, &toplevel->link); + + zwlr_foreign_toplevel_handle_v1_add_listener(zwlr_toplevel, &toplevel_impl, + toplevel); +} + +static void toplevel_manager_handle_finished(void *data, + struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager) { + zwlr_foreign_toplevel_manager_v1_destroy(toplevel_manager); +} + +static const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_impl = { + .toplevel = toplevel_manager_handle_toplevel, + .finished = toplevel_manager_handle_finished, +}; + +struct wl_seat *seat = NULL; +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + if (strcmp(interface, wl_output_interface.name) == 0) { + struct wl_output *output = wl_registry_bind(registry, name, + &wl_output_interface, version); + wl_output_set_user_data(output, (void*)(size_t)name); // assign some ID to the output + } else if (strcmp(interface, + zwlr_foreign_toplevel_manager_v1_interface.name) == 0) { + toplevel_manager = wl_registry_bind(registry, name, + &zwlr_foreign_toplevel_manager_v1_interface, 1); + + wl_list_init(&toplevel_list); + zwlr_foreign_toplevel_manager_v1_add_listener(toplevel_manager, + &toplevel_manager_impl, NULL); + } else if (strcmp(interface, wl_seat_interface.name) == 0 && seat == NULL) { + seat = wl_registry_bind(registry, name, &wl_seat_interface, version); + } +} + +static void handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) { + // who cares +} + +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = handle_global_remove, +}; + +/* return NULL when id == -1 + * exit if the given ID cannot be found in the list of toplevels */ +static struct toplevel_v1 *toplevel_by_id_or_bail(int32_t id) { + if (id == -1) { + return NULL; + } + + struct toplevel_v1 *toplevel; + wl_list_for_each(toplevel, &toplevel_list, link) { + if (toplevel->id == (uint32_t)id) { + return toplevel; + } + } + + fprintf(stderr, "No toplevel with the given id: %d\n", id); + exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) { + int focus_id = -1, close_id = -1; + int maximize_id = -1, unmaximize_id = -1; + int minimize_id = -1, restore_id = -1; + int one_shot = 1; + int c; + + // TODO maybe print usage with -h? + while ((c = getopt(argc, argv, "f:a:u:i:r:c:m")) != -1) { + switch (c) { + case 'f': + focus_id = atoi(optarg); + break; + case 'a': + maximize_id = atoi(optarg); + break; + case 'u': + unmaximize_id = atoi(optarg); + break; + case 'i': + minimize_id = atoi(optarg); + break; + case 'r': + restore_id = atoi(optarg); + break; + case 'c': + close_id = atoi(optarg); + break; + case 'm': + one_shot = 0; + break; + } + } + + struct wl_display *display = wl_display_connect(NULL); + if (display == NULL) { + fprintf(stderr, "Failed to create display\n"); + return EXIT_FAILURE; + } + + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_dispatch(display); + wl_display_roundtrip(display); + + if (toplevel_manager == NULL) { + fprintf(stderr, "wlr-foreign-toplevel not available\n"); + return EXIT_FAILURE; + } + wl_display_roundtrip(display); // load list of toplevels + wl_display_roundtrip(display); // load toplevel details + + struct toplevel_v1 *toplevel; + if ((toplevel = toplevel_by_id_or_bail(focus_id))) { + zwlr_foreign_toplevel_handle_v1_activate(toplevel->zwlr_toplevel, seat); + } + if ((toplevel = toplevel_by_id_or_bail(maximize_id))) { + zwlr_foreign_toplevel_handle_v1_set_maximized(toplevel->zwlr_toplevel); + } + if ((toplevel = toplevel_by_id_or_bail(unmaximize_id))) { + zwlr_foreign_toplevel_handle_v1_unset_maximized(toplevel->zwlr_toplevel); + } + if ((toplevel = toplevel_by_id_or_bail(minimize_id))) { + zwlr_foreign_toplevel_handle_v1_set_minimized(toplevel->zwlr_toplevel); + } + if ((toplevel = toplevel_by_id_or_bail(restore_id))) { + zwlr_foreign_toplevel_handle_v1_unset_minimized(toplevel->zwlr_toplevel); + } + if ((toplevel = toplevel_by_id_or_bail(close_id))) { + zwlr_foreign_toplevel_handle_v1_close(toplevel->zwlr_toplevel); + } + + wl_display_flush(display); + + if (one_shot == 0) { + while (wl_display_dispatch(display) != -1) { + // This space intentionally left blank + } + } + + return EXIT_SUCCESS; +} diff --git a/examples/meson.build b/examples/meson.build index cbf39347..589a7326 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -108,6 +108,10 @@ examples = { 'text-input': { 'src': 'text-input.c', 'dep': [wayland_cursor, wayland_client, wlr_protos, wlroots], + }, + 'foreign-toplevel': { + 'src': 'foreign-toplevel.c', + 'dep': [wayland_client, wlr_protos, wlroots], }, } diff --git a/include/rootston/desktop.h b/include/rootston/desktop.h index cd85e794..b1fcaca0 100644 --- a/include/rootston/desktop.h +++ b/include/rootston/desktop.h @@ -4,6 +4,7 @@ #include <wayland-server.h> #include <wlr/config.h> #include <wlr/types/wlr_compositor.h> +#include <wlr/types/wlr_foreign_toplevel_management_v1.h> #include <wlr/types/wlr_gamma_control_v1.h> #include <wlr/types/wlr_gamma_control.h> #include <wlr/types/wlr_idle_inhibit_v1.h> @@ -63,6 +64,7 @@ struct roots_desktop { struct wlr_tablet_manager_v2 *tablet_v2; struct wlr_pointer_constraints_v1 *pointer_constraints; struct wlr_presentation *presentation; + struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager_v1; struct wl_listener new_output; struct wl_listener layout_change; diff --git a/include/rootston/view.h b/include/rootston/view.h index e67aaf36..b1feb5ce 100644 --- a/include/rootston/view.h +++ b/include/rootston/view.h @@ -3,6 +3,7 @@ #include <stdbool.h> #include <wlr/config.h> #include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_foreign_toplevel_management_v1.h> #include <wlr/types/wlr_surface.h> #include <wlr/types/wlr_xdg_decoration_v1.h> #include <wlr/types/wlr_xdg_shell_v6.h> @@ -18,6 +19,8 @@ struct roots_wl_shell_surface { struct wl_listener request_maximize; struct wl_listener request_fullscreen; struct wl_listener set_state; + struct wl_listener set_title; + struct wl_listener set_class; struct wl_listener surface_commit; }; @@ -33,6 +36,8 @@ struct roots_xdg_surface_v6 { struct wl_listener request_resize; struct wl_listener request_maximize; struct wl_listener request_fullscreen; + struct wl_listener set_title; + struct wl_listener set_app_id; struct wl_listener surface_commit; @@ -52,6 +57,9 @@ struct roots_xdg_surface { struct wl_listener request_resize; struct wl_listener request_maximize; struct wl_listener request_fullscreen; + struct wl_listener set_title; + struct wl_listener set_app_id; + struct wl_listener surface_commit; @@ -71,6 +79,8 @@ struct roots_xwayland_surface { struct wl_listener request_fullscreen; struct wl_listener map; struct wl_listener unmap; + struct wl_listener set_title; + struct wl_listener set_class; struct wl_listener surface_commit; }; @@ -132,6 +142,11 @@ struct roots_view { struct wlr_surface *wlr_surface; struct wl_list children; // roots_view_child::link + struct wlr_foreign_toplevel_handle_v1 *toplevel_handle; + struct wl_listener toplevel_handle_request_maximize; + struct wl_listener toplevel_handle_request_activate; + struct wl_listener toplevel_handle_request_close; + struct wl_listener new_subsurface; struct { @@ -218,6 +233,10 @@ bool view_center(struct roots_view *view); void view_setup(struct roots_view *view); void view_teardown(struct roots_view *view); +void view_set_title(struct roots_view *view, const char *title); +void view_set_app_id(struct roots_view *view, const char *app_id); +void view_create_foreign_toplevel_handle(struct roots_view *view); + void view_get_deco_box(const struct roots_view *view, struct wlr_box *box); enum roots_deco_part { diff --git a/include/wlr/types/meson.build b/include/wlr/types/meson.build index 72debcf3..e2ce86e3 100644 --- a/include/wlr/types/meson.build +++ b/include/wlr/types/meson.build @@ -5,6 +5,7 @@ install_headers( 'wlr_cursor.h', 'wlr_data_device.h', 'wlr_export_dmabuf_v1.h', + 'wlr_foreign_toplevel_management_v1.h', 'wlr_gamma_control_v1.h', 'wlr_gamma_control.h', 'wlr_gtk_primary_selection.h', diff --git a/include/wlr/types/wlr_foreign_toplevel_management_v1.h b/include/wlr/types/wlr_foreign_toplevel_management_v1.h new file mode 100644 index 00000000..75ae0e64 --- /dev/null +++ b/include/wlr/types/wlr_foreign_toplevel_management_v1.h @@ -0,0 +1,120 @@ +/* + * 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_MANAGEMENT_V1_H +#define WLR_TYPES_WLR_FOREIGN_TOPLEVEL_MANAGEMENT_V1_H + +#include <wayland-server.h> +#include <wlr/types/wlr_output.h> + +struct wlr_foreign_toplevel_manager_v1 { + struct wl_event_loop *event_loop; + struct wl_global *global; + struct wl_list resources; + struct wl_list toplevels; // wlr_foreign_toplevel_handle_v1::link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +enum wlr_foreign_toplevel_handle_v1_state { + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED = 1, + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED = 2, + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED = 4, +}; + +struct wlr_foreign_toplevel_handle_v1_output { + struct wl_list link; // wlr_foreign_toplevel_handle_v1::outputs + struct wl_listener output_destroy; + struct wlr_output *output; + + struct wlr_foreign_toplevel_handle_v1 *toplevel; +}; + +struct wlr_foreign_toplevel_handle_v1 { + struct wlr_foreign_toplevel_manager_v1 *manager; + struct wl_list resources; + struct wl_list link; + struct wl_event_source *idle_source; + + char *title; + char *app_id; + struct wl_list outputs; // wlr_foreign_toplevel_v1_output + uint32_t state; // wlr_foreign_toplevel_v1_state + + struct { + // wlr_foreign_toplevel_handle_v1_maximized_event + struct wl_signal request_maximize; + //wlr_foreign_toplevel_handle_v1_minimized_event + struct wl_signal request_minimize; + //wlr_foreign_toplevel_handle_v1_activated_event + struct wl_signal request_activate; + struct wl_signal request_close; + + //wlr_foreign_toplevel_handle_v1_set_rectangle_event + struct wl_signal set_rectangle; + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_foreign_toplevel_handle_v1_maximized_event { + struct wlr_foreign_toplevel_handle_v1 *toplevel; + bool maximized; +}; + +struct wlr_foreign_toplevel_handle_v1_minimized_event { + struct wlr_foreign_toplevel_handle_v1 *toplevel; + bool minimized; +}; + +struct wlr_foreign_toplevel_handle_v1_activated_event { + struct wlr_foreign_toplevel_handle_v1 *toplevel; + struct wlr_seat *seat; +}; + +struct wlr_foreign_toplevel_handle_v1_set_rectangle_event { + struct wlr_foreign_toplevel_handle_v1 *toplevel; + struct wlr_surface *surface; + int32_t x, y, width, height; +}; + +struct wlr_foreign_toplevel_manager_v1 *wlr_foreign_toplevel_manager_v1_create( + struct wl_display *display); +void wlr_foreign_toplevel_manager_v1_destroy( + struct wlr_foreign_toplevel_manager_v1 *manager); + +struct wlr_foreign_toplevel_handle_v1 *wlr_foreign_toplevel_handle_v1_create( + struct wlr_foreign_toplevel_manager_v1 *manager); +void wlr_foreign_toplevel_handle_v1_destroy( + struct wlr_foreign_toplevel_handle_v1 *toplevel); + +void wlr_foreign_toplevel_handle_v1_set_title( + struct wlr_foreign_toplevel_handle_v1 *toplevel, const char *title); +void wlr_foreign_toplevel_handle_v1_set_app_id( + struct wlr_foreign_toplevel_handle_v1 *toplevel, const char *app_id); + +void wlr_foreign_toplevel_handle_v1_output_enter( + struct wlr_foreign_toplevel_handle_v1 *toplevel, struct wlr_output *output); +void wlr_foreign_toplevel_handle_v1_output_leave( + struct wlr_foreign_toplevel_handle_v1 *toplevel, struct wlr_output *output); + +void wlr_foreign_toplevel_handle_v1_set_maximized( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool maximized); +void wlr_foreign_toplevel_handle_v1_set_minimized( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool minimized); +void wlr_foreign_toplevel_handle_v1_set_activated( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool activated); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index dfe2a5ec..58f57046 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -30,6 +30,7 @@ protocols = [ 'virtual-keyboard-unstable-v1.xml', 'wlr-export-dmabuf-unstable-v1.xml', 'wlr-gamma-control-unstable-v1.xml', + 'wlr-foreign-toplevel-management-unstable-v1.xml', 'wlr-input-inhibitor-unstable-v1.xml', 'wlr-layer-shell-unstable-v1.xml', 'wlr-screencopy-unstable-v1.xml', @@ -47,6 +48,7 @@ client_protocols = [ 'text-input-unstable-v3.xml', 'wlr-export-dmabuf-unstable-v1.xml', 'wlr-gamma-control-unstable-v1.xml', + 'wlr-foreign-toplevel-management-unstable-v1.xml', 'wlr-input-inhibitor-unstable-v1.xml', 'wlr-layer-shell-unstable-v1.xml', 'wlr-screencopy-unstable-v1.xml', diff --git a/protocol/wlr-foreign-toplevel-management-unstable-v1.xml b/protocol/wlr-foreign-toplevel-management-unstable-v1.xml new file mode 100644 index 00000000..234d9535 --- /dev/null +++ b/protocol/wlr-foreign-toplevel-management-unstable-v1.xml @@ -0,0 +1,235 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="wlr_foreign_toplevel_management_unstable_v1"> + <copyright> + Copyright © 2018 Ilia Bozhinov + + 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="zwlr_foreign_toplevel_manager_v1" version="1"> + <description summary="list and control opened apps"> + The purpose of this protocol is to enable the creation of taskbars + and docks by providing them with a list of opened applications and + letting them request certain actions on them, like maximizing, etc. + + After a client binds the zwlr_foreign_toplevel_manager_v1, each opened + toplevel window will be sent via the toplevel event + </description> + + <event name="toplevel"> + <description summary="a toplevel has been created"> + This event is emitted whenever a new toplevel window is created. It + is emitted for all toplevels, regardless of the app that has created + them. + + All initial details of the toplevel(title, app_id, states, etc.) will + be sent immediately after this event via the corresponding events in + zwlr_foreign_toplevel_handle_v1. + </description> + <arg name="toplevel" type="new_id" interface="zwlr_foreign_toplevel_handle_v1"/> + </event> + + <request name="stop"> + <description summary="stop sending events"> + Indicates the client no longer wishes to receive events for new toplevels. + However the compositor may emit further toplevel_created events, until + the finished event is emitted. + + The client must not send any more requests after this one. + </description> + </request> + + <event name="finished"> + <description summary="the compositor has finished with the toplevel manager"> + This event indicates that the compositor is done sending events to the + zwlr_foreign_toplevel_manager_v1. The server will destroy the object + immediately after sending this request, so it will become invalid and + the client should free any resources associated with it. + </description> + </event> + </interface> + + <interface name="zwlr_foreign_toplevel_handle_v1" version="1"> + <description summary="an opened toplevel"> + A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel + window. Each app may have multiple opened toplevels. + + Each toplevel has a list of outputs it is visible on, conveyed to the + client with the output_enter and output_leave events. + </description> + + <event name="title"> + <description summary="title change"> + This event is emitted whenever the title of the toplevel changes. + </description> + <arg name="title" type="string"/> + </event> + + <event name="app_id"> + <description summary="app-id change"> + This event is emitted whenever the app-id of the toplevel changes. + </description> + <arg name="app_id" type="string"/> + </event> + + <event name="output_enter"> + <description summary="toplevel entered an output"> + This event is emitted whenever the toplevel becomes visible on + the given output. A toplevel may be visible on multiple outputs. + </description> + <arg name="output" type="object" interface="wl_output"/> + </event> + + <event name="output_leave"> + <description summary="toplevel left an output"> + This event is emitted whenever the toplevel stops being visible on + the given output. It is guaranteed that an entered-output event + with the same output has been emitted before this event. + </description> + <arg name="output" type="object" interface="wl_output"/> + </event> + + <request name="set_maximized"> + <description summary="requests that the toplevel be maximized"> + Requests that the toplevel be maximized. If the maximized state actually + changes, this will be indicated by the state event. + </description> + </request> + + <request name="unset_maximized"> + <description summary="requests that the toplevel be unmaximized"> + Requests that the toplevel be unmaximized. If the maximized state actually + changes, this will be indicated by the state event. + </description> + </request> + + <request name="set_minimized"> + <description summary="requests that the toplevel be minimized"> + Requests that the toplevel be minimized. If the minimized state actually + changes, this will be indicated by the state event. + </description> + </request> + + <request name="unset_minimized"> + <description summary="requests that the toplevel be unminimized"> + Requests that the toplevel be unminimized. If the minimized state actually + changes, this will be indicated by the state event. + </description> + </request> + + <request name="activate"> + <description summary="activate the toplevel"> + Request that this toplevel be activated on the given seat. + There is no guarantee the toplevel will be actually activated. + </description> + <arg name="seat" type="object" interface="wl_seat"/> + </request> + + <enum name="state"> + <description summary="types of states on the toplevel"> + The different states that a toplevel can have. These have the same meaning + as the states with the same names defined in xdg-toplevel + </description> + + <entry name="maximized" value="0" summary="the toplevel is maximized"/> + <entry name="minimized" value="1" summary="the toplevel is minimized"/> + <entry name="activated" value="2" summary="the toplevel is active"/> + </enum> + + <event name="state"> + <description summary="the toplevel state changed"> + This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 + is created and each time the toplevel state changes, either because of a + compositor action or because of a request in this protocol. + </description> + + <arg name="state" type="array"/> + </event> + + <event name="done"> + <description summary="all information about the toplevel has been sent"> + This event is sent after all changes in the toplevel state have been + sent. + + This allows changes to the zwlr_foreign_toplevel_handle_v1 properties + to be seen as atomic, even if they happen via multiple events. + </description> + </event> + + <request name="close"> + <description summary="request that the toplevel be closed"> + Send a request to the toplevel to close itself. The compositor would + typically use a shell-specific method to carry out this request, for + example by sending the xdg_toplevel.close event. However, this gives + no guarantees the toplevel will actually be destroyed. If and when + this happens, the zwlr_foreign_toplevel_handle_v1.closed event will + be emitted. + </description> + </request> + + <request name="set_rectangle"> + <description summary="the rectangle which represents the toplevel"> + The rectangle of the surface specified in this request corresponds to + the place where the app using this protocol represents the given toplevel. + It can be used by the compositor as a hint for some operations, e.g + minimizing. The client is however not required to set this, in which + case the compositor is free to decide some default value. + + If the client specifies more than one rectangle, only the last one is + considered. + + The dimensions are given in surface-local coordinates. + Setting width=height=0 removes the already-set rectangle. + </description> + + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="x" type="int"/> + <arg name="y" type="int"/> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </request> + + <enum name="error"> + <entry name="invalid_rectangle" value="0" + summary="the provided rectangle is invalid"/> + </enum> + + <event name="closed"> + <description summary="this toplevel has been destroyed"> + This event means the toplevel has been destroyed. It is guaranteed there + won't be any more events for this zwlr_foreign_toplevel_handle_v1. The + toplevel itself becomes inert so any requests will be ignored except the + destroy request. + </description> + </event> + + <request name="destroy" type="destructor"> + <description summary="destroy the zwlr_foreign_toplevel_handle_v1 object"> + Destroys the zwlr_foreign_toplevel_handle_v1 object. + + This request should be called either when the client does not want to + use the toplevel anymore or after the closed event to finalize the + destruction of the object. + </description> + </request> + </interface> +</protocol> diff --git a/rootston/desktop.c b/rootston/desktop.c index 69f025e1..b41a3079 100644 --- a/rootston/desktop.c +++ b/rootston/desktop.c @@ -123,9 +123,17 @@ static void view_update_output(const struct roots_view *view, output->wlr_output, &box); if (intersected && !intersects) { wlr_surface_send_leave(view->wlr_surface, output->wlr_output); + if (view->toplevel_handle) { + wlr_foreign_toplevel_handle_v1_output_leave( + view->toplevel_handle, output->wlr_output); + } } if (!intersected && intersects) { wlr_surface_send_enter(view->wlr_surface, output->wlr_output); + if (view->toplevel_handle) { + wlr_foreign_toplevel_handle_v1_output_enter( + view->toplevel_handle, output->wlr_output); + } } } } @@ -149,6 +157,11 @@ void view_activate(struct roots_view *view, bool activate) { if (view->activate) { view->activate(view, activate); } + + if (view->toplevel_handle) { + wlr_foreign_toplevel_handle_v1_set_activated(view->toplevel_handle, + activate); + } } void view_resize(struct roots_view *view, uint32_t width, uint32_t height) { @@ -225,6 +238,11 @@ void view_maximize(struct roots_view *view, bool maximized) { view->maximize(view, maximized); } + if (view->toplevel_handle) { + wlr_foreign_toplevel_handle_v1_set_maximized(view->toplevel_handle, + maximized); + } + if (!view->maximized && maximized) { view->maximized = true; view->saved.x = view->box.x; @@ -501,6 +519,11 @@ void view_unmap(struct roots_view *view) { view->wlr_surface = NULL; view->box.width = view->box.height = 0; + + if (view->toplevel_handle) { + wlr_foreign_toplevel_handle_v1_destroy(view->toplevel_handle); + view->toplevel_handle = NULL; + } } void view_initial_focus(struct roots_view *view) { @@ -520,6 +543,7 @@ void view_setup(struct roots_view *view) { } view_update_output(view, NULL); + view_create_foreign_toplevel_handle(view); } void view_apply_damage(struct roots_view *view) { @@ -575,6 +599,66 @@ void view_update_decorated(struct roots_view *view, bool decorated) { view_damage_whole(view); } +void view_set_title(struct roots_view *view, const char *title) { + if (view->toplevel_handle) { + wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle, title); + } +} + +void view_set_app_id(struct roots_view *view, const char *app_id) { + if (view->toplevel_handle) { + wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle, app_id); + } +} + +static void handle_toplevel_handle_request_maximize(struct wl_listener *listener, + void *data) { + struct roots_view *view = wl_container_of(listener, view, + toplevel_handle_request_maximize); + struct wlr_foreign_toplevel_handle_v1_maximized_event *event = data; + view_maximize(view, event->maximized); +} + +static void handle_toplevel_handle_request_activate(struct wl_listener *listener, + void *data) { + struct roots_view *view = + wl_container_of(listener, view, toplevel_handle_request_activate); + struct wlr_foreign_toplevel_handle_v1_activated_event *event = data; + + struct roots_seat *seat; + wl_list_for_each(seat, &view->desktop->server->input->seats, link) { + if (event->seat == seat->seat) { + roots_seat_set_focus(seat, view); + } + } +} + +static void handle_toplevel_handle_request_close(struct wl_listener *listener, + void *data) { + struct roots_view *view = + wl_container_of(listener, view, toplevel_handle_request_close); + view_close(view); +} + +void view_create_foreign_toplevel_handle(struct roots_view *view) { + view->toplevel_handle = + wlr_foreign_toplevel_handle_v1_create( + view->desktop->foreign_toplevel_manager_v1); + + view->toplevel_handle_request_maximize.notify = + handle_toplevel_handle_request_maximize; + wl_signal_add(&view->toplevel_handle->events.request_maximize, + &view->toplevel_handle_request_maximize); + view->toplevel_handle_request_activate.notify = + handle_toplevel_handle_request_activate; + wl_signal_add(&view->toplevel_handle->events.request_activate, + &view->toplevel_handle_request_activate); + view->toplevel_handle_request_close.notify = + handle_toplevel_handle_request_close; + wl_signal_add(&view->toplevel_handle->events.request_close, + &view->toplevel_handle_request_close); +} + static bool view_at(struct roots_view *view, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) { if (view->type == ROOTS_WL_SHELL_VIEW && @@ -995,6 +1079,8 @@ struct roots_desktop *desktop_create(struct roots_server *server, desktop->presentation = wlr_presentation_create(server->wl_display, server->backend); + desktop->foreign_toplevel_manager_v1 = + wlr_foreign_toplevel_manager_v1_create(server->wl_display); return desktop; } diff --git a/rootston/wl_shell.c b/rootston/wl_shell.c index 0f7228bb..2bf4f4c2 100644 --- a/rootston/wl_shell.c +++ b/rootston/wl_shell.c @@ -87,6 +87,8 @@ static void destroy(struct roots_view *view) { wl_list_remove(&roots_surface->request_maximize.link); wl_list_remove(&roots_surface->request_fullscreen.link); wl_list_remove(&roots_surface->set_state.link); + wl_list_remove(&roots_surface->set_title.link); + wl_list_remove(&roots_surface->set_class.link); wl_list_remove(&roots_surface->surface_commit.link); free(roots_surface); } @@ -150,6 +152,20 @@ static void handle_set_state(struct wl_listener *listener, void *data) { } } +static void handle_set_title(struct wl_listener *listener, void *data) { + struct roots_wl_shell_surface *roots_surface = + wl_container_of(listener, roots_surface, set_state); + view_set_title(roots_surface->view, + roots_surface->view->wl_shell_surface->title); +} + +static void handle_set_class(struct wl_listener *listener, void *data) { + struct roots_wl_shell_surface *roots_surface = + wl_container_of(listener, roots_surface, set_state); + view_set_app_id(roots_surface->view, + roots_surface->view->wl_shell_surface->class); +} + static void handle_surface_commit(struct wl_listener *listener, void *data) { struct roots_wl_shell_surface *roots_surface = wl_container_of(listener, roots_surface, surface_commit); @@ -225,8 +241,13 @@ void handle_wl_shell_surface(struct wl_listener *listener, void *data) { handle_request_fullscreen; wl_signal_add(&surface->events.request_fullscreen, &roots_surface->request_fullscreen); + roots_surface->set_state.notify = handle_set_state; wl_signal_add(&surface->events.set_state, &roots_surface->set_state); + roots_surface->set_title.notify = handle_set_title; + wl_signal_add(&surface->events.set_title, &roots_surface->set_title); + roots_surface->set_class.notify = handle_set_class; + wl_signal_add(&surface->events.set_class, &roots_surface->set_class); roots_surface->surface_commit.notify = handle_surface_commit; wl_signal_add(&surface->surface->events.commit, &roots_surface->surface_commit); @@ -249,6 +270,11 @@ void handle_wl_shell_surface(struct wl_listener *listener, void *data) { view_map(view, surface->surface); view_setup(view); + wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle, + view->wl_shell_surface->title ?: "none"); + wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle, + view->wl_shell_surface->class ?: "none"); + if (surface->state == WLR_WL_SHELL_SURFACE_STATE_TRANSIENT) { // We need to map it relative to the parent bool found = false; diff --git a/rootston/xdg_shell.c b/rootston/xdg_shell.c index d3fc5372..da8909ba 100644 --- a/rootston/xdg_shell.c +++ b/rootston/xdg_shell.c @@ -265,6 +265,8 @@ static void destroy(struct roots_view *view) { wl_list_remove(&roots_xdg_surface->request_resize.link); wl_list_remove(&roots_xdg_surface->request_maximize.link); wl_list_remove(&roots_xdg_surface->request_fullscreen.link); + wl_list_remove(&roots_xdg_surface->set_title.link); + wl_list_remove(&roots_xdg_surface->set_app_id.link); roots_xdg_surface->view->xdg_surface->data = NULL; free(roots_xdg_surface); } @@ -326,6 +328,22 @@ static void handle_request_fullscreen(struct wl_listener *listener, view_set_fullscreen(view, e->fullscreen, e->output); } +static void handle_set_title(struct wl_listener *listener, void *data) { + struct roots_xdg_surface *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, set_title); + + view_set_title(roots_xdg_surface->view, + roots_xdg_surface->view->xdg_surface->toplevel->title); +} + +static void handle_set_app_id(struct wl_listener *listener, void *data) { + struct roots_xdg_surface *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, set_app_id); + + view_set_app_id(roots_xdg_surface->view, + roots_xdg_surface->view->xdg_surface->toplevel->app_id); +} + static void handle_surface_commit(struct wl_listener *listener, void *data) { struct roots_xdg_surface *roots_surface = wl_container_of(listener, roots_surface, surface_commit); @@ -382,6 +400,11 @@ static void handle_map(struct wl_listener *listener, void *data) { view_map(view, view->xdg_surface->surface); view_setup(view); + + wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle, + view->xdg_surface->toplevel->title ?: "none"); + wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle, + view->xdg_surface->toplevel->app_id ?: "none"); } static void handle_unmap(struct wl_listener *listener, void *data) { @@ -438,6 +461,11 @@ void handle_xdg_shell_surface(struct wl_listener *listener, void *data) { roots_surface->request_fullscreen.notify = handle_request_fullscreen; wl_signal_add(&surface->toplevel->events.request_fullscreen, &roots_surface->request_fullscreen); + roots_surface->set_title.notify = handle_set_title; + wl_signal_add(&surface->toplevel->events.set_title, &roots_surface->set_title); + roots_surface->set_app_id.notify = handle_set_app_id; + wl_signal_add(&surface->toplevel->events.set_app_id, + &roots_surface->set_app_id); roots_surface->new_popup.notify = handle_new_popup; wl_signal_add(&surface->events.new_popup, &roots_surface->new_popup); surface->data = roots_surface; diff --git a/rootston/xdg_shell_v6.c b/rootston/xdg_shell_v6.c index 6bdf749f..8d989aef 100644 --- a/rootston/xdg_shell_v6.c +++ b/rootston/xdg_shell_v6.c @@ -265,6 +265,8 @@ static void destroy(struct roots_view *view) { wl_list_remove(&roots_xdg_surface->request_resize.link); wl_list_remove(&roots_xdg_surface->request_maximize.link); wl_list_remove(&roots_xdg_surface->request_fullscreen.link); + wl_list_remove(&roots_xdg_surface->set_title.link); + wl_list_remove(&roots_xdg_surface->set_app_id.link); free(roots_xdg_surface); } @@ -325,6 +327,22 @@ static void handle_request_fullscreen(struct wl_listener *listener, view_set_fullscreen(view, e->fullscreen, e->output); } +static void handle_set_title(struct wl_listener *listener, void *data) { + struct roots_xdg_surface_v6 *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, set_title); + + view_set_title(roots_xdg_surface->view, + roots_xdg_surface->view->xdg_surface_v6->toplevel->title); +} + +static void handle_set_app_id(struct wl_listener *listener, void *data) { + struct roots_xdg_surface_v6 *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, set_app_id); + + view_set_app_id(roots_xdg_surface->view, + roots_xdg_surface->view->xdg_surface_v6->toplevel->app_id); +} + static void handle_surface_commit(struct wl_listener *listener, void *data) { struct roots_xdg_surface_v6 *roots_surface = wl_container_of(listener, roots_surface, surface_commit); @@ -381,6 +399,11 @@ static void handle_map(struct wl_listener *listener, void *data) { view_map(view, view->xdg_surface_v6->surface); view_setup(view); + + wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle, + view->xdg_surface_v6->toplevel->title ?: "none"); + wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle, + view->xdg_surface_v6->toplevel->app_id ?: "none"); } static void handle_unmap(struct wl_listener *listener, void *data) { @@ -437,6 +460,11 @@ void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data) { roots_surface->request_fullscreen.notify = handle_request_fullscreen; wl_signal_add(&surface->toplevel->events.request_fullscreen, &roots_surface->request_fullscreen); + roots_surface->set_title.notify = handle_set_title; + wl_signal_add(&surface->toplevel->events.set_title, &roots_surface->set_title); + roots_surface->set_app_id.notify = handle_set_app_id; + wl_signal_add(&surface->toplevel->events.set_app_id, + &roots_surface->set_app_id); roots_surface->new_popup.notify = handle_new_popup; wl_signal_add(&surface->events.new_popup, &roots_surface->new_popup); diff --git a/rootston/xwayland.c b/rootston/xwayland.c index d98f808b..f3f962e8 100644 --- a/rootston/xwayland.c +++ b/rootston/xwayland.c @@ -114,6 +114,8 @@ static void destroy(struct roots_view *view) { wl_list_remove(&roots_surface->request_move.link); wl_list_remove(&roots_surface->request_resize.link); wl_list_remove(&roots_surface->request_maximize.link); + wl_list_remove(&roots_surface->set_title.link); + wl_list_remove(&roots_surface->set_class.link); wl_list_remove(&roots_surface->map.link); wl_list_remove(&roots_surface->unmap.link); free(roots_surface); @@ -198,6 +200,22 @@ static void handle_request_fullscreen(struct wl_listener *listener, view_set_fullscreen(view, xwayland_surface->fullscreen, NULL); } +static void handle_set_title(struct wl_listener *listener, void *data) { + struct roots_xwayland_surface *roots_surface = + wl_container_of(listener, roots_surface, set_title); + + view_set_title(roots_surface->view, + roots_surface->view->xwayland_surface->title); +} + +static void handle_set_class(struct wl_listener *listener, void *data) { + struct roots_xwayland_surface *roots_surface = + wl_container_of(listener, roots_surface, set_class); + + view_set_app_id(roots_surface->view, + roots_surface->view->xwayland_surface->class); +} + static void handle_surface_commit(struct wl_listener *listener, void *data) { struct roots_xwayland_surface *roots_surface = wl_container_of(listener, roots_surface, surface_commit); @@ -250,6 +268,11 @@ static void handle_map(struct wl_listener *listener, void *data) { } view_setup(view); + + wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle, + view->xwayland_surface->title ?: "none"); + wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle, + view->xwayland_surface->class ?: "none"); } else { view_initial_focus(view); } @@ -261,7 +284,6 @@ static void handle_unmap(struct wl_listener *listener, void *data) { struct roots_view *view = roots_surface->view; wl_list_remove(&roots_surface->surface_commit.link); - view_unmap(view); } @@ -300,6 +322,11 @@ void handle_xwayland_surface(struct wl_listener *listener, void *data) { roots_surface->request_fullscreen.notify = handle_request_fullscreen; wl_signal_add(&surface->events.request_fullscreen, &roots_surface->request_fullscreen); + roots_surface->set_title.notify = handle_set_title; + wl_signal_add(&surface->events.set_title, &roots_surface->set_title); + roots_surface->set_class.notify = handle_set_class; + wl_signal_add(&surface->events.set_class, + &roots_surface->set_class); struct roots_view *view = view_create(desktop); if (view == NULL) { diff --git a/types/meson.build b/types/meson.build index 03e5fcba..643db233 100644 --- a/types/meson.build +++ b/types/meson.build @@ -28,6 +28,7 @@ lib_wlr_types = static_library( 'wlr_compositor.c', 'wlr_cursor.c', 'wlr_export_dmabuf_v1.c', + 'wlr_foreign_toplevel_management_v1.c', 'wlr_gamma_control_v1.c', 'wlr_gamma_control.c', 'wlr_gtk_primary_selection.c', diff --git a/types/wlr_foreign_toplevel_management_v1.c b/types/wlr_foreign_toplevel_management_v1.c new file mode 100644 index 00000000..7570716c --- /dev/null +++ b/types/wlr_foreign_toplevel_management_v1.c @@ -0,0 +1,578 @@ +#define _POSIX_C_SOURCE 200809L + +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <wlr/types/wlr_foreign_toplevel_management_v1.h> +#include <wlr/types/wlr_seat.h> +#include <wlr/util/log.h> +#include "util/signal.h" +#include "wlr-foreign-toplevel-management-unstable-v1-protocol.h" + +#define FOREIGN_TOPLEVEL_MANAGEMENT_V1_VERSION 1 + +static const struct zwlr_foreign_toplevel_handle_v1_interface toplevel_handle_impl; + +static struct wlr_foreign_toplevel_handle_v1 *toplevel_handle_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_foreign_toplevel_handle_v1_interface, + &toplevel_handle_impl)); + return wl_resource_get_user_data(resource); +} + +static void toplevel_handle_send_maximized_event(struct wl_resource *resource, + bool state) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = + toplevel_handle_from_resource(resource); + if (!toplevel) { + return; + } + struct wlr_foreign_toplevel_handle_v1_maximized_event event = { + .toplevel = toplevel, + .maximized = state, + }; + wlr_signal_emit_safe(&toplevel->events.request_maximize, &event); +} + +void foreign_toplevel_handle_set_maximized(struct wl_client *client, + struct wl_resource *resource) { + toplevel_handle_send_maximized_event(resource, true); +} + +void foreign_toplevel_handle_unset_maximized(struct wl_client *client, + struct wl_resource *resource) { + toplevel_handle_send_maximized_event(resource, false); +} + +static void toplevel_send_minimized_event(struct wl_resource *resource, + bool state) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = + toplevel_handle_from_resource(resource); + if (!toplevel) { + return; + } + + struct wlr_foreign_toplevel_handle_v1_minimized_event event = { + .toplevel = toplevel, + .minimized = state, + }; + wlr_signal_emit_safe(&toplevel->events.request_minimize, &event); +} + +static void foreign_toplevel_handle_set_minimized(struct wl_client *client, + struct wl_resource *resource) { + toplevel_send_minimized_event(resource, true); +} + +static void foreign_toplevel_handle_unset_minimized(struct wl_client *client, + struct wl_resource *resource) { + toplevel_send_minimized_event(resource, false); +} + +static void foreign_toplevel_handle_activate(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = + toplevel_handle_from_resource(resource); + if (!toplevel) { + return; + } + + struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat); + struct wlr_foreign_toplevel_handle_v1_activated_event event = { + .toplevel = toplevel, + .seat = seat_client->seat, + }; + wlr_signal_emit_safe(&toplevel->events.request_activate, &event); +} + +static void foreign_toplevel_handle_close(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = + toplevel_handle_from_resource(resource); + if (!toplevel) { + return; + } + wlr_signal_emit_safe(&toplevel->events.request_close, toplevel); +} + +static void foreign_toplevel_handle_set_rectangle(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *surface, + int32_t x, int32_t y, int32_t width, int32_t height) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = + toplevel_handle_from_resource(resource); + if (!toplevel) { + return; + } + + if (width < 0 || height < 0) { + wl_resource_post_error(resource, + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ERROR_INVALID_RECTANGLE, + "invalid rectangle passed to set_rectangle: width/height < 0"); + return; + } + + struct wlr_foreign_toplevel_handle_v1_set_rectangle_event event = { + .toplevel = toplevel, + .surface = wlr_surface_from_resource(surface), + .x = x, + .y = y, + .width = width, + .height = height, + }; + wlr_signal_emit_safe(&toplevel->events.set_rectangle, &event); +} + +static void foreign_toplevel_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwlr_foreign_toplevel_handle_v1_interface toplevel_handle_impl = { + .set_maximized = foreign_toplevel_handle_set_maximized, + .unset_maximized = foreign_toplevel_handle_unset_maximized, + .set_minimized = foreign_toplevel_handle_set_minimized, + .unset_minimized = foreign_toplevel_handle_unset_minimized, + .activate = foreign_toplevel_handle_activate, + .close = foreign_toplevel_handle_close, + .set_rectangle = foreign_toplevel_handle_set_rectangle, + .destroy = foreign_toplevel_handle_destroy +}; + +static void toplevel_idle_send_done(void *data) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = data; + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + zwlr_foreign_toplevel_handle_v1_send_done(resource); + } + + toplevel->idle_source = NULL; +} + +static void toplevel_update_idle_source( + struct wlr_foreign_toplevel_handle_v1 *toplevel) { + if (toplevel->idle_source) { + return; + } + + toplevel->idle_source = wl_event_loop_add_idle(toplevel->manager->event_loop, + toplevel_idle_send_done, toplevel); +} + +void wlr_foreign_toplevel_handle_v1_set_title( + struct wlr_foreign_toplevel_handle_v1 *toplevel, const char *title) { + free(toplevel->title); + toplevel->title = strdup(title); + + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + zwlr_foreign_toplevel_handle_v1_send_title(resource, title); + } + + toplevel_update_idle_source(toplevel); +} + +void wlr_foreign_toplevel_handle_v1_set_app_id( + struct wlr_foreign_toplevel_handle_v1 *toplevel, const char *app_id) { + free(toplevel->app_id); + toplevel->app_id = strdup(app_id); + + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + zwlr_foreign_toplevel_handle_v1_send_app_id(resource, app_id); + } + + toplevel_update_idle_source(toplevel); +} + +static void send_output_to_resource(struct wl_resource *resource, + struct wlr_output *output, bool enter) { + struct wl_client *client = wl_resource_get_client(resource); + struct wl_resource *output_resource; + + wl_resource_for_each(output_resource, &output->resources) { + if (wl_resource_get_client(output_resource) == client) { + if (enter) { + zwlr_foreign_toplevel_handle_v1_send_output_enter(resource, + output_resource); + } else { + zwlr_foreign_toplevel_handle_v1_send_output_leave(resource, + output_resource); + } + } + } +} + +static void toplevel_send_output(struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wlr_output *output, bool enter) { + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + send_output_to_resource(resource, output, enter); + } + + toplevel_update_idle_source(toplevel); +} + +static void toplevel_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output = + wl_container_of(listener, toplevel_output, output_destroy); + wlr_foreign_toplevel_handle_v1_output_leave(toplevel_output->toplevel, + toplevel_output->output); +} + +void wlr_foreign_toplevel_handle_v1_output_enter( + struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wlr_output *output) { + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output; + wl_list_for_each(toplevel_output, &toplevel->outputs, link) { + if (toplevel_output->output == output) { + return; // we have already sent output_enter event + } + } + + toplevel_output = + calloc(1, sizeof(struct wlr_foreign_toplevel_handle_v1_output)); + if (!toplevel_output) { + wlr_log(WLR_ERROR, "failed to allocate memory for toplevel output"); + return; + } + + toplevel_output->output = output; + toplevel_output->toplevel = toplevel; + wl_list_insert(&toplevel->outputs, &toplevel_output->link); + + toplevel_output->output_destroy.notify = toplevel_handle_output_destroy; + wl_signal_add(&output->events.destroy, &toplevel_output->output_destroy); + + toplevel_send_output(toplevel, output, true); +} + +static void toplevel_output_destroy( + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output) { + wl_list_remove(&toplevel_output->link); + wl_list_remove(&toplevel_output->output_destroy.link); + free(toplevel_output); +} + +void wlr_foreign_toplevel_handle_v1_output_leave( + struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wlr_output *output) { + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output_iterator; + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output = NULL; + + wl_list_for_each(toplevel_output_iterator, &toplevel->outputs, link) { + if (toplevel_output_iterator->output == output) { + toplevel_output = toplevel_output_iterator; + break; + } + } + + if (toplevel_output) { + toplevel_send_output(toplevel, output, false); + toplevel_output_destroy(toplevel_output); + } else { + // XXX: log an error? crash? + } +} + +static bool fill_array_from_toplevel_state(struct wl_array *array, + uint32_t state) { + if (state & WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED) { + uint32_t *index = wl_array_add(array, sizeof(uint32_t)); + if (index == NULL) { + return false; + } + *index = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED; + } + if (state & WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED) { + uint32_t *index = wl_array_add(array, sizeof(uint32_t)); + if (index == NULL) { + return false; + } + *index = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED; + } + if (state & WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) { + uint32_t *index = wl_array_add(array, sizeof(uint32_t)); + if (index == NULL) { + return false; + } + *index = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED; + } + + return true; +} + +static void toplevel_send_state(struct wlr_foreign_toplevel_handle_v1 *toplevel) { + struct wl_array states; + wl_array_init(&states); + bool r = fill_array_from_toplevel_state(&states, toplevel->state); + if (!r) { + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + wl_resource_post_no_memory(resource); + } + + wl_array_release(&states); + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + zwlr_foreign_toplevel_handle_v1_send_state(resource, &states); + } + + wl_array_release(&states); + toplevel_update_idle_source(toplevel); +} + +void wlr_foreign_toplevel_handle_v1_set_maximized( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool maximized) { + if (maximized) { + toplevel->state |= WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED; + } else { + toplevel->state &= ~WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED; + } + toplevel_send_state(toplevel); +} + +void wlr_foreign_toplevel_handle_v1_set_minimized( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool minimized) { + if (minimized) { + toplevel->state |= WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED; + } else { + toplevel->state &= ~WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED; + } + toplevel_send_state(toplevel); +} + +void wlr_foreign_toplevel_handle_v1_set_activated( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool activated) { + if (activated) { + toplevel->state |= WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED; + } else { + toplevel->state &= ~WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED; + } + toplevel_send_state(toplevel); +} + +void wlr_foreign_toplevel_handle_v1_destroy( + struct wlr_foreign_toplevel_handle_v1 *toplevel) { + if (!toplevel) { + return; + } + + wlr_signal_emit_safe(&toplevel->events.destroy, toplevel); + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &toplevel->resources) { + zwlr_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)); + } + + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output, *tmp2; + wl_list_for_each_safe(toplevel_output, tmp2, &toplevel->outputs, link) { + toplevel_output_destroy(toplevel_output); + } + + if (toplevel->idle_source) { + wl_event_source_remove(toplevel->idle_source); + } + + wl_list_remove(&toplevel->link); + + free(toplevel->title); + free(toplevel->app_id); + 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_foreign_toplevel_handle_v1 *toplevel, + struct wl_resource *manager_resource) { + struct wl_client *client = wl_resource_get_client(manager_resource); + struct wl_resource *resource = wl_resource_create(client, + &zwlr_foreign_toplevel_handle_v1_interface, + wl_resource_get_version(manager_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)); + zwlr_foreign_toplevel_manager_v1_send_toplevel(manager_resource, resource); + return resource; +} + +struct wlr_foreign_toplevel_handle_v1 * +wlr_foreign_toplevel_handle_v1_create( + struct wlr_foreign_toplevel_manager_v1 *manager) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = calloc(1, + sizeof(struct wlr_foreign_toplevel_handle_v1)); + if (!toplevel) { + return NULL; + } + + wl_list_insert(&manager->toplevels, &toplevel->link); + toplevel->manager = manager; + + wl_list_init(&toplevel->resources); + wl_list_init(&toplevel->outputs); + + wl_signal_init(&toplevel->events.request_maximize); + wl_signal_init(&toplevel->events.request_minimize); + wl_signal_init(&toplevel->events.request_activate); + wl_signal_init(&toplevel->events.request_close); + wl_signal_init(&toplevel->events.set_rectangle); + wl_signal_init(&toplevel->events.destroy); + + struct wl_resource *manager_resource, *tmp; + wl_resource_for_each_safe(manager_resource, tmp, &manager->resources) { + create_toplevel_resource_for_resource(toplevel, manager_resource); + } + + return toplevel; +} + +static const struct zwlr_foreign_toplevel_manager_v1_interface + foreign_toplevel_manager_impl; + +static void foreign_toplevel_manager_handle_stop(struct wl_client *client, + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_foreign_toplevel_manager_v1_interface, + &foreign_toplevel_manager_impl)); + + zwlr_foreign_toplevel_manager_v1_send_finished(resource); + wl_resource_destroy(resource); +} + +static const struct zwlr_foreign_toplevel_manager_v1_interface + foreign_toplevel_manager_impl = { + .stop = foreign_toplevel_manager_handle_stop +}; + +static void foreign_toplevel_manager_resource_destroy( + struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void toplevel_send_details_to_toplevel_resource( + struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wl_resource *resource) { + if (toplevel->title) { + zwlr_foreign_toplevel_handle_v1_send_title(resource, + toplevel->title); + } + if (toplevel->app_id) { + zwlr_foreign_toplevel_handle_v1_send_app_id(resource, + toplevel->app_id); + } + + struct wlr_foreign_toplevel_handle_v1_output *output; + wl_list_for_each(output, &toplevel->outputs, link) { + send_output_to_resource(resource, output->output, true); + } + + struct wl_array states; + wl_array_init(&states); + bool r = fill_array_from_toplevel_state(&states, toplevel->state); + if (!r) { + wl_resource_post_no_memory(resource); + wl_array_release(&states); + return; + } + + zwlr_foreign_toplevel_handle_v1_send_state(resource, &states); + wl_array_release(&states); + + zwlr_foreign_toplevel_handle_v1_send_done(resource); +} + +static void foreign_toplevel_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_foreign_toplevel_manager_v1 *manager = data; + struct wl_resource *resource = wl_resource_create(client, + &zwlr_foreign_toplevel_manager_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &foreign_toplevel_manager_impl, + manager, foreign_toplevel_manager_resource_destroy); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); + + struct wlr_foreign_toplevel_handle_v1 *toplevel, *tmp; + wl_list_for_each_safe(toplevel, tmp, &manager->toplevels, link) { + struct wl_resource *toplevel_resource = + create_toplevel_resource_for_resource(toplevel, resource); + toplevel_send_details_to_toplevel_resource(toplevel, + toplevel_resource); + } +} + +void wlr_foreign_toplevel_manager_v1_destroy( + struct wlr_foreign_toplevel_manager_v1 *manager) { + if (!manager) { + return; + } + + struct wlr_foreign_toplevel_handle_v1 *toplevel, *tmp_toplevel; + wl_list_for_each_safe(toplevel, tmp_toplevel, &manager->toplevels, link) { + wlr_foreign_toplevel_handle_v1_destroy(toplevel); + } + + struct wl_resource *resource, *tmp_resource; + wl_resource_for_each_safe(resource, tmp_resource, &manager->resources) { + wl_resource_destroy(resource); + } + + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + + wl_global_destroy(manager->global); + free(manager); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_foreign_toplevel_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wlr_foreign_toplevel_manager_v1_destroy(manager); +} + +struct wlr_foreign_toplevel_manager_v1 *wlr_foreign_toplevel_manager_v1_create( + struct wl_display *display) { + struct wlr_foreign_toplevel_manager_v1 *manager = calloc(1, + sizeof(struct wlr_foreign_toplevel_manager_v1)); + if (!manager) { + return NULL; + } + + manager->event_loop = wl_display_get_event_loop(display); + manager->global = wl_global_create(display, + &zwlr_foreign_toplevel_manager_v1_interface, + FOREIGN_TOPLEVEL_MANAGEMENT_V1_VERSION, manager, + foreign_toplevel_manager_bind); + if (!manager->global) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.destroy); + wl_list_init(&manager->resources); + wl_list_init(&manager->toplevels); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} |