]> git.mdlowis.com Git - proto/labwc.git/commitdiff
window-switcher: add `order` parameter to allow stable window list ordering
authorMaik Broemme <mbroemme@libmpq.org>
Mon, 8 Dec 2025 17:54:23 +0000 (18:54 +0100)
committerGitHub <noreply@github.com>
Mon, 8 Dec 2025 17:54:23 +0000 (18:54 +0100)
Add a new configuration option to control the window switcher traversal order.

`order="focus"` cycling is convenient for quick toggling, but some users - me as well -
prefer a stable taskbar-like order which can now be achieved with `order="age"`.

docs/labwc-config.5.scd
include/config/rcxml.h
include/config/types.h
include/labwc.h
include/view.h
src/config/rcxml.c
src/cycle/cycle.c
src/xdg.c
src/xwayland.c

index 4e7d7ad8ca9dad92701c0bda1af8b8965e927d9e..4cb89cd50e2535316a169ddb84c5e82470726dd1 100644 (file)
@@ -349,7 +349,7 @@ this is for compatibility with Openbox.
 </windowSwitcher>
 ```
 
-*<windowSwitcher preview="" outlines="" allWorkspaces="" unshade="">*
+*<windowSwitcher preview="" outlines="" allWorkspaces="" unshade="" order="">*
        *preview* [yes|no] Preview the contents of the selected window when
        switching between windows. Default is yes.
 
@@ -363,6 +363,11 @@ this is for compatibility with Openbox.
        *unshade* [yes|no] Temporarily unshade windows when switching between
        them and permanently unshade on the final selection. Default is yes.
 
+       *order* [focus|age] The order in which windows are cycled. *focus* cycles by
+       recent focus history, starting with the previously focused window. *age* cycles
+       by creation/open order, a stable taskbar-style ordering that doesn’t change on
+       focus. Default is *focus*.
+
 *<windowSwitcher><osd show="" style="" output="" thumbnailLabelFormat="" />*
        *show* [yes|no] Draw the OnScreenDisplay when switching between
        windows. Default is yes.
index 3e4f15a2219df519292e0cdc760dbe22643633e1..94eb6ebe9118926fbd1c37e81fa3dd4aab430279 100644 (file)
@@ -186,6 +186,7 @@ struct rcxml {
                enum cycle_osd_style style;
                enum cycle_osd_output_criteria output_criteria;
                char *thumbnail_label_format;
+               enum window_switcher_order order;
        } window_switcher;
 
        struct wl_list window_rules; /* struct window_rule.link */
index 757796a6d6e67dc45dbd0bc0f2f699ccd24bb6bd..99c5929eee415bf2c930a7fe2486a212492b73f5 100644 (file)
@@ -107,6 +107,11 @@ enum lab_window_type {
        LAB_WINDOW_TYPE_LEN
 };
 
+enum window_switcher_order {
+       WINDOW_SWITCHER_ORDER_FOCUS,
+       WINDOW_SWITCHER_ORDER_AGE,
+};
+
 enum cycle_osd_style {
        CYCLE_OSD_STYLE_CLASSIC,
        CYCLE_OSD_STYLE_THUMBNAIL,
index 40bff8767b89c8fb41c39dadf813ec68a08d3b26..3d3ca2a3794b969335b965ccf37d164e5a54bc9c 100644 (file)
@@ -188,6 +188,7 @@ struct server {
        struct wl_listener xdg_toplevel_icon_set_icon;
 
        struct wl_list views;
+       uint64_t next_view_creation_id;
        struct wl_list unmanaged_surfaces;
 
        struct seat seat;
index 2f8aac85f6a0c50e51a968c852108fd12af29426..fae462dbfa0bcdb019563d3d19319c55c5e5b6a4 100644 (file)
@@ -174,6 +174,7 @@ struct view {
 
        bool mapped;
        bool been_mapped;
+       uint64_t creation_id;
        enum lab_ssd_mode ssd_mode;
        enum ssd_preference ssd_preference;
        bool shaded;
index d369433765153ed14c0fbb28ee766368c2c412a2..eacd3d6459e546e40fccfc3d921abc6965fe5538 100644 (file)
@@ -1240,6 +1240,15 @@ entry(xmlNode *node, char *nodename, char *content)
                        wlr_log(WLR_ERROR, "Invalid windowSwitcher output %s: "
                                "should be one of all|focused|cursor", content);
                }
+       } else if (!strcasecmp(nodename, "order.windowSwitcher")) {
+               if (!strcasecmp(content, "focus")) {
+                       rc.window_switcher.order = WINDOW_SWITCHER_ORDER_FOCUS;
+               } else if (!strcasecmp(content, "age")) {
+                       rc.window_switcher.order = WINDOW_SWITCHER_ORDER_AGE;
+               } else {
+                       wlr_log(WLR_ERROR, "Invalid windowSwitcher order %s: "
+                               "should be one of focus|age", content);
+               }
 
        /* The following two are for backward compatibility only. */
        } else if (!strcasecmp(nodename, "show.windowSwitcher")) {
@@ -1485,6 +1494,7 @@ rcxml_init(void)
        rc.window_switcher.criteria = LAB_VIEW_CRITERIA_CURRENT_WORKSPACE
                | LAB_VIEW_CRITERIA_ROOT_TOPLEVEL
                | LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER;
+       rc.window_switcher.order = WINDOW_SWITCHER_ORDER_FOCUS;
 
        rc.resize_indicator = LAB_RESIZE_INDICATOR_NEVER;
        rc.resize_draw_contents = true;
index 8eabaf6cd4a7bca8b952d4d871db47f10c22d2fb..adf9b05eda0e70b8845a59fe6ec7a05e564da219 100644 (file)
@@ -276,13 +276,31 @@ create_osd_on_output(struct output *output)
        assert(output->cycle_osd.tree);
 }
 
+static void
+insert_view_ordered_by_age(struct wl_list *views, struct view *new_view)
+{
+       struct wl_list *link = views;
+       struct view *view;
+       wl_list_for_each(view, views, cycle_link) {
+               if (view->creation_id >= new_view->creation_id) {
+                       break;
+               }
+               link = &view->cycle_link;
+       }
+       wl_list_insert(link, &new_view->cycle_link);
+}
+
 /* Return false on failure */
 static bool
 init_cycle(struct server *server)
 {
        struct view *view;
        for_each_view(view, &server->views, rc.window_switcher.criteria) {
-               wl_list_append(&server->cycle.views, &view->cycle_link);
+               if (rc.window_switcher.order == WINDOW_SWITCHER_ORDER_AGE) {
+                       insert_view_ordered_by_age(&server->cycle.views, view);
+               } else {
+                       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");
index 668dfe450cdeecb3166089b83ff5c5a07864c79f..9ff245414a314cc3221aa1ed40966b28534e82e6 100644 (file)
--- a/src/xdg.c
+++ b/src/xdg.c
@@ -1085,6 +1085,7 @@ handle_new_xdg_toplevel(struct wl_listener *listener, void *data)
        CONNECT_SIGNAL(xdg_surface, xdg_toplevel_view, new_popup);
 
        wl_list_insert(&server->views, &view->link);
+       view->creation_id = server->next_view_creation_id++;
 }
 
 static void
index 564fa3e2bab69fb1a259b3210f71533a3af51d4a..17eb995e5328d17288e221daa9877d3ab5658d56 100644 (file)
@@ -1036,6 +1036,7 @@ xwayland_view_create(struct server *server,
        CONNECT_SIGNAL(xsurface, xwayland_view, map_request);
 
        wl_list_insert(&view->server->views, &view->link);
+       view->creation_id = view->server->next_view_creation_id++;
 
        if (xsurface->surface) {
                handle_associate(&xwayland_view->associate, NULL);