diff --git a/include/wlr/types/wlr_ext_foreign_toplevel_info_v1.h b/include/wlr/types/wlr_ext_foreign_toplevel_info_v1.h new file mode 100644 index 000000000..2bf840158 --- /dev/null +++ b/include/wlr/types/wlr_ext_foreign_toplevel_info_v1.h @@ -0,0 +1,108 @@ +/* + * 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_INFO_V1_H +#define WLR_TYPES_WLR_FOREIGN_TOPLEVEL_INFO_V1_H + +#include +#include + +struct wlr_ext_foreign_toplevel_info_v1 { + struct wl_event_loop *event_loop; + struct wl_global *global; + struct wl_list resources; // wl_resource_get_link + struct wl_list toplevels; // wlr_ext_foreign_toplevel_handle_v1::link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +enum wlr_ext_foreign_toplevel_handle_v1_state { + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED = (1 << 0), + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED = (1 << 1), + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED = (1 << 2), + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN = (1 << 3), +}; + +struct wlr_ext_foreign_toplevel_handle_v1_output { + struct wl_list link; // wlr_ext_foreign_toplevel_handle_v1::outputs + struct wl_listener output_destroy; + struct wlr_output *output; + + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel; +}; + +struct wlr_ext_foreign_toplevel_handle_v1 { + struct wlr_ext_foreign_toplevel_info_v1 *info; + struct wl_list resources; // wl_resource_get_link + struct wl_list link; // wlr_ext_foreign_toplevel_info_v1::toplevels + struct wl_event_source *idle_source; // May be NULL + + char *title; // May be NULL + char *app_id; // May be NULL + struct wlr_ext_foreign_toplevel_handle_v1 *parent; // May be NULL + struct wl_list outputs; // wlr_ext_foreign_toplevel_handle_v1_output + uint32_t state; // wlr_ext_foreign_toplevel_handle_v1_state + + struct { + struct wl_signal destroy; // wlr_ext_foreign_toplevel_handle_v1 * + } events; + + void *data; +}; + +struct wlr_ext_foreign_toplevel_info_v1 *wlr_ext_foreign_toplevel_info_v1_create( + struct wl_display *display); + +struct wlr_ext_foreign_toplevel_handle_v1 *wlr_ext_foreign_toplevel_handle_v1_create( + struct wlr_ext_foreign_toplevel_info_v1 *info); + +/* Destroy the given toplevel handle, sending the closed event to any + * client. Also, if the destroyed toplevel is set as a parent of any + * other valid toplevel, clients still holding a handle to both are + * sent a parent signal with NULL parent. If this is not desired, the + * caller should ensure that any child toplevels are destroyed before + * the parent. */ +void wlr_ext_foreign_toplevel_handle_v1_destroy( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel); + +void wlr_ext_foreign_toplevel_handle_v1_set_title( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, const char *title); +void wlr_ext_foreign_toplevel_handle_v1_set_app_id( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, const char *app_id); + +void wlr_ext_foreign_toplevel_handle_v1_output_enter( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, struct wlr_output *output); +void wlr_ext_foreign_toplevel_handle_v1_output_leave( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, struct wlr_output *output); + +void wlr_ext_foreign_toplevel_handle_v1_set_maximized( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, bool maximized); +void wlr_ext_foreign_toplevel_handle_v1_set_minimized( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, bool minimized); +void wlr_ext_foreign_toplevel_handle_v1_set_activated( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, bool activated); +void wlr_ext_foreign_toplevel_handle_v1_set_fullscreen( + struct wlr_ext_foreign_toplevel_handle_v1* toplevel, bool fullscreen); + +/* Set the parent of a toplevel. If the parent changed from its previous + * value, also sends a parent event to all clients that hold handles to + * both toplevel and parent (no message is sent to clients that have + * previously destroyed their parent handle). NULL is allowed as the + * parent, meaning no parent exists. */ +void wlr_ext_foreign_toplevel_handle_v1_set_parent( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, + struct wlr_ext_foreign_toplevel_handle_v1 *parent); + + +#endif diff --git a/protocol/foreign-toplevel-info-unstable-v1.xml b/protocol/foreign-toplevel-info-unstable-v1.xml new file mode 100644 index 000000000..f98f56946 --- /dev/null +++ b/protocol/foreign-toplevel-info-unstable-v1.xml @@ -0,0 +1,174 @@ + + + + Copyright © 2018 Ilia Bozhinov + Copyright © 2020 Isaac Freund + + 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. + + + + + The purpose of this protocol is to enable clients such as taskbars + or docks to access a list of opened applications and basic properties + thereof. + + The secondary purpose of this protocol is to provide protocol object + handles for toplevels which may be used to address said toplevels in + other protocols (e.g. to target a toplevel for screencopy). + + After a client binds the zext_foreign_toplevel_info_v1, each opened + toplevel window will be sent via the toplevel event + + + + + 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 properties of the toplevel (title, app_id, states, etc.) + will be sent immediately after this event via the corresponding + events in zext_foreign_toplevel_handle_v1. + + + + + + + This request indicates that 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. + + + + + + This event indicates that the compositor is done sending events + to the zext_foreign_toplevel_info_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. + + + + + + + A zext_foreign_toplevel_handle_v1 object represents an open toplevel + window. A single app may have multiple open toplevels. + + Each toplevel has a list of outputs it is visible on, exposed to the + client via the output_enter and output_leave events. + + + + + This request should be called either when the client will no longer + use the zext_foreign_toplevel_handle_v1 or after the closed event + has been received to allow destruction of the object. + + + + + + The server will emit no further events on the + zext_foreign_toplevel_handle_v1 after this event. Any requests received + aside from the destroy request will be ignored. Upon receiving this + event, the client should make the destroy request to allow freeing + of resources. + + + + + + This event is sent after all changes in the toplevel state have + been sent. + + This allows changes to the zext_foreign_toplevel_handle_v1 properties + to be seen as atomic, even if they happen via multiple events. + + Note: this is is not sent after the closed event. + + + + + + This event is emitted whenever the title of the toplevel changes. + + + + + + + This event is emitted whenever the app_id of the toplevel changes. + + + + + + + This event is emitted whenever the toplevel becomes visible on the + given output. A toplevel may be visible on multiple outputs. + + + + + + + This event is emitted whenever the toplevel is no longer visible + on a given output. It is guaranteed that an output_enter event with + the same output has been emitted before this event. + + + + + + + The different states that a toplevel may have. These have the same + meaning as the states with the same names defined in xdg-toplevel + + + + + + + + + + This event is emitted once on creation of the + zext_foreign_toplevel_handle_v1 and again whenever the state of the + toplevel changes. + + + + + + + This event is emitted whenever the parent of the toplevel changes. + + No event is emitted when the parent handle is destroyed by the client. + + + + + diff --git a/protocol/meson.build b/protocol/meson.build index b9b74ca57..5799a469a 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -44,6 +44,7 @@ protocols = { 'wlr-output-power-management-unstable-v1': 'wlr-output-power-management-unstable-v1.xml', 'wlr-screencopy-unstable-v1': 'wlr-screencopy-unstable-v1.xml', 'wlr-virtual-pointer-unstable-v1': 'wlr-virtual-pointer-unstable-v1.xml', + 'foreign-toplevel-info-unstable-v1': 'foreign-toplevel-info-unstable-v1.xml', } protocols_code = {} diff --git a/types/meson.build b/types/meson.build index 80978176e..00870e1c0 100644 --- a/types/meson.build +++ b/types/meson.build @@ -22,6 +22,7 @@ wlr_files += files( 'wlr_cursor.c', 'wlr_data_control_v1.c', 'wlr_export_dmabuf_v1.c', + 'wlr_ext_foreign_toplevel_info_v1.c', 'wlr_foreign_toplevel_management_v1.c', 'wlr_fullscreen_shell_v1.c', 'wlr_gamma_control_v1.c', diff --git a/types/wlr_ext_foreign_toplevel_info_v1.c b/types/wlr_ext_foreign_toplevel_info_v1.c new file mode 100644 index 000000000..adc5b47ac --- /dev/null +++ b/types/wlr_ext_foreign_toplevel_info_v1.c @@ -0,0 +1,528 @@ +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include "util/signal.h" +#include "foreign-toplevel-info-unstable-v1-protocol.h" + +#define FOREIGN_TOPLEVEL_INFO_V1_VERSION 1 + +static const struct zext_foreign_toplevel_handle_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 zext_foreign_toplevel_handle_v1_interface toplevel_handle_impl = { + .destroy = foreign_toplevel_handle_destroy, +}; + +static void toplevel_idle_send_done(void *data) { + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel = data; + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + zext_foreign_toplevel_handle_v1_send_done(resource); + } + + toplevel->idle_source = NULL; +} + +static void toplevel_update_idle_source( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel) { + if (toplevel->idle_source) { + return; + } + + toplevel->idle_source = wl_event_loop_add_idle(toplevel->info->event_loop, + toplevel_idle_send_done, toplevel); +} + +void wlr_ext_foreign_toplevel_handle_v1_set_title( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, const char *title) { + free(toplevel->title); + toplevel->title = strdup(title); + if (toplevel->title == NULL) { + wlr_log(WLR_ERROR, "failed to allocate memory for toplevel title"); + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + zext_foreign_toplevel_handle_v1_send_title(resource, title); + } + + toplevel_update_idle_source(toplevel); +} + +void wlr_ext_foreign_toplevel_handle_v1_set_app_id( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, const char *app_id) { + free(toplevel->app_id); + toplevel->app_id = strdup(app_id); + if (toplevel->app_id == NULL) { + wlr_log(WLR_ERROR, "failed to allocate memory for toplevel app_id"); + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + zext_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) { + zext_foreign_toplevel_handle_v1_send_output_enter(resource, + output_resource); + } else { + zext_foreign_toplevel_handle_v1_send_output_leave(resource, + output_resource); + } + } + } +} + +static void toplevel_send_output(struct wlr_ext_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_ext_foreign_toplevel_handle_v1_output *toplevel_output = + wl_container_of(listener, toplevel_output, output_destroy); + wlr_ext_foreign_toplevel_handle_v1_output_leave(toplevel_output->toplevel, + toplevel_output->output); +} + +void wlr_ext_foreign_toplevel_handle_v1_output_enter( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, + struct wlr_output *output) { + struct wlr_ext_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_ext_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_ext_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_ext_foreign_toplevel_handle_v1_output_leave( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, + struct wlr_output *output) { + struct wlr_ext_foreign_toplevel_handle_v1_output *toplevel_output; + + wl_list_for_each(toplevel_output, &toplevel->outputs, link) { + if (toplevel_output->output == output) { + break; + } + } + + // The output must be in the list, leaving an output that was never + // entered is forbidden. + assert(&toplevel_output->link != &toplevel->outputs); + + toplevel_send_output(toplevel, output, false); + toplevel_output_destroy(toplevel_output); +} + +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 = ZEXT_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 = ZEXT_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 = ZEXT_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED; + } + if (state & WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) { + uint32_t *index = wl_array_add(array, sizeof(uint32_t)); + if (index == NULL) { + return false; + } + *index = ZEXT_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN; + } + + return true; +} + +static void toplevel_send_state( + struct wlr_ext_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) { + zext_foreign_toplevel_handle_v1_send_state(resource, &states); + } + + wl_array_release(&states); + toplevel_update_idle_source(toplevel); +} + +void wlr_ext_foreign_toplevel_handle_v1_set_maximized( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, bool maximized) { + if (maximized == (toplevel->state & + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED)) { + return; + } + 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_ext_foreign_toplevel_handle_v1_set_minimized( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, bool minimized) { + if (minimized == (toplevel->state & + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED)) { + return; + } + 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_ext_foreign_toplevel_handle_v1_set_activated( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, bool activated) { + if (activated == (toplevel->state & + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED)) { + return; + } + 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_ext_foreign_toplevel_handle_v1_set_fullscreen( + struct wlr_ext_foreign_toplevel_handle_v1 * toplevel, bool fullscreen) { + if (fullscreen == (toplevel->state & + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN)) { + return; + } + if (fullscreen) { + toplevel->state |= WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN; + } else { + toplevel->state &= ~WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN; + } + toplevel_send_state(toplevel); +} + +static void toplevel_resource_send_parent( + struct wl_resource *toplevel_resource, + struct wlr_ext_foreign_toplevel_handle_v1 *parent) { + struct wl_client *client = wl_resource_get_client(toplevel_resource); + struct wl_resource *parent_resource = NULL; + if (parent) { + parent_resource = wl_resource_find_for_client(&parent->resources, client); + if (!parent_resource) { + /* don't send an event if this client destroyed the parent handle */ + return; + } + } + zext_foreign_toplevel_handle_v1_send_parent(toplevel_resource, + parent_resource); +} + +void wlr_ext_foreign_toplevel_handle_v1_set_parent( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, + struct wlr_ext_foreign_toplevel_handle_v1 *parent) { + if (parent == toplevel->parent) { + /* only send parent event to the clients if there was a change */ + return; + } + struct wl_resource *toplevel_resource, *tmp; + wl_resource_for_each_safe(toplevel_resource, tmp, &toplevel->resources) { + toplevel_resource_send_parent(toplevel_resource, parent); + } + toplevel->parent = parent; + toplevel_update_idle_source(toplevel); +} + +void wlr_ext_foreign_toplevel_handle_v1_destroy( + struct wlr_ext_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) { + zext_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_ext_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); + + /* need to ensure no other toplevels hold a pointer to this one as + * a parent so that a later call to foreign_toplevel_info_bind() + * will not result in a segfault */ + struct wlr_ext_foreign_toplevel_handle_v1 *tl, *tmp3; + wl_list_for_each_safe(tl, tmp3, &toplevel->info->toplevels, link) { + if (tl->parent == toplevel) { + /* Note: we send a parent signal to all clients in this case; + * the caller should first destroy the child handles if it + * wishes to avoid this behavior. */ + wlr_ext_foreign_toplevel_handle_v1_set_parent(tl, NULL); + } + } + + 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_ext_foreign_toplevel_handle_v1 *toplevel, + struct wl_resource *info_resource) { + struct wl_client *client = wl_resource_get_client(info_resource); + struct wl_resource *resource = wl_resource_create(client, + &zext_foreign_toplevel_handle_v1_interface, + wl_resource_get_version(info_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)); + zext_foreign_toplevel_info_v1_send_toplevel(info_resource, resource); + return resource; +} + +struct wlr_ext_foreign_toplevel_handle_v1 * +wlr_ext_foreign_toplevel_handle_v1_create( + struct wlr_ext_foreign_toplevel_info_v1 *info) { + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel = calloc(1, + sizeof(struct wlr_ext_foreign_toplevel_handle_v1)); + if (!toplevel) { + return NULL; + } + + wl_list_insert(&info->toplevels, &toplevel->link); + toplevel->info = info; + + wl_list_init(&toplevel->resources); + wl_list_init(&toplevel->outputs); + + wl_signal_init(&toplevel->events.destroy); + + struct wl_resource *info_resource, *tmp; + wl_resource_for_each_safe(info_resource, tmp, &info->resources) { + create_toplevel_resource_for_resource(toplevel, info_resource); + } + + return toplevel; +} + +static const struct zext_foreign_toplevel_info_v1_interface + foreign_toplevel_info_impl; + +static void foreign_toplevel_info_handle_stop(struct wl_client *client, + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zext_foreign_toplevel_info_v1_interface, + &foreign_toplevel_info_impl)); + + zext_foreign_toplevel_info_v1_send_finished(resource); + wl_resource_destroy(resource); +} + +static const struct zext_foreign_toplevel_info_v1_interface + foreign_toplevel_info_impl = { + .stop = foreign_toplevel_info_handle_stop +}; + +static void foreign_toplevel_info_resource_destroy( + struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void toplevel_send_details_to_toplevel_resource( + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, + struct wl_resource *resource) { + if (toplevel->title) { + zext_foreign_toplevel_handle_v1_send_title(resource, toplevel->title); + } + if (toplevel->app_id) { + zext_foreign_toplevel_handle_v1_send_app_id(resource, toplevel->app_id); + } + + struct wlr_ext_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; + } + + zext_foreign_toplevel_handle_v1_send_state(resource, &states); + wl_array_release(&states); + + toplevel_resource_send_parent(resource, toplevel->parent); + + zext_foreign_toplevel_handle_v1_send_done(resource); +} + +static void foreign_toplevel_info_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_ext_foreign_toplevel_info_v1 *info = data; + struct wl_resource *resource = wl_resource_create(client, + &zext_foreign_toplevel_info_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &foreign_toplevel_info_impl, + info, foreign_toplevel_info_resource_destroy); + + wl_list_insert(&info->resources, wl_resource_get_link(resource)); + + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, *tmp; + /* First loop: create a handle for all toplevels for all clients. + * Separation into two loops avoid the case where a child handle + * is created before a parent handle, so the parent relationship + * could not be sent to a client. */ + wl_list_for_each_safe(toplevel, tmp, &info->toplevels, link) { + create_toplevel_resource_for_resource(toplevel, resource); + } + /* Second loop: send details about each toplevel. */ + wl_list_for_each_safe(toplevel, tmp, &info->toplevels, link) { + struct wl_resource *toplevel_resource = + wl_resource_find_for_client(&toplevel->resources, client); + 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_info_v1 *info = + wl_container_of(listener, info, display_destroy); + wlr_signal_emit_safe(&info->events.destroy, info); + wl_list_remove(&info->display_destroy.link); + wl_global_destroy(info->global); + free(info); +} + +struct wlr_ext_foreign_toplevel_info_v1 *wlr_ext_foreign_toplevel_info_v1_create( + struct wl_display *display) { + struct wlr_ext_foreign_toplevel_info_v1 *info = calloc(1, + sizeof(struct wlr_ext_foreign_toplevel_info_v1)); + if (!info) { + return NULL; + } + + info->event_loop = wl_display_get_event_loop(display); + info->global = wl_global_create(display, + &zext_foreign_toplevel_info_v1_interface, + FOREIGN_TOPLEVEL_INFO_V1_VERSION, info, + foreign_toplevel_info_bind); + if (!info->global) { + free(info); + return NULL; + } + + wl_signal_init(&info->events.destroy); + wl_list_init(&info->resources); + wl_list_init(&info->toplevels); + + info->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &info->display_destroy); + + return info; +}