]> git.mdlowis.com Git - proto/labwc.git/commitdiff
cycle: remember cycled window list in server->cycle.views
authortokyo4j <hrak1529@gmail.com>
Sun, 30 Nov 2025 08:47:40 +0000 (17:47 +0900)
committerJohan Malm <johanmalm@users.noreply.github.com>
Sun, 30 Nov 2025 21:33:46 +0000 (21:33 +0000)
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
include/labwc.h
include/view.h
src/cycle/cycle.c
src/cycle/osd-classic.c
src/cycle/osd-thumbnail.c
src/server.c
src/view.c

index d4ac72fca982d75a5fd050058afedc498fc3b1e5..aaecff507a8f17691373317986d062377f1f3b75 100644 (file)
@@ -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.
index abe07a49723d21c7e623c5f6e7eaae6844bee6e0..40bff8767b89c8fb41c39dadf813ec68a08d3b26 100644 (file)
@@ -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;
index 9ad11cd90ff6cdfa22906e2c54926b76ff5fd67f..d8c67e4284f32b60165701ca236c4f9b1a560295 100644 (file)
@@ -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
index 85d37faafadee29f1b4c680c87a389c76bbcfcdb..292e64365456ba33ab99ac9bbd479bd26273e817 100644 (file)
@@ -4,8 +4,8 @@
 #include <wlr/types/wlr_scene.h>
 #include <wlr/util/box.h>
 #include <wlr/util/log.h>
-#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;
-
-       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 cycle_state *cycle = &server->cycle;
+       assert(cycle->selected_view);
+       assert(!wl_list_empty(&server->cycle.views));
+
+       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;
 }
index f954701e5970f1397bce10618b7f8bf345d2cf24..67cfb8db338cf18f2410c6f5a47b665a32c6518b 100644 (file)
@@ -4,11 +4,11 @@
 #include <wlr/types/wlr_scene.h>
 #include <wlr/util/box.h>
 #include <wlr/util/log.h>
-#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;
index 5dd157c18ac43554c4c7efcf4a3db986aceee6de..2245d1ad4e952726e559dd3bde83fbe6e1b95241 100644 (file)
@@ -5,11 +5,11 @@
 #include <wlr/types/wlr_output_layout.h>
 #include <wlr/types/wlr_scene.h>
 #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;
                }
index 11037a48a4072db8f7ffddf10874b4d1b5d8d9bb..9f271b1053c3abb205932fb414a7b364d2e57dd4 100644 (file)
@@ -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) {
index d56c3fea6917a8aa57f95b51aee0a6103b1aea74..cc16536ffd868bd222d5dfef760ad4e7b2ed6759 100644 (file)
@@ -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)