cycle: support <action name="NextWindow" workspace="current|all">

This commit deprecates <windowSwitcher allWorkspaces="yes|no"> and adds
per-action argument <action name="NextWindow" workspace="current|all">.
This commit is contained in:
tokyo4j 2025-12-12 04:10:54 +09:00 committed by Hiroaki Yamamoto
parent caa9b90e80
commit a5c6ff499c
10 changed files with 78 additions and 35 deletions

View file

@ -125,13 +125,17 @@ Actions are used in menus and keyboard/mouse bindings.
Resize and move the active window back to its untiled or unmaximized Resize and move the active window back to its untiled or unmaximized
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" />*++ *<action name="NextWindow" workspace="current" />*++
*<action name="PreviousWindow" />* *<action name="PreviousWindow" workspace="current" />*
Cycle focus to next/previous window, respectively. Cycle focus to next/previous window, respectively.
Default keybind for NextWindow is Alt-Tab. 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.
The arrow keys are used to move forwards/backwards while cycling. *workspace* [all|current]
This determines whether to cycle through windows on all workspaces or the
current workspace. Default is "current".
*<action name="Reconfigure" />* *<action name="Reconfigure" />*
Re-load configuration and theme files. Re-load configuration and theme files.

View file

@ -339,7 +339,7 @@ this is for compatibility with Openbox.
## WINDOW SWITCHER ## WINDOW SWITCHER
``` ```
<windowSwitcher preview="yes" outlines="yes" allWorkspaces="no"> <windowSwitcher preview="yes" outlines="yes">
<osd show="yes" style="classic" output="all" thumbnailLabelFormat="%T" /> <osd show="yes" style="classic" output="all" thumbnailLabelFormat="%T" />
<fields> <fields>
<field content="icon" width="5%" /> <field content="icon" width="5%" />
@ -349,17 +349,13 @@ this is for compatibility with Openbox.
</windowSwitcher> </windowSwitcher>
``` ```
*<windowSwitcher preview="" outlines="" allWorkspaces="" unshade="" order="">* *<windowSwitcher preview="" outlines="" unshade="" order="">*
*preview* [yes|no] Preview the contents of the selected window when *preview* [yes|no] Preview the contents of the selected window when
switching between windows. Default is yes. switching between windows. Default is yes.
*outlines* [yes|no] Draw an outline around the selected window when *outlines* [yes|no] Draw an outline around the selected window when
switching between windows. Default is yes. switching between windows. Default is yes.
*allWorkspaces* [yes|no] Show windows regardless of what workspace
they are on. Default no (that is only windows on the current workspace
are shown).
*unshade* [yes|no] Temporarily unshade windows when switching between *unshade* [yes|no] Temporarily unshade windows when switching between
them and permanently unshade on the final selection. Default is yes. them and permanently unshade on the final selection. Default is yes.

View file

@ -77,7 +77,7 @@
</font> </font>
</theme> </theme>
<windowSwitcher preview="yes" outlines="yes" allWorkspaces="no" unshade="yes"> <windowSwitcher preview="yes" outlines="yes" unshade="yes">
<osd show="yes" style="classic" output="all" thumbnailLabelFormat="%T" /> <osd show="yes" style="classic" output="all" thumbnailLabelFormat="%T" />
<fields> <fields>
<field content="icon" width="5%" /> <field content="icon" width="5%" />
@ -119,7 +119,7 @@
then workspace name, then identifier/app-id, then the window title. then workspace name, then identifier/app-id, then the window title.
It uses 100% of OSD window width. It uses 100% of OSD window width.
<windowSwitcher preview="no" outlines="no" allWorkspaces="yes"> <windowSwitcher preview="no" outlines="no">
<osd show="yes" /> <osd show="yes" />
<fields> <fields>
<field content="custom" format="foobar %b %3s %-10o %-20W %-10i %t" width="100%" /> <field content="custom" format="foobar %b %3s %-10o %-20W %-10i %t" width="100%" />

View file

@ -182,7 +182,7 @@ struct rcxml {
bool outlines; bool outlines;
bool unshade; bool unshade;
enum window_switcher_order order; enum window_switcher_order order;
enum lab_view_criteria criteria; enum cycle_workspace_filter workspace_filter; /* deprecated */
struct { struct {
bool show; bool show;
enum cycle_osd_style style; enum cycle_osd_style style;

View file

@ -117,6 +117,11 @@ enum cycle_osd_style {
CYCLE_OSD_STYLE_THUMBNAIL, CYCLE_OSD_STYLE_THUMBNAIL,
}; };
enum cycle_workspace_filter {
CYCLE_WORKSPACE_ALL,
CYCLE_WORKSPACE_CURRENT,
};
enum cycle_output_filter { enum cycle_output_filter {
CYCLE_OUTPUT_ALL, CYCLE_OUTPUT_ALL,
CYCLE_OUTPUT_CURSOR, CYCLE_OUTPUT_CURSOR,

View file

@ -4,6 +4,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <wayland-server-core.h> #include <wayland-server-core.h>
#include "config/types.h"
struct output; struct output;
@ -42,13 +43,18 @@ struct cycle_osd_field {
struct wl_list link; /* struct rcxml.window_switcher.osd.fields */ struct wl_list link; /* struct rcxml.window_switcher.osd.fields */
}; };
struct cycle_filter {
enum cycle_workspace_filter workspace;
};
struct buf; struct buf;
struct view; struct view;
struct server; struct server;
struct wlr_scene_node; struct wlr_scene_node;
/* Begin window switcher */ /* Begin window switcher */
void cycle_begin(struct server *server, enum lab_cycle_dir direction); void cycle_begin(struct server *server, enum lab_cycle_dir direction,
struct cycle_filter filter);
/* Cycle the selected view in the window switcher */ /* Cycle the selected view in the window switcher */
void cycle_step(struct server *server, enum lab_cycle_dir direction); void cycle_step(struct server *server, enum lab_cycle_dir direction);

View file

@ -5,6 +5,7 @@
#include <wlr/util/box.h> #include <wlr/util/box.h>
#include <wlr/util/log.h> #include <wlr/util/log.h>
#include "common/set.h" #include "common/set.h"
#include "cycle.h"
#include "input/cursor.h" #include "input/cursor.h"
#include "overlay.h" #include "overlay.h"
@ -310,6 +311,7 @@ struct server {
struct wlr_scene_node *preview_node; struct wlr_scene_node *preview_node;
struct wlr_scene_node *preview_dummy; struct wlr_scene_node *preview_dummy;
struct lab_scene_rect *preview_outline; struct lab_scene_rect *preview_outline;
struct cycle_filter filter;
} cycle; } cycle;
struct theme *theme; struct theme *theme;

View file

@ -366,6 +366,20 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
goto cleanup; goto cleanup;
} }
break; break;
case ACTION_TYPE_NEXT_WINDOW:
case ACTION_TYPE_PREVIOUS_WINDOW:
if (!strcasecmp(argument, "workspace")) {
if (!strcasecmp(content, "all")) {
action_arg_add_int(action, argument, CYCLE_WORKSPACE_ALL);
} else if (!strcasecmp(content, "current")) {
action_arg_add_int(action, argument, CYCLE_WORKSPACE_CURRENT);
} else {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content);
}
goto cleanup;
}
break;
case ACTION_TYPE_SHOW_MENU: case ACTION_TYPE_SHOW_MENU:
if (!strcmp(argument, "menu")) { if (!strcmp(argument, "menu")) {
action_arg_add_str(action, argument, content); action_arg_add_str(action, argument, content);
@ -1126,19 +1140,20 @@ run_action(struct view *view, struct server *server, struct action *action,
} }
break; break;
case ACTION_TYPE_NEXT_WINDOW: case ACTION_TYPE_NEXT_WINDOW:
case ACTION_TYPE_PREVIOUS_WINDOW: {
enum lab_cycle_dir dir = (action->type == ACTION_TYPE_NEXT_WINDOW) ?
LAB_CYCLE_DIR_FORWARD : LAB_CYCLE_DIR_BACKWARD;
struct cycle_filter filter = {
.workspace = action_get_int(action, "workspace",
rc.window_switcher.workspace_filter),
};
if (server->input_mode == LAB_INPUT_STATE_CYCLE) { if (server->input_mode == LAB_INPUT_STATE_CYCLE) {
cycle_step(server, LAB_CYCLE_DIR_FORWARD); cycle_step(server, dir);
} else { } else {
cycle_begin(server, LAB_CYCLE_DIR_FORWARD); cycle_begin(server, dir, filter);
}
break;
case ACTION_TYPE_PREVIOUS_WINDOW:
if (server->input_mode == LAB_INPUT_STATE_CYCLE) {
cycle_step(server, LAB_CYCLE_DIR_BACKWARD);
} else {
cycle_begin(server, LAB_CYCLE_DIR_BACKWARD);
} }
break; break;
}
case ACTION_TYPE_RECONFIGURE: case ACTION_TYPE_RECONFIGURE:
kill(getpid(), SIGHUP); kill(getpid(), SIGHUP);
break; break;

View file

@ -1269,10 +1269,16 @@ entry(xmlNode *node, char *nodename, char *content)
} else if (!strcasecmp(nodename, "outlines.windowSwitcher")) { } else if (!strcasecmp(nodename, "outlines.windowSwitcher")) {
set_bool(content, &rc.window_switcher.outlines); set_bool(content, &rc.window_switcher.outlines);
} else if (!strcasecmp(nodename, "allWorkspaces.windowSwitcher")) { } else if (!strcasecmp(nodename, "allWorkspaces.windowSwitcher")) {
if (parse_bool(content, -1) == true) { int ret = parse_bool(content, -1);
rc.window_switcher.criteria &= if (ret < 0) {
~LAB_VIEW_CRITERIA_CURRENT_WORKSPACE; wlr_log(WLR_ERROR, "Invalid value for <windowSwitcher"
" allWorkspaces=\"\">: '%s'", content);
} else {
rc.window_switcher.workspace_filter = ret ?
CYCLE_WORKSPACE_ALL : CYCLE_WORKSPACE_CURRENT;
} }
wlr_log(WLR_ERROR, "<windowSwitcher allWorkspaces=\"\" /> is deprecated."
" Use <action name=\"NextWindow\" workspace=\"\"> instead.");
} else if (!strcasecmp(nodename, "unshade.windowSwitcher")) { } else if (!strcasecmp(nodename, "unshade.windowSwitcher")) {
set_bool(content, &rc.window_switcher.unshade); set_bool(content, &rc.window_switcher.unshade);
@ -1493,9 +1499,7 @@ rcxml_init(void)
rc.window_switcher.preview = true; rc.window_switcher.preview = true;
rc.window_switcher.outlines = true; rc.window_switcher.outlines = true;
rc.window_switcher.unshade = true; rc.window_switcher.unshade = true;
rc.window_switcher.criteria = LAB_VIEW_CRITERIA_CURRENT_WORKSPACE rc.window_switcher.workspace_filter = CYCLE_WORKSPACE_CURRENT;
| LAB_VIEW_CRITERIA_ROOT_TOPLEVEL
| LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER;
rc.window_switcher.order = WINDOW_SWITCHER_ORDER_FOCUS; rc.window_switcher.order = WINDOW_SWITCHER_ORDER_FOCUS;
rc.resize_indicator = LAB_RESIZE_INDICATOR_NEVER; rc.resize_indicator = LAB_RESIZE_INDICATOR_NEVER;

View file

@ -17,7 +17,7 @@
#include "theme.h" #include "theme.h"
#include "view.h" #include "view.h"
static bool init_cycle(struct server *server); static bool init_cycle(struct server *server, struct cycle_filter filter);
static void update_cycle(struct server *server); static void update_cycle(struct server *server);
static void destroy_cycle(struct server *server); static void destroy_cycle(struct server *server);
@ -93,9 +93,10 @@ cycle_reinitialize(struct server *server)
struct view *selected_view = cycle->selected_view; struct view *selected_view = cycle->selected_view;
struct view *selected_view_prev = struct view *selected_view_prev =
get_next_selected_view(server, LAB_CYCLE_DIR_BACKWARD); get_next_selected_view(server, LAB_CYCLE_DIR_BACKWARD);
struct cycle_filter filter = cycle->filter;
destroy_cycle(server); destroy_cycle(server);
if (init_cycle(server)) { if (init_cycle(server, filter)) {
/* /*
* Preserve the selected view (or its previous view) if it's * Preserve the selected view (or its previous view) if it's
* still in the cycle list * still in the cycle list
@ -152,13 +153,14 @@ restore_preview_node(struct server *server)
} }
void void
cycle_begin(struct server *server, enum lab_cycle_dir direction) cycle_begin(struct server *server, enum lab_cycle_dir direction,
struct cycle_filter filter)
{ {
if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) {
return; return;
} }
if (!init_cycle(server)) { if (!init_cycle(server, filter)) {
return; return;
} }
@ -314,10 +316,17 @@ insert_view_ordered_by_age(struct wl_list *views, struct view *new_view)
/* Return false on failure */ /* Return false on failure */
static bool static bool
init_cycle(struct server *server) init_cycle(struct server *server, struct cycle_filter filter)
{ {
enum lab_view_criteria criteria =
LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER
| LAB_VIEW_CRITERIA_ROOT_TOPLEVEL;
if (filter.workspace == CYCLE_WORKSPACE_CURRENT) {
criteria |= LAB_VIEW_CRITERIA_CURRENT_WORKSPACE;
}
struct view *view; struct view *view;
for_each_view(view, &server->views, rc.window_switcher.criteria) { for_each_view(view, &server->views, criteria) {
if (rc.window_switcher.order == WINDOW_SWITCHER_ORDER_AGE) { if (rc.window_switcher.order == WINDOW_SWITCHER_ORDER_AGE) {
insert_view_ordered_by_age(&server->cycle.views, view); insert_view_ordered_by_age(&server->cycle.views, view);
} else { } else {
@ -328,6 +337,7 @@ init_cycle(struct server *server)
wlr_log(WLR_DEBUG, "no views to switch between"); wlr_log(WLR_DEBUG, "no views to switch between");
return false; return false;
} }
server->cycle.filter = filter;
if (rc.window_switcher.osd.show) { if (rc.window_switcher.osd.show) {
/* Create OSD */ /* Create OSD */
@ -406,4 +416,5 @@ destroy_cycle(struct server *server)
} }
server->cycle.selected_view = NULL; server->cycle.selected_view = NULL;
server->cycle.filter = (struct cycle_filter){0};
} }