From dfe428ae14e65944f8d7d4ed4d7bb83e8d895ae2 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Mon, 29 Dec 2025 02:50:45 +0900 Subject: [PATCH 1/6] cycle: refactor to aggregate type definitions into cycle.h We declared `cycle_state` struct in `labwc.h` and `cycle_osd_scene` struct in `output.h`, which was unclean in terms of separation of concerns. So this commit firstly moves `cycle_state` to `cycle.h`, then replaces `cycle_osd_scene` in `output.h` with `cycle_osd_output` in `cycle.h` which is dynamically allocated in a similar manner to `session_lock_output`. This ensures that all states about alt-tabbing are stored in `server->cycle`. Also, this commit fixes a rare memory leak in `output->cycle_osd.items` when an output is destroyed while alt-tabbing, by freeing it when the osd tree is destroyed. --- include/cycle.h | 33 +++++++++++++++++---- include/labwc.h | 11 +------ include/output.h | 5 ---- src/cycle/cycle.c | 60 +++++++++++++++++++++++++-------------- src/cycle/osd-classic.c | 26 ++++++++--------- src/cycle/osd-thumbnail.c | 30 ++++++++++---------- src/output.c | 1 - src/server.c | 1 + 8 files changed, 95 insertions(+), 72 deletions(-) diff --git a/include/cycle.h b/include/cycle.h index d5430cb4..2e5b57ec 100644 --- a/include/cycle.h +++ b/include/cycle.h @@ -4,6 +4,7 @@ #include #include +#include #include "config/types.h" struct output; @@ -49,6 +50,27 @@ struct cycle_filter { enum cycle_app_id_filter app_id; }; +struct cycle_state { + struct view *selected_view; + struct wl_list views; + struct wl_list osd_outputs; /* struct cycle_osd_output.link */ + bool preview_was_shaded; + bool preview_was_enabled; + struct wlr_scene_node *preview_node; + struct wlr_scene_node *preview_dummy; + struct lab_scene_rect *preview_outline; + struct cycle_filter filter; +}; + +struct cycle_osd_output { + struct wl_list link; /* struct cycle_state.osd_outputs */ + struct output *output; + struct wl_listener tree_destroy; + + struct wl_list items; /* struct cycle_osd_item.link */ + struct wlr_scene_tree *tree; +}; + struct buf; struct view; struct server; @@ -92,15 +114,14 @@ struct cycle_osd_item { struct cycle_osd_impl { /* - * Create a scene-tree of OSD for an output. - * This sets output->cycle_osd.{items,tree}. + * Create a scene-tree of OSD for an output and fill + * osd_output->items. */ - void (*create)(struct output *output); + void (*init)(struct cycle_osd_output *osd_output); /* - * Update output->cycle_osd.tree to highlight - * server->cycle_state.selected_view. + * Update the OSD to highlight server->cycle.selected_view. */ - void (*update)(struct output *output); + void (*update)(struct cycle_osd_output *osd_output); }; extern struct cycle_osd_impl cycle_osd_classic_impl; diff --git a/include/labwc.h b/include/labwc.h index ee10af12..47af8875 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -303,16 +303,7 @@ struct server { struct wlr_security_context_manager_v1 *security_context_manager_v1; /* Set when in cycle (alt-tab) mode */ - struct cycle_state { - struct view *selected_view; - struct wl_list views; - bool preview_was_shaded; - bool preview_was_enabled; - struct wlr_scene_node *preview_node; - struct wlr_scene_node *preview_dummy; - struct lab_scene_rect *preview_outline; - struct cycle_filter filter; - } cycle; + struct cycle_state cycle; struct theme *theme; diff --git a/include/output.h b/include/output.h index 25001247..2b1e4bcf 100644 --- a/include/output.h +++ b/include/output.h @@ -19,11 +19,6 @@ struct output { struct wlr_scene_tree *session_lock_tree; struct wlr_scene_buffer *workspace_osd; - struct cycle_osd_scene { - struct wl_list items; /* struct cycle_osd_item */ - struct wlr_scene_tree *tree; - } cycle_osd; - /* In output-relative scene coordinates */ struct wlr_box usable_area; diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index e0391f1d..88e713f1 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -6,6 +6,7 @@ #include #include "common/lab-scene-rect.h" #include "common/list.h" +#include "common/mem.h" #include "common/scene-helpers.h" #include "config/rcxml.h" #include "labwc.h" @@ -314,6 +315,21 @@ insert_view_ordered_by_age(struct wl_list *views, struct view *new_view) wl_list_insert(link, &new_view->cycle_link); } +static void +handle_osd_tree_destroy(struct wl_listener *listener, void *data) +{ + struct cycle_osd_output *osd_output = + wl_container_of(listener, osd_output, tree_destroy); + struct cycle_osd_item *item, *tmp; + wl_list_for_each_safe(item, tmp, &osd_output->items, link) { + wl_list_remove(&item->link); + free(item); + } + wl_list_remove(&osd_output->tree_destroy.link); + wl_list_remove(&osd_output->link); + free(osd_output); +} + /* Return false on failure */ static bool init_cycle(struct server *server, struct cycle_filter filter) @@ -366,8 +382,17 @@ init_cycle(struct server *server, struct cycle_filter filter) if (!output_is_usable(output)) { continue; } - get_osd_impl()->create(output); - assert(output->cycle_osd.tree); + + struct cycle_osd_output *osd_output = znew(*osd_output); + wl_list_append(&server->cycle.osd_outputs, &osd_output->link); + osd_output->output = output; + wl_list_init(&osd_output->items); + + get_osd_impl()->init(osd_output); + + osd_output->tree_destroy.notify = handle_osd_tree_destroy; + wl_signal_add(&osd_output->tree->node.events.destroy, + &osd_output->tree_destroy); } } @@ -380,11 +405,9 @@ update_cycle(struct server *server) struct cycle_state *cycle = &server->cycle; if (rc.window_switcher.osd.show) { - struct output *output; - wl_list_for_each(output, &server->outputs, link) { - if (output->cycle_osd.tree) { - get_osd_impl()->update(output); - } + struct cycle_osd_output *osd_output; + wl_list_for_each(osd_output, &cycle->osd_outputs, link) { + get_osd_impl()->update(osd_output); } } @@ -394,8 +417,8 @@ update_cycle(struct server *server) /* Outline current window */ if (rc.window_switcher.outlines) { - if (view_is_focusable(server->cycle.selected_view)) { - update_preview_outlines(server->cycle.selected_view); + if (view_is_focusable(cycle->selected_view)) { + update_preview_outlines(cycle->selected_view); } } } @@ -404,17 +427,10 @@ update_cycle(struct server *server) static void destroy_cycle(struct server *server) { - struct output *output; - wl_list_for_each(output, &server->outputs, link) { - struct cycle_osd_item *item, *tmp; - wl_list_for_each_safe(item, tmp, &output->cycle_osd.items, link) { - wl_list_remove(&item->link); - free(item); - } - if (output->cycle_osd.tree) { - wlr_scene_node_destroy(&output->cycle_osd.tree->node); - output->cycle_osd.tree = NULL; - } + struct cycle_osd_output *osd_output, *tmp; + wl_list_for_each_safe(osd_output, tmp, &server->cycle.osd_outputs, link) { + /* calls handle_osd_tree_destroy() */ + wlr_scene_node_destroy(&osd_output->tree->node); } restore_preview_node(server); @@ -424,8 +440,8 @@ destroy_cycle(struct server *server) server->cycle.preview_outline = NULL; } - struct view *view, *tmp; - wl_list_for_each_safe(view, tmp, &server->cycle.views, cycle_link) { + struct view *view, *tmp2; + wl_list_for_each_safe(view, tmp2, &server->cycle.views, cycle_link) { wl_list_remove(&view->cycle_link); view->cycle_link = (struct wl_list){0}; } diff --git a/src/cycle/osd-classic.c b/src/cycle/osd-classic.c index 8e01dc24..c0509249 100644 --- a/src/cycle/osd-classic.c +++ b/src/cycle/osd-classic.c @@ -77,10 +77,9 @@ create_fields_scene(struct server *server, struct view *view, } static void -cycle_osd_classic_create(struct output *output) +cycle_osd_classic_init(struct cycle_osd_output *osd_output) { - assert(!output->cycle_osd.tree && wl_list_empty(&output->cycle_osd.items)); - + struct output *output = osd_output->output; struct server *server = output->server; struct theme *theme = server->theme; struct window_switcher_classic_theme *switcher_theme = @@ -104,7 +103,7 @@ cycle_osd_classic_create(struct output *output) h += switcher_theme->item_height; } - output->cycle_osd.tree = wlr_scene_tree_create(output->cycle_osd_tree); + osd_output->tree = wlr_scene_tree_create(output->cycle_osd_tree); float *text_color = theme->osd_label_text_color; float *bg_color = theme->osd_bg_color; @@ -118,7 +117,7 @@ cycle_osd_classic_create(struct output *output) .width = w, .height = h, }; - lab_scene_rect_create(output->cycle_osd.tree, &bg_opts); + lab_scene_rect_create(osd_output->tree, &bg_opts); int y = padding; @@ -136,7 +135,7 @@ cycle_osd_classic_create(struct output *output) } struct scaled_font_buffer *font_buffer = - scaled_font_buffer_create(output->cycle_osd.tree); + scaled_font_buffer_create(osd_output->tree); wlr_scene_node_set_position(&font_buffer->scene_buffer->node, x, y + (switcher_theme->item_height - font_height(&font)) / 2); scaled_font_buffer_update(font_buffer, workspace_name, 0, @@ -159,9 +158,9 @@ cycle_osd_classic_create(struct output *output) struct view *view; wl_list_for_each(view, &server->cycle.views, cycle_link) { struct cycle_osd_classic_item *item = znew(*item); - wl_list_append(&output->cycle_osd.items, &item->base.link); + wl_list_append(&osd_output->items, &item->base.link); item->base.view = view; - item->base.tree = wlr_scene_tree_create(output->cycle_osd.tree); + item->base.tree = wlr_scene_tree_create(osd_output->tree); node_descriptor_create(&item->base.tree->node, LAB_NODE_CYCLE_OSD_ITEM, NULL, item); /* @@ -218,23 +217,24 @@ cycle_osd_classic_create(struct output *output) error:; /* Center OSD */ - wlr_scene_node_set_position(&output->cycle_osd.tree->node, + wlr_scene_node_set_position(&osd_output->tree->node, output_box.x + (output_box.width - w) / 2, output_box.y + (output_box.height - h) / 2); } static void -cycle_osd_classic_update(struct output *output) +cycle_osd_classic_update(struct cycle_osd_output *osd_output) { + struct server *server = osd_output->output->server; struct cycle_osd_classic_item *item; - wl_list_for_each(item, &output->cycle_osd.items, base.link) { - bool active = item->base.view == output->server->cycle.selected_view; + wl_list_for_each(item, &osd_output->items, base.link) { + bool active = item->base.view == server->cycle.selected_view; wlr_scene_node_set_enabled(&item->normal_tree->node, !active); wlr_scene_node_set_enabled(&item->active_tree->node, active); } } struct cycle_osd_impl cycle_osd_classic_impl = { - .create = cycle_osd_classic_create, + .init = cycle_osd_classic_init, .update = cycle_osd_classic_update, }; diff --git a/src/cycle/osd-thumbnail.c b/src/cycle/osd-thumbnail.c index d17b1aa0..dde48786 100644 --- a/src/cycle/osd-thumbnail.c +++ b/src/cycle/osd-thumbnail.c @@ -117,9 +117,9 @@ create_label(struct wlr_scene_tree *parent, struct view *view, static struct cycle_osd_thumbnail_item * create_item_scene(struct wlr_scene_tree *parent, struct view *view, - struct output *output) + struct cycle_osd_output *osd_output) { - struct server *server = output->server; + struct server *server = osd_output->output->server; struct theme *theme = server->theme; struct window_switcher_thumbnail_theme *switcher_theme = &theme->osd_window_switcher_thumbnail; @@ -137,7 +137,7 @@ create_item_scene(struct wlr_scene_tree *parent, struct view *view, } struct cycle_osd_thumbnail_item *item = znew(*item); - wl_list_append(&output->cycle_osd.items, &item->base.link); + wl_list_append(&osd_output->items, &item->base.link); struct wlr_scene_tree *tree = wlr_scene_tree_create(parent); node_descriptor_create(&tree->node, LAB_NODE_CYCLE_OSD_ITEM, NULL, item); item->base.tree = tree; @@ -159,7 +159,7 @@ create_item_scene(struct wlr_scene_tree *parent, struct view *view, switcher_theme->item_height, (float[4]) {0}); /* thumbnail */ - struct wlr_buffer *thumb_buffer = render_thumb(output, view); + struct wlr_buffer *thumb_buffer = render_thumb(osd_output->output, view); if (thumb_buffer) { struct wlr_scene_buffer *thumb_scene_buffer = wlr_scene_buffer_create(tree, thumb_buffer); @@ -226,17 +226,16 @@ get_items_geometry(struct output *output, struct theme *theme, } static void -cycle_osd_thumbnail_create(struct output *output) +cycle_osd_thumbnail_init(struct cycle_osd_output *osd_output) { - assert(!output->cycle_osd.tree && wl_list_empty(&output->cycle_osd.items)); - + struct output *output = osd_output->output; struct server *server = output->server; struct theme *theme = server->theme; struct window_switcher_thumbnail_theme *switcher_theme = &theme->osd_window_switcher_thumbnail; int padding = theme->osd_border_width + switcher_theme->padding; - output->cycle_osd.tree = wlr_scene_tree_create(output->cycle_osd_tree); + osd_output->tree = wlr_scene_tree_create(output->cycle_osd_tree); int nr_views = wl_list_length(&server->cycle.views); assert(nr_views > 0); @@ -248,7 +247,7 @@ cycle_osd_thumbnail_create(struct output *output) int index = 0; wl_list_for_each(view, &server->cycle.views, cycle_link) { struct cycle_osd_thumbnail_item *item = create_item_scene( - output->cycle_osd.tree, view, output); + osd_output->tree, view, osd_output); if (!item) { break; } @@ -268,7 +267,7 @@ cycle_osd_thumbnail_create(struct output *output) .height = nr_rows * switcher_theme->item_height + 2 * padding, }; struct lab_scene_rect *bg = - lab_scene_rect_create(output->cycle_osd.tree, &bg_opts); + lab_scene_rect_create(osd_output->tree, &bg_opts); wlr_scene_node_lower_to_bottom(&bg->tree->node); /* center */ @@ -277,15 +276,16 @@ cycle_osd_thumbnail_create(struct output *output) &output_box); int lx = output_box.x + (output_box.width - bg_opts.width) / 2; int ly = output_box.y + (output_box.height - bg_opts.height) / 2; - wlr_scene_node_set_position(&output->cycle_osd.tree->node, lx, ly); + wlr_scene_node_set_position(&osd_output->tree->node, lx, ly); } static void -cycle_osd_thumbnail_update(struct output *output) +cycle_osd_thumbnail_update(struct cycle_osd_output *osd_output) { + struct server *server = osd_output->output->server; struct cycle_osd_thumbnail_item *item; - wl_list_for_each(item, &output->cycle_osd.items, base.link) { - bool active = (item->base.view == output->server->cycle.selected_view); + wl_list_for_each(item, &osd_output->items, base.link) { + bool active = (item->base.view == server->cycle.selected_view); wlr_scene_node_set_enabled(&item->active_bg->tree->node, active); wlr_scene_node_set_enabled( &item->active_label->scene_buffer->node, active); @@ -295,6 +295,6 @@ cycle_osd_thumbnail_update(struct output *output) } struct cycle_osd_impl cycle_osd_thumbnail_impl = { - .create = cycle_osd_thumbnail_create, + .init = cycle_osd_thumbnail_init, .update = cycle_osd_thumbnail_update, }; diff --git a/src/output.c b/src/output.c index f8c19c56..d25d2736 100644 --- a/src/output.c +++ b/src/output.c @@ -542,7 +542,6 @@ handle_new_output(struct wl_listener *listener, void *data) wl_signal_add(&wlr_output->events.request_state, &output->request_state); wl_list_init(&output->regions); - wl_list_init(&output->cycle_osd.items); /* * Create layer-trees (background, bottom, top and overlay) and diff --git a/src/server.c b/src/server.c index 9f271b10..23b83d3b 100644 --- a/src/server.c +++ b/src/server.c @@ -550,6 +550,7 @@ server_init(struct server *server) wl_list_init(&server->views); wl_list_init(&server->unmanaged_surfaces); wl_list_init(&server->cycle.views); + wl_list_init(&server->cycle.osd_outputs); server->scene = wlr_scene_create(); if (!server->scene) { From 8ad96c0410ea7db34d6f93e735901ad695a8eb19 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Mon, 8 Dec 2025 17:14:31 +0900 Subject: [PATCH 2/6] cycle: add server->cycle_preview_tree This doesn't change any behaviors. --- include/labwc.h | 1 + src/cycle/cycle.c | 10 +++------- src/server.c | 32 +++++++++++++++++--------------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/include/labwc.h b/include/labwc.h index 47af8875..c4848a11 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -238,6 +238,7 @@ struct server { /* Tree for unmanaged xsurfaces without initialized view (usually popups) */ struct wlr_scene_tree *unmanaged_tree; #endif + struct wlr_scene_tree *cycle_preview_tree; /* Tree for built in menu */ struct wlr_scene_tree *menu_tree; diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index 88e713f1..390cf06c 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -40,7 +40,8 @@ update_preview_outlines(struct view *view) .border_width = theme->osd_window_switcher_preview_border_width, }; rect = lab_scene_rect_create(&server->scene->tree, &opts); - wlr_scene_node_place_above(&rect->tree->node, &server->menu_tree->node); + wlr_scene_node_place_above(&rect->tree->node, + &server->cycle_preview_tree->node); server->cycle.preview_outline = rect; } @@ -245,13 +246,8 @@ preview_selected_view(struct view *view) cycle->preview_was_shaded = true; } - /* - * FIXME: This abuses an implementation detail of the always-on-top tree. - * Create a permanent server->osd_preview_tree instead that can - * also be used as parent for the preview outlines. - */ wlr_scene_node_reparent(cycle->preview_node, - view->server->view_tree_always_on_top); + view->server->cycle_preview_tree); /* Finally raise selected node to the top */ wlr_scene_node_raise_to_top(cycle->preview_node); diff --git a/src/server.c b/src/server.c index 23b83d3b..2e52fb45 100644 --- a/src/server.c +++ b/src/server.c @@ -564,21 +564,22 @@ server_init(struct server *server) * z-order for nodes which cover the whole work-area. For per-output * scene-trees, see handle_new_output() in src/output.c * - * | Type | Scene Tree | Per Output | Example - * | ------------------- | ---------------- | ---------- | ------- - * | ext-session | lock-screen | Yes | swaylock - * | window switcher OSD | cycle_osd_tree | Yes | - * | compositor-menu | menu_tree | No | root-menu - * | layer-shell | layer-popups | Yes | - * | layer-shell | overlay-layer | Yes | - * | layer-shell | top-layer | Yes | waybar - * | xwayland-OR | unmanaged | No | dmenu - * | xdg-popups | xdg-popups | No | - * | toplevels windows | always-on-top | No | - * | toplevels windows | normal | No | firefox - * | toplevels windows | always-on-bottom | No | pcmanfm-qt --desktop - * | layer-shell | bottom-layer | Yes | waybar - * | layer-shell | background-layer | Yes | swaybg + * | Scene Tree | Description + * | ---------------------------------- | ------------------------------------- + * | output->session_lock_tree | session lock surfaces (e.g. swaylock) + * | output->cycle_osd_tree | window switcher's on-screen display + * | server->cycle_preview_tree | window switcher's previewed window + * | server->menu_tree | labwc's server-side menus + * | output->layer_popup_tree | xdg popups on layer surfaces + * | output->layer_tree[3] | overlay layer surfaces (e.g. rofi) + * | output->layer_tree[2] | top layer surfaces (e.g. waybar) + * | server->unmanaged_tree | unmanaged X11 surfaces (e.g. dmenu) + * | server->xdg_popup_tree | xdg popups on xdg windows + * | server->view_tree_always_on_top | always-on-top xdg/X11 windows + * | server->view_tree | normal xdg/X11 windows (e.g. firefox) + * | server->view_tree_always_on_bottom | always-on-bottom xdg/X11 windows + * | output->layer_tree[1] | bottom layer surfaces + * | output->layer_tree[0] | background layer surfaces (e.g. swaybg) */ server->view_tree_always_on_bottom = wlr_scene_tree_create(&server->scene->tree); @@ -589,6 +590,7 @@ server_init(struct server *server) server->unmanaged_tree = wlr_scene_tree_create(&server->scene->tree); #endif server->menu_tree = wlr_scene_tree_create(&server->scene->tree); + server->cycle_preview_tree = wlr_scene_tree_create(&server->scene->tree); workspaces_init(server); From 9f50971e0bd0b7c4eaa5d6fc44edadb9b677d976 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Wed, 31 Dec 2025 19:15:11 +0900 Subject: [PATCH 3/6] Halt window switcher on Reconfigure There was an invalid memory access (since introduction of thumbnail style in 2e9292b) with following steps: 1. Press Alt-Tab 2. Update `