window-switcher: add age order to window cycling

- Add a new configuration option to control the window switcher
  traversal order.

- Document cycle windows behavior with new option `order` in
  `<windowSwitcher>`
This commit is contained in:
Maik Broemme 2025-12-04 16:51:40 +01:00
parent 06505d24c8
commit 9962f44d55
No known key found for this signature in database
GPG key ID: 27BE1125704B8B02
9 changed files with 63 additions and 2 deletions

View 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,32 @@ 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] Cycle windows behavior.
"focus" cycles by focus history. The list is sorted by the last time
each window had focus - most recently focused first.
- Using the window switcher (started with Alt+Tab by default) moves to
the next most recently used window - the classic behavior where you
typically toggle between the last two windows, then move further
back in time.
- If you focus a window, it jumps to the front of this list
immediately.
"age" cycles by creation/open order - the same stable order you
typically see in the taskbar.
- New windows are appended at the end of the list.
- Using the window switcher (started with Alt+Tab by default) moves to
the next window in that fixed open order; backward goes the other
way.
- The order doesn't change when you focus a window, so repeated cycles
are predictable.
Default is "focus".
*<windowSwitcher><osd show="" style="" output="" thumbnailLabelFormat="" />*
*show* [yes|no] Draw the OnScreenDisplay when switching between
windows. Default is yes.

View file

@ -185,6 +185,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 */

View 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,

View file

@ -188,6 +188,7 @@ struct server {
struct wl_listener xdg_toplevel_icon_set_icon;
struct wl_list views;
uint64_t next_view_creation_iid;
struct wl_list unmanaged_surfaces;
struct seat seat;

View file

@ -174,6 +174,7 @@ struct view {
bool mapped;
bool been_mapped;
uint64_t creation_iid;
enum lab_ssd_mode ssd_mode;
enum ssd_preference ssd_preference;
bool shaded;

View file

@ -1233,6 +1233,12 @@ 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;
}
/* The following two are for backward compatibility only. */
} else if (!strcasecmp(nodename, "show.windowSwitcher")) {
@ -1477,6 +1483,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;

View 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_iid >= new_view->creation_iid) {
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");

View file

@ -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_iid = server->next_view_creation_iid++;
}
static void

View 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_iid = view->server->next_view_creation_iid++;
if (xsurface->surface) {
handle_associate(&xwayland_view->associate, NULL);