mirror of
https://github.com/labwc/labwc.git
synced 2026-02-06 04:06:33 -05:00
Merge pull request #433 from Consolatis/feature/regions
Add SnapToRegion
This commit is contained in:
commit
1bcc0aa5db
21 changed files with 703 additions and 9 deletions
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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>*
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
<!--
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#include <cairo.h>
|
||||
#include <wayland-server-core.h>
|
||||
|
||||
struct wlr_scene_tree;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
76
include/regions.h
Normal 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 */
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
27
src/action.c
27
src/action.c
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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, ¤t_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(¤t_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(®ion->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;
|
||||
}
|
||||
|
|
|
|||
15
src/cursor.c
15
src/cursor.c
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ labwc_sources = files(
|
|||
'node.c',
|
||||
'osd.c',
|
||||
'output.c',
|
||||
'regions.c',
|
||||
'resistance.c',
|
||||
'seat.c',
|
||||
'server.c',
|
||||
|
|
|
|||
13
src/osd.c
13
src/osd.c
|
|
@ -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;
|
||||
|
|
|
|||
17
src/output.c
17
src/output.c
|
|
@ -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
248
src/regions.c
Normal 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(®ion->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, ®ion_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 = ®ion->geo;
|
||||
perc = ®ion->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(®ion->link);
|
||||
zfree(region->name);
|
||||
if (seat && seat->region_active == region) {
|
||||
seat->region_active = NULL;
|
||||
}
|
||||
zfree(region);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
122
src/view.c
122
src/view.c
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue