From dad5e72dbf51a154e1ab15c24c58ea55a562c211 Mon Sep 17 00:00:00 2001 From: Maik Broemme Date: Sun, 2 Nov 2025 22:29:51 +0100 Subject: [PATCH] window-rules/foreign-toplevel: add per-window with 4 visibility modes Introduce a per-window rule `` that controls where a window's *taskbar entry* appears. This affects taskbar listing only (via foreign-toplevel output membership); it does not change sticky/omnipresent state, focus, or workspace membership. Supported values (case-insensitive): - here -> show only when both match: the window's monitor AND its active workspace - monitor -> show only on the window's monitor, across that monitor's workspaces (default if the key is omitted) - workspace -> show on all monitors currently on the window's workspace - everywhere -> show on all monitors and all workspaces --- include/foreign-toplevel/foreign.h | 1 + include/foreign-toplevel/wlr-foreign.h | 1 + include/window-rules.h | 19 ++++++++ src/config/rcxml.c | 28 +++++++++++ src/foreign-toplevel/foreign.c | 7 +++ src/foreign-toplevel/wlr-foreign.c | 64 +++++++++++++++++++------- src/view.c | 5 ++ src/window-rules.c | 19 ++++++++ src/workspaces.c | 9 ++++ 9 files changed, 136 insertions(+), 17 deletions(-) diff --git a/include/foreign-toplevel/foreign.h b/include/foreign-toplevel/foreign.h index bc226776..16f155f7 100644 --- a/include/foreign-toplevel/foreign.h +++ b/include/foreign-toplevel/foreign.h @@ -9,5 +9,6 @@ struct foreign_toplevel *foreign_toplevel_create(struct view *view); void foreign_toplevel_set_parent(struct foreign_toplevel *toplevel, struct foreign_toplevel *parent); void foreign_toplevel_destroy(struct foreign_toplevel *toplevel); +void foreign_toplevel_refresh_outputs(struct foreign_toplevel *toplevel); #endif /* LABWC_FOREIGN_TOPLEVEL_H */ diff --git a/include/foreign-toplevel/wlr-foreign.h b/include/foreign-toplevel/wlr-foreign.h index 2da44752..74e7ecae 100644 --- a/include/foreign-toplevel/wlr-foreign.h +++ b/include/foreign-toplevel/wlr-foreign.h @@ -35,5 +35,6 @@ void wlr_foreign_toplevel_init(struct wlr_foreign_toplevel *wlr_toplevel, void wlr_foreign_toplevel_set_parent(struct wlr_foreign_toplevel *wlr_toplevel, struct wlr_foreign_toplevel *parent); void wlr_foreign_toplevel_finish(struct wlr_foreign_toplevel *wlr_toplevel); +void wlr_foreign_toplevel_refresh_outputs(struct wlr_foreign_toplevel *wlr_toplevel); #endif /* LABWC_WLR_FOREIGN_TOPLEVEL_H */ diff --git a/include/window-rules.h b/include/window-rules.h index 1bee4c09..270fcc55 100644 --- a/include/window-rules.h +++ b/include/window-rules.h @@ -17,6 +17,22 @@ enum property { LAB_PROP_TRUE, }; +/* + * Taskbar scope for per-window task listing. + * - 'UNSPECIFIED' backward compatible fallback to: monitor + * - 'HERE' THIS monitor & THIS workspace + * - 'MONITOR' THIS monitor & ALL workspaces (default) + * - 'WORKSPACE' ALL monitors & THIS workspace + * - 'EVERYWHERE' ALL monitors & ALL workspaces + */ +enum taskbar_scope { + LAB_TASKBAR_SCOPE_UNSPECIFIED = 0, + LAB_TASKBAR_SCOPE_HERE, + LAB_TASKBAR_SCOPE_MONITOR, + LAB_TASKBAR_SCOPE_WORKSPACE, + LAB_TASKBAR_SCOPE_EVERYWHERE, +}; + /* * 'identifier' represents: * - 'app_id' for native Wayland windows @@ -41,6 +57,8 @@ struct window_rule { enum property fixed_position; enum property icon_prefer_client; + enum taskbar_scope scope_taskbar; + struct wl_list link; /* struct rcxml.window_rules */ }; @@ -48,5 +66,6 @@ struct view; void window_rules_apply(struct view *view, enum window_rule_event event); enum property window_rules_get_property(struct view *view, const char *property); +enum taskbar_scope window_rules_get_taskbar_scope(struct view *view, const char *scope); #endif /* LABWC_WINDOW_RULES_H */ diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 6ed1522b..f6e0a557 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -243,6 +243,32 @@ set_property(const char *str, enum property *variable) *variable = ret ? LAB_PROP_TRUE : LAB_PROP_FALSE; } +static void +set_taskbar_scope(const char *str, enum taskbar_scope *variable) +{ + if (!str) { + *variable = LAB_TASKBAR_SCOPE_UNSPECIFIED; + return; + } + if (!strcasecmp(str, "here")) { + *variable = LAB_TASKBAR_SCOPE_HERE; + return; + } + if (!strcasecmp(str, "monitor")) { + *variable = LAB_TASKBAR_SCOPE_MONITOR; + return; + } + if (!strcasecmp(str, "workspace")) { + *variable = LAB_TASKBAR_SCOPE_WORKSPACE; + return; + } + if (!strcasecmp(str, "everywhere")) { + *variable = LAB_TASKBAR_SCOPE_EVERYWHERE; + return; + } + *variable = LAB_TASKBAR_SCOPE_UNSPECIFIED; +} + static void fill_window_rule(xmlNode *node) { @@ -300,6 +326,8 @@ fill_window_rule(xmlNode *node) set_property(content, &window_rule->ignore_configure_request); } else if (!strcasecmp(key, "fixedPosition")) { set_property(content, &window_rule->fixed_position); + } else if (!strcasecmp(key, "taskbarScope")) { + set_taskbar_scope(content, &window_rule->scope_taskbar); } } diff --git a/src/foreign-toplevel/foreign.c b/src/foreign-toplevel/foreign.c index 14563431..0f1c4b8f 100644 --- a/src/foreign-toplevel/foreign.c +++ b/src/foreign-toplevel/foreign.c @@ -43,3 +43,10 @@ foreign_toplevel_destroy(struct foreign_toplevel *toplevel) ext_foreign_toplevel_finish(&toplevel->ext_toplevel); free(toplevel); } + +void +foreign_toplevel_refresh_outputs(struct foreign_toplevel *toplevel) +{ + assert(toplevel); + wlr_foreign_toplevel_refresh_outputs(&toplevel->wlr_toplevel); +} diff --git a/src/foreign-toplevel/wlr-foreign.c b/src/foreign-toplevel/wlr-foreign.c index a84775f5..25491b9e 100644 --- a/src/foreign-toplevel/wlr-foreign.c +++ b/src/foreign-toplevel/wlr-foreign.c @@ -6,6 +6,7 @@ #include "labwc.h" #include "output.h" #include "view.h" +#include "window-rules.h" /* wlr signals */ static void @@ -116,23 +117,7 @@ handle_new_outputs(struct wl_listener *listener, void *data) wl_container_of(listener, wlr_toplevel, on_view.new_outputs); assert(wlr_toplevel->handle); - /* - * Loop over all outputs and notify foreign_toplevel clients about changes. - * wlr_foreign_toplevel_handle_v1_output_xxx() keeps track of the active - * outputs internally and merges the events. It also listens to output - * destroy events so its fine to just relay the current state and let - * wlr_foreign_toplevel handle the rest. - */ - struct output *output; - wl_list_for_each(output, &wlr_toplevel->view->server->outputs, link) { - if (view_on_output(wlr_toplevel->view, output)) { - wlr_foreign_toplevel_handle_v1_output_enter( - wlr_toplevel->handle, output->wlr_output); - } else { - wlr_foreign_toplevel_handle_v1_output_leave( - wlr_toplevel->handle, output->wlr_output); - } - } + wlr_foreign_toplevel_refresh_outputs(wlr_toplevel); } static void @@ -246,3 +231,48 @@ wlr_foreign_toplevel_finish(struct wlr_foreign_toplevel *wlr_toplevel) wlr_foreign_toplevel_handle_v1_destroy(wlr_toplevel->handle); assert(!wlr_toplevel->handle); } + +void +wlr_foreign_toplevel_refresh_outputs(struct wlr_foreign_toplevel *wlr_toplevel) +{ + if (!wlr_toplevel || !wlr_toplevel->handle) { + return; + } + + /* + * Loop over all outputs and notify foreign_toplevel clients about changes. + * wlr_foreign_toplevel_handle_v1_output_xxx() keeps track of the active + * outputs internally and merges the events. It also listens to output + * destroy events so its fine to just relay the current state and let + * wlr_foreign_toplevel handle the rest. + */ + struct output *output; + wl_list_for_each(output, &wlr_toplevel->view->server->outputs, link) { + bool visible = false; + switch (window_rules_get_taskbar_scope(wlr_toplevel->view, "taskbarScope")) { + case LAB_TASKBAR_SCOPE_HERE: + visible = view_on_output(wlr_toplevel->view, output) + && (wlr_toplevel->view->workspace == + wlr_toplevel->view->server->workspaces.current); + break; + case LAB_TASKBAR_SCOPE_MONITOR: + case LAB_TASKBAR_SCOPE_UNSPECIFIED: + visible = view_on_output(wlr_toplevel->view, output); + break; + case LAB_TASKBAR_SCOPE_WORKSPACE: + visible = (wlr_toplevel->view->workspace == + wlr_toplevel->view->server->workspaces.current); + break; + case LAB_TASKBAR_SCOPE_EVERYWHERE: + visible = true; + break; + } + if (visible) { + wlr_foreign_toplevel_handle_v1_output_enter(wlr_toplevel->handle, + output->wlr_output); + } else { + wlr_foreign_toplevel_handle_v1_output_leave(wlr_toplevel->handle, + output->wlr_output); + } + } +} diff --git a/src/view.c b/src/view.c index 87ef8566..b9c1f20f 100644 --- a/src/view.c +++ b/src/view.c @@ -1662,6 +1662,11 @@ view_move_to_workspace(struct view *view, struct workspace *workspace) wlr_scene_node_reparent(&view->scene_tree->node, workspace->tree); } + + /* Refresh toplevel outputs after moving view */ + if (view->foreign_toplevel) { + foreign_toplevel_refresh_outputs(view->foreign_toplevel); + } } static void diff --git a/src/window-rules.c b/src/window-rules.c index 0b8f1101..fd12edf5 100644 --- a/src/window-rules.c +++ b/src/window-rules.c @@ -114,3 +114,22 @@ window_rules_get_property(struct view *view, const char *property) } return LAB_PROP_UNSPECIFIED; } + +enum taskbar_scope +window_rules_get_taskbar_scope(struct view *view, const char *scope) +{ + assert(scope); + + struct window_rule *rule; + wl_list_for_each_reverse(rule, &rc.window_rules, link) { + if (view_matches_criteria(rule, view)) { + if (rule->scope_taskbar + && !strcasecmp(scope, "taskbarScope")) { + return rule->scope_taskbar; + } + } + } + + /* backward compatible fallback to: monitor */ + return LAB_TASKBAR_SCOPE_UNSPECIFIED; +} diff --git a/src/workspaces.c b/src/workspaces.c index f56d170a..3ff9466e 100644 --- a/src/workspaces.c +++ b/src/workspaces.c @@ -16,6 +16,7 @@ #include "common/list.h" #include "common/mem.h" #include "config/rcxml.h" +#include "foreign-toplevel/foreign.h" #include "input/keyboard.h" #include "labwc.h" #include "output.h" @@ -442,6 +443,14 @@ workspaces_switch_to(struct workspace *target, bool update_focus) /* Make sure new views will spawn on the new workspace */ server->workspaces.current = target; + /* Refresh toplevel outputs after switching workspace */ + struct view *v_iter; + wl_list_for_each(v_iter, &server->views, link) { + if (v_iter->foreign_toplevel) { + foreign_toplevel_refresh_outputs(v_iter->foreign_toplevel); + } + } + struct view *grabbed_view = server->grabbed_view; if (grabbed_view && !view_is_always_on_top(grabbed_view)) { view_move_to_workspace(grabbed_view, target);