diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index fb616340..47ee1801 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -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. **++ -** +**++ +**++ +**++ 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". diff --git a/include/cycle.h b/include/cycle.h index 9bb4cb69..440b938f 100644 --- a/include/cycle.h +++ b/include/cycle.h @@ -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); diff --git a/include/view.h b/include/view.h index da7aef02..a179338e 100644 --- a/include/view.h +++ b/include/view.h @@ -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 */ diff --git a/src/action.c b/src/action.c index 34435e02..daf8fb30 100644 --- a/src/action.c +++ b/src/action.c @@ -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; diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index 2649f1e5..14682c9f 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -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) diff --git a/src/view.c b/src/view.c index 9b6604ad..bcbd366e 100644 --- a/src/view.c +++ b/src/view.c @@ -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; } } diff --git a/src/window-rules.c b/src/window-rules.c index 3911be7d..f43b92f4 100644 --- a/src/window-rules.c +++ b/src/window-rules.c @@ -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;