From 51727cf8f702ee2e2c8617fddcf19a4edcf2a51f Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:37:22 +0200 Subject: [PATCH 01/13] SnapToRegion: Add implementation --- include/common/graphic-helpers.h | 1 + include/regions.h | 30 +++++++++++++++++++++++++ src/regions.c | 38 ++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 include/regions.h create mode 100644 src/regions.c diff --git a/include/common/graphic-helpers.h b/include/common/graphic-helpers.h index 6ee4d2f6..c8c8a1dc 100644 --- a/include/common/graphic-helpers.h +++ b/include/common/graphic-helpers.h @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ +#include #include struct wlr_scene_tree; diff --git a/include/regions.h b/include/regions.h new file mode 100644 index 00000000..f071c64a --- /dev/null +++ b/include/regions.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __LABWC_REGIONS_H +#define __LABWC_REGIONS_H + +//FIXME: ignore that there could be more than a single seat + +struct seat; +struct view; +struct server; +struct output; +struct wl_list; +struct wlr_box; + +/* 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; +}; + +void regions_init(struct server *server, struct seat *seat); +void regions_update(struct output *output); +void regions_destroy(struct wl_list *regions); + +#endif /* __LABWC_REGIONS_H */ diff --git a/src/regions.c b/src/regions.c new file mode 100644 index 00000000..47b75d88 --- /dev/null +++ b/src/regions.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include "common/graphic-helpers.h" +#include "common/mem.h" +#include "labwc.h" +#include "regions.h" + +void +regions_init(struct server *server, struct seat *seat) +{ + /* To be filled later */ +} + +void +regions_update(struct output *output) +{ + /* To be filled later */ +} + +void +regions_destroy(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); + zfree(region); + } +} + From 43fe1383855beaa7887e9bff4539d620891382df Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:21:02 +0200 Subject: [PATCH 02/13] SnapToRegion: Add config parser --- include/config/rcxml.h | 3 ++ include/regions.h | 3 ++ src/config/rcxml.c | 73 ++++++++++++++++++++++++++++++++++++++++++ src/meson.build | 1 + src/regions.c | 6 ++++ 5 files changed, 86 insertions(+) diff --git a/include/config/rcxml.h b/include/config/rcxml.h index ed1679e4..975e6754 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -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; diff --git a/include/regions.h b/include/regions.h index f071c64a..86bd570e 100644 --- a/include/regions.h +++ b/include/regions.h @@ -23,6 +23,9 @@ struct region { } center; }; +/* Can be used as a cheap check to detect if there are any regions configured */ +bool regions_available(void); + void regions_init(struct server *server, struct seat *seat); void regions_update(struct output *output); void regions_destroy(struct wl_list *regions); diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 7627a2a9..4341bf5c 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #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 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. */ 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(&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; } diff --git a/src/meson.build b/src/meson.build index ff4bff8b..d303ac4c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -14,6 +14,7 @@ labwc_sources = files( 'node.c', 'osd.c', 'output.c', + 'regions.c', 'resistance.c', 'seat.c', 'server.c', diff --git a/src/regions.c b/src/regions.c index 47b75d88..2b8ea87a 100644 --- a/src/regions.c +++ b/src/regions.c @@ -12,6 +12,12 @@ #include "labwc.h" #include "regions.h" +bool +regions_available(void) +{ + return !wl_list_empty(&rc.regions); +} + void regions_init(struct server *server, struct seat *seat) { From 67952cd749d6d6d906e55456cfcd57c8aa6de4ad Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Wed, 6 Jul 2022 16:57:25 +0200 Subject: [PATCH 03/13] SnapToRegion: Wire up output and handle usable_area changes --- include/labwc.h | 2 ++ src/output.c | 12 +++++++++++- src/regions.c | 30 +++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/include/labwc.h b/include/labwc.h index 1d383d1f..9f728a00 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -309,6 +309,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; diff --git a/src/output.c b/src/output.c index a43bc097..b30ba643 100644 --- a/src/output.c +++ b/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,7 @@ static void output_destroy_notify(struct wl_listener *listener, void *data) { struct output *output = wl_container_of(listener, output, destroy); + regions_destroy(&output->regions); wl_list_remove(&output->link); wl_list_remove(&output->frame.link); wl_list_remove(&output->destroy.link); @@ -148,6 +150,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. @@ -446,6 +450,7 @@ void output_update_usable_area(struct output *output) { if (update_usable_area(output)) { + regions_update(output); desktop_arrange_all_views(output->server); } } @@ -457,7 +462,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(output); + } else if (layout_changed) { + regions_update(output); + } } if (usable_area_changed || layout_changed) { desktop_arrange_all_views(server); diff --git a/src/regions.c b/src/regions.c index 2b8ea87a..52d2c5d3 100644 --- a/src/regions.c +++ b/src/regions.c @@ -8,6 +8,7 @@ #include #include #include "common/graphic-helpers.h" +#include "common/list.h" #include "common/mem.h" #include "labwc.h" #include "regions.h" @@ -27,7 +28,34 @@ regions_init(struct server *server, struct seat *seat) void regions_update(struct output *output) { - /* To be filled later */ + assert(output); + + struct region *region; + struct wlr_box usable = output_usable_area_in_layout_coords(output); + + /* Initialize regions */ + if (wl_list_empty(&output->regions)) { + 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 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 From 9d3e309a22d06c9c5f8a45c6f62fde3e908edabc Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:04:21 +0200 Subject: [PATCH 04/13] SnapToRegion: Add view_snap_to_region() --- include/view.h | 3 +++ src/view.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/include/view.h b/include/view.h index 3ee49a67..b9c09793 100644 --- a/include/view.h +++ b/include/view.h @@ -50,6 +50,7 @@ struct view { bool minimized; bool maximized; uint32_t tiled; /* private, enum view_edge in src/view.c */ + struct region *tiled_region; struct wlr_output *fullscreen; /* geometry of the wlr_surface contained within the view */ @@ -134,6 +135,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); diff --git a/src/view.c b/src/view.c index c53e16a3..7e14756a 100644 --- a/src/view.c +++ b/src/view.c @@ -5,6 +5,7 @@ #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 +294,7 @@ void view_store_natural_geometry(struct view *view) { assert(view); - if (view->maximized || view->tiled) { + if (view->maximized || view->tiled || view->tiled_region) { /* Do not overwrite the stored geometry with special cases */ return; } @@ -341,6 +342,30 @@ view_apply_natural_geometry(struct view *view) } } +static void +view_apply_region_geometry(struct view *view) +{ + assert(view); + assert(view->tiled_region); + + /* Create a copy of the original region geometry */ + struct wlr_box geo = view->tiled_region->geo; + + /* 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 +444,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_apply_region_geometry(view); } else { return false; } @@ -461,6 +488,7 @@ view_set_untiled(struct view *view) { assert(view); view->tiled = VIEW_EDGE_INVALID; + view->tiled_region = NULL; } void @@ -859,6 +887,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) { From 96a591297d8369adee1b1223e1721e5553d009c6 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Wed, 6 Jul 2022 08:06:48 +0200 Subject: [PATCH 05/13] SnapToRegion: Add SnapToRegion action --- include/regions.h | 2 ++ src/action.c | 27 +++++++++++++++++++++++++++ src/regions.c | 14 ++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/include/regions.h b/include/regions.h index 86bd570e..7b9b2241 100644 --- a/include/regions.h +++ b/include/regions.h @@ -30,4 +30,6 @@ void regions_init(struct server *server, struct seat *seat); void regions_update(struct output *output); void regions_destroy(struct wl_list *regions); +struct region *regions_from_name(const char *region_name, struct output *output); + #endif /* __LABWC_REGIONS_H */ diff --git a/src/action.c b/src/action.c index c191bdbd..09992ef1 100644 --- a/src/action.c +++ b/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: diff --git a/src/regions.c b/src/regions.c index 52d2c5d3..4b299730 100644 --- a/src/regions.c +++ b/src/regions.c @@ -25,6 +25,20 @@ regions_init(struct server *server, struct seat *seat) /* To be filled later */ } +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; +} + void regions_update(struct output *output) { From 0c31886061e9e0c3b228dab1153253760661dcd1 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:26:22 +0200 Subject: [PATCH 06/13] SnapToRegion: Add regions_from_cursor() --- include/regions.h | 1 + src/regions.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/include/regions.h b/include/regions.h index 7b9b2241..5717d4f8 100644 --- a/include/regions.h +++ b/include/regions.h @@ -30,6 +30,7 @@ void regions_init(struct server *server, struct seat *seat); void regions_update(struct output *output); void regions_destroy(struct wl_list *regions); +struct region *regions_from_cursor(struct server *server); struct region *regions_from_name(const char *region_name, struct output *output); #endif /* __LABWC_REGIONS_H */ diff --git a/src/regions.c b/src/regions.c index 4b299730..e7063d95 100644 --- a/src/regions.c +++ b/src/regions.c @@ -3,6 +3,7 @@ #define _POSIX_C_SOURCE 200809L #include #include +#include #include #include #include @@ -39,6 +40,37 @@ regions_from_name(const char *region_name, struct output *output) 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_update(struct output *output) { From 7e99d8ba08c5bed2fa9e0f7d0dfda4f9267cf715 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Wed, 6 Jul 2022 07:19:28 +0200 Subject: [PATCH 07/13] SnapToRegion: Add overlay while moving and pressing a modifier --- include/labwc.h | 2 ++ include/regions.h | 2 ++ src/cursor.c | 15 +++++++++++++ src/debug.c | 4 ++-- src/desktop.c | 13 ++++++++++++ src/interactive.c | 2 ++ src/keyboard.c | 7 +++++- src/osd.c | 13 ++++++++++++ src/regions.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++- src/server.c | 2 ++ 10 files changed, 110 insertions(+), 4 deletions(-) diff --git a/include/labwc.h b/include/labwc.h index 9f728a00..230a6401 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -156,6 +156,8 @@ struct seat { struct wlr_scene_tree *icons; } drag; + struct wlr_scene_rect *region_overlay; + struct wl_client *active_client_while_inhibited; struct wl_list inputs; struct wl_listener new_input; diff --git a/include/regions.h b/include/regions.h index 5717d4f8..180d83aa 100644 --- a/include/regions.h +++ b/include/regions.h @@ -32,5 +32,7 @@ void regions_destroy(struct wl_list *regions); 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 server *server, struct seat *seat); #endif /* __LABWC_REGIONS_H */ diff --git a/src/cursor.c b/src/cursor.c index 3cfc6b1c..9b534e23 100644 --- a/src/cursor.c +++ b/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, &server->seat); + } + } } static void diff --git a/src/debug.c b/src/debug.c index 2af52ca3..9ad0910b 100644 --- a/src/debug.c +++ b/src/debug.c @@ -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); diff --git a/src/desktop.c b/src/desktop.c index 28a7518f..ce17d8d3 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -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; diff --git a/src/interactive.c b/src/interactive.c index f21c915e..bd4b87d0 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only #include "labwc.h" +#include "regions.h" #include "view.h" static int @@ -141,6 +142,7 @@ interactive_finish(struct view *view) { if (view->server->grabbed_view == view) { enum input_mode mode = view->server->input_mode; + regions_hide_overlay(view->server, &view->server->seat); view->server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; view->server->grabbed_view = NULL; if (mode == LAB_INPUT_STATE_MOVE) { diff --git a/src/keyboard.c b/src/keyboard.c index b1aca0d4..edac5ed8 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -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(server, seat); + } } } wlr_seat_keyboard_notify_modifiers(seat->seat, &wlr_keyboard->modifiers); diff --git a/src/osd.c b/src/osd.c index 582842da..b1b6b843 100644 --- a/src/osd.c +++ b/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; diff --git a/src/regions.c b/src/regions.c index e7063d95..28333466 100644 --- a/src/regions.c +++ b/src/regions.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include "common/mem.h" #include "labwc.h" #include "regions.h" +#include "view.h" bool regions_available(void) @@ -23,7 +25,19 @@ regions_available(void) void regions_init(struct server *server, struct seat *seat) { - /* To be filled later */ + assert(server); + assert(seat); + + float *color; + float solid[4] = { 0.5, 0.5, 0.7, 1 }; + float trans[4] = { 0.25, 0.25, 0.35, 0.5 }; + if (wlr_renderer_is_pixman(server->renderer)) { + color = solid; + } else { + color = trans; + } + seat->region_overlay = wlr_scene_rect_create(&server->scene->tree, 0, 0, color); + wlr_scene_node_set_enabled(&seat->region_overlay->node, false); } struct region * @@ -71,6 +85,44 @@ regions_from_cursor(struct server *server) return closest_region; } +void +regions_show_overlay(struct view *view, struct seat *seat, struct region *region) +{ + assert(view); + assert(seat); + assert(region); + static struct region *old_region; + + struct wlr_scene_rect *overlay = seat->region_overlay; + if (old_region != region) { + wlr_scene_rect_set_size(overlay, region->geo.width, region->geo.height); + wlr_scene_node_set_position(&overlay->node, region->geo.x, region->geo.y); + old_region = region; + } + if (overlay->node.parent != view->scene_tree->node.parent) { + wlr_scene_node_reparent(&overlay->node, view->scene_tree->node.parent); + wlr_scene_node_place_below(&overlay->node, &view->scene_tree->node); + } + if (!overlay->node.enabled) { + wlr_scene_node_set_enabled(&overlay->node, true); + } +} + +void +regions_hide_overlay(struct server *server, struct seat *seat) +{ + assert(server); + assert(seat); + + struct wlr_scene_rect *overlay = seat->region_overlay; + if (overlay->node.enabled) { + wlr_scene_node_set_enabled(&overlay->node, false); + } + if (overlay->node.parent != &server->scene->tree) { + wlr_scene_node_reparent(&overlay->node, &server->scene->tree); + } +} + void regions_update(struct output *output) { diff --git a/src/server.c b/src/server.c index e36c2880..e4c41e09 100644 --- a/src/server.c +++ b/src/server.c @@ -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" @@ -388,6 +389,7 @@ server_init(struct server *server) &server->output_power_manager_set_mode); layers_init(server); + regions_init(server, &server->seat); #if HAVE_XWAYLAND xwayland_server_init(server, compositor); From 25cea94a320aa624f06100942f82bd62b06ea9ae Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:05:34 +0200 Subject: [PATCH 08/13] SnapToRegion: Snap when ending a move while pressing a modifier --- src/interactive.c | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/interactive.c b/src/interactive.c index bd4b87d0..3b3746f8 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -45,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->tiled || view->tiled_region) { /* * Un-maximize and restore natural width/height. * Don't reset tiled state yet since we may want @@ -137,6 +137,27 @@ 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) { @@ -146,9 +167,11 @@ interactive_finish(struct view *view) 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 */ From 4edd67de17bae43f7e09b0210ddc45a002c77907 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Thu, 7 Jul 2022 19:05:54 +0200 Subject: [PATCH 09/13] SnapToRegion: Evacuate tiled views from destroying outputs --- include/regions.h | 1 + include/view.h | 5 +++++ src/output.c | 2 ++ src/regions.c | 20 ++++++++++++++++++++ src/view.c | 28 ++++++++++++++++++++++++++++ 5 files changed, 56 insertions(+) diff --git a/include/regions.h b/include/regions.h index 180d83aa..f583a310 100644 --- a/include/regions.h +++ b/include/regions.h @@ -28,6 +28,7 @@ bool regions_available(void); void regions_init(struct server *server, struct seat *seat); void regions_update(struct output *output); +void regions_evacuate_output(struct output *output); void regions_destroy(struct wl_list *regions); struct region *regions_from_cursor(struct server *server); diff --git a/include/view.h b/include/view.h index b9c09793..c04885cc 100644 --- a/include/view.h +++ b/include/view.h @@ -50,7 +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 or already free'd */ 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 */ diff --git a/src/output.c b/src/output.c index b30ba643..c28186ac 100644 --- a/src/output.c +++ b/src/output.c @@ -41,6 +41,7 @@ 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->regions); wl_list_remove(&output->link); wl_list_remove(&output->frame.link); @@ -307,6 +308,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; } diff --git a/src/regions.c b/src/regions.c index 28333466..35937a2e 100644 --- a/src/regions.c +++ b/src/regions.c @@ -156,6 +156,26 @@ regions_update(struct output *output) } } +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); + } + break; + } + } + } +} + void regions_destroy(struct wl_list *regions) { diff --git a/src/view.c b/src/view.c index 7e14756a..4e572b3a 100644 --- a/src/view.c +++ b/src/view.c @@ -2,6 +2,7 @@ #include #include #include +#include "common/mem.h" #include "common/scene-helpers.h" #include "labwc.h" #include "menu/menu.h" @@ -348,6 +349,29 @@ view_apply_region_geometry(struct view *view) assert(view); assert(view->tiled_region); + 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 */ + 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; @@ -980,6 +1004,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); From eb5c8cfdad57b40762d868edd45c05ecd4615175 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Tue, 13 Sep 2022 08:15:33 +0200 Subject: [PATCH 10/13] SnapToRegion: Add dynamic overlay Either uses a half transparent single rect if running hardware accelerated or uses a solid struct multirect outline if not. --- include/labwc.h | 6 ++- include/regions.h | 16 +++++--- src/config/rcxml.c | 2 +- src/cursor.c | 2 +- src/interactive.c | 2 +- src/keyboard.c | 2 +- src/output.c | 2 +- src/regions.c | 98 +++++++++++++++++++++++++++++++--------------- src/server.c | 2 - src/view.c | 1 + 10 files changed, 88 insertions(+), 45 deletions(-) diff --git a/include/labwc.h b/include/labwc.h index 230a6401..596bb0d7 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -47,6 +47,7 @@ #include "cursor.h" #include "config/keybind.h" #include "config/rcxml.h" +#include "regions.h" #if HAVE_NLS #include #include @@ -156,7 +157,9 @@ struct seat { struct wlr_scene_tree *icons; } drag; - struct wlr_scene_rect *region_overlay; + /* 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; @@ -312,6 +315,7 @@ struct output { struct wlr_box usable_area; struct wl_list regions; /* struct region.link */ + struct wlr_box regions_usable_area; struct lab_data_buffer *osd_buffer; diff --git a/include/regions.h b/include/regions.h index f583a310..87806123 100644 --- a/include/regions.h +++ b/include/regions.h @@ -2,14 +2,13 @@ #ifndef __LABWC_REGIONS_H #define __LABWC_REGIONS_H -//FIXME: ignore that there could be more than a single seat - 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 { @@ -23,17 +22,24 @@ struct region { } 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); -void regions_init(struct server *server, struct seat *seat); void regions_update(struct output *output); void regions_evacuate_output(struct output *output); -void regions_destroy(struct wl_list *regions); +void regions_destroy(struct seat *seat, struct wl_list *regions); 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 server *server, struct seat *seat); +void regions_hide_overlay(struct seat *seat); #endif /* __LABWC_REGIONS_H */ diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 4341bf5c..068e6d3c 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -868,7 +868,7 @@ rcxml_finish(void) zfree(w); } - regions_destroy(&rc.regions); + regions_destroy(NULL, &rc.regions); /* Reset state vars for starting fresh when Reload is triggered */ current_keybind = NULL; diff --git a/src/cursor.c b/src/cursor.c index 9b534e23..9be1e474 100644 --- a/src/cursor.c +++ b/src/cursor.c @@ -198,7 +198,7 @@ process_cursor_move(struct server *server, uint32_t time) if (region) { regions_show_overlay(view, &server->seat, region); } else { - regions_hide_overlay(server, &server->seat); + regions_hide_overlay(&server->seat); } } } diff --git a/src/interactive.c b/src/interactive.c index 3b3746f8..f1007441 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -163,7 +163,7 @@ interactive_finish(struct view *view) { if (view->server->grabbed_view == view) { enum input_mode mode = view->server->input_mode; - regions_hide_overlay(view->server, &view->server->seat); + 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) { diff --git a/src/keyboard.c b/src/keyboard.c index edac5ed8..39414cd8 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -71,7 +71,7 @@ keyboard_modifiers_notify(struct wl_listener *listener, void *data) workspaces_osd_hide(seat); } if (server->grabbed_view) { - regions_hide_overlay(server, seat); + regions_hide_overlay(seat); } } } diff --git a/src/output.c b/src/output.c index c28186ac..fa258ba9 100644 --- a/src/output.c +++ b/src/output.c @@ -42,7 +42,7 @@ 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->regions); + regions_destroy(&output->server->seat, &output->regions); wl_list_remove(&output->link); wl_list_remove(&output->frame.link); wl_list_remove(&output->destroy.link); diff --git a/src/regions.c b/src/regions.c index 35937a2e..727a78c3 100644 --- a/src/regions.c +++ b/src/regions.c @@ -22,22 +22,30 @@ regions_available(void) return !wl_list_empty(&rc.regions); } -void -regions_init(struct server *server, struct seat *seat) +static void +overlay_create(struct seat *seat) { - assert(server); - assert(seat); + assert(!seat->region_overlay.tree); - float *color; - float solid[4] = { 0.5, 0.5, 0.7, 1 }; - float trans[4] = { 0.25, 0.25, 0.35, 0.5 }; - if (wlr_renderer_is_pixman(server->renderer)) { - color = solid; + 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 { - color = trans; + /* 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); } - seat->region_overlay = wlr_scene_rect_create(&server->scene->tree, 0, 0, color); - wlr_scene_node_set_enabled(&seat->region_overlay->node, false); } struct region * @@ -91,36 +99,53 @@ regions_show_overlay(struct view *view, struct seat *seat, struct region *region assert(view); assert(seat); assert(region); - static struct region *old_region; - struct wlr_scene_rect *overlay = seat->region_overlay; - if (old_region != region) { - wlr_scene_rect_set_size(overlay, region->geo.width, region->geo.height); - wlr_scene_node_set_position(&overlay->node, region->geo.x, region->geo.y); - old_region = region; + /* Don't show active region */ + if (seat->region_active == region) { + return; } - if (overlay->node.parent != view->scene_tree->node.parent) { - wlr_scene_node_reparent(&overlay->node, view->scene_tree->node.parent); - wlr_scene_node_place_below(&overlay->node, &view->scene_tree->node); + + if (!seat->region_overlay.tree) { + overlay_create(seat); } - if (!overlay->node.enabled) { - wlr_scene_node_set_enabled(&overlay->node, true); + + /* 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 server *server, struct seat *seat) +regions_hide_overlay(struct seat *seat) { - assert(server); assert(seat); + if (!seat->region_active) { + return; + } - struct wlr_scene_rect *overlay = seat->region_overlay; - if (overlay->node.enabled) { - wlr_scene_node_set_enabled(&overlay->node, false); - } - if (overlay->node.parent != &server->scene->tree) { - wlr_scene_node_reparent(&overlay->node, &server->scene->tree); + 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 @@ -142,6 +167,12 @@ regions_update(struct output *output) } } + if (wlr_box_equal(&output->regions_usable_area, &usable)) { + /* Nothing changed */ + return; + } + output->regions_usable_area = usable; + /* Update regions */ struct wlr_box *perc, *geo; wl_list_for_each(region, &output->regions, link) { @@ -177,13 +208,16 @@ regions_evacuate_output(struct output *output) } void -regions_destroy(struct wl_list *regions) +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); } } diff --git a/src/server.c b/src/server.c index e4c41e09..e36c2880 100644 --- a/src/server.c +++ b/src/server.c @@ -20,7 +20,6 @@ #include "labwc.h" #include "layers.h" #include "menu/menu.h" -#include "regions.h" #include "theme.h" #include "view.h" #include "workspaces.h" @@ -389,7 +388,6 @@ server_init(struct server *server) &server->output_power_manager_set_mode); layers_init(server); - regions_init(server, &server->seat); #if HAVE_XWAYLAND xwayland_server_init(server, compositor); diff --git a/src/view.c b/src/view.c index 4e572b3a..d8364ab6 100644 --- a/src/view.c +++ b/src/view.c @@ -993,6 +993,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) { From 550e40b56bf72cfdd3ef8317fb94ef12e9f6f775 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Fri, 16 Sep 2022 06:08:28 +0200 Subject: [PATCH 11/13] SnapToRegion: Respect core.gap setting --- src/view.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/view.c b/src/view.c index d8364ab6..0208ec58 100644 --- a/src/view.c +++ b/src/view.c @@ -375,6 +375,38 @@ view_apply_region_geometry(struct view *view) /* 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; From 07ee56176de415646e52206d45cc61c713f6d012 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Sun, 1 Jan 2023 18:12:20 +0100 Subject: [PATCH 12/13] SnapToRegion: Allow for live config updates --- include/labwc.h | 1 - include/regions.h | 33 +++++++++++++++++++++++++- include/view.h | 3 ++- src/interactive.c | 2 +- src/output.c | 9 ++++--- src/regions.c | 60 +++++++++++++++++++++++++++++++++-------------- src/server.c | 2 ++ src/view.c | 18 ++++++++++---- 8 files changed, 99 insertions(+), 29 deletions(-) diff --git a/include/labwc.h b/include/labwc.h index 596bb0d7..6a68514e 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -315,7 +315,6 @@ struct output { struct wlr_box usable_area; struct wl_list regions; /* struct region.link */ - struct wlr_box regions_usable_area; struct lab_data_buffer *osd_buffer; diff --git a/include/regions.h b/include/regions.h index 87806123..e77cd3f0 100644 --- a/include/regions.h +++ b/include/regions.h @@ -33,12 +33,43 @@ struct region_overlay { /* Can be used as a cheap check to detect if there are any regions configured */ bool regions_available(void); -void regions_update(struct output *output); +/** + * 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); diff --git a/include/view.h b/include/view.h index c04885cc..8cf3859f 100644 --- a/include/view.h +++ b/include/view.h @@ -51,7 +51,7 @@ struct view { bool maximized; uint32_t tiled; /* private, enum view_edge in src/view.c */ - /* Pointer to an output owned struct region, may be NULL or already free'd */ + /* 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; @@ -132,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); diff --git a/src/interactive.c b/src/interactive.c index f1007441..83731d52 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -45,7 +45,7 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges) */ return; } - if (view->maximized || view->tiled || view->tiled_region) { + if (view->maximized || view_is_tiled(view)) { /* * Un-maximize and restore natural width/height. * Don't reset tiled state yet since we may want diff --git a/src/output.c b/src/output.c index fa258ba9..991a1653 100644 --- a/src/output.c +++ b/src/output.c @@ -212,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); } @@ -452,7 +455,7 @@ void output_update_usable_area(struct output *output) { if (update_usable_area(output)) { - regions_update(output); + regions_update_geometry(output); desktop_arrange_all_views(output->server); } } @@ -466,9 +469,9 @@ output_update_all_usable_areas(struct server *server, bool layout_changed) wl_list_for_each(output, &server->outputs, link) { if (update_usable_area(output)) { usable_area_changed = true; - regions_update(output); + regions_update_geometry(output); } else if (layout_changed) { - regions_update(output); + regions_update_geometry(output); } } if (usable_area_changed || layout_changed) { diff --git a/src/regions.c b/src/regions.c index 727a78c3..7ca2d9d2 100644 --- a/src/regions.c +++ b/src/regions.c @@ -149,30 +149,52 @@ regions_hide_overlay(struct seat *seat) } void -regions_update(struct output *output) +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); - /* Initialize regions */ - if (wl_list_empty(&output->regions)) { - 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); - } - } - - if (wlr_box_equal(&output->regions_usable_area, &usable)) { - /* Nothing changed */ - return; - } - output->regions_usable_area = usable; - /* Update regions */ struct wlr_box *perc, *geo; wl_list_for_each(region, &output->regions, link) { @@ -201,6 +223,8 @@ regions_evacuate_output(struct output *output) view->tiled_region_evacuate = xstrdup(region->name); } + /* Prevent carrying around a dangling pointer */ + view->tiled_region = NULL; break; } } diff --git a/src/server.c b/src/server.c index e36c2880..d8a816df 100644 --- a/src/server.c +++ b/src/server.c @@ -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 diff --git a/src/view.c b/src/view.c index 0208ec58..0e620b2a 100644 --- a/src/view.c +++ b/src/view.c @@ -295,7 +295,7 @@ void view_store_natural_geometry(struct view *view) { assert(view); - if (view->maximized || view->tiled || view->tiled_region) { + if (view->maximized || view_is_tiled(view)) { /* Do not overwrite the stored geometry with special cases */ return; } @@ -347,7 +347,7 @@ static void view_apply_region_geometry(struct view *view) { assert(view); - assert(view->tiled_region); + assert(view->tiled_region || view->tiled_region_evacuate); if (view->tiled_region_evacuate) { /* View was evacuated from a destroying output */ @@ -357,7 +357,7 @@ view_apply_region_geometry(struct view *view) return; } - /* Get new output local region */ + /* Get new output local region, may be NULL */ view->tiled_region = regions_from_name( view->tiled_region_evacuate, output); @@ -500,7 +500,7 @@ 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) { + } else if (view->tiled_region || view->tiled_region_evacuate) { view_apply_region_geometry(view); } else { return false; @@ -538,6 +538,15 @@ 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) @@ -545,6 +554,7 @@ view_set_untiled(struct view *view) assert(view); view->tiled = VIEW_EDGE_INVALID; view->tiled_region = NULL; + zfree(view->tiled_region_evacuate); } void From 111f48b4852d2b1bb4ad8223a8a0a27d71941bb6 Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Sat, 7 Jan 2023 03:30:10 +0100 Subject: [PATCH 13/13] SnapToRegion: Add documentation --- docs/labwc-actions.5.scd | 4 ++++ docs/labwc-config.5.scd | 10 ++++++++++ docs/rc.xml.all | 27 +++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index 9cc2eceb..10c3b542 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -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". +** + Resize and move active window according to the given region. + See labwc-config(5) for further information on how to define regions. + ** Cycle focus to next window. diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index e00debe1..070e4c04 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -104,6 +104,16 @@ The rest of this man page describes configuration options. ** [yes|no] Maximize window if Move operation ends on the top edge. Default is yes. +## REGIONS + +** + 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 ** diff --git a/docs/rc.xml.all b/docs/rc.xml.all index 52552b72..2c86fb7d 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -81,6 +81,21 @@ + + + +