From 754fb31f047521b90805e00a0bd28094eece48f4 Mon Sep 17 00:00:00 2001 From: Ilia Bozhinov Date: Tue, 7 Apr 2020 12:55:59 +0200 Subject: [PATCH] wlr-workspace: first version add wlr-workspace header start wlr_workspace impl by copying from wlr_foreign_toplevel add more stubs workspace: implement output_enter/leave add create methods start with impl destroy finish impelmenttation of workspace-unstable implement wlr-workspace example small fixes to the example client fix names as requested in review --- examples/meson.build | 5 + examples/wlr-workspace.c | 338 +++++++++++++++ include/wlr/types/wlr_workspace_v1.h | 122 ++++++ protocol/meson.build | 1 + protocol/wlr-workspace-unstable-v1.xml | 270 ++++++++++++ types/meson.build | 1 + types/wlr_workspace_v1.c | 544 +++++++++++++++++++++++++ 7 files changed, 1281 insertions(+) create mode 100644 examples/wlr-workspace.c create mode 100644 include/wlr/types/wlr_workspace_v1.h create mode 100644 protocol/wlr-workspace-unstable-v1.xml create mode 100644 types/wlr_workspace_v1.c diff --git a/examples/meson.build b/examples/meson.build index 1ce02f0d4..e9d79a5a6 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -185,6 +185,11 @@ clients = { 'input-method-unstable-v2', ], }, + 'wlr-workspace': { + 'src': 'wlr-workspace.c', + 'dep': [wlroots], + 'proto': ['wlr-workspace-unstable-v1'], + }, } foreach name, info : compositors diff --git a/examples/wlr-workspace.c b/examples/wlr-workspace.c new file mode 100644 index 000000000..eab65a28c --- /dev/null +++ b/examples/wlr-workspace.c @@ -0,0 +1,338 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include "wlr-workspace-unstable-v1-client-protocol.h" + +#define WLR_WORKSPACE_VERSION 1 + +/** + * Usage: + * 1. wlr-workspace + * List all workspace groups and their workspaces + * 2. wlr-workspace -w X + * Focus workspace with name X + * 3. wlr-workspace -m + * Continuously monitor for changes and print new state. + */ + +enum workspace_state_field { + WORKSPACE_FOCUSED = (1 << 0), +}; + +struct workspace_state { + char *name; + struct wl_array coordinates; + uint32_t state; +}; + +static void copy_state(struct workspace_state *current, + struct workspace_state *pending) { + + current->state = pending->state; + wl_array_copy(¤t->coordinates, &pending->coordinates); + + if (pending->name) { + free(current->name); + current->name = pending->name; + pending->name = NULL; + } +} + +struct workspace_v1 { + struct wl_list link; + struct zwlr_workspace_handle_v1 *handle; + struct workspace_state current, pending; +}; + +static void print_workspace(struct workspace_v1 *workspace) { + printf("--> workspace name=%s, focused=%d, coordinates=(", + workspace->current.name ?: "(n/a)", + !!(workspace->current.state & WORKSPACE_FOCUSED)); + + bool is_first = true; + int32_t *pos; + wl_array_for_each(pos, &workspace->current.coordinates) { + if (!is_first) { + printf(","); + } + printf("%d", *pos); + is_first = false; + } + + printf(")\n"); +} + +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_WORKSPACE_HANDLE_V1_STATE_ACTIVE) + state |= WORKSPACE_FOCUSED; + } + + return state; +} + + +static void workspace_handle_name(void *data, + struct zwlr_workspace_handle_v1 *workspace_handle_v1, + const char *name) { + struct workspace_v1 *workspace = (struct workspace_v1*) + zwlr_workspace_handle_v1_get_user_data(workspace_handle_v1); + + free(workspace->pending.name); + workspace->pending.name = strdup(name); +} + +static void workspace_handle_coordinates(void *data, + struct zwlr_workspace_handle_v1 *workspace_handle, + struct wl_array *coordinates) { + struct workspace_v1 *workspace = (struct workspace_v1*) + zwlr_workspace_handle_v1_get_user_data(workspace_handle); + wl_array_copy(&workspace->pending.coordinates, coordinates); +} + +static void workspace_handle_state(void *data, + struct zwlr_workspace_handle_v1 *workspace_handle, + struct wl_array *state) { + struct workspace_v1 *workspace = (struct workspace_v1*) + zwlr_workspace_handle_v1_get_user_data(workspace_handle); + workspace->pending.state = array_to_state(state); +} + +static void workspace_handle_remove(void *data, + struct zwlr_workspace_handle_v1 *workspace_handle) { + struct workspace_v1 *workspace = (struct workspace_v1*) + zwlr_workspace_handle_v1_get_user_data(workspace_handle); + zwlr_workspace_handle_v1_destroy(workspace_handle); + + wl_list_remove(&workspace->link); + free(workspace->current.name); + free(workspace->pending.name); + wl_array_release(&workspace->pending.coordinates); + wl_array_release(&workspace->current.coordinates); + free(workspace); +} + +static const struct zwlr_workspace_handle_v1_listener workspace_listener = { + .name = workspace_handle_name, + .coordinates = workspace_handle_coordinates, + .state = workspace_handle_state, + .remove = workspace_handle_remove, +}; + +struct group_v1 { + struct wl_list link; + + int32_t id; + struct wl_list workspaces; +}; + +static void group_handle_output_enter(void *data, + struct zwlr_workspace_group_handle_v1 *group_handle, + struct wl_output *output) { + struct group_v1 *group = (struct group_v1*) + zwlr_workspace_group_handle_v1_get_user_data(group_handle); + printf("Group %d output_enter %u\n", group->id, + (uint32_t)(size_t)wl_output_get_user_data(output)); +} + +static void group_handle_output_leave(void *data, + struct zwlr_workspace_group_handle_v1 *group_handle, + struct wl_output *output) { + struct group_v1 *group = (struct group_v1*) + zwlr_workspace_group_handle_v1_get_user_data(group_handle); + printf("Group %d output_leave %u\n", group->id, + (uint32_t)(size_t)wl_output_get_user_data(output)); +} + +static void group_handle_workspace(void *data, + struct zwlr_workspace_group_handle_v1 *group_handle, + struct zwlr_workspace_handle_v1 *workspace_handle) { + struct group_v1 *group = (struct group_v1*) + zwlr_workspace_group_handle_v1_get_user_data(group_handle); + struct workspace_v1 *workspace = (struct workspace_v1*) + calloc(1, sizeof(struct workspace_v1)); + + wl_list_insert(&group->workspaces, &workspace->link); + wl_array_init(&workspace->pending.coordinates); + wl_array_init(&workspace->current.coordinates); + + workspace->handle = workspace_handle; + zwlr_workspace_handle_v1_add_listener(workspace_handle, + &workspace_listener, NULL); + zwlr_workspace_handle_v1_set_user_data(workspace_handle, workspace); +} + +static void group_handle_remove(void *data, + struct zwlr_workspace_group_handle_v1 *group_handle) { + struct group_v1 *group = (struct group_v1*) + zwlr_workspace_group_handle_v1_get_user_data(group_handle); + wl_list_remove(&group->link); + if (!wl_list_empty(&group->workspaces)) { + printf("Compositor bug! Group destroyed before its workspaces.\n"); + } + + free(group); +} + +static const struct zwlr_workspace_group_handle_v1_listener group_listener = { + .output_enter = group_handle_output_enter, + .output_leave = group_handle_output_leave, + .workspace = group_handle_workspace, + .remove = group_handle_remove, +}; + +static struct zwlr_workspace_manager_v1 *workspace_manager = NULL; +static struct wl_list group_list; +static int32_t last_group_id = 0; + +static void workspace_manager_handle_workspace_group(void *data, + struct zwlr_workspace_manager_v1 *zwlr_workspace_manager_v1, + struct zwlr_workspace_group_handle_v1 *workspace_group) { + struct group_v1 *group = (struct group_v1*) + calloc(1, sizeof(struct group_v1)); + group->id = last_group_id++; + wl_list_init(&group->workspaces); + wl_list_insert(&group_list, &group->link); + + zwlr_workspace_group_handle_v1_add_listener(workspace_group, + &group_listener, NULL); + zwlr_workspace_group_handle_v1_set_user_data(workspace_group, group); +} + +static void workspace_manager_handle_done(void *data, + struct zwlr_workspace_manager_v1 *zwlr_workspace_manager_v1) { + + printf("*** Workspace configuration ***\n"); + struct group_v1 *group; + wl_list_for_each(group, &group_list, link) { + printf("> Group id=%d\n", group->id); + struct workspace_v1 *workspace; + wl_list_for_each(workspace, &group->workspaces, link) { + copy_state(&workspace->current, &workspace->pending); + print_workspace(workspace); + } + } +} + +static void workspace_manager_handle_finished(void *data, + struct zwlr_workspace_manager_v1 *zwlr_workspace_manager_v1) { + zwlr_workspace_manager_v1_destroy(zwlr_workspace_manager_v1); +} + +static const struct zwlr_workspace_manager_v1_listener workspace_manager_impl = { + .workspace_group = workspace_manager_handle_workspace_group, + .done = workspace_manager_handle_done, + .finished = workspace_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_workspace_manager_v1_interface.name) == 0) { + workspace_manager = wl_registry_bind(registry, name, + &zwlr_workspace_manager_v1_interface, WLR_WORKSPACE_VERSION); + + wl_list_init(&group_list); + zwlr_workspace_manager_v1_add_listener(workspace_manager, + &workspace_manager_impl, NULL); + } +} + +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, +}; + +static struct workspace_v1 *workspace_by_name_or_bail(const char *name) { + struct workspace_v1 *workspace; + struct group_v1 *group; + + wl_list_for_each(group, &group_list, link) { + wl_list_for_each(workspace, &group->workspaces, link) { + if (workspace->current.name && + strcmp(workspace->current.name, name) == 0) { + return workspace; + } + } + } + + fprintf(stderr, "No workspace with the given name: %s\n", name); + exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) { + int c; + char *focus_name = NULL; + bool monitor = false; + + while ((c = getopt(argc, argv, "w:m")) != -1) { + switch (c) { + case 'w': + focus_name = strdup(optarg); + break; + case 'm': + monitor = true; + 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 (workspace_manager == NULL) { + fprintf(stderr, "wlr-workspace not available\n"); + return EXIT_FAILURE; + } + wl_display_roundtrip(display); // load workspace groups + wl_display_roundtrip(display); // load details + + if (focus_name != NULL) { + struct workspace_v1 *focus = workspace_by_name_or_bail(focus_name); + + // unfocus all workspaces + struct workspace_v1 *workspace; + struct group_v1 *group; + + wl_list_for_each(group, &group_list, link) { + wl_list_for_each(workspace, &group->workspaces, link) { + zwlr_workspace_handle_v1_deactivate(workspace->handle); + } + } + zwlr_workspace_handle_v1_activate(focus->handle); + zwlr_workspace_manager_v1_commit(workspace_manager); + } + + wl_display_flush(display); + + if (monitor != false) { + while (wl_display_dispatch(display) != -1) { + // This space intentionally left blank + } + } + + return EXIT_SUCCESS; +} diff --git a/include/wlr/types/wlr_workspace_v1.h b/include/wlr/types/wlr_workspace_v1.h new file mode 100644 index 000000000..0e6d6a0fb --- /dev/null +++ b/include/wlr/types/wlr_workspace_v1.h @@ -0,0 +1,122 @@ +/* + * 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_WORKSPACE_V1_H +#define WLR_TYPES_WLR_WORKSPACE_V1_H + +#include +#include + +struct wlr_workspace_manager_v1 { + struct wl_event_loop *event_loop; + struct wl_event_source *idle_source; + + struct wl_global *global; + struct wl_list resources; // wl_resource_get_link + struct wl_list groups; // wlr_workspace_group_handle_v1::link + + struct wl_listener display_destroy; + + struct { + struct wl_signal commit; // wlr_workspace_manager_v1 + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_workspace_group_handle_v1 { + struct wl_list link; // wlr_workspace_manager_v1::groups + struct wl_list resources; // wl_resource_get_link + + struct wl_list workspaces; // wlr_workspace_handle_v1::link + struct wl_list outputs; // wlr_workspace_group_handle_v1_output::link + + struct wlr_workspace_manager_v1 *manager; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_workspace_group_handle_v1_output { + struct wl_list link; // wlr_workspace_group_handle_v1::outputs + struct wl_listener output_destroy; + struct wlr_output *output; + + struct wlr_workspace_group_handle_v1 *group_handle; +}; + +enum wlr_workspace_handle_v1_state +{ + WLR_WORKSPACE_HANDLE_V1_STATE_ACTIVE = 1 << 0, +}; + +struct wlr_workspace_handle_v1 { + struct wl_list link; // wlr_workspace_group_handle_v1::workspaces + struct wl_list resources; + + struct wlr_workspace_group_handle_v1 *group; + + // request from the client + uint32_t pending, current; + + // set by the compositor + uint32_t server_state; + + char *name; + struct wl_array coordinates; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_workspace_manager_v1 *wlr_workspace_manager_v1_create( + struct wl_display *display); + +struct wlr_workspace_group_handle_v1 *wlr_workspace_group_handle_v1_create( + struct wlr_workspace_manager_v1 *manager); + +/** + * Destroy the workspace group and all workspaces inside it. + */ +void wlr_workspace_group_handle_v1_destroy( + struct wlr_workspace_group_handle_v1 *group); + +/** + * Create a new workspace in the workspace group. + * Note that the compositor must set the workspace name immediately after + * creating it. + */ +struct wlr_workspace_handle_v1 *wlr_workspace_handle_v1_create( + struct wlr_workspace_group_handle_v1 *group); + +void wlr_workspace_handle_v1_destroy( + struct wlr_workspace_handle_v1 *workspace); + +void wlr_workspace_group_handle_v1_output_enter( + struct wlr_workspace_group_handle_v1 *group, struct wlr_output *output); + +void wlr_workspace_group_handle_v1_output_leave( + struct wlr_workspace_group_handle_v1 *group, struct wlr_output *output); + +void wlr_workspace_handle_v1_set_name(struct wlr_workspace_handle_v1 *workspace, + const char* name); + +void wlr_workspace_handle_v1_set_coordinates( + struct wlr_workspace_handle_v1 *workspace, struct wl_array *coordinates); + +void wlr_workspace_handle_v1_set_active( + struct wlr_workspace_handle_v1 *workspace, bool active); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index 87d944a1a..fc05bc61a 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -43,6 +43,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', + 'wlr-workspace-unstable-v1': 'wlr-workspace-unstable-v1.xml', } protocols_code = {} diff --git a/protocol/wlr-workspace-unstable-v1.xml b/protocol/wlr-workspace-unstable-v1.xml new file mode 100644 index 000000000..ae0e44dbb --- /dev/null +++ b/protocol/wlr-workspace-unstable-v1.xml @@ -0,0 +1,270 @@ + + + + Copyright © 2019 Christopher Billington + Copyright © 2020 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. + + + + + Workspaces, also called virtual desktops, are groups of surfaces. A + compositor with a concept of workspaces may only show some such groups of + surfaces (those of 'active' workspaces) at a time. 'Activating' a + workspace is a request for the compositor to display that workspace's + surfaces as normal, whereas the compositor may hide or otherwise + de-emphasise surfaces that are associated only with 'inactive' workspaces. + Workspaces are grouped by which sets of outputs they correspond to, and + may contain surfaces only from those outputs. In this way, it is possible + for each output to have its own set of workspaces, or for all outputs (or + any other arbitrary grouping) to share workspaces. Compositors may + optionally conceptually arrange each group of workspaces in an + N-dimensional grid. + + The purpose of this protocol is to enable the creation of taskbars and + docks by providing them with a list of workspaces and their properties, + and allowing them to activate and deactivate workspaces. + + After a client binds the zwlr_workspace_manager_v1, each workspace will be + sent via the workspace event. + + + + + This event is emitted whenever a new workspace group has been created. + + All initial details of the workspace group (workspaces, outputs) will be + sent immediately after this event via the corresponding events in + zwlr_workspace_group_handle_v1. + + + + + + + The client must send this request after it has finished sending other + requests. The compositor must process a series of requests preceding a + commit request atomically. + + This allows changes to the workspace properties to be seen as atomic, + even if they happen via multiple events, and even if they involve + multiple zwlr_workspace_handle_v1 objects, for example, deactivating one + workspace and activating another. + + + + + + This event is sent after all changes in all workspace groups have been + sent. + + This allows changes to one or more zwlr_workspace_group_handle_v1 + properties to be seen as atomic, even if they happen via multiple + events. In particular, an output moving from one workspace group to + another sends an output_enter event and an output_leave event to the two + zwlr_workspace_group_handle_v1 objects in question. The compositor sends + the done event only after updating the output information in both + workspace groups. + + + + + + This event indicates that the compositor is done sending events to the + zwlr_workspace_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. + + + + + + Indicates the client no longer wishes to receive events for new + workspace groups. However the compositor may emit further workspace + events, until the finished event is emitted. + + The client must not send any more requests after this one. + + + + + + + A zwlr_workspace_group_handle_v1 object represents a a workspace group + that is assigned a set of outputs and contains a number of workspaces. + + The set of outputs assigned to the workspace group is conveyed to the client via + output_enter and output_leave events, and its workspaces are conveyed with + workspace events. + + + + + This event is emitted whenever an output is assigned to the workspace + group. + + + + + + + This event is emitted whenever an output is removed from the workspace + group. + + + + + + + This event is emitted whenever a new workspace has been created. + + All initial details of the workspace (name, coordinates, state) will + be sent immediately after this event via the corresponding events in + zwlr_workspace_handle_v1. + + + + + + + This event means the zwlr_workspace_group_handle_v1 has been destroyed. + It is guaranteed there won't be any more events for this + zwlr_workspace_group_handle_v1. The zwlr_workspace_group_handle_v1 becomes + inert so any requests will be ignored except the destroy request. + + The compositor must remove all workspaces belonging to a workspace group + before removing the workspace group. + + + + + + Destroys the zwlr_workspace_handle_v1 object. + + This request should be called either when the client does not want to + use the workspace object any more or after the remove event to finalize + the destruction of the object. + + + + + + + A zwlr_workspace_handle_v1 object represents a a workspace that handles a + group of surfaces. + + Each workspace has a name, conveyed to the client with the name event; a + list of states, conveyed to the client with the state event; and + optionally a set of coordinates, conveyed to the client with the + coordinates event. The client may request that the compositor activate or + deactivate the workspace. + + + + + This event is emitted immediately after the zwlr_workspace_handle_v1 is + created and whenever the name of the workspace changes. + + + + + + + This event is used to organize workspaces into an N-dimensional grid + within a workspace group, and if supported, is emitted immediately after + the zwlr_workspace_handle_v1 is created and whenever the coordinates of + the workspace change. Compositors may not send this event if they do not + conceptually arrange workspaces in this way. If compositors simply + number workspaces, without any geometric interpretation, they may send + 1D coordinates, which clients should not interpret as implying any + geometry. Sending an empty array means that the compositor no longer + orders the workspace geometrically. + + Coordinates have an arbitrary number of dimensions N with an uint32 + position along each dimension. By convention if N > 1, the first + dimension is X, the second Y, the third Z, and so on. The compositor may + chose to utilize these events for a more novel workspace layout + convention, however. No guarantee is made about the grid being filled or + bounded; there may be a workspace at coordinate 1 and another at + coordinate 1000 and none in between. Within a workspace group, however, + workspaces must have unique coordinates of equal dimensionality. + + + + + + + This event is emitted immediately after the zwlr_workspace_handle_v1 is + created and each time the workspace state changes, either because of a + compositor action or because of a request in this protocol. + + + + + + + The different states that a workspace can have. + + + + + + + + This event means the zwlr_workspace_handle_v1 has been destroyed. It is + guaranteed there won't be any more events for this + zwlr_workspace_handle_v1. The zwlr_workspace_handle_v1 becomes inert so + any requests will be ignored except the destroy request. + + + + + + Destroys the zwlr_workspace_handle_v1 object. + + This request should be called either when the client does not want to + use the workspace object any more or after the remove event to finalize + the destruction of the object. + + + + + + Request that this workspace be activated. + + There is no guarantee the workspace will be actually activated, and + behaviour may be compositor-dependent. For example, activating a + workspace may or may not deactivate all other workspaces in the same + group. + + + + + + Request that this workspace be deactivated. + + There is no guarantee the workspace will be actually deactivated. + + + + diff --git a/types/meson.build b/types/meson.build index 47431429b..d1d832c58 100644 --- a/types/meson.build +++ b/types/meson.build @@ -61,6 +61,7 @@ wlr_files += files( 'wlr_viewporter.c', 'wlr_virtual_keyboard_v1.c', 'wlr_virtual_pointer_v1.c', + 'wlr_workspace_v1.c', 'wlr_xcursor_manager.c', 'wlr_xdg_decoration_v1.c', 'wlr_xdg_foreign_v1.c', diff --git a/types/wlr_workspace_v1.c b/types/wlr_workspace_v1.c new file mode 100644 index 000000000..240b40bf2 --- /dev/null +++ b/types/wlr_workspace_v1.c @@ -0,0 +1,544 @@ +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include "util/signal.h" + +#include "wlr-workspace-unstable-v1-protocol.h" + +#define WORKSPACE_V1_VERSION 1 + +static void workspace_manager_idle_send_done(void *data) { + struct wlr_workspace_manager_v1 *manager = data; + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &manager->resources) { + zwlr_workspace_manager_v1_send_done(resource); + } + + manager->idle_source = NULL; +} + +static void workspace_manager_update_idle_source( + struct wlr_workspace_manager_v1 *manager) { + if (manager->idle_source) { + return; + } + + manager->idle_source = wl_event_loop_add_idle(manager->event_loop, + workspace_manager_idle_send_done, manager); +} + +static const struct zwlr_workspace_handle_v1_interface workspace_handle_impl; +static struct wlr_workspace_handle_v1 *workspace_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_workspace_handle_v1_interface, + &workspace_handle_impl)); + return wl_resource_get_user_data(resource); +} + +static void workspace_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void workspace_handle_activate(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_workspace_handle_v1 *workspace = workspace_from_resource(resource); + if (!workspace) { + return; + } + + workspace->pending |= WLR_WORKSPACE_HANDLE_V1_STATE_ACTIVE; +} + +static void workspace_handle_deactivate(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_workspace_handle_v1 *workspace = workspace_from_resource(resource); + if (!workspace) { + return; + } + + workspace->pending &= ~WLR_WORKSPACE_HANDLE_V1_STATE_ACTIVE; +} + +static void workspace_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static bool fill_array_from_workspace_state(struct wl_array *array, + uint32_t state) { + if (state & WLR_WORKSPACE_HANDLE_V1_STATE_ACTIVE) { + uint32_t *index = wl_array_add(array, sizeof(uint32_t)); + if (index == NULL) { + return false; + } + *index = ZWLR_WORKSPACE_HANDLE_V1_STATE_ACTIVE; + } + + return true; +} + +static void workspace_handle_send_details_to_resource( + struct wlr_workspace_handle_v1 *workspace, struct wl_resource *resource) { + if (workspace->name) { + zwlr_workspace_handle_v1_send_name(resource, workspace->name); + } + + if (workspace->coordinates.size > 0) { + zwlr_workspace_handle_v1_send_coordinates(resource, + &workspace->coordinates); + } + + struct wl_array state; + wl_array_init(&state); + if (!fill_array_from_workspace_state(&state, workspace->server_state)) { + wl_resource_post_no_memory(resource); + wl_array_release(&state); + return; + } + + zwlr_workspace_handle_v1_send_state(resource, &state); +} + +static const struct zwlr_workspace_handle_v1_interface workspace_handle_impl = { + .destroy = workspace_handle_destroy, + .activate = workspace_handle_activate, + .deactivate = workspace_handle_deactivate, +}; + +void wlr_workspace_handle_v1_set_name(struct wlr_workspace_handle_v1 *workspace, + const char* name) { + free(workspace->name); + workspace->name = strdup(name); + + struct wl_resource *tmp, *resource; + wl_resource_for_each_safe(resource, tmp, &workspace->resources) { + zwlr_workspace_handle_v1_send_name(resource, name); + } + + workspace_manager_update_idle_source(workspace->group->manager); +} + +void wlr_workspace_handle_v1_set_coordinates( + struct wlr_workspace_handle_v1 *workspace, struct wl_array *coordinates) { + wl_array_copy(&workspace->coordinates, coordinates); + + struct wl_resource *tmp, *resource; + wl_resource_for_each_safe(resource, tmp, &workspace->resources) { + zwlr_workspace_handle_v1_send_coordinates(resource, coordinates); + } + + workspace_manager_update_idle_source(workspace->group->manager); +} + +void wlr_workspace_handle_v1_set_active( + struct wlr_workspace_handle_v1 *workspace, bool activate) { + if (activate) { + workspace->server_state |= WLR_WORKSPACE_HANDLE_V1_STATE_ACTIVE; + } else { + workspace->server_state &= ~WLR_WORKSPACE_HANDLE_V1_STATE_ACTIVE; + } + + struct wl_array state; + wl_array_init(&state); + + if (!fill_array_from_workspace_state(&state, workspace->server_state)) { + struct wl_resource *resource; + wl_resource_for_each(resource, &workspace->resources) { + wl_resource_post_no_memory(resource); + } + + wl_array_release(&state); + return; + } + + struct wl_resource *tmp, *resource; + wl_resource_for_each_safe(resource, tmp, &workspace->resources) { + zwlr_workspace_handle_v1_send_state(resource, &state); + } + + wl_array_release(&state); + workspace_manager_update_idle_source(workspace->group->manager); +} + +static struct wl_resource *create_workspace_resource_for_group_resource( + struct wlr_workspace_handle_v1 *workspace, + struct wl_resource *group_resource) { + + struct wl_client *client = wl_resource_get_client(group_resource); + struct wl_resource *resource = wl_resource_create(client, + &zwlr_workspace_handle_v1_interface, + wl_resource_get_version(group_resource), 0); + if (!resource) { + wl_client_post_no_memory(client); + return NULL; + } + + wl_resource_set_implementation(resource, &workspace_handle_impl, workspace, + workspace_handle_resource_destroy); + + wl_list_insert(&workspace->resources, wl_resource_get_link(resource)); + zwlr_workspace_group_handle_v1_send_workspace(group_resource, resource); + + return resource; +} + +struct wlr_workspace_handle_v1 *wlr_workspace_handle_v1_create( + struct wlr_workspace_group_handle_v1 *group) { + struct wlr_workspace_handle_v1 *workspace = calloc(1, + sizeof(struct wlr_workspace_handle_v1)); + if (!workspace) { + return NULL; + } + + workspace->group = group; + wl_list_insert(&group->workspaces, &workspace->link); + wl_array_init(&workspace->coordinates); + wl_list_init(&workspace->resources); + wl_signal_init(&workspace->events.destroy); + + struct wl_resource *tmp, *group_resource; + wl_resource_for_each_safe(group_resource, tmp, &group->resources) { + create_workspace_resource_for_group_resource(workspace, group_resource); + } + + return workspace; +} + +void wlr_workspace_handle_v1_destroy( + struct wlr_workspace_handle_v1 *workspace) { + if (!workspace) { + return; + } + + wlr_signal_emit_safe(&workspace->events.destroy, workspace); + + workspace_manager_update_idle_source(workspace->group->manager); + + struct wl_resource *tmp, *resource; + wl_resource_for_each_safe(resource, tmp, &workspace->resources) { + zwlr_workspace_handle_v1_send_remove(resource); + + wl_resource_set_user_data(resource, NULL); + wl_list_remove(&resource->link); + wl_list_init(&resource->link); + } + + wl_array_release(&workspace->coordinates); + wl_list_remove(&workspace->link); + free(workspace->name); +} + +static void workspace_group_handle_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +const struct zwlr_workspace_group_handle_v1_interface workspace_group_impl = { + .destroy = workspace_group_handle_handle_destroy, +}; + +static void workspace_group_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +/** + * Create the workspace group resource and child workspace resources as well. + */ +static struct wl_resource *create_workspace_group_resource_for_resource( + struct wlr_workspace_group_handle_v1 *group, + struct wl_resource *manager_resource) { + struct wl_client *client = wl_resource_get_client(manager_resource); + struct wl_resource *resource = wl_resource_create(client, + &zwlr_workspace_group_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, &workspace_group_impl, group, + workspace_group_resource_destroy); + + wl_list_insert(&group->resources, wl_resource_get_link(resource)); + zwlr_workspace_manager_v1_send_workspace_group(manager_resource, resource); + + struct wlr_workspace_handle_v1 *tmp, *workspace; + wl_list_for_each_safe(workspace, tmp, &group->workspaces, link) { + struct wl_resource *workspace_resource = + create_workspace_resource_for_group_resource(workspace, resource); + workspace_handle_send_details_to_resource(workspace, workspace_resource); + } + + return resource; +} + +static void send_output_to_group_resource(struct wl_resource *group_resource, + struct wlr_output *output, bool enter) { + struct wl_client *client = wl_resource_get_client(group_resource); + struct wl_resource *output_resource, *tmp; + + wl_resource_for_each_safe(output_resource, tmp, &output->resources) { + if (wl_resource_get_client(output_resource) == client) { + if (enter) { + zwlr_workspace_group_handle_v1_send_output_enter(group_resource, + output_resource); + } else { + zwlr_workspace_group_handle_v1_send_output_leave(group_resource, + output_resource); + } + } + } +} + +static void group_send_output(struct wlr_workspace_group_handle_v1 *group, + struct wlr_output *output, bool enter) { + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &group->resources) { + send_output_to_group_resource(resource, output, enter); + } +} + +static void workspace_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_workspace_group_handle_v1_output *output = + wl_container_of(listener, output, output_destroy); + wlr_workspace_group_handle_v1_output_leave(output->group_handle, + output->output); +} + +void wlr_workspace_group_handle_v1_output_enter( + struct wlr_workspace_group_handle_v1 *group, struct wlr_output *output) { + struct wlr_workspace_group_handle_v1_output *group_output; + wl_list_for_each(group_output, &group->outputs, link) { + if (group_output->output == output) { + return; // we have already sent output_enter event + } + } + + group_output = calloc(1, sizeof(struct wlr_workspace_group_handle_v1_output)); + if (!group_output) { + wlr_log(WLR_ERROR, "failed to allocate memory for workspace output"); + return; + } + + group_output->output = output; + group_output->group_handle = group; + wl_list_insert(&group->outputs, &group_output->link); + + group_output->output_destroy.notify = workspace_handle_output_destroy; + wl_signal_add(&output->events.destroy, &group_output->output_destroy); + + group_send_output(group, output, true); +} + +static void group_output_destroy( + struct wlr_workspace_group_handle_v1_output *output) { + wl_list_remove(&output->link); + wl_list_remove(&output->output_destroy.link); + free(output); +} + +void wlr_workspace_group_handle_v1_output_leave( + struct wlr_workspace_group_handle_v1 *group, struct wlr_output *output) { + struct wlr_workspace_group_handle_v1_output *group_output_iterator; + struct wlr_workspace_group_handle_v1_output *group_output = NULL; + + wl_list_for_each(group_output_iterator, &group->outputs, link) { + if (group_output_iterator->output == output) { + group_output = group_output_iterator; + break; + } + } + + if (group_output) { + group_send_output(group, output, false); + group_output_destroy(group_output); + } else { + // XXX: log an error? crash? + } +} + +static void group_send_details_to_resource( + struct wlr_workspace_group_handle_v1 *group, + struct wl_resource *resource) { + struct wlr_workspace_group_handle_v1_output *output; + wl_list_for_each(output, &group->outputs, link) { + send_output_to_group_resource(resource, output->output, true); + } +} + +struct wlr_workspace_group_handle_v1 *wlr_workspace_group_handle_v1_create( + struct wlr_workspace_manager_v1 *manager) { + + struct wlr_workspace_group_handle_v1 *group = calloc(1, + sizeof(struct wlr_workspace_group_handle_v1)); + if (!group) { + return NULL; + } + + group->manager = manager; + wl_list_insert(&manager->groups, &group->link); + + wl_list_init(&group->outputs); + wl_list_init(&group->resources); + wl_list_init(&group->workspaces); + wl_signal_init(&group->events.destroy); + + struct wl_resource *tmp, *manager_resource; + wl_resource_for_each_safe(manager_resource, tmp, &manager->resources) { + create_workspace_group_resource_for_resource(group, manager_resource); + } + + return group; +} + +void wlr_workspace_group_handle_v1_destroy( + struct wlr_workspace_group_handle_v1 *group) { + if (!group) { + return; + } + + struct wlr_workspace_handle_v1 *workspace, *tmp; + wl_list_for_each_safe(workspace, tmp, &group->workspaces, link) { + wlr_workspace_handle_v1_destroy(workspace); + } + + wlr_signal_emit_safe(&group->events.destroy, group); + workspace_manager_update_idle_source(group->manager); + + struct wlr_workspace_group_handle_v1_output *output, *tmp2; + wl_list_for_each_safe(output, tmp2, &group->outputs, link) { + group_output_destroy(output); + } + + struct wl_resource *tmp3, *resource; + wl_resource_for_each_safe(resource, tmp3, &group->resources) { + zwlr_workspace_group_handle_v1_send_remove(resource); + + wl_resource_set_user_data(resource, NULL); + wl_list_remove(&resource->link); + wl_list_init(&resource->link); + } + + free(group); +} + +static const struct zwlr_workspace_manager_v1_interface workspace_manager_impl; + +static struct wlr_workspace_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_workspace_manager_v1_interface, + &workspace_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void workspace_manager_commit(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_workspace_manager_v1 *manager = manager_from_resource(resource); + if (!manager) { + return; + } + + struct wlr_workspace_group_handle_v1 *group; + struct wlr_workspace_handle_v1 *workspace; + wl_list_for_each(group, &manager->groups, link) { + wl_list_for_each(workspace, &group->workspaces, link) { + workspace->current = workspace->pending; + } + } + + wlr_signal_emit_safe(&manager->events.commit, manager); +} + +static void workspace_manager_stop(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_workspace_manager_v1 *manager = manager_from_resource(resource); + if (!manager) { + return; + } + + zwlr_workspace_manager_v1_send_finished(resource); + wl_resource_destroy(resource); +} + +static const struct zwlr_workspace_manager_v1_interface + workspace_manager_impl = { + .commit = workspace_manager_commit, + .stop = workspace_manager_stop, +}; + +static void workspace_manager_resource_destroy( struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void workspace_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_workspace_manager_v1 *manager = data; + struct wl_resource *resource = wl_resource_create(client, + &zwlr_workspace_manager_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &workspace_manager_impl, + manager, workspace_manager_resource_destroy); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); + + struct wlr_workspace_group_handle_v1 *group, *tmp; + wl_list_for_each_safe(group, tmp, &manager->groups, link) { + struct wl_resource *group_resource = + create_workspace_group_resource_for_resource(group, resource); + group_send_details_to_resource(group, group_resource); + } + + zwlr_workspace_manager_v1_send_done(resource); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_workspace_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + + free(manager); +} + +struct wlr_workspace_manager_v1 *wlr_workspace_manager_v1_create( + struct wl_display *display) { + + struct wlr_workspace_manager_v1 *manager = calloc(1, + sizeof(struct wlr_workspace_manager_v1)); + if (!manager) { + return NULL; + } + + manager->event_loop = wl_display_get_event_loop(display); + manager->global = wl_global_create(display, + &zwlr_workspace_manager_v1_interface, + WORKSPACE_V1_VERSION, manager, + workspace_manager_bind); + if (!manager->global) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.destroy); + wl_signal_init(&manager->events.commit); + wl_list_init(&manager->resources); + wl_list_init(&manager->groups); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +}