Add Next/PreviousWindowImmediate to switch windows without OSD
Some checks failed
labwc.github.io / notify (push) Has been cancelled

Co-authored-by: @johanmalm
This commit is contained in:
elviosak 2026-04-28 22:42:39 -03:00 committed by Johan Malm
parent bc7498dc6f
commit 6ce25978e7
7 changed files with 94 additions and 9 deletions

View file

@ -126,13 +126,18 @@ Actions are used in menus and keyboard/mouse bindings.
position if it had been maximized or tiled to a direction or region. position if it had been maximized or tiled to a direction or region.
*<action name="NextWindow" workspace="current" output="all" identifier="all" />*++ *<action name="NextWindow" workspace="current" output="all" identifier="all" />*++
*<action name="PreviousWindow" workspace="current" output="all" identifier="all" />* *<action name="PreviousWindow" workspace="current" output="all" identifier="all" />*++
*<action name="NextWindowImmediate" workspace="current" output="all" identifier="all" />*++
*<action name="PreviousWindowImmediate" workspace="current" output="all" identifier="all" />*++
Cycle focus to next/previous window, respectively. Cycle focus to next/previous window, respectively.
Default keybinds for NextWindow and PreviousWindow are Alt-Tab and Default keybinds for NextWindow and PreviousWindow are Alt-Tab and
Shift-Alt-Tab. While cycling through windows, the arrow keys move the Shift-Alt-Tab. While cycling through windows, the arrow keys move the
selected window forwards/backwards and the escape key halts the cycling. selected window forwards/backwards and the escape key halts the cycling.
NextWindowImmediate and PreviousWindowImmediate skip the Window Switcher
and OSD, useful for binding to keys without modifiers.
*workspace* [all|current] *workspace* [all|current]
This determines whether to cycle through windows on all workspaces or the This determines whether to cycle through windows on all workspaces or the
current workspace. Default is "current". current workspace. Default is "current".

View file

@ -103,6 +103,10 @@ void cycle_finish(bool switch_focus);
/* Re-initialize the window switcher */ /* Re-initialize the window switcher */
void cycle_reinitialize(void); void cycle_reinitialize(void);
/* Immediately cycle to next/previous window */
void cycle_immediate(enum lab_cycle_dir direction,
struct cycle_filter filter);
/* Focus the clicked window and close OSD */ /* Focus the clicked window and close OSD */
void cycle_on_cursor_release(struct wlr_scene_node *node); void cycle_on_cursor_release(struct wlr_scene_node *node);

View file

@ -630,4 +630,6 @@ enum lab_placement_policy view_placement_parse(const char *policy);
/* xdg.c */ /* xdg.c */
struct wlr_xdg_surface *xdg_surface_from_view(struct view *view); struct wlr_xdg_surface *xdg_surface_from_view(struct view *view);
bool view_matches_criteria(struct view *view, enum lab_view_criteria criteria);
#endif /* LABWC_VIEW_H */ #endif /* LABWC_VIEW_H */

View file

@ -84,6 +84,8 @@ struct action_arg_list {
X(SHRINK_TO_EDGE, "ShrinkToEdge") \ X(SHRINK_TO_EDGE, "ShrinkToEdge") \
X(NEXT_WINDOW, "NextWindow") \ X(NEXT_WINDOW, "NextWindow") \
X(PREVIOUS_WINDOW, "PreviousWindow") \ X(PREVIOUS_WINDOW, "PreviousWindow") \
X(NEXT_WINDOW_IMMEDIATE, "NextWindowImmediate") \
X(PREVIOUS_WINDOW_IMMEDIATE, "PreviousWindowImmediate") \
X(RECONFIGURE, "Reconfigure") \ X(RECONFIGURE, "Reconfigure") \
X(SHOW_MENU, "ShowMenu") \ X(SHOW_MENU, "ShowMenu") \
X(TOGGLE_MAXIMIZE, "ToggleMaximize") \ X(TOGGLE_MAXIMIZE, "ToggleMaximize") \
@ -337,6 +339,8 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
break; break;
case ACTION_TYPE_NEXT_WINDOW: case ACTION_TYPE_NEXT_WINDOW:
case ACTION_TYPE_PREVIOUS_WINDOW: case ACTION_TYPE_PREVIOUS_WINDOW:
case ACTION_TYPE_NEXT_WINDOW_IMMEDIATE:
case ACTION_TYPE_PREVIOUS_WINDOW_IMMEDIATE:
if (!strcasecmp(argument, "workspace")) { if (!strcasecmp(argument, "workspace")) {
if (!strcasecmp(content, "all")) { if (!strcasecmp(content, "all")) {
action_arg_add_int(action, argument, CYCLE_WORKSPACE_ALL); action_arg_add_int(action, argument, CYCLE_WORKSPACE_ALL);
@ -1146,6 +1150,21 @@ run_action(struct view *view, struct action *action,
} }
break; break;
} }
case ACTION_TYPE_NEXT_WINDOW_IMMEDIATE:
case ACTION_TYPE_PREVIOUS_WINDOW_IMMEDIATE: {
enum lab_cycle_dir dir = (action->type == ACTION_TYPE_NEXT_WINDOW_IMMEDIATE) ?
LAB_CYCLE_DIR_FORWARD : LAB_CYCLE_DIR_BACKWARD;
struct cycle_filter filter = {
.workspace = action_get_int(action, "workspace",
rc.window_switcher.workspace_filter),
.output = action_get_int(action, "output",
CYCLE_OUTPUT_ALL),
.app_id = action_get_int(action, "identifier",
CYCLE_APP_ID_ALL),
};
cycle_immediate(dir, filter);
break;
}
case ACTION_TYPE_RECONFIGURE: case ACTION_TYPE_RECONFIGURE:
kill(getpid(), SIGHUP); kill(getpid(), SIGHUP);
break; break;

View file

@ -322,6 +322,61 @@ handle_osd_tree_destroy(struct wl_listener *listener, void *data)
free(osd_output); free(osd_output);
} }
static struct wl_list *prev(struct wl_list *elm) { return elm->prev; }
static struct wl_list *next(struct wl_list *elm) { return elm->next; }
void
cycle_immediate(enum lab_cycle_dir direction, struct cycle_filter filter)
{
if (wl_list_empty(&server.views)) {
return;
}
enum lab_view_criteria criteria =
LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER
| LAB_VIEW_CRITERIA_NO_DIALOG;
if (filter.workspace == CYCLE_WORKSPACE_CURRENT) {
criteria |= LAB_VIEW_CRITERIA_CURRENT_WORKSPACE;
}
uint64_t cycle_outputs = get_outputs_by_filter(filter.output);
const char *cycle_app_id = NULL;
if (filter.app_id == CYCLE_APP_ID_CURRENT && server.active_view) {
cycle_app_id = server.active_view->app_id;
}
struct wl_list *head = &server.views;
struct wl_list *(*iter)(struct wl_list *list);
iter = direction == LAB_CYCLE_DIR_FORWARD ? next : prev;
struct wl_list *from = (direction == LAB_CYCLE_DIR_FORWARD) && server.active_view
? &server.active_view->link : head;
for (struct wl_list *elm = iter(from); elm != head; elm = iter(elm)) {
struct view *view = wl_container_of(elm, view, link);
if (!view_matches_criteria(view, criteria)) {
continue;
}
if (filter.output != CYCLE_OUTPUT_ALL) {
if (!view->output || !(cycle_outputs & view->output->id_bit)) {
continue;
}
}
if (cycle_app_id && strcmp(view->app_id, cycle_app_id) != 0) {
continue;
}
if (server.active_view && direction == LAB_CYCLE_DIR_FORWARD) {
/*
* When cycling forward, the current active view needs to be
* sent to back to keep the same sequence and avoid getting
* stuck in the 2 topmost views.
*/
view_move_to_back(server.active_view);
}
desktop_focus_view(view, true);
break;
}
cursor_update_focus();
}
/* Return false on failure */ /* Return false on failure */
static bool static bool
init_cycle(struct cycle_filter filter) init_cycle(struct cycle_filter filter)

View file

@ -80,7 +80,7 @@ struct view_query *
view_query_create(void) view_query_create(void)
{ {
struct view_query *query = znew(*query); struct view_query *query = znew(*query);
/* Must be synced with view_matches_criteria() in window-rules.c */ /* Must be synced with view_matches_rule() in window-rules.c */
query->window_type = LAB_WINDOW_TYPE_INVALID; query->window_type = LAB_WINDOW_TYPE_INVALID;
query->maximized = VIEW_AXIS_INVALID; query->maximized = VIEW_AXIS_INVALID;
query->decoration = LAB_SSD_MODE_INVALID; query->decoration = LAB_SSD_MODE_INVALID;
@ -263,8 +263,8 @@ view_get_root(struct view *view)
return view; return view;
} }
static bool bool
matches_criteria(struct view *view, enum lab_view_criteria criteria) view_matches_criteria(struct view *view, enum lab_view_criteria criteria)
{ {
if (!view_is_focusable(view)) { if (!view_is_focusable(view)) {
return false; return false;
@ -316,7 +316,7 @@ view_next(struct wl_list *head, struct view *view, enum lab_view_criteria criter
for (elm = elm->next; elm != head; elm = elm->next) { for (elm = elm->next; elm != head; elm = elm->next) {
view = wl_container_of(elm, view, link); view = wl_container_of(elm, view, link);
if (matches_criteria(view, criteria)) { if (view_matches_criteria(view, criteria)) {
return view; return view;
} }
} }
@ -332,7 +332,7 @@ view_prev(struct wl_list *head, struct view *view, enum lab_view_criteria criter
for (elm = elm->prev; elm != head; elm = elm->prev) { for (elm = elm->prev; elm != head; elm = elm->prev) {
view = wl_container_of(elm, view, link); view = wl_container_of(elm, view, link);
if (matches_criteria(view, criteria)) { if (view_matches_criteria(view, criteria)) {
return view; return view;
} }
} }

View file

@ -24,7 +24,7 @@ other_instances_exist(struct view *self, struct view_query *query)
} }
static bool static bool
view_matches_criteria(struct window_rule *rule, struct view *view) view_matches_rule(struct window_rule *rule, struct view *view)
{ {
struct view_query query = { struct view_query query = {
.identifier = rule->identifier, .identifier = rule->identifier,
@ -52,7 +52,7 @@ window_rules_apply(struct view *view, enum window_rule_event event)
if (rule->event != event) { if (rule->event != event) {
continue; continue;
} }
if (view_matches_criteria(rule, view)) { if (view_matches_rule(rule, view)) {
actions_run(view, &rule->actions, NULL); actions_run(view, &rule->actions, NULL);
} }
} }
@ -81,7 +81,7 @@ window_rules_get_property(struct view *view, const char *property)
* attribute would still return here if that property was asked * attribute would still return here if that property was asked
* for. * for.
*/ */
if (view_matches_criteria(rule, view)) { if (view_matches_rule(rule, view)) {
if (rule->server_decoration if (rule->server_decoration
&& !strcasecmp(property, "serverDecoration")) { && !strcasecmp(property, "serverDecoration")) {
return rule->server_decoration; return rule->server_decoration;