This commit is contained in:
RMT 2026-03-17 23:53:31 -03:00 committed by GitHub
commit afe4c6c840
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 225 additions and 1 deletions

View file

@ -125,6 +125,23 @@ Actions are used in menus and keyboard/mouse bindings.
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.
*<action name="CycleInRegion" region="value" windowOverlapPercent="value" regionOverlapPercent="value" />*
Cycle focus to the next window in the given region.
If _windowOverlapPercent_ is set and > 0, it is the minimum percentage of
the window area that must lie inside the region.
If _regionOverlapPercent_ is set and > 0, it is the minimum percentage of
the region area that must lie inside the window.
If both are omitted or set to 0, a window matches only if it is explicitly
assigned to the region (e.g. tiled to it), regardless of window size.
If _region_ is omitted, cycle focus to the next window on the entire
screen (still honoring the standard window-switcher criteria such as
current-workspace filtering).
See labwc-config(5) for further information on how to define regions.
*<action name="NextWindow" workspace="current" output="all" identifier="all" />*++
*<action name="PreviousWindow" workspace="current" output="all" identifier="all" />*
Cycle focus to next/previous window, respectively.

View file

@ -2,6 +2,7 @@
#ifndef LABWC_H
#define LABWC_H
#include "config.h"
#include "config/types.h"
#include <wlr/util/box.h>
#include <wlr/util/log.h>
#include "common/set.h"
@ -359,6 +360,30 @@ void desktop_focus_output(struct output *output);
*/
void desktop_update_top_layer_visibility(struct server *server);
/**
* desktop_cycle_view_in_region() - return a view to "cycle" to,
* filtered to views that are mostly inside a region.
*
* @active_view: Reference point for finding the next view.
* @region: If non-NULL, only return views mostly inside this region.
* @criteria: Base filtering (e.g. current workspace).
* @view_threshold: Fraction of the view area that must lie inside @region.
* If <= 0, this check is disabled.
* @region_threshold: Fraction of the region area that must lie inside the
* view. If <= 0, this check is disabled.
*
* If both thresholds are disabled, a view only matches if it is explicitly
* assigned to the region (e.g. tiled to it).
*
* Note: If @active_view is not in-region, the topmost matching view is
* returned first. If @active_view is in-region, iteration reverses to
* avoid bouncing between the two most recent views when focusing raises.
*/
struct view *desktop_cycle_view_in_region(struct server *server,
struct view *active_view, struct region *region,
enum lab_view_criteria criteria, double view_threshold,
double region_threshold);
/**
* desktop_focus_topmost_view() - focus the topmost view on the current
* workspace, skipping views that claim not to want focus (those can

View file

@ -112,6 +112,7 @@ enum action_type {
ACTION_TYPE_TOGGLE_SNAP_TO_REGION,
ACTION_TYPE_SNAP_TO_REGION,
ACTION_TYPE_UNSNAP,
ACTION_TYPE_CYCLE_IN_REGION,
ACTION_TYPE_TOGGLE_KEYBINDS,
ACTION_TYPE_FOCUS_OUTPUT,
ACTION_TYPE_MOVE_TO_OUTPUT,
@ -182,6 +183,7 @@ const char *action_names[] = {
"ToggleSnapToRegion",
"SnapToRegion",
"UnSnap",
"CycleInRegion",
"ToggleKeybinds",
"FocusOutput",
"MoveToOutput",
@ -516,6 +518,17 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
goto cleanup;
}
break;
case ACTION_TYPE_CYCLE_IN_REGION:
if (!strcasecmp(argument, "region")) {
action_arg_add_str(action, argument, content);
goto cleanup;
}
if (!strcasecmp(argument, "windowOverlapPercent")
|| !strcasecmp(argument, "regionOverlapPercent")) {
action_arg_add_int(action, argument, atoi(content));
goto cleanup;
}
break;
case ACTION_TYPE_FOCUS_OUTPUT:
case ACTION_TYPE_MOVE_TO_OUTPUT:
if (!strcmp(argument, "output")) {
@ -1249,6 +1262,50 @@ run_action(struct view *view, struct server *server, struct action *action,
view_toggle_visible_on_all_workspaces(view);
}
break;
case ACTION_TYPE_CYCLE_IN_REGION:
{
struct view *active_view = view ? view : server->active_view;
enum lab_view_criteria criteria =
LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER
| LAB_VIEW_CRITERIA_NO_DIALOG;
int window_overlap_percent = action_get_int(action, "windowOverlapPercent", 0);
int region_overlap_percent = action_get_int(action, "regionOverlapPercent", 0);
if (rc.window_switcher.workspace_filter == CYCLE_WORKSPACE_CURRENT) {
criteria |= LAB_VIEW_CRITERIA_CURRENT_WORKSPACE;
}
window_overlap_percent = CLAMP(window_overlap_percent, 0, 100);
region_overlap_percent = CLAMP(region_overlap_percent, 0, 100);
double view_threshold = (double)window_overlap_percent / 100.0;
double region_threshold = (double)region_overlap_percent / 100.0;
const char *region_name = action_get_str(action, "region", NULL);
struct region *region = NULL;
if (region_name) {
if (!active_view) {
break;
}
struct output *output = active_view->output;
if (!output_is_usable(output)) {
break;
}
region = regions_from_name(region_name, output);
if (!region) {
wlr_log(WLR_ERROR, "Invalid CycleInRegion region: '%s'",
region_name);
break;
}
}
struct view *new_view = desktop_cycle_view_in_region(
server, active_view, region, criteria, view_threshold, region_threshold);
if (new_view) {
desktop_focus_view(new_view, /*raise*/ true);
}
break;
}
case ACTION_TYPE_FOCUS:
if (view) {
desktop_focus_view(view, /*raise*/ false);

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "config.h"
#include <assert.h>
#include <string.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_layer_shell_v1.h>
#include <wlr/types/wlr_output_layout.h>
@ -8,6 +9,7 @@
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_subcompositor.h>
#include <wlr/types/wlr_xdg_shell.h>
#include "common/macros.h"
#include "common/scene-helpers.h"
#include "dnd.h"
#include "labwc.h"
@ -16,6 +18,7 @@
#include "output.h"
#include "ssd.h"
#include "view.h"
#include "regions.h"
#include "workspaces.h"
#if HAVE_XWAYLAND
@ -134,6 +137,129 @@ desktop_focus_view_or_surface(struct seat *seat, struct view *view,
}
}
static bool
view_is_assigned_to_region(struct view *view, struct region *region)
{
if (!view || !region) {
return false;
}
if (view->tiled_region && view->tiled_region->name
&& !strcmp(view->tiled_region->name, region->name)) {
return true;
}
if (view->tiled_region_evacuate
&& !strcmp(view->tiled_region_evacuate, region->name)) {
return true;
}
return false;
}
static bool
view_matches_region(struct view *view, struct region *region,
double view_threshold, double region_threshold)
{
if (!view || !region) {
return false;
}
if (view_is_assigned_to_region(view, region)) {
return true;
}
if (view_threshold <= 0 && region_threshold <= 0) {
return false;
}
struct wlr_box overlap;
wlr_box_intersection(&overlap, &view->current, &region->geo);
double overlap_area = (double)overlap.height * (double)overlap.width;
if (overlap_area <= 0) {
return false;
}
if (view_threshold > 0) {
double view_area = (double)view->current.height * (double)view->current.width;
if (view_area > 0 && overlap_area >= view_threshold * view_area) {
return true;
}
}
if (region_threshold > 0) {
double region_area = (double)region->geo.height * (double)region->geo.width;
if (region_area > 0 && overlap_area >= region_threshold * region_area) {
return true;
}
}
return false;
}
static struct view *
cycle_prev_wrap(struct wl_list *head, struct view *from,
enum lab_view_criteria criteria)
{
struct view *view = view_prev(head, from, criteria);
return view ? view : view_prev(head, NULL, criteria);
}
struct view *
desktop_cycle_view_in_region(struct server *server, struct view *active_view,
struct region *region, enum lab_view_criteria criteria,
double view_threshold, double region_threshold)
{
if (!active_view) {
struct view *cur;
for_each_view(cur, &server->views, criteria) {
if (cur->minimized) {
continue;
}
if (!region || view_matches_region(cur, region,
view_threshold, region_threshold)) {
return cur;
}
}
return NULL;
}
if (region && (!active_view
|| !view_matches_region(active_view, region,
view_threshold, region_threshold))) {
/*
* If the currently focused view is not in-region, always focus the
* topmost matching view first.
*/
struct view *cur;
for_each_view(cur, &server->views, criteria) {
if (!cur->minimized && view_matches_region(cur, region,
view_threshold, region_threshold)) {
return cur;
}
}
return NULL;
}
/*
* If the currently active/focused window is already in-region, cycle
* through in reverse order so that we cycle through all windows, and
* not just the two most recent (focusing raises).
*/
struct view *start = active_view;
struct view *cur = cycle_prev_wrap(&server->views, start, criteria);
while (cur && cur != start) {
if (!cur->minimized
&& (!region
|| view_matches_region(cur, region,
view_threshold, region_threshold))) {
return cur;
}
cur = cycle_prev_wrap(&server->views, cur, criteria);
}
return NULL;
}
static struct view *
desktop_topmost_focusable_view(struct server *server)
{
@ -384,4 +510,3 @@ get_cursor_context(struct server *server)
*/
return ret;
}