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.
This commit is contained in:
tokyo4j 2025-11-30 17:47:40 +09:00 committed by Johan Malm
parent b6c1a9ea59
commit 9f5ff391cc
8 changed files with 92 additions and 110 deletions

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

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

View 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

View 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;
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;
}

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

View 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;
}

View 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) {

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