Merge pull request #433 from Consolatis/feature/regions

Add SnapToRegion
This commit is contained in:
Johan Malm 2023-01-11 20:24:07 +00:00 committed by GitHub
commit 1bcc0aa5db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 703 additions and 9 deletions

View file

@ -46,6 +46,10 @@ Actions are used in menus and keyboard/mouse bindings.
Resize window to fill half the output in the given direction. Supports
directions "left", "up", "right", "down" and "center".
*<action name="SnapToRegion" region="snap-1" />*
Resize and move active window according to the given region.
See labwc-config(5) for further information on how to define regions.
*<action name="NextWindow">*
Cycle focus to next window.

View file

@ -104,6 +104,16 @@ The rest of this man page describes configuration options.
*<snapping><topMaximize>* [yes|no]
Maximize window if Move operation ends on the top edge. Default is yes.
## REGIONS
*<regions><region name="snap-1" x="10%" y="10%" width="80%" height="80%">*
Define snap regions. The regions are calculated based on the usable area
of each output. Usable area in this context means space not exclusively
used by layershell clients like panels. The "%" character is required.
Windows can either be snapped to regions by keeping a keyboard modifier
pressed while moving a window (Ctrl, Alt, Shift, Logo) or by using the
SnapToRegion action. By default there are no regions defined.
## WORKSPACES
*<desktops><names><name>*

View file

@ -81,6 +81,21 @@
</names>
</desktops>
<!-- Percent based regions based on output usable area, % char is required -->
<!--
<regions>
<region name="top-left" x="0%" y="0%" height="50%" width="50%" />
<region name="top" x="0%" y="0%" height="50%" width="100%" />
<region name="top-right" x="50%" y="0%" height="50%" width="50%" />
<region name="left" x="0%" y="0%" height="100%" width="50%" />
<region name="center" x="10%" y="10%" height="80%" width="80%" />
<region name="right" x="50%" y="0%" height="100%" width="50%" />
<region name="bottom-left" x="0%" y="50%" height="50%" width="50%" />
<region name="bottom" x="0%" y="50%" height="50%" width="100%" />
<region name="bottom-right" x="50%" y="50%" height="50%" width="50%" />
</regions>
-->
<!--
Keybind actions are specified in labwc-actions(5)
The following keybind modifiers are supported:
@ -155,6 +170,18 @@
<keybind key="XF86_MonBrightnessDown">
<action name="Execute" command="brightnessctl set 10%-" />
</keybind>
<!-- SnapToRegion via W-Numpad -->
<!--
<keybind key="W-KP_7"><action name="SnapToRegion" region="top-left" /></keybind>
<keybind key="W-KP_8"><action name="SnapToRegion" region="top" /></keybind>
<keybind key="W-KP_9"><action name="SnapToRegion" region="top-right" /></keybind>
<keybind key="W-KP_4"><action name="SnapToRegion" region="left" /></keybind>
<keybind key="W-KP_5"><action name="SnapToRegion" region="center" /></keybind>
<keybind key="W-KP_6"><action name="SnapToRegion" region="right" /></keybind>
<keybind key="W-KP_1"><action name="SnapToRegion" region="bottom-left" /></keybind>
<keybind key="W-KP_2"><action name="SnapToRegion" region="bottom" /></keybind>
<keybind key="W-KP_3"><action name="SnapToRegion" region="bottom-right" /></keybind>
-->
</keyboard>
<!--

View file

@ -1,5 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <cairo.h>
#include <wayland-server-core.h>
struct wlr_scene_tree;

View file

@ -59,6 +59,9 @@ struct rcxml {
int popuptime;
struct wl_list workspaces; /* struct workspace.link */
} workspace_config;
/* Regions */
struct wl_list regions; /* struct region.link */
};
extern struct rcxml rc;

View file

@ -47,6 +47,7 @@
#include "cursor.h"
#include "config/keybind.h"
#include "config/rcxml.h"
#include "regions.h"
#if HAVE_NLS
#include <libintl.h>
#include <locale.h>
@ -156,6 +157,10 @@ struct seat {
struct wlr_scene_tree *icons;
} drag;
/* Private use by regions.c */
struct region *region_active;
struct region_overlay region_overlay;
struct wl_client *active_client_while_inhibited;
struct wl_list inputs;
struct wl_listener new_input;
@ -309,6 +314,8 @@ struct output {
struct wlr_scene_buffer *workspace_osd;
struct wlr_box usable_area;
struct wl_list regions; /* struct region.link */
struct lab_data_buffer *osd_buffer;
struct wl_listener destroy;

76
include/regions.h Normal file
View file

@ -0,0 +1,76 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef __LABWC_REGIONS_H
#define __LABWC_REGIONS_H
struct seat;
struct view;
struct server;
struct output;
struct wl_list;
struct wlr_box;
struct multi_rect;
/* Double use: rcxml.c for config and output.c for usage */
struct region {
struct wl_list link; /* struct rcxml.regions, struct output.regions */
char *name;
struct wlr_box geo;
struct wlr_box percentage;
struct {
int x;
int y;
} center;
};
struct region_overlay {
struct wlr_scene_tree *tree;
union {
struct wlr_scene_rect *overlay;
struct multi_rect *pixman_overlay;
};
};
/* Can be used as a cheap check to detect if there are any regions configured */
bool regions_available(void);
/**
* regions_reconfigure*() - re-initializes all regions from struct rc.
*
* - all views are evacuated from the given output (or all of them)
* - all output local regions are destroyed
* - new output local regions are created from struct rc
* - the region geometry is re-calculated
*/
void regions_reconfigure(struct server *server);
void regions_reconfigure_output(struct output *output);
/* re-calculate the geometry based on usable area */
void regions_update_geometry(struct output *output);
/**
* Mark all views which are currently region-tiled to the given output as
* evacuated. This means that the view->tiled_region pointer is reset to
* NULL but view->tiled_region_evacuate is set to a copy of the region name.
*
* The next time desktop_arrange_all_views() causes a call to
* view_apply_region_geometry() it will try to find a new output and then
* search for a region with the same name. If found, view->tiled_region will
* be set to the new region and view->tiled_region_evacuate will be free'd.
*
* If no region with the old name is found (e.g. the user deleted or renamed
* the region in rc.xml and caused a Reconfigure) the view will be reset to
* non-tiled state and view->tiled_region_evacuate will be free'd.
*/
void regions_evacuate_output(struct output *output);
/* Free all regions in given wl_list pointer */
void regions_destroy(struct seat *seat, struct wl_list *regions);
/* Get output local region from cursor or name, may be NULL */
struct region *regions_from_cursor(struct server *server);
struct region *regions_from_name(const char *region_name, struct output *output);
void regions_show_overlay(struct view *view, struct seat *seat, struct region *region);
void regions_hide_overlay(struct seat *seat);
#endif /* __LABWC_REGIONS_H */

View file

@ -50,6 +50,12 @@ struct view {
bool minimized;
bool maximized;
uint32_t tiled; /* private, enum view_edge in src/view.c */
/* Pointer to an output owned struct region, may be NULL */
struct region *tiled_region;
/* Set to region->name when tiled_region is free'd by a destroying output */
char *tiled_region_evacuate;
struct wlr_output *fullscreen;
/* geometry of the wlr_surface contained within the view */
@ -126,6 +132,7 @@ void view_toggle_maximize(struct view *view);
void view_toggle_decorations(struct view *view);
void view_toggle_always_on_top(struct view *view);
bool view_is_always_on_top(struct view *view);
bool view_is_tiled(struct view *view);
void view_move_to_workspace(struct view *view, struct workspace *workspace);
void view_set_decorations(struct view *view, bool decorations);
void view_toggle_fullscreen(struct view *view);
@ -134,6 +141,8 @@ void view_discover_output(struct view *view);
void view_move_to_edge(struct view *view, const char *direction);
void view_snap_to_edge(struct view *view, const char *direction,
bool store_natural_geometry);
void view_snap_to_region(struct view *view, struct region *region,
bool store_natural_geometry);
const char *view_get_string_prop(struct view *view, const char *prop);
void view_update_title(struct view *view);
void view_update_app_id(struct view *view);

View file

@ -13,6 +13,7 @@
#include "debug.h"
#include "labwc.h"
#include "menu/menu.h"
#include "regions.h"
#include "ssd.h"
#include "view.h"
#include "workspaces.h"
@ -58,6 +59,7 @@ enum action_type {
ACTION_TYPE_RESIZE,
ACTION_TYPE_GO_TO_DESKTOP,
ACTION_TYPE_SEND_TO_DESKTOP,
ACTION_TYPE_SNAP_TO_REGION
};
const char *action_names[] = {
@ -85,6 +87,7 @@ const char *action_names[] = {
"Resize",
"GoToDesktop",
"SendToDesktop",
"SnapToRegion",
NULL
};
@ -111,6 +114,9 @@ action_arg_from_xml_node(struct action *action, char *nodename, char *content)
} else if (!strcmp(nodename, "to.action")) {
/* GoToDesktop, SendToDesktop */
action_arg_add_str(action, NULL, content);
} else if (!strcmp(nodename, "region.action")) {
/* SnapToRegion */
action_arg_add_str(action, NULL, content);
}
}
@ -418,6 +424,27 @@ actions_run(struct view *activator, struct server *server,
}
}
break;
case ACTION_TYPE_SNAP_TO_REGION:
if (!arg) {
wlr_log(WLR_ERROR, "Missing argument for SnapToRegion");
break;
}
if (!view) {
break;
}
struct output *output = view->output;
if (!output) {
break;
}
const char *region_name = action_str_from_arg(arg);
struct region *region = regions_from_name(region_name, output);
if (region) {
view_snap_to_region(view, region,
/*store_natural_geometry*/ true);
} else {
wlr_log(WLR_ERROR, "Invalid SnapToRegion id: '%s'", region_name);
}
break;
case ACTION_TYPE_NONE:
break;
case ACTION_TYPE_INVALID:

View file

@ -12,6 +12,7 @@
#include <strings.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/util/box.h>
#include <wlr/util/log.h>
#include "action.h"
#include "common/list.h"
@ -22,8 +23,10 @@
#include "config/libinput.h"
#include "config/mousebind.h"
#include "config/rcxml.h"
#include "regions.h"
#include "workspaces.h"
static bool in_regions;
static bool in_keybind;
static bool in_mousebind;
static bool in_libinput_category;
@ -33,6 +36,7 @@ static struct libinput_category *current_libinput_category;
static const char *current_mouse_context;
static struct action *current_keybind_action;
static struct action *current_mousebind_action;
static struct region *current_region;
enum font_place {
FONT_PLACE_NONE = 0,
@ -46,6 +50,44 @@ enum font_place {
static void load_default_key_bindings(void);
static void load_default_mouse_bindings(void);
static void
fill_region(char *nodename, char *content)
{
string_truncate_at_pattern(nodename, ".region.regions");
if (!strcasecmp(nodename, "region.regions")) {
current_region = znew(*current_region);
wl_list_append(&rc.regions, &current_region->link);
} else if (!content) {
/* intentionally left empty */
} else if (!current_region) {
wlr_log(WLR_ERROR, "Expecting <region name=\"\" before %s='%s'",
nodename, content);
} else if (!strcasecmp(nodename, "name")) {
/* Prevent leaking memory if config contains multiple names */
if (!current_region->name) {
current_region->name = xstrdup(content);
}
} else if (strstr("xywidtheight", nodename) && !strchr(content, '%')) {
wlr_log(WLR_ERROR, "Removing invalid region '%s': %s='%s' misses"
" a trailing %%", current_region->name, nodename, content);
wl_list_remove(&current_region->link);
zfree(current_region->name);
zfree(current_region);
} else if (!strcmp(nodename, "x")) {
current_region->percentage.x = atoi(content);
} else if (!strcmp(nodename, "y")) {
current_region->percentage.y = atoi(content);
} else if (!strcmp(nodename, "width")) {
current_region->percentage.width = atoi(content);
} else if (!strcmp(nodename, "height")) {
current_region->percentage.height = atoi(content);
} else {
wlr_log(WLR_ERROR, "Unexpected data in region parser: %s=\"%s\"",
nodename, content);
}
}
static void
fill_keybind(char *nodename, char *content)
{
@ -321,6 +363,10 @@ entry(xmlNode *node, char *nodename, char *content)
if (in_libinput_category) {
fill_libinput_category(nodename, content);
}
if (in_regions) {
fill_region(nodename, content);
return;
}
/* handle nodes without content, e.g. <keyboard><default /> */
if (!strcmp(nodename, "default.keyboard")) {
@ -457,6 +503,12 @@ xml_tree_walk(xmlNode *node)
in_libinput_category = false;
continue;
}
if (!strcasecmp((char *)n->name, "regions")) {
in_regions = true;
traverse(n);
in_regions = false;
continue;
}
traverse(n);
}
}
@ -502,6 +554,7 @@ rcxml_init()
rc.cycle_preview_outlines = true;
rc.workspace_config.popuptime = INT_MIN;
wl_list_init(&rc.workspace_config.workspaces);
wl_list_init(&rc.regions);
}
static struct {
@ -693,6 +746,23 @@ post_processing(void)
if (rc.workspace_config.popuptime == INT_MIN) {
rc.workspace_config.popuptime = 1000;
}
struct region *region, *region_tmp;
wl_list_for_each_safe(region, region_tmp, &rc.regions, link) {
struct wlr_box box = region->percentage;
bool invalid = !region->name
|| box.x < 0 || box.x > 100
|| box.y < 0 || box.y > 100
|| box.width <= 0 || box.width > 100
|| box.height <= 0 || box.height > 100;
if (invalid) {
wlr_log(WLR_ERROR,
"Removing invalid region '%s': %d%% x %d%% @ %d%%,%d%%",
region->name, box.width, box.height, box.x, box.y);
wl_list_remove(&region->link);
zfree(region->name);
free(region);
}
}
}
static void
@ -798,6 +868,8 @@ rcxml_finish(void)
zfree(w);
}
regions_destroy(NULL, &rc.regions);
/* Reset state vars for starting fresh when Reload is triggered */
current_keybind = NULL;
current_mousebind = NULL;
@ -805,4 +877,5 @@ rcxml_finish(void)
current_mouse_context = NULL;
current_keybind_action = NULL;
current_mousebind_action = NULL;
current_region = NULL;
}

View file

@ -12,6 +12,7 @@
#include "dnd.h"
#include "labwc.h"
#include "menu/menu.h"
#include "regions.h"
#include "resistance.h"
#include "ssd.h"
#include "view.h"
@ -186,6 +187,20 @@ process_cursor_move(struct server *server, uint32_t time)
dy += server->grab_box.y;
resistance_move_apply(view, &dx, &dy);
view_move(view, dx, dy);
/* Region overlay */
if (!regions_available()) {
return;
}
struct wlr_keyboard *keyboard = &server->seat.keyboard_group->keyboard;
if (keyboard_any_modifiers_pressed(keyboard)) {
struct region *region = regions_from_cursor(server);
if (region) {
regions_show_overlay(view, &server->seat, region);
} else {
regions_hide_overlay(&server->seat);
}
}
}
static void

View file

@ -110,10 +110,10 @@ get_special(struct server *server, struct wlr_scene_node *node)
#endif
struct wlr_scene_tree *grand_parent =
node->parent ? node->parent->node.parent : NULL;
if (grand_parent == server->view_tree) {
if (grand_parent == server->view_tree && node->data) {
last_view = node_view_from_node(node);
}
if (node->parent == server->view_tree_always_on_top) {
if (node->parent == server->view_tree_always_on_top && node->data) {
last_view = node_view_from_node(node);
}
const char *view_part = get_view_part(last_view, node);

View file

@ -135,6 +135,10 @@ first_view(struct server *server)
struct wl_list *list_head =
&server->workspace_current->tree->children;
wl_list_for_each_reverse(node, list_head, link) {
if (!node->data) {
/* We found some non-view, most likely the region overlay */
continue;
}
struct view *view = node_view_from_node(node);
if (isfocusable(view)) {
return view;
@ -200,6 +204,11 @@ desktop_cycle_view(struct server *server, struct view *start_view,
list_item = iter(list_item);
}
node = wl_container_of(list_item, node, link);
if (!node->data) {
/* We found some non-view, most likely the region overlay */
view = NULL;
continue;
}
view = node_view_from_node(node);
if (isfocusable(view)) {
return view;
@ -218,6 +227,10 @@ topmost_mapped_view(struct server *server)
struct wlr_scene_node *node;
node_list = &server->workspace_current->tree->children;
wl_list_for_each_reverse(node, node_list, link) {
if (!node->data) {
/* We found some non-view, most likely the region overlay */
continue;
}
view = node_view_from_node(node);
if (view->mapped) {
return view;

View file

@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "labwc.h"
#include "regions.h"
#include "view.h"
static int
@ -44,7 +45,7 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges)
*/
return;
}
if (view->maximized || view->tiled) {
if (view->maximized || view_is_tiled(view)) {
/*
* Un-maximize and restore natural width/height.
* Don't reset tiled state yet since we may want
@ -136,17 +137,41 @@ snap_to_edge(struct view *view)
return true;
}
static bool
snap_to_region(struct view *view)
{
if (!regions_available()) {
return false;
}
struct wlr_keyboard *keyboard =
&view->server->seat.keyboard_group->keyboard;
if (keyboard_any_modifiers_pressed(keyboard)) {
struct region *region = regions_from_cursor(view->server);
if (region) {
view_snap_to_region(view, region,
/*store_natural_geometry*/ false);
return true;
}
}
return false;
}
void
interactive_finish(struct view *view)
{
if (view->server->grabbed_view == view) {
enum input_mode mode = view->server->input_mode;
regions_hide_overlay(&view->server->seat);
view->server->input_mode = LAB_INPUT_STATE_PASSTHROUGH;
view->server->grabbed_view = NULL;
if (mode == LAB_INPUT_STATE_MOVE) {
if (!snap_to_edge(view)) {
/* Reset tiled state if not snapped */
view_set_untiled(view);
if (!snap_to_region(view)) {
if (!snap_to_edge(view)) {
/* Reset tiled state if not snapped */
view_set_untiled(view);
}
}
}
/* Update focus/cursor image */

View file

@ -5,6 +5,7 @@
#include "action.h"
#include "key-state.h"
#include "labwc.h"
#include "regions.h"
#include "workspaces.h"
static bool should_cancel_cycling_on_next_key_release;
@ -55,7 +56,8 @@ keyboard_modifiers_notify(struct wl_listener *listener, void *data)
struct wlr_keyboard_key_event *event = data;
struct wlr_keyboard *wlr_keyboard = keyboard->wlr_keyboard;
if (server->osd_state.cycle_view || seat->workspace_osd_shown_by_modifier) {
if (server->osd_state.cycle_view || server->grabbed_view
|| seat->workspace_osd_shown_by_modifier) {
if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED
&& !keyboard_any_modifiers_pressed(wlr_keyboard)) {
if (server->osd_state.cycle_view) {
@ -68,6 +70,9 @@ keyboard_modifiers_notify(struct wl_listener *listener, void *data)
if (seat->workspace_osd_shown_by_modifier) {
workspaces_osd_hide(seat);
}
if (server->grabbed_view) {
regions_hide_overlay(seat);
}
}
}
wlr_seat_keyboard_notify_modifiers(seat->seat, &wlr_keyboard->modifiers);

View file

@ -14,6 +14,7 @@ labwc_sources = files(
'node.c',
'osd.c',
'output.c',
'regions.c',
'resistance.c',
'seat.c',
'server.c',

View file

@ -66,6 +66,10 @@ get_osd_height(struct wl_list *node_list)
struct view *view;
struct wlr_scene_node *node;
wl_list_for_each(node, node_list, link) {
if (!node->data) {
/* We found some non-view, most likely the region overlay */
continue;
}
view = node_view_from_node(node);
if (!isfocusable(view)) {
continue;
@ -215,6 +219,11 @@ preview_cycled_view(struct view *view)
osd_state->preview_node = &view->scene_tree->node;
osd_state->preview_anchor = lab_wlr_scene_get_prev_node(
osd_state->preview_node);
while (osd_state->preview_anchor && !osd_state->preview_anchor->data) {
/* Ignore non-view nodes */
osd_state->preview_anchor = lab_wlr_scene_get_prev_node(
osd_state->preview_anchor);
}
/* Store node enabled / minimized state and force-enable if disabled */
osd_state->preview_was_enabled = osd_state->preview_node->enabled;
@ -288,6 +297,10 @@ render_osd(cairo_t *cairo, int w, int h, struct wl_list *node_list,
/* Draw text for each node */
wl_list_for_each_reverse(node, node_list, link) {
if (!node->data) {
/* We found some non-view, most likely the region overlay */
continue;
}
struct view *view = node_view_from_node(node);
if (!isfocusable(view)) {
continue;

View file

@ -19,6 +19,7 @@
#include "labwc.h"
#include "layers.h"
#include "node.h"
#include "regions.h"
#include "view.h"
static void
@ -40,6 +41,8 @@ static void
output_destroy_notify(struct wl_listener *listener, void *data)
{
struct output *output = wl_container_of(listener, output, destroy);
regions_evacuate_output(output);
regions_destroy(&output->server->seat, &output->regions);
wl_list_remove(&output->link);
wl_list_remove(&output->frame.link);
wl_list_remove(&output->destroy.link);
@ -148,6 +151,8 @@ new_output_notify(struct wl_listener *listener, void *data)
output->frame.notify = output_frame_notify;
wl_signal_add(&wlr_output->events.frame, &output->frame);
wl_list_init(&output->regions);
/*
* Create layer-trees (background, bottom, top and overlay) and
* a layer-popup-tree.
@ -207,6 +212,9 @@ new_output_notify(struct wl_listener *listener, void *data)
output->scene_output = wlr_scene_get_scene_output(server->scene, wlr_output);
assert(output->scene_output);
/* Create regions from config */
regions_reconfigure_output(output);
server->pending_output_layout_change--;
do_output_layout_change(server);
}
@ -303,6 +311,7 @@ output_config_apply(struct server *server,
}
if (need_to_remove) {
regions_evacuate_output(output);
wlr_output_layout_remove(server->output_layout, o);
output->scene_output = NULL;
}
@ -446,6 +455,7 @@ void
output_update_usable_area(struct output *output)
{
if (update_usable_area(output)) {
regions_update_geometry(output);
desktop_arrange_all_views(output->server);
}
}
@ -457,7 +467,12 @@ output_update_all_usable_areas(struct server *server, bool layout_changed)
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
usable_area_changed |= update_usable_area(output);
if (update_usable_area(output)) {
usable_area_changed = true;
regions_update_geometry(output);
} else if (layout_changed) {
regions_update_geometry(output);
}
}
if (usable_area_changed || layout_changed) {
desktop_arrange_all_views(server);

248
src/regions.c Normal file
View file

@ -0,0 +1,248 @@
// SPDX-License-Identifier: GPL-2.0-only
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <float.h>
#include <math.h>
#include <string.h>
#include <wlr/render/pixman.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/util/box.h>
#include <wlr/util/log.h>
#include "common/graphic-helpers.h"
#include "common/list.h"
#include "common/mem.h"
#include "labwc.h"
#include "regions.h"
#include "view.h"
bool
regions_available(void)
{
return !wl_list_empty(&rc.regions);
}
static void
overlay_create(struct seat *seat)
{
assert(!seat->region_overlay.tree);
struct server *server = seat->server;
struct wlr_scene_tree *parent = wlr_scene_tree_create(&server->scene->tree);
seat->region_overlay.tree = parent;
wlr_scene_node_set_enabled(&parent->node, false);
if (!wlr_renderer_is_pixman(server->renderer)) {
/* Hardware assisted rendering: Half transparent overlay */
float color[4] = { 0.25, 0.25, 0.35, 0.5 };
seat->region_overlay.overlay = wlr_scene_rect_create(parent, 0, 0, color);
} else {
/* Software rendering: Outlines */
int line_width = server->theme->osd_border_width;
float *colors[3] = {
server->theme->osd_bg_color,
server->theme->osd_label_text_color,
server->theme->osd_bg_color
};
seat->region_overlay.pixman_overlay = multi_rect_create(parent, colors, line_width);
}
}
struct region *
regions_from_name(const char *region_name, struct output *output)
{
assert(region_name);
assert(output);
struct region *region;
wl_list_for_each(region, &output->regions, link) {
if (!strcmp(region->name, region_name)) {
return region;
}
}
return NULL;
}
struct region *
regions_from_cursor(struct server *server)
{
assert(server);
double lx = server->seat.cursor->x;
double ly = server->seat.cursor->y;
struct wlr_output *wlr_output = wlr_output_layout_output_at(
server->output_layout, lx, ly);
struct output *output = output_from_wlr_output(server, wlr_output);
if (!output) {
return NULL;
}
double dist;
double dist_min = DBL_MAX;
struct region *closest_region = NULL;
struct region *region;
wl_list_for_each(region, &output->regions, link) {
if (wlr_box_contains_point(&region->geo, lx, ly)) {
/* No need for sqrt((x1 - x2)^2 + (y1 - y2)^2) as we just compare */
dist = pow(region->center.x - lx, 2) + pow(region->center.y - ly, 2);
if (dist < dist_min) {
closest_region = region;
dist_min = dist;
}
}
}
return closest_region;
}
void
regions_show_overlay(struct view *view, struct seat *seat, struct region *region)
{
assert(view);
assert(seat);
assert(region);
/* Don't show active region */
if (seat->region_active == region) {
return;
}
if (!seat->region_overlay.tree) {
overlay_create(seat);
}
/* Update overlay */
struct server *server = seat->server;
struct wlr_scene_node *node = &seat->region_overlay.tree->node;
if (!wlr_renderer_is_pixman(server->renderer)) {
/* Hardware assisted rendering: Half transparent overlay */
wlr_scene_rect_set_size(seat->region_overlay.overlay,
region->geo.width, region->geo.height);
} else {
/* Software rendering: Outlines */
multi_rect_set_size(seat->region_overlay.pixman_overlay,
region->geo.width, region->geo.height);
}
if (node->parent != view->scene_tree->node.parent) {
wlr_scene_node_reparent(node, view->scene_tree->node.parent);
wlr_scene_node_place_below(node, &view->scene_tree->node);
}
wlr_scene_node_set_position(node, region->geo.x, region->geo.y);
wlr_scene_node_set_enabled(node, true);
seat->region_active = region;
}
void
regions_hide_overlay(struct seat *seat)
{
assert(seat);
if (!seat->region_active) {
return;
}
struct server *server = seat->server;
struct wlr_scene_node *node = &seat->region_overlay.tree->node;
wlr_scene_node_set_enabled(node, false);
if (node->parent != &server->scene->tree) {
wlr_scene_node_reparent(node, &server->scene->tree);
}
seat->region_active = NULL;
}
void
regions_reconfigure_output(struct output *output)
{
assert(output);
/* Evacuate views and destroy current regions */
if (!wl_list_empty(&output->regions)) {
regions_evacuate_output(output);
regions_destroy(&output->server->seat, &output->regions);
}
/* Initialize regions from config */
struct region *region;
wl_list_for_each(region, &rc.regions, link) {
struct region *region_new = znew(*region_new);
/* Create a copy */
region_new->name = xstrdup(region->name);
region_new->percentage = region->percentage;
wl_list_append(&output->regions, &region_new->link);
}
/* Update region geometries */
regions_update_geometry(output);
}
void
regions_reconfigure(struct server *server)
{
struct output *output;
/* Evacuate views and initialize regions from config */
wl_list_for_each(output, &server->outputs, link) {
regions_reconfigure_output(output);
}
/* Tries to match the evacuated views to the new regions */
desktop_arrange_all_views(server);
}
void
regions_update_geometry(struct output *output)
{
assert(output);
struct region *region;
struct wlr_box usable = output_usable_area_in_layout_coords(output);
/* Update regions */
struct wlr_box *perc, *geo;
wl_list_for_each(region, &output->regions, link) {
geo = &region->geo;
perc = &region->percentage;
geo->x = usable.x + usable.width * perc->x / 100;
geo->y = usable.y + usable.height * perc->y / 100;
geo->width = usable.width * perc->width / 100;
geo->height = usable.height * perc->height / 100;
region->center.x = geo->x + geo->width / 2;
region->center.y = geo->y + geo->height / 2;
}
}
void
regions_evacuate_output(struct output *output)
{
assert(output);
struct view *view;
struct region *region;
wl_list_for_each(view, &output->server->views, link) {
wl_list_for_each(region, &output->regions, link) {
if (view->tiled_region == region) {
if (!view->tiled_region_evacuate) {
view->tiled_region_evacuate =
xstrdup(region->name);
}
/* Prevent carrying around a dangling pointer */
view->tiled_region = NULL;
break;
}
}
}
}
void
regions_destroy(struct seat *seat, struct wl_list *regions)
{
assert(regions);
struct region *region, *region_tmp;
wl_list_for_each_safe(region, region_tmp, regions, link) {
wl_list_remove(&region->link);
zfree(region->name);
if (seat && seat->region_active == region) {
seat->region_active = NULL;
}
zfree(region);
}
}

View file

@ -20,6 +20,7 @@
#include "labwc.h"
#include "layers.h"
#include "menu/menu.h"
#include "regions.h"
#include "theme.h"
#include "view.h"
#include "workspaces.h"
@ -49,6 +50,7 @@ reload_config_and_theme(void)
menu_reconfigure(g_server);
seat_reconfigure(g_server);
regions_reconfigure(g_server);
}
static int

View file

@ -2,9 +2,11 @@
#include <assert.h>
#include <stdio.h>
#include <strings.h>
#include "common/mem.h"
#include "common/scene-helpers.h"
#include "labwc.h"
#include "menu/menu.h"
#include "regions.h"
#include "ssd.h"
#include "view.h"
#include "workspaces.h"
@ -293,7 +295,7 @@ void
view_store_natural_geometry(struct view *view)
{
assert(view);
if (view->maximized || view->tiled) {
if (view->maximized || view_is_tiled(view)) {
/* Do not overwrite the stored geometry with special cases */
return;
}
@ -341,6 +343,85 @@ view_apply_natural_geometry(struct view *view)
}
}
static void
view_apply_region_geometry(struct view *view)
{
assert(view);
assert(view->tiled_region || view->tiled_region_evacuate);
if (view->tiled_region_evacuate) {
/* View was evacuated from a destroying output */
struct output *output = view_output(view);
if (!output) {
wlr_log(WLR_INFO, "apply region geometry failed: no more ouputs");
return;
}
/* Get new output local region, may be NULL */
view->tiled_region = regions_from_name(
view->tiled_region_evacuate, output);
/* Get rid of the evacuate instruction */
zfree(view->tiled_region_evacuate);
if (!view->tiled_region) {
/* Existing region name doesn't exist in rc.xml anymore */
view_set_untiled(view);
view_apply_natural_geometry(view);
return;
}
}
/* Create a copy of the original region geometry */
struct wlr_box geo = view->tiled_region->geo;
/* Adjust for rc.gap */
struct output *output = view_output(view);
if (rc.gap && output) {
double half_gap = rc.gap / 2.0;
struct wlr_fbox offset = {
.x = half_gap,
.y = half_gap,
.width = -rc.gap,
.height = -rc.gap
};
struct wlr_box usable =
output_usable_area_in_layout_coords(output);
if (geo.x == usable.x) {
offset.x += half_gap;
offset.width -= half_gap;
}
if (geo.y == usable.y) {
offset.y += half_gap;
offset.height -= half_gap;
}
if (geo.x + geo.width == usable.x + usable.width) {
offset.width -= half_gap;
}
if (geo.y + geo.height == usable.y + usable.height) {
offset.height -= half_gap;
}
geo.x += offset.x;
geo.y += offset.y;
geo.width += offset.width;
geo.height += offset.height;
}
/* And adjust for current view */
struct border margin = ssd_get_margin(view->ssd);
geo.x += margin.left;
geo.y += margin.top;
geo.width -= margin.left + margin.right;
geo.height -= margin.top + margin.bottom;
if (view->w == geo.width && view->h == geo.height) {
/* move horizontally/vertically without changing size */
view_move(view, geo.x, geo.y);
} else {
view_move_resize(view, geo);
}
}
static void
view_apply_tiled_geometry(struct view *view, struct output *output)
{
@ -419,6 +500,8 @@ view_apply_special_geometry(struct view *view)
view_apply_maximized_geometry(view);
} else if (view->tiled) {
view_apply_tiled_geometry(view, NULL);
} else if (view->tiled_region || view->tiled_region_evacuate) {
view_apply_region_geometry(view);
} else {
return false;
}
@ -455,12 +538,23 @@ view_restore_to(struct view *view, struct wlr_box geometry)
view_move_resize(view, geometry);
}
bool
view_is_tiled(struct view *view)
{
if (!view) {
return false;
}
return view->tiled || view->tiled_region || view->tiled_region_evacuate;
}
/* Reset tiled state of view without changing geometry */
void
view_set_untiled(struct view *view)
{
assert(view);
view->tiled = VIEW_EDGE_INVALID;
view->tiled_region = NULL;
zfree(view->tiled_region_evacuate);
}
void
@ -859,6 +953,27 @@ view_snap_to_edge(struct view *view, const char *direction,
view_apply_tiled_geometry(view, output);
}
void
view_snap_to_region(struct view *view, struct region *region,
bool store_natural_geometry)
{
assert(view);
assert(region);
if (view->fullscreen) {
return;
}
if (view->maximized) {
/* Unmaximize + keep using existing natural_geometry */
view_maximize(view, false, /*store_natural_geometry*/ false);
} else if (store_natural_geometry) {
/* store current geometry as new natural_geometry */
view_store_natural_geometry(view);
}
view_set_untiled(view);
view->tiled_region = region;
view_apply_region_geometry(view);
}
const char *
view_get_string_prop(struct view *view, const char *prop)
{
@ -920,6 +1035,7 @@ view_destroy(struct view *view)
server->input_mode = LAB_INPUT_STATE_PASSTHROUGH;
server->grabbed_view = NULL;
need_cursor_update = true;
regions_hide_overlay(&server->seat);
}
if (server->focused_view == view) {
@ -931,6 +1047,10 @@ view_destroy(struct view *view)
seat_reset_pressed(&server->seat);
}
if (view->tiled_region_evacuate) {
zfree(view->tiled_region_evacuate);
}
osd_on_view_destroy(view);
undecorate(view);