From 81f70027eb024f14a3d83715bb2ddc9a6975db57 Mon Sep 17 00:00:00 2001 From: Manuel Coenen Date: Tue, 31 Mar 2026 14:31:23 +0200 Subject: [PATCH] feat: implement ext-workspaces-v1 --- README.md | 2 +- config.def.h | 3 +++ plumbing.c | 22 ++++++++++++++++++++++ vwl.c | 31 ++++++++++++++++++++++++++++++- vwl.h | 1 + 5 files changed, 57 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c275a04..ed1e952 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ wlroots-based Wayland compositor with virtual outputs and physical cursor continuity. Originally forked from dwl. -`LOC: 7448 total, 2880 vwl.c` +`LOC: 7493 total, 2903 vwl.c` ## Features diff --git a/config.def.h b/config.def.h index c5e43cc..b3309f5 100644 --- a/config.def.h +++ b/config.def.h @@ -111,6 +111,9 @@ static const VirtualOutputRule vorules[] = { { NULL, NULL, 0, 0, 0, 0, 0.55f, 1, &layouts[0], (LENGTH(layouts) > 1) ? &layouts[1] : &layouts[0], NULL, 0 }, }; +/* workspaces */ +static const bool ext_workspace_move_to_selvout = 0; + /* keyboard */ static const struct xkb_rule_names xkb_rules = { /* can specify fields: rules, model, layout, variant, options */ diff --git a/plumbing.c b/plumbing.c index 7524260..4cdc098 100644 --- a/plumbing.c +++ b/plumbing.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -192,6 +193,7 @@ void setcursorshape(struct wl_listener *listener, void *data); void requeststartdrag(struct wl_listener *listener, void *data); void startdrag(struct wl_listener *listener, void *data); void locksession(struct wl_listener *listener, void *data); +void extworkspacecommit(struct wl_listener *listener, void *data); #ifdef XWAYLAND void createnotifyx11(struct wl_listener *listener, void *data); void xwaylandready(struct wl_listener *listener, void *data); @@ -226,6 +228,7 @@ struct wl_listener request_set_cursor_shape = {.notify = setcursorshape}; struct wl_listener request_start_drag = {.notify = requeststartdrag}; struct wl_listener start_drag = {.notify = startdrag}; struct wl_listener new_session_lock = {.notify = locksession}; +struct wl_listener ext_workspace_commit = {.notify = extworkspacecommit}; #ifdef XWAYLAND struct wl_listener new_xwayland_surface = {.notify = createnotifyx11}; struct wl_listener xwayland_ready = {.notify = xwaylandready}; @@ -274,6 +277,7 @@ cleanuplisteners(void) wl_list_remove(&request_start_drag.link); wl_list_remove(&start_drag.link); wl_list_remove(&new_session_lock.link); + wl_list_remove(&ext_workspace_commit.link); share_cleanuplisteners(); #ifdef XWAYLAND wl_list_remove(&new_xwayland_surface.link); @@ -402,6 +406,24 @@ buttonpress(struct wl_listener *listener, void *data) wlr_seat_pointer_notify_button(seat, event->time_msec, event->button, event->state); } +void +extworkspacecommit(struct wl_listener *listener, void *data) +{ + struct wlr_ext_workspace_v1_commit_event *event = data; + + struct wlr_ext_workspace_v1_request *req; + wl_list_for_each(req, event->requests, link) { + if (req->type == WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE) { + struct Workspace *ws = req->activate.workspace->data; + if (ext_workspace_move_to_selvout) { + ipc_move_workspace_to_vout(ws, selvout); + } else { + view(&(Arg){.ui = ws->id}); + } + } + } +} + void cursorframe(struct wl_listener *listener, void *data) { diff --git a/vwl.c b/vwl.c index b4cf6d8..495c056 100644 --- a/vwl.c +++ b/vwl.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -237,6 +238,9 @@ static struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr; static struct wlr_cursor_shape_manager_v1 *cursor_shape_mgr; static struct wlr_output_power_manager_v1 *power_mgr; +struct wlr_ext_workspace_manager_v1 *ext_workspace_manager; +struct wlr_ext_workspace_group_handle_v1 *ext_workspace_group; + static struct wlr_pointer_constraints_v1 *pointer_constraints; static struct wlr_relative_pointer_manager_v1 *relative_pointer_mgr; @@ -277,6 +281,7 @@ extern struct wl_listener request_set_cursor_shape; extern struct wl_listener request_start_drag; extern struct wl_listener start_drag; extern struct wl_listener new_session_lock; +extern struct wl_listener ext_workspace_commit; #ifdef XWAYLAND static void activatex11(struct wl_listener *listener, void *data); @@ -462,6 +467,7 @@ cleanupmon(struct wl_listener *listener, void *data) wl_list_remove(&m->frame.link); wl_list_remove(&m->link); wl_list_remove(&m->request_state.link); + wlr_ext_workspace_group_handle_v1_output_leave(ext_workspace_group, m->wlr_output); if (m->lock_surface) destroylocksurface(&m->destroy_lock_surface, NULL); m->wlr_output->data = NULL; @@ -657,6 +663,8 @@ createmon(struct wl_listener *listener, void *data) else wlr_output_layout_add(output_layout, wlr_output, m->monitor_area.x, m->monitor_area.y); + wlr_ext_workspace_group_handle_v1_output_enter(ext_workspace_group, wlr_output); + matched_count = 0; first_vout = NULL; for (i = 0; i < LENGTH(vorules); i++) { @@ -1229,6 +1237,7 @@ focusclient(Client *c, int lift) tabhdr_update(selvout->mon, selvout, area, focustoptiledvout(selvout)); } c->isurgent = 0; + wlr_ext_workspace_handle_v1_set_urgent(c->ws->ext_workspace, false); /* Don't change border color if there is an exclusive focus or we are * handling a drag operation */ @@ -2053,6 +2062,10 @@ setup(void) break; } } + + ext_workspace_manager = wlr_ext_workspace_manager_v1_create(dpy, 1); + ext_workspace_group = wlr_ext_workspace_group_handle_v1_create(ext_workspace_manager, 0); + wl_signal_add(&ext_workspace_manager->events.commit, &ext_workspace_commit); for (i = 0; i < WORKSPACE_COUNT; i++) { Workspace *ws = &workspaces[i]; ws->id = i; @@ -2069,6 +2082,12 @@ setup(void) ws->was_orphaned = false; ws->orphan_vout_name[0] = '\0'; ws->orphan_monitor_name[0] = '\0'; + ws->ext_workspace = wlr_ext_workspace_handle_v1_create(ext_workspace_manager, ws->name, + EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE); + ws->ext_workspace->data = ws; + wlr_ext_workspace_handle_v1_set_group(ws->ext_workspace, ext_workspace_group); + wlr_ext_workspace_handle_v1_set_name(ws->ext_workspace, ws->name); + wlr_ext_workspace_handle_v1_set_hidden(ws->ext_workspace, true); } selws = NULL; spawnrules_init(); @@ -2695,8 +2714,10 @@ urgent(struct wl_listener *listener, void *data) c->isurgent = 1; updateipc(); - if (client_surface(c)->mapped) + if (client_surface(c)->mapped) { updatebordercolor(c, c == focustop(selmon)); + wlr_ext_workspace_handle_v1_set_urgent(c->ws->ext_workspace, true); + } } void @@ -3245,6 +3266,10 @@ wsattach(VirtualOutput *vout, Workspace *ws) return; if (ws->vout == vout) return; + + /* This is (almost) a no-op if nothing else changes */ + wlr_ext_workspace_handle_v1_set_hidden(ws->ext_workspace, false); + old = ws->vout; if (old) { if (old->ws == ws) { @@ -3275,6 +3300,8 @@ wsactivate(VirtualOutput *vout, Workspace *ws, int focus_change) if (old == ws) return; wssave(vout); + if (old && old->ext_workspace) + wlr_ext_workspace_handle_v1_set_active(old->ext_workspace, false); if (ws && ws->vout != vout) wsattach(vout, ws); if (ws) { @@ -3296,6 +3323,8 @@ wsactivate(VirtualOutput *vout, Workspace *ws, int focus_change) if (focus_change) focusclient(focustopvout(vout), 1); } + if (ws && ws->ext_workspace) + wlr_ext_workspace_handle_v1_set_active(ws->ext_workspace, true); } static void diff --git a/vwl.h b/vwl.h index 62f3de7..b150539 100644 --- a/vwl.h +++ b/vwl.h @@ -206,6 +206,7 @@ struct Workspace { char orphan_vout_name[WORKSPACE_NAME_LEN]; char orphan_monitor_name[WORKSPACE_NAME_LEN]; bool was_orphaned; /* Track if workspace was orphaned during monitor removal */ + struct wlr_ext_workspace_handle_v1 *ext_workspace; }; struct VirtualOutput {