diff --git a/include/sway/output.h b/include/sway/output.h index 787527ee7..ae2e50d36 100644 --- a/include/sway/output.h +++ b/include/sway/output.h @@ -63,6 +63,7 @@ struct sway_output { struct wl_listener request_state; struct wlr_color_transform *color_transform; + struct wlr_ext_workspace_group_handle_v1 *ext_workspace_group; struct timespec last_presentation; uint32_t refresh_nsec; diff --git a/include/sway/server.h b/include/sway/server.h index 978f05d0a..318cd39c1 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -127,6 +127,9 @@ struct sway_server { struct wl_listener tearing_control_new_object; struct wl_list tearing_controllers; // sway_tearing_controller::link + struct wlr_ext_workspace_manager_v1 *workspace_manager_v1; + struct wl_listener workspace_manager_v1_commit; + struct wl_list pending_launcher_ctxs; // launcher_ctx::link // The timeout for transactions, after which a transaction is applied diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h index e8b9903e2..7ce48f173 100644 --- a/include/sway/tree/workspace.h +++ b/include/sway/tree/workspace.h @@ -3,6 +3,7 @@ #include #include +#include #include "sway/config.h" #include "sway/tree/container.h" #include "sway/tree/node.h" @@ -51,6 +52,7 @@ struct sway_workspace { bool urgent; struct sway_workspace_state current; + struct wlr_ext_workspace_handle_v1 *ext_workspace; // Always set. }; struct workspace_config *workspace_find_config(const char *ws_name); @@ -160,4 +162,8 @@ void workspace_squash(struct sway_workspace *workspace); void workspace_move_to_output(struct sway_workspace *workspace, struct sway_output *output); +void sway_ext_workspace_init(void); +void sway_ext_workspace_output_enable(struct sway_output *output); +void sway_ext_workspace_output_disable(struct sway_output *output); + #endif diff --git a/sway/commands/rename.c b/sway/commands/rename.c index 0d36cc21e..63fac05cc 100644 --- a/sway/commands/rename.c +++ b/sway/commands/rename.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "log.h" #include "stringop.h" #include "sway/commands.h" @@ -95,6 +96,8 @@ struct cmd_results *cmd_rename(int argc, char **argv) { free(workspace->name); workspace->name = new_name; + wlr_ext_workspace_handle_v1_set_name(workspace->ext_workspace, workspace->name); + output_sort_workspaces(workspace->output); ipc_event_workspace(NULL, workspace, "rename"); diff --git a/sway/input/seat.c b/sway/input/seat.c index ab31b6746..ebdbd91ed 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -1204,6 +1205,15 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n ipc_event_window(container, "focus"); } + if (last_workspace && last_workspace != new_workspace) { + wlr_ext_workspace_handle_v1_set_active(last_workspace->ext_workspace, + workspace_is_visible(last_workspace)); + } + if (new_workspace) { + wlr_ext_workspace_handle_v1_set_active(new_workspace->ext_workspace, + workspace_is_visible(new_workspace)); + } + // Move sticky containers to new workspace if (new_workspace && new_output_last_ws && new_workspace != new_output_last_ws) { diff --git a/sway/server.c b/sway/server.c index 5d446579f..a49017424 100644 --- a/sway/server.c +++ b/sway/server.c @@ -63,6 +63,7 @@ #include "sway/server.h" #include "sway/input/cursor.h" #include "sway/tree/root.h" +#include "sway/tree/workspace.h" #if WLR_HAS_XWAYLAND #include @@ -377,6 +378,7 @@ bool server_init(struct sway_server *server) { wlr_foreign_toplevel_manager_v1_create(server->wl_display); sway_session_lock_init(); + sway_ext_workspace_init(); #if WLR_HAS_DRM_BACKEND server->drm_lease_manager= @@ -543,6 +545,7 @@ void server_fini(struct sway_server *server) { wl_list_remove(&server->xdg_toplevel_tag_manager_v1_set_tag.link); wl_list_remove(&server->request_set_cursor_shape.link); wl_list_remove(&server->new_foreign_toplevel_capture_request.link); + wl_list_remove(&server->workspace_manager_v1_commit.link); input_manager_finish(server->input); // TODO: free sway-specific resources diff --git a/sway/tree/output.c b/sway/tree/output.c index 90ec9331d..c401d0f1d 100644 --- a/sway/tree/output.c +++ b/sway/tree/output.c @@ -2,6 +2,8 @@ #include #include #include +#include +#include "sway/tree/workspace.h" #include "sway/ipc-server.h" #include "sway/layers.h" #include "sway/output.h" @@ -153,6 +155,7 @@ void output_enable(struct sway_output *output) { output->enabled = true; list_add(root->outputs, output); + sway_ext_workspace_output_enable(output); restore_workspaces(output); struct sway_workspace *ws = NULL; @@ -292,6 +295,7 @@ void output_disable(struct sway_output *output) { destroy_layers(output); output_evacuate(output); + sway_ext_workspace_output_disable(output); } void output_begin_destroy(struct sway_output *output) { @@ -333,6 +337,10 @@ void output_add_workspace(struct sway_output *output, } list_add(output->workspaces, workspace); workspace->output = output; + if (workspace->output && workspace->output->ext_workspace_group) { + wlr_ext_workspace_handle_v1_set_group(workspace->ext_workspace, + workspace->output->ext_workspace_group); + } node_set_dirty(&output->node); node_set_dirty(&workspace->node); } diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c index e479cbad4..d366d19aa 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c @@ -3,8 +3,12 @@ #include #include #include +#include #include +#include +#include "log.h" #include "stringop.h" +#include "sway/desktop/transaction.h" #include "sway/input/input-manager.h" #include "sway/input/cursor.h" #include "sway/input/seat.h" @@ -17,9 +21,124 @@ #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "list.h" -#include "log.h" #include "util.h" +static const uint32_t WORKSPACE_CAPABILITIES = + EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE | + EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ASSIGN; + +static const uint32_t GROUP_CAPABILITIES = + EXT_WORKSPACE_GROUP_HANDLE_V1_GROUP_CAPABILITIES_CREATE_WORKSPACE; + +// Helper to find the output associated with a workspace group. +static struct sway_output *group_to_output( + struct wlr_ext_workspace_group_handle_v1 *group) { + for (int i = 0; i < root->outputs->length; i++) { + struct sway_output *output = root->outputs->items[i]; + if (output->ext_workspace_group == group) { + return output; + } + } + abort(); // unreachable +} + +// Callback for ext-workspace-v1 commit events. +static void handle_commit(struct wl_listener *listener, void *data) { + struct sway_server *server = + wl_container_of(listener, server, workspace_manager_v1_commit); + struct wlr_ext_workspace_v1_commit_event *event = data; + + struct wlr_ext_workspace_v1_request *req, *tmp; + wl_list_for_each_safe(req, tmp, event->requests, link) { + switch (req->type) { + case WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE: + if (req->activate.workspace) { + workspace_switch(req->activate.workspace->data); + } + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE:; + struct sway_output *output = group_to_output(req->create_workspace.group); + sway_assert(output, "NULL output given to create_workspace"); + + char *name; + if (req->create_workspace.name) { + if (workspace_by_name(req->create_workspace.name)) { + sway_log(SWAY_ERROR, "Refusing to create workspace with duplicate name."); + break; // Already exists. + } + name = strdup(req->create_workspace.name); + } else { + name = workspace_next_name(output->wlr_output->name); + } + + struct sway_workspace *new_ws = workspace_create(output, name); + if (new_ws) { + workspace_switch(new_ws); + } + free(name); + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_ASSIGN:; + if (!req->assign.workspace || !req->assign.group) break; + + struct sway_workspace *ws = req->assign.workspace->data; + struct sway_output *new_output = group_to_output(req->assign.group); + struct sway_output *old_output = ws->output; + workspace_move_to_output(ws, new_output); + arrange_output(old_output); + arrange_output(new_output); + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_DEACTIVATE: + case WLR_EXT_WORKSPACE_V1_REQUEST_REMOVE: + break; // No-op. + } + } + + transaction_commit_dirty(); +} + +// Initialize ext-workspace. Must be called once at startup. +void sway_ext_workspace_init(void) { + server.workspace_manager_v1 = + wlr_ext_workspace_manager_v1_create(server.wl_display, 1); + if (!server.workspace_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create ext_workspace_manager_v1"); + return; + } + + server.workspace_manager_v1_commit.notify = handle_commit; + wl_signal_add(&server.workspace_manager_v1->events.commit, + &server.workspace_manager_v1_commit); +} + +// Must be called whenever an output is enabled. +void sway_ext_workspace_output_enable(struct sway_output *output) { + if (!output->wlr_output) { + return; + } + + output->ext_workspace_group = + wlr_ext_workspace_group_handle_v1_create( + server.workspace_manager_v1, GROUP_CAPABILITIES); + if (!output->ext_workspace_group) { + sway_log(SWAY_ERROR, "Failed to create workspace group for output '%s'", + output->wlr_output->name); + return; + } + + wlr_ext_workspace_group_handle_v1_output_enter( + output->ext_workspace_group, output->wlr_output); +} + +// Must be called whenever an output is disabled. +void sway_ext_workspace_output_disable(struct sway_output *output) { + if (!output->ext_workspace_group) { + return; + } + + wlr_ext_workspace_group_handle_v1_destroy(output->ext_workspace_group); + output->ext_workspace_group = NULL; +} + struct workspace_config *workspace_find_config(const char *ws_name) { for (int i = 0; i < config->workspace_configs->length; ++i) { struct workspace_config *wsc = config->workspace_configs->items[i]; @@ -70,6 +189,16 @@ struct sway_workspace *workspace_create(struct sway_output *output, sway_log(SWAY_ERROR, "Unable to allocate sway_workspace"); return NULL; } + + ws->ext_workspace = wlr_ext_workspace_handle_v1_create( + server.workspace_manager_v1, NULL, WORKSPACE_CAPABILITIES); + if (!ws->ext_workspace) { + sway_log(SWAY_ERROR, "Failed to create ext_workspace for '%s'", name); + free(ws); + return NULL; + } + ws->ext_workspace->data = ws; + node_init(&ws->node, N_WORKSPACE, ws); bool failed = false; @@ -79,6 +208,7 @@ struct sway_workspace *workspace_create(struct sway_output *output, if (failed) { wlr_scene_node_destroy(&ws->layers.tiling->node); wlr_scene_node_destroy(&ws->layers.fullscreen->node); + wlr_ext_workspace_handle_v1_destroy(ws->ext_workspace); free(ws); return NULL; } @@ -127,6 +257,13 @@ struct sway_workspace *workspace_create(struct sway_output *output, output_add_workspace(output, ws); output_sort_workspaces(output); + wlr_ext_workspace_handle_v1_set_name(ws->ext_workspace, ws->name); + if (ws->output && ws->output->ext_workspace_group) { + wlr_ext_workspace_handle_v1_set_group(ws->ext_workspace, + ws->output->ext_workspace_group); + } + wlr_ext_workspace_handle_v1_set_active(ws->ext_workspace, + workspace_is_visible(ws)); ipc_event_workspace(NULL, ws, "init"); wl_signal_emit_mutable(&root->events.new_node, &ws->node); @@ -163,6 +300,9 @@ void workspace_begin_destroy(struct sway_workspace *workspace) { ipc_event_workspace(NULL, workspace, "empty"); // intentional wl_signal_emit_mutable(&workspace->node.events.destroy, &workspace->node); + wlr_ext_workspace_handle_v1_destroy(workspace->ext_workspace); + workspace->ext_workspace = NULL; + if (workspace->output) { workspace_detach(workspace); } @@ -687,6 +827,7 @@ void workspace_detect_urgent(struct sway_workspace *workspace) { if (workspace->urgent != new_urgent) { workspace->urgent = new_urgent; + wlr_ext_workspace_handle_v1_set_urgent(workspace->ext_workspace, workspace->urgent); ipc_event_workspace(NULL, workspace, "urgent"); } }