window-rules/foreign-toplevel: add per-window <taskbarScope> with 4 visibility modes

Introduce a per-window rule `<taskbarScope>` 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
This commit is contained in:
Maik Broemme 2025-11-02 22:29:51 +01:00
parent 7223056ffc
commit dad5e72dbf
No known key found for this signature in database
GPG key ID: 27BE1125704B8B02
9 changed files with 136 additions and 17 deletions

View file

@ -9,5 +9,6 @@ struct foreign_toplevel *foreign_toplevel_create(struct view *view);
void foreign_toplevel_set_parent(struct foreign_toplevel *toplevel, void foreign_toplevel_set_parent(struct foreign_toplevel *toplevel,
struct foreign_toplevel *parent); struct foreign_toplevel *parent);
void foreign_toplevel_destroy(struct foreign_toplevel *toplevel); void foreign_toplevel_destroy(struct foreign_toplevel *toplevel);
void foreign_toplevel_refresh_outputs(struct foreign_toplevel *toplevel);
#endif /* LABWC_FOREIGN_TOPLEVEL_H */ #endif /* LABWC_FOREIGN_TOPLEVEL_H */

View file

@ -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, void wlr_foreign_toplevel_set_parent(struct wlr_foreign_toplevel *wlr_toplevel,
struct wlr_foreign_toplevel *parent); struct wlr_foreign_toplevel *parent);
void wlr_foreign_toplevel_finish(struct wlr_foreign_toplevel *wlr_toplevel); 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 */ #endif /* LABWC_WLR_FOREIGN_TOPLEVEL_H */

View file

@ -17,6 +17,22 @@ enum property {
LAB_PROP_TRUE, 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: * 'identifier' represents:
* - 'app_id' for native Wayland windows * - 'app_id' for native Wayland windows
@ -41,6 +57,8 @@ struct window_rule {
enum property fixed_position; enum property fixed_position;
enum property icon_prefer_client; enum property icon_prefer_client;
enum taskbar_scope scope_taskbar;
struct wl_list link; /* struct rcxml.window_rules */ 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); 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 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 */ #endif /* LABWC_WINDOW_RULES_H */

View file

@ -243,6 +243,32 @@ set_property(const char *str, enum property *variable)
*variable = ret ? LAB_PROP_TRUE : LAB_PROP_FALSE; *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 static void
fill_window_rule(xmlNode *node) fill_window_rule(xmlNode *node)
{ {
@ -300,6 +326,8 @@ fill_window_rule(xmlNode *node)
set_property(content, &window_rule->ignore_configure_request); set_property(content, &window_rule->ignore_configure_request);
} else if (!strcasecmp(key, "fixedPosition")) { } else if (!strcasecmp(key, "fixedPosition")) {
set_property(content, &window_rule->fixed_position); set_property(content, &window_rule->fixed_position);
} else if (!strcasecmp(key, "taskbarScope")) {
set_taskbar_scope(content, &window_rule->scope_taskbar);
} }
} }

View file

@ -43,3 +43,10 @@ foreign_toplevel_destroy(struct foreign_toplevel *toplevel)
ext_foreign_toplevel_finish(&toplevel->ext_toplevel); ext_foreign_toplevel_finish(&toplevel->ext_toplevel);
free(toplevel); free(toplevel);
} }
void
foreign_toplevel_refresh_outputs(struct foreign_toplevel *toplevel)
{
assert(toplevel);
wlr_foreign_toplevel_refresh_outputs(&toplevel->wlr_toplevel);
}

View file

@ -6,6 +6,7 @@
#include "labwc.h" #include "labwc.h"
#include "output.h" #include "output.h"
#include "view.h" #include "view.h"
#include "window-rules.h"
/* wlr signals */ /* wlr signals */
static void 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); wl_container_of(listener, wlr_toplevel, on_view.new_outputs);
assert(wlr_toplevel->handle); assert(wlr_toplevel->handle);
/* wlr_foreign_toplevel_refresh_outputs(wlr_toplevel);
* 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);
}
}
} }
static void 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); wlr_foreign_toplevel_handle_v1_destroy(wlr_toplevel->handle);
assert(!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);
}
}
}

View file

@ -1662,6 +1662,11 @@ view_move_to_workspace(struct view *view, struct workspace *workspace)
wlr_scene_node_reparent(&view->scene_tree->node, wlr_scene_node_reparent(&view->scene_tree->node,
workspace->tree); workspace->tree);
} }
/* Refresh toplevel outputs after moving view */
if (view->foreign_toplevel) {
foreign_toplevel_refresh_outputs(view->foreign_toplevel);
}
} }
static void static void

View file

@ -114,3 +114,22 @@ window_rules_get_property(struct view *view, const char *property)
} }
return LAB_PROP_UNSPECIFIED; 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;
}

View file

@ -16,6 +16,7 @@
#include "common/list.h" #include "common/list.h"
#include "common/mem.h" #include "common/mem.h"
#include "config/rcxml.h" #include "config/rcxml.h"
#include "foreign-toplevel/foreign.h"
#include "input/keyboard.h" #include "input/keyboard.h"
#include "labwc.h" #include "labwc.h"
#include "output.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 */ /* Make sure new views will spawn on the new workspace */
server->workspaces.current = target; 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; struct view *grabbed_view = server->grabbed_view;
if (grabbed_view && !view_is_always_on_top(grabbed_view)) { if (grabbed_view && !view_is_always_on_top(grabbed_view)) {
view_move_to_workspace(grabbed_view, target); view_move_to_workspace(grabbed_view, target);