From 9f5ff391ccf37a3ad5be33a08f8ff9df161c7ba9 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sun, 30 Nov 2025 17:47:40 +0900 Subject: [PATCH] cycle: remember cycled window list in server->cycle.views This allows changing the cycled order in the future, e.g. focused order vs created order. Functionally, this commit also changes the initially selected window; before this commit, the previous/next of the topmost window was always selected, but now the previous/next of the active window is selected first if it is in the cycled list. This won't change behaviors for most users, but this ensures that the user can go back to the focused window with Alt-Tab + Alt-Shift-Tab even when it is not the topmost window. This commit fixes the TODO in the previous commit by trying to preserve the selected view when a view is destroyed during window cycling. --- include/cycle.h | 2 +- include/labwc.h | 1 + include/view.h | 12 +--- src/cycle/cycle.c | 114 +++++++++++++++++++++++--------------- src/cycle/osd-classic.c | 18 +++--- src/cycle/osd-thumbnail.c | 12 ++-- src/server.c | 1 + src/view.c | 42 -------------- 8 files changed, 92 insertions(+), 110 deletions(-) diff --git a/include/cycle.h b/include/cycle.h index d4ac72fc..aaecff50 100644 --- a/include/cycle.h +++ b/include/cycle.h @@ -87,7 +87,7 @@ struct cycle_osd_impl { * Create a scene-tree of OSD for an output. * This sets output->cycle_osd.{items,tree}. */ - void (*create)(struct output *output, struct wl_array *views); + void (*create)(struct output *output); /* * Update output->cycle_osd.tree to highlight * server->cycle_state.selected_view. diff --git a/include/labwc.h b/include/labwc.h index abe07a49..40bff876 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -303,6 +303,7 @@ struct server { /* 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; diff --git a/include/view.h b/include/view.h index 9ad11cd9..d8c67e42 100644 --- a/include/view.h +++ b/include/view.h @@ -134,6 +134,9 @@ struct view { const struct view_impl *impl; struct wl_list link; + /* This is cleared when the view is not in the cycle list */ + struct wl_list cycle_link; + /* * The primary output that the view is displayed on. Specifically: * @@ -384,15 +387,6 @@ struct view *view_next(struct wl_list *head, struct view *view, struct view *view_prev(struct wl_list *head, struct view *view, enum lab_view_criteria criteria); -/* - * Same as `view_next()` except that they iterate one whole cycle rather than - * stopping at the list-head - */ -struct view *view_next_no_head_stop(struct wl_list *head, struct view *from, - enum lab_view_criteria criteria); -struct view *view_prev_no_head_stop(struct wl_list *head, struct view *from, - enum lab_view_criteria criteria); - /** * view_array_append() - Append views that match criteria to array * @server: server context diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index 85d37faa..292e6436 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -4,8 +4,8 @@ #include #include #include -#include "common/array.h" #include "common/lab-scene-rect.h" +#include "common/list.h" #include "common/scene-helpers.h" #include "config/rcxml.h" #include "labwc.h" @@ -48,36 +48,36 @@ update_preview_outlines(struct view *view) wlr_scene_node_set_position(&rect->tree->node, geo.x, geo.y); } -/* - * Returns the view to select next in the window switcher. - * If !start_view, the second focusable view is returned. - */ +/* Returns the view to select next in the window switcher. */ static struct view * -get_next_selected_view(struct server *server, struct view *start_view, - enum lab_cycle_dir dir) +get_next_selected_view(struct server *server, enum lab_cycle_dir dir) { - struct view *(*iter)(struct wl_list *head, struct view *view, - enum lab_view_criteria criteria); - bool forwards = dir == LAB_CYCLE_DIR_FORWARD; - iter = forwards ? view_next_no_head_stop : view_prev_no_head_stop; + struct cycle_state *cycle = &server->cycle; + assert(cycle->selected_view); + assert(!wl_list_empty(&server->cycle.views)); - enum lab_view_criteria criteria = rc.window_switcher.criteria; - - /* - * Views are listed in stacking order, topmost first. Usually the - * topmost view is already focused, so when iterating in the forward - * direction we pre-select the view second from the top: - * - * View #1 (on top, currently focused) - * View #2 (pre-selected) - * View #3 - * ... - */ - if (!start_view && forwards) { - start_view = iter(&server->views, NULL, criteria); + struct wl_list *link; + if (dir == LAB_CYCLE_DIR_FORWARD) { + link = cycle->selected_view->cycle_link.next; + if (link == &server->cycle.views) { + link = link->next; + } + } else { + link = cycle->selected_view->cycle_link.prev; + if (link == &server->cycle.views) { + link = link->prev; + } } + struct view *view = wl_container_of(link, view, cycle_link); + return view; +} - return iter(&server->views, start_view, criteria); +static struct view * +get_first_view(struct wl_list *views) +{ + assert(!wl_list_empty(views)); + struct view *view = wl_container_of(views->next, view, cycle_link); + return view; } void @@ -90,11 +90,25 @@ cycle_reinitialize(struct server *server) return; } + struct view *selected_view = cycle->selected_view; + struct view *selected_view_prev = + get_next_selected_view(server, LAB_CYCLE_DIR_BACKWARD); + destroy_cycle(server); if (init_cycle(server)) { - /* TODO: try to select the same view */ - cycle->selected_view = get_next_selected_view(server, NULL, - LAB_CYCLE_DIR_FORWARD); + /* + * Preserve the selected view (or its previous view) if it's + * still in the cycle list + */ + if (selected_view->cycle_link.next) { + cycle->selected_view = selected_view; + } else if (selected_view_prev->cycle_link.next) { + cycle->selected_view = selected_view_prev; + } else { + /* should be unreachable */ + wlr_log(WLR_ERROR, "could not find view to select"); + cycle->selected_view = get_first_view(&server->cycle.views); + } update_cycle(server); } else { /* Failed to re-init window switcher, exit */ @@ -148,8 +162,16 @@ cycle_begin(struct server *server, enum lab_cycle_dir direction) return; } - server->cycle.selected_view = get_next_selected_view(server, - server->cycle.selected_view, direction); + struct view *active_view = server->active_view; + if (active_view && active_view->cycle_link.next) { + /* Select the active view it's in the cycle list */ + server->cycle.selected_view = active_view; + } else { + /* Otherwise, select the first view in the cycle list */ + server->cycle.selected_view = get_first_view(&server->cycle.views); + } + /* Pre-select the next view in the given direction */ + server->cycle.selected_view = get_next_selected_view(server, direction); seat_focus_override_begin(&server->seat, LAB_INPUT_STATE_CYCLE, LAB_CURSOR_DEFAULT); @@ -164,8 +186,7 @@ cycle_step(struct server *server, enum lab_cycle_dir direction) { assert(server->input_mode == LAB_INPUT_STATE_CYCLE); - server->cycle.selected_view = get_next_selected_view(server, - server->cycle.selected_view, direction); + server->cycle.selected_view = get_next_selected_view(server, direction); update_cycle(server); } @@ -246,12 +267,12 @@ get_osd_impl(void) } static void -create_osd_on_output(struct output *output, struct wl_array *views) +create_osd_on_output(struct output *output) { if (!output_is_usable(output)) { return; } - get_osd_impl()->create(output, views); + get_osd_impl()->create(output); assert(output->cycle_osd.tree); } @@ -259,12 +280,12 @@ create_osd_on_output(struct output *output, struct wl_array *views) static bool init_cycle(struct server *server) { - struct wl_array views; - wl_array_init(&views); - view_array_append(server, &views, rc.window_switcher.criteria); - if (wl_array_len(&views) <= 0) { + struct view *view; + for_each_view(view, &server->views, rc.window_switcher.criteria) { + wl_list_append(&server->cycle.views, &view->cycle_link); + } + if (wl_list_empty(&server->cycle.views)) { wlr_log(WLR_DEBUG, "no views to switch between"); - wl_array_release(&views); return false; } @@ -274,12 +295,12 @@ init_cycle(struct server *server) case CYCLE_OSD_OUTPUT_ALL: { struct output *output; wl_list_for_each(output, &server->outputs, link) { - create_osd_on_output(output, &views); + create_osd_on_output(output); } break; } case CYCLE_OSD_OUTPUT_POINTER: - create_osd_on_output(output_nearest_to_cursor(server), &views); + create_osd_on_output(output_nearest_to_cursor(server)); break; case CYCLE_OSD_OUTPUT_KEYBOARD: { struct output *output; @@ -289,13 +310,12 @@ init_cycle(struct server *server) /* Fallback to pointer, if there is no active_view */ output = output_nearest_to_cursor(server); } - create_osd_on_output(output, &views); + create_osd_on_output(output); break; } } } - wl_array_release(&views); return true; } @@ -349,5 +369,11 @@ 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) { + wl_list_remove(&view->cycle_link); + view->cycle_link = (struct wl_list){0}; + } + server->cycle.selected_view = NULL; } diff --git a/src/cycle/osd-classic.c b/src/cycle/osd-classic.c index f954701e..67cfb8db 100644 --- a/src/cycle/osd-classic.c +++ b/src/cycle/osd-classic.c @@ -4,11 +4,11 @@ #include #include #include -#include "common/array.h" #include "common/buf.h" #include "common/font.h" #include "common/lab-scene-rect.h" #include "common/list.h" +#include "common/mem.h" #include "common/string-helpers.h" #include "config/rcxml.h" #include "cycle.h" @@ -18,6 +18,7 @@ #include "scaled-buffer/scaled-font-buffer.h" #include "scaled-buffer/scaled-icon-buffer.h" #include "theme.h" +#include "view.h" #include "workspaces.h" struct cycle_osd_classic_item { @@ -76,7 +77,7 @@ create_fields_scene(struct server *server, struct view *view, } static void -cycle_osd_classic_create(struct output *output, struct wl_array *views) +cycle_osd_classic_create(struct output *output) { assert(!output->cycle_osd.tree && wl_list_empty(&output->cycle_osd.items)); @@ -87,6 +88,7 @@ cycle_osd_classic_create(struct output *output, struct wl_array *views) int padding = theme->osd_border_width + switcher_theme->padding; bool show_workspace = wl_list_length(&rc.workspace_config.workspaces) > 1; const char *workspace_name = server->workspaces.current->name; + int nr_views = wl_list_length(&server->cycle.views); struct wlr_box output_box; wlr_output_layout_get_box(server->output_layout, output->wlr_output, @@ -96,7 +98,7 @@ cycle_osd_classic_create(struct output *output, struct wl_array *views) if (switcher_theme->width_is_percent) { w = output_box.width * switcher_theme->width / 100; } - int h = wl_array_len(views) * switcher_theme->item_height + 2 * padding; + int h = nr_views * switcher_theme->item_height + 2 * padding; if (show_workspace) { /* workspace indicator */ h += switcher_theme->item_height; @@ -155,11 +157,11 @@ cycle_osd_classic_create(struct output *output, struct wl_array *views) } /* Draw text for each node */ - struct view **view; - wl_array_for_each(view, views) { + 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); - item->base.view = *view; + item->base.view = view; item->base.tree = wlr_scene_tree_create(output->cycle_osd.tree); node_descriptor_create(&item->base.tree->node, LAB_NODE_CYCLE_OSD_ITEM, NULL, item); @@ -207,9 +209,9 @@ cycle_osd_classic_create(struct output *output, struct wl_array *views) w - 2 * padding, switcher_theme->item_height, (float[4]) {0}); wlr_scene_node_set_position(&hitbox->node, padding, y); - create_fields_scene(server, *view, item->normal_tree, + create_fields_scene(server, view, item->normal_tree, text_color, bg_color, field_widths_sum, x, y); - create_fields_scene(server, *view, item->active_tree, + create_fields_scene(server, view, item->active_tree, text_color, active_bg_color, field_widths_sum, x, y); y += switcher_theme->item_height; diff --git a/src/cycle/osd-thumbnail.c b/src/cycle/osd-thumbnail.c index 5dd157c1..2245d1ad 100644 --- a/src/cycle/osd-thumbnail.c +++ b/src/cycle/osd-thumbnail.c @@ -5,11 +5,11 @@ #include #include #include "config/rcxml.h" -#include "common/array.h" #include "common/box.h" #include "common/buf.h" #include "common/lab-scene-rect.h" #include "common/list.h" +#include "common/mem.h" #include "cycle.h" #include "labwc.h" #include "node.h" @@ -226,7 +226,7 @@ get_items_geometry(struct output *output, struct theme *theme, } static void -cycle_osd_thumbnail_create(struct output *output, struct wl_array *views) +cycle_osd_thumbnail_create(struct output *output) { assert(!output->cycle_osd.tree && wl_list_empty(&output->cycle_osd.items)); @@ -238,17 +238,17 @@ cycle_osd_thumbnail_create(struct output *output, struct wl_array *views) output->cycle_osd.tree = wlr_scene_tree_create(output->cycle_osd_tree); - int nr_views = wl_array_len(views); + int nr_views = wl_list_length(&server->cycle.views); assert(nr_views > 0); int nr_rows, nr_cols; get_items_geometry(output, theme, nr_views, &nr_rows, &nr_cols); /* items */ - struct view **view; + struct view *view; int index = 0; - wl_array_for_each(view, views) { + 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); + output->cycle_osd.tree, view, output); if (!item) { break; } diff --git a/src/server.c b/src/server.c index 11037a48..9f271b10 100644 --- a/src/server.c +++ b/src/server.c @@ -549,6 +549,7 @@ server_init(struct server *server) wl_list_init(&server->views); wl_list_init(&server->unmanaged_surfaces); + wl_list_init(&server->cycle.views); server->scene = wlr_scene_create(); if (!server->scene) { diff --git a/src/view.c b/src/view.c index d56c3fea..cc16536f 100644 --- a/src/view.c +++ b/src/view.c @@ -345,48 +345,6 @@ view_prev(struct wl_list *head, struct view *view, enum lab_view_criteria criter return NULL; } -struct view * -view_next_no_head_stop(struct wl_list *head, struct view *from, - enum lab_view_criteria criteria) -{ - assert(head); - - struct wl_list *elm = from ? &from->link : head; - - struct wl_list *end = elm; - for (elm = elm->next; elm != end; elm = elm->next) { - if (elm == head) { - continue; - } - struct view *view = wl_container_of(elm, view, link); - if (matches_criteria(view, criteria)) { - return view; - } - } - return from; -} - -struct view * -view_prev_no_head_stop(struct wl_list *head, struct view *from, - enum lab_view_criteria criteria) -{ - assert(head); - - struct wl_list *elm = from ? &from->link : head; - - struct wl_list *end = elm; - for (elm = elm->prev; elm != end; elm = elm->prev) { - if (elm == head) { - continue; - } - struct view *view = wl_container_of(elm, view, link); - if (matches_criteria(view, criteria)) { - return view; - } - } - return from; -} - void view_array_append(struct server *server, struct wl_array *views, enum lab_view_criteria criteria)