mirror of
https://github.com/labwc/labwc.git
synced 2026-04-06 07:15:40 -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
16c5373be5
commit
6a4d937050
4 changed files with 225 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
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
57
src/action.c
57
src/action.c
|
|
@ -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);
|
||||
|
|
|
|||
127
src/desktop.c
127
src/desktop.c
|
|
@ -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, ®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 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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue