mirror of
https://github.com/labwc/labwc.git
synced 2026-06-13 14:33:18 -04:00
Add CycleInRegion action
CycleInRegion will cycle focus between all windows/views that are within the given region, to support position-based focusing. The window/region matching supports: * when a window is explicitly assigned to a region * an optional `windowOverlapPercentage` arg, when at least the given percentage of the window's geometry is within the region's geometry. * an optional `regionOverlapPercentage` arg, when at least the given percentage of the region's geometry is within the window's geometry. To support immediate focus cycling and to avoid ping-pong'ing between the top two windows, we cycle through windows within a region in reverse-order. When we are not focused within a region, we instead focus the top-most (first) window, as that is what the user will expect. If the `region` arg is not specified, the action will cycle through all windows on the given desktop instead.
This commit is contained in:
parent
d5b5b765c7
commit
688ea9a6b4
4 changed files with 224 additions and 1 deletions
|
|
@ -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
|
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="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="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="NextWindowImmediate" workspace="current" output="all" identifier="all" />*++
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#ifndef LABWC_H
|
#ifndef LABWC_H
|
||||||
#define LABWC_H
|
#define LABWC_H
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "config/types.h"
|
||||||
#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"
|
||||||
|
|
@ -382,6 +383,30 @@ void desktop_focus_output(struct output *output);
|
||||||
*/
|
*/
|
||||||
void desktop_update_top_layer_visibility(void);
|
void desktop_update_top_layer_visibility(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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
|
* desktop_focus_topmost_view() - focus the topmost view on the current
|
||||||
* workspace, skipping views that claim not to want focus (those can
|
* workspace, skipping views that claim not to want focus (those can
|
||||||
|
|
|
||||||
56
src/action.c
56
src/action.c
|
|
@ -114,6 +114,7 @@ struct action_arg_list {
|
||||||
X(TOGGLE_SNAP_TO_REGION, "ToggleSnapToRegion") \
|
X(TOGGLE_SNAP_TO_REGION, "ToggleSnapToRegion") \
|
||||||
X(SNAP_TO_REGION, "SnapToRegion") \
|
X(SNAP_TO_REGION, "SnapToRegion") \
|
||||||
X(UNSNAP, "UnSnap") \
|
X(UNSNAP, "UnSnap") \
|
||||||
|
X(CYCLE_IN_REGION, "CycleInRegion") \
|
||||||
X(TOGGLE_KEYBINDS, "ToggleKeybinds") \
|
X(TOGGLE_KEYBINDS, "ToggleKeybinds") \
|
||||||
X(FOCUS_OUTPUT, "FocusOutput") \
|
X(FOCUS_OUTPUT, "FocusOutput") \
|
||||||
X(MOVE_TO_OUTPUT, "MoveToOutput") \
|
X(MOVE_TO_OUTPUT, "MoveToOutput") \
|
||||||
|
|
@ -487,6 +488,17 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
break;
|
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_FOCUS_OUTPUT:
|
||||||
case ACTION_TYPE_MOVE_TO_OUTPUT:
|
case ACTION_TYPE_MOVE_TO_OUTPUT:
|
||||||
if (!strcmp(argument, "output")) {
|
if (!strcmp(argument, "output")) {
|
||||||
|
|
@ -1230,6 +1242,50 @@ run_action(struct view *view, struct action *action,
|
||||||
view_toggle_visible_on_all_workspaces(view);
|
view_toggle_visible_on_all_workspaces(view);
|
||||||
}
|
}
|
||||||
break;
|
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(
|
||||||
|
active_view, region, criteria, view_threshold, region_threshold);
|
||||||
|
if (new_view) {
|
||||||
|
desktop_focus_view(new_view, /*raise*/ true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case ACTION_TYPE_FOCUS:
|
case ACTION_TYPE_FOCUS:
|
||||||
if (view) {
|
if (view) {
|
||||||
desktop_focus_view(view, /*raise*/ false);
|
desktop_focus_view(view, /*raise*/ false);
|
||||||
|
|
|
||||||
127
src/desktop.c
127
src/desktop.c
|
|
@ -1,6 +1,7 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-only
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
#include <wlr/types/wlr_cursor.h>
|
#include <wlr/types/wlr_cursor.h>
|
||||||
#include <wlr/types/wlr_layer_shell_v1.h>
|
#include <wlr/types/wlr_layer_shell_v1.h>
|
||||||
#include <wlr/types/wlr_output_layout.h>
|
#include <wlr/types/wlr_output_layout.h>
|
||||||
|
|
@ -8,6 +9,7 @@
|
||||||
#include <wlr/types/wlr_seat.h>
|
#include <wlr/types/wlr_seat.h>
|
||||||
#include <wlr/types/wlr_subcompositor.h>
|
#include <wlr/types/wlr_subcompositor.h>
|
||||||
#include <wlr/types/wlr_xdg_shell.h>
|
#include <wlr/types/wlr_xdg_shell.h>
|
||||||
|
#include "common/macros.h"
|
||||||
#include "common/scene-helpers.h"
|
#include "common/scene-helpers.h"
|
||||||
#include "config/rcxml.h"
|
#include "config/rcxml.h"
|
||||||
#include "dnd.h"
|
#include "dnd.h"
|
||||||
|
|
@ -18,6 +20,7 @@
|
||||||
#include "show-desktop.h"
|
#include "show-desktop.h"
|
||||||
#include "ssd.h"
|
#include "ssd.h"
|
||||||
#include "view.h"
|
#include "view.h"
|
||||||
|
#include "regions.h"
|
||||||
#include "workspaces.h"
|
#include "workspaces.h"
|
||||||
|
|
||||||
#if HAVE_XWAYLAND
|
#if HAVE_XWAYLAND
|
||||||
|
|
@ -196,6 +199,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, ®ion->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 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 *
|
static struct view *
|
||||||
desktop_topmost_focusable_view(void)
|
desktop_topmost_focusable_view(void)
|
||||||
{
|
{
|
||||||
|
|
@ -444,4 +570,3 @@ get_cursor_context(void)
|
||||||
*/
|
*/
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue