diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd
index a8d8bba5..ee413354 100644
--- a/docs/labwc-actions.5.scd
+++ b/docs/labwc-actions.5.scd
@@ -99,6 +99,10 @@ Actions are used in menus and keyboard/mouse bindings.
Resize and move active window according to the given region.
See labwc-config(5) for further information on how to define regions.
+**
+ Cycle focus to the next window (mostly) in the given region.
+ See labwc-config(5) for further information on how to define regions.
+
**
Cycle focus to next window.
diff --git a/include/labwc.h b/include/labwc.h
index 419a5ee0..9c44d0e7 100644
--- a/include/labwc.h
+++ b/include/labwc.h
@@ -454,6 +454,14 @@ enum lab_cycle_dir {
struct view *desktop_cycle_view(struct server *server, struct view *start_view,
enum lab_cycle_dir dir);
+/**
+ * desktop_cycle_view_near_region - return view to 'cycle' to within region
+ * @active_view: reference point for finding next view to cycle to
+ * Note: If !active_view, the top-most focusable view is returned
+ */
+struct view *desktop_cycle_view_near_region(struct server *server,
+ struct view *active_view, struct region *region);
+
/**
* desktop_focus_topmost_view() - focus the topmost view on the current
* workspace, skipping views that claim not to want focus (those can
diff --git a/src/action.c b/src/action.c
index 6f6bee94..2076d28d 100644
--- a/src/action.c
+++ b/src/action.c
@@ -100,6 +100,7 @@ enum action_type {
ACTION_TYPE_SEND_TO_DESKTOP,
ACTION_TYPE_GO_TO_DESKTOP,
ACTION_TYPE_SNAP_TO_REGION,
+ ACTION_TYPE_CYCLE_NEAR_REGION,
ACTION_TYPE_TOGGLE_KEYBINDS,
ACTION_TYPE_FOCUS_OUTPUT,
ACTION_TYPE_MOVE_TO_OUTPUT,
@@ -158,6 +159,7 @@ const char *action_names[] = {
"SendToDesktop",
"GoToDesktop",
"SnapToRegion",
+ "CycleNearRegion",
"ToggleKeybinds",
"FocusOutput",
"MoveToOutput",
@@ -405,6 +407,12 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
goto cleanup;
}
break;
+ case ACTION_TYPE_CYCLE_NEAR_REGION:
+ if (!strcmp(argument, "region")) {
+ action_arg_add_str(action, argument, content);
+ goto cleanup;
+ }
+ break;
case ACTION_TYPE_FOCUS_OUTPUT:
if (!strcmp(argument, "output")) {
action_arg_add_str(action, argument, content);
@@ -545,6 +553,9 @@ action_is_valid(struct action *action)
case ACTION_TYPE_SNAP_TO_REGION:
arg_name = "region";
break;
+ case ACTION_TYPE_CYCLE_NEAR_REGION:
+ arg_name = "region";
+ break;
case ACTION_TYPE_FOCUS_OUTPUT:
arg_name = "output";
break;
@@ -1026,6 +1037,26 @@ actions_run(struct view *activator, struct server *server,
wlr_log(WLR_ERROR, "Invalid SnapToRegion id: '%s'", region_name);
}
break;
+ case ACTION_TYPE_CYCLE_NEAR_REGION:
+ {
+ if (!view) {
+ break;
+ }
+ struct output *output = view->output;
+ if (!output_is_usable(output)) {
+ break;
+ }
+ const char *region_name = action_get_str(action, "region", NULL);
+ struct region *region = regions_from_name(region_name, output);
+ if (region) {
+ struct view *new_view = desktop_cycle_view_near_region(
+ server, view, region);
+ if (new_view) {
+ desktop_focus_view(new_view, /*raise*/ true);
+ }
+ }
+ }
+ break;
case ACTION_TYPE_TOGGLE_KEYBINDS:
if (view) {
view_toggle_keybinds(view);
diff --git a/src/desktop.c b/src/desktop.c
index 2c0db207..3f29111b 100644
--- a/src/desktop.c
+++ b/src/desktop.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "config.h"
#include
+#include "common/macros.h"
#include "common/scene-helpers.h"
#include "common/surface-helpers.h"
#include "dnd.h"
@@ -139,6 +140,32 @@ desktop_cycle_view(struct server *server, struct view *start_view,
return iter(&server->views, start_view, criteria);
}
+struct view *
+desktop_cycle_view_near_region(struct server *server, struct view *active_view,
+ struct region *region)
+{
+ struct view *(*iter)(struct wl_list *head, struct view *view,
+ enum lab_view_criteria criteria);
+ enum lab_view_criteria criteria = rc.window_switcher.criteria |
+ LAB_VIEW_CRITERIA_CURRENT_WORKSPACE;
+
+ struct view *cur;
+ struct wlr_box intersect;
+
+ iter = view_prev_no_head_stop;
+
+ cur = iter(&server->views, active_view, criteria);
+ while (cur && cur != active_view) {
+ wlr_box_intersection(&intersect, &cur->current, ®ion->geo);
+ if (!cur->minimized && !wlr_box_empty(&intersect)) {
+ return cur;
+ }
+ cur = iter(&server->views, cur, criteria);
+ }
+ /* no matching views found */
+ return NULL;
+}
+
struct view *
desktop_topmost_focusable_view(struct server *server)
{