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.
*<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.
Default keybinds for NextWindow and PreviousWindow are Alt-Tab and
Shift-Alt-Tab. While cycling through windows, the arrow keys move the
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]
This determines whether to cycle through windows on all workspaces or the
current workspace. Default is "current".

View file

@ -103,6 +103,10 @@ void cycle_finish(bool switch_focus);
/* Re-initialize the window switcher */
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 */
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 */
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 */

View file

@ -84,6 +84,8 @@ struct action_arg_list {
X(SHRINK_TO_EDGE, "ShrinkToEdge") \
X(NEXT_WINDOW, "NextWindow") \
X(PREVIOUS_WINDOW, "PreviousWindow") \
X(NEXT_WINDOW_IMMEDIATE, "NextWindowImmediate") \
X(PREVIOUS_WINDOW_IMMEDIATE, "PreviousWindowImmediate") \
X(RECONFIGURE, "Reconfigure") \
X(SHOW_MENU, "ShowMenu") \
X(TOGGLE_MAXIMIZE, "ToggleMaximize") \
@ -337,6 +339,8 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
break;
case ACTION_TYPE_NEXT_WINDOW:
case ACTION_TYPE_PREVIOUS_WINDOW:
case ACTION_TYPE_NEXT_WINDOW_IMMEDIATE:
case ACTION_TYPE_PREVIOUS_WINDOW_IMMEDIATE:
if (!strcasecmp(argument, "workspace")) {
if (!strcasecmp(content, "all")) {
action_arg_add_int(action, argument, CYCLE_WORKSPACE_ALL);
@ -1146,6 +1150,21 @@ run_action(struct view *view, struct action *action,
}
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:
kill(getpid(), SIGHUP);
break;

View file

@ -322,6 +322,61 @@ handle_osd_tree_destroy(struct wl_listener *listener, void *data)
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 */
static bool
init_cycle(struct cycle_filter filter)

View file

@ -80,7 +80,7 @@ struct view_query *
view_query_create(void)
{
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->maximized = VIEW_AXIS_INVALID;
query->decoration = LAB_SSD_MODE_INVALID;
@ -263,8 +263,8 @@ view_get_root(struct view *view)
return view;
}
static bool
matches_criteria(struct view *view, enum lab_view_criteria criteria)
bool
view_matches_criteria(struct view *view, enum lab_view_criteria criteria)
{
if (!view_is_focusable(view)) {
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) {
view = wl_container_of(elm, view, link);
if (matches_criteria(view, criteria)) {
if (view_matches_criteria(view, criteria)) {
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) {
view = wl_container_of(elm, view, link);
if (matches_criteria(view, criteria)) {
if (view_matches_criteria(view, criteria)) {
return view;
}
}

View file

@ -24,7 +24,7 @@ other_instances_exist(struct view *self, struct view_query *query)
}
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 = {
.identifier = rule->identifier,
@ -52,7 +52,7 @@ window_rules_apply(struct view *view, enum window_rule_event event)
if (rule->event != event) {
continue;
}
if (view_matches_criteria(rule, view)) {
if (view_matches_rule(rule, view)) {
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
* for.
*/
if (view_matches_criteria(rule, view)) {
if (view_matches_rule(rule, view)) {
if (rule->server_decoration
&& !strcasecmp(property, "serverDecoration")) {
return rule->server_decoration;