labwc/src/view.c

2454 lines
58 KiB
C
Raw Normal View History

2021-09-24 21:45:48 +01:00
// SPDX-License-Identifier: GPL-2.0-only
#include <assert.h>
#include <stdio.h>
#include <strings.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_security_context_v1.h>
#include "common/macros.h"
#include "common/match.h"
#include "common/mem.h"
#include "common/scene-helpers.h"
#include "input/keyboard.h"
2021-03-20 14:36:40 +00:00
#include "labwc.h"
#include "menu/menu.h"
#include "osd.h"
#include "placement.h"
#include "regions.h"
#include "resize-indicator.h"
#include "snap-constraints.h"
#include "snap.h"
2023-01-01 19:35:07 +01:00
#include "ssd.h"
#include "view.h"
#include "window-rules.h"
2022-06-15 02:02:50 +02:00
#include "workspaces.h"
#include "xwayland.h"
#if HAVE_XWAYLAND
#include <wlr/xwayland.h>
#endif
#define LAB_FALLBACK_WIDTH 640
#define LAB_FALLBACK_HEIGHT 480
struct view *
view_from_wlr_surface(struct wlr_surface *surface)
{
assert(surface);
/*
* TODO:
* - find a way to get rid of xdg/xwayland-specific stuff
* - look up root/toplevel surface if passed a subsurface?
*/
struct wlr_xdg_surface *xdg_surface =
wlr_xdg_surface_try_from_wlr_surface(surface);
if (xdg_surface) {
return xdg_surface->data;
}
#if HAVE_XWAYLAND
struct wlr_xwayland_surface *xsurface =
wlr_xwayland_surface_try_from_wlr_surface(surface);
if (xsurface) {
return xsurface->data;
}
#endif
return NULL;
}
static const struct wlr_security_context_v1_state *
security_context_from_view(struct view *view)
{
if (view && view->surface && view->surface->resource) {
struct wl_client *client = wl_resource_get_client(view->surface->resource);
return wlr_security_context_manager_v1_lookup_client(
view->server->security_context_manager_v1, client);
}
return NULL;
}
struct view_query *
view_query_create(void)
{
struct view_query *query = znew(*query);
query->window_type = -1;
return query;
}
void
view_query_free(struct view_query *query)
{
wl_list_remove(&query->link);
free(query->identifier);
free(query->title);
free(query->sandbox_engine);
free(query->sandbox_app_id);
free(query);
}
bool
view_matches_query(struct view *view, struct view_query *query)
{
bool match = true;
bool empty = true;
const char *identifier = view_get_string_prop(view, "app_id");
if (match && query->identifier) {
empty = false;
match &= identifier && match_glob(query->identifier, identifier);
}
const char *title = view_get_string_prop(view, "title");
if (match && query->title) {
empty = false;
match &= title && match_glob(query->title, title);
}
if (match && query->window_type >= 0) {
empty = false;
match &= view_contains_window_type(view, query->window_type);
}
if (match && query->sandbox_engine) {
const struct wlr_security_context_v1_state *security_context =
security_context_from_view(view);
empty = false;
match &= security_context && security_context->sandbox_engine
&& match_glob(query->sandbox_engine, security_context->sandbox_engine);
}
if (match && query->sandbox_app_id) {
const struct wlr_security_context_v1_state *security_context =
security_context_from_view(view);
empty = false;
match &= security_context && security_context->app_id
&& match_glob(query->sandbox_app_id, security_context->app_id);
}
return !empty && match;
}
static bool
matches_criteria(struct view *view, enum lab_view_criteria criteria)
{
if (!view_is_focusable(view)) {
return false;
}
if (criteria & LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) {
/*
* Always-on-top views are always on the current desktop and are
* special in that they live in a different tree.
*/
struct server *server = view->server;
if (view->scene_tree->node.parent != server->workspace_current->tree
&& !view_is_always_on_top(view)) {
return false;
}
}
if (criteria & LAB_VIEW_CRITERIA_FULLSCREEN) {
if (!view->fullscreen) {
return false;
}
}
2023-11-21 03:08:04 +01:00
if (criteria & LAB_VIEW_CRITERIA_ALWAYS_ON_TOP) {
if (!view_is_always_on_top(view)) {
return false;
}
}
if (criteria & LAB_VIEW_CRITERIA_ROOT_TOPLEVEL) {
if (view != view_get_root(view)) {
return false;
}
}
if (criteria & LAB_VIEW_CRITERIA_NO_ALWAYS_ON_TOP) {
if (view_is_always_on_top(view)) {
return false;
}
}
if (criteria & LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER) {
if (window_rules_get_property(view, "skipWindowSwitcher") == LAB_PROP_TRUE) {
return false;
}
}
return true;
}
struct view *
view_next(struct wl_list *head, struct view *view, enum lab_view_criteria criteria)
{
assert(head);
struct wl_list *elm = view ? &view->link : head;
for (elm = elm->next; elm != head; elm = elm->next) {
view = wl_container_of(elm, view, link);
if (matches_criteria(view, criteria)) {
return view;
}
}
return NULL;
}
struct view *
view_next_no_head_stop(struct wl_list *head, struct view *from,
enum lab_view_criteria criteria)
{
assert(head);
struct wl_list *elm = from ? &from->link : head;
struct wl_list *end = elm;
for (elm = elm->next; elm != end; elm = elm->next) {
if (elm == head) {
continue;
}
struct view *view = wl_container_of(elm, view, link);
if (matches_criteria(view, criteria)) {
return view;
}
}
return from;
}
struct view *
view_prev_no_head_stop(struct wl_list *head, struct view *from,
enum lab_view_criteria criteria)
{
assert(head);
struct wl_list *elm = from ? &from->link : head;
struct wl_list *end = elm;
for (elm = elm->prev; elm != end; elm = elm->prev) {
if (elm == head) {
continue;
}
struct view *view = wl_container_of(elm, view, link);
if (matches_criteria(view, criteria)) {
return view;
}
}
return from;
}
void
view_array_append(struct server *server, struct wl_array *views,
enum lab_view_criteria criteria)
{
struct view *view;
for_each_view(view, &server->views, criteria) {
struct view **entry = wl_array_add(views, sizeof(*entry));
if (!entry) {
wlr_log(WLR_ERROR, "wl_array_add(): out of memory");
continue;
}
*entry = view;
}
}
enum view_wants_focus
view_wants_focus(struct view *view)
{
assert(view);
if (view->impl->wants_focus) {
return view->impl->wants_focus(view);
}
return VIEW_WANTS_FOCUS_ALWAYS;
}
bool
view_contains_window_type(struct view *view, enum window_type window_type)
{
assert(view);
if (view->impl->contains_window_type) {
return view->impl->contains_window_type(view, window_type);
}
return false;
}
bool
view_is_focusable(struct view *view)
{
assert(view);
if (!view->surface) {
return false;
}
if (view_wants_focus(view) != VIEW_WANTS_FOCUS_ALWAYS) {
return false;
}
return (view->mapped || view->minimized);
}
/**
* All view_apply_xxx_geometry() functions must *not* modify
* any state besides repositioning or resizing the view.
*
* They may be called repeatably during output layout changes.
*/
enum view_edge
view_edge_invert(enum view_edge edge)
{
switch (edge) {
case VIEW_EDGE_LEFT:
return VIEW_EDGE_RIGHT;
case VIEW_EDGE_RIGHT:
return VIEW_EDGE_LEFT;
case VIEW_EDGE_UP:
return VIEW_EDGE_DOWN;
case VIEW_EDGE_DOWN:
return VIEW_EDGE_UP;
case VIEW_EDGE_CENTER:
case VIEW_EDGE_INVALID:
default:
return VIEW_EDGE_INVALID;
}
}
static struct wlr_box
view_get_edge_snap_box(struct view *view, struct output *output,
enum view_edge edge)
{
2023-08-05 23:53:01 +02:00
struct wlr_box usable = output_usable_area_scaled(output);
int x_offset = edge == VIEW_EDGE_RIGHT
? (usable.width + rc.gap) / 2 : rc.gap;
int y_offset = edge == VIEW_EDGE_DOWN
? (usable.height + rc.gap) / 2 : rc.gap;
int base_width, base_height;
switch (edge) {
case VIEW_EDGE_LEFT:
case VIEW_EDGE_RIGHT:
base_width = (usable.width - 3 * rc.gap) / 2;
base_height = usable.height - 2 * rc.gap;
break;
case VIEW_EDGE_UP:
case VIEW_EDGE_DOWN:
base_width = usable.width - 2 * rc.gap;
base_height = (usable.height - 3 * rc.gap) / 2;
break;
default:
case VIEW_EDGE_CENTER:
base_width = usable.width - 2 * rc.gap;
base_height = usable.height - 2 * rc.gap;
break;
}
struct border margin = ssd_get_margin(view->ssd);
struct wlr_box dst = {
.x = x_offset + usable.x + margin.left,
.y = y_offset + usable.y + margin.top,
.width = base_width - margin.left - margin.right,
.height = base_height - margin.top - margin.bottom,
};
return dst;
}
static bool
view_discover_output(struct view *view, struct wlr_box *geometry)
{
assert(view);
2023-02-28 11:46:48 -05:00
assert(!view->fullscreen);
if (!geometry) {
geometry = &view->current;
}
struct output *output =
output_nearest_to(view->server,
geometry->x + geometry->width / 2,
geometry->y + geometry->height / 2);
if (output && output != view->output) {
view->output = output;
return true;
}
return false;
}
static void
set_adaptive_sync_fullscreen(struct view *view)
{
if (rc.adaptive_sync != LAB_ADAPTIVE_SYNC_FULLSCREEN) {
return;
}
/* Enable adaptive sync if view is fullscreen */
output_enable_adaptive_sync(view->output->wlr_output, view->fullscreen);
wlr_output_commit(view->output->wlr_output);
}
void
view_set_activated(struct view *view, bool activated)
{
assert(view);
ssd_set_active(view->ssd, activated);
if (view->impl->set_activated) {
view->impl->set_activated(view, activated);
}
2023-02-01 09:27:25 +01:00
if (view->toplevel.handle) {
wlr_foreign_toplevel_handle_v1_set_activated(
2023-02-01 09:27:25 +01:00
view->toplevel.handle, activated);
}
if (rc.kb_layout_per_window) {
if (!activated) {
/* Store configured keyboard layout per view */
view->keyboard_layout =
view->server->seat.keyboard_group->keyboard.modifiers.group;
} else {
/* Switch to previously stored keyboard layout */
keyboard_update_layout(&view->server->seat, view->keyboard_layout);
}
}
set_adaptive_sync_fullscreen(view);
}
2023-02-28 11:46:48 -05:00
void
view_set_output(struct view *view, struct output *output)
{
assert(view);
assert(!view->fullscreen);
if (!output_is_usable(output)) {
wlr_log(WLR_ERROR, "invalid output set for view");
return;
}
view->output = output;
}
2021-12-23 12:24:24 +01:00
void
view_close(struct view *view)
{
assert(view);
2021-12-23 12:24:24 +01:00
if (view->impl->close) {
view->impl->close(view);
}
}
static void
view_update_outputs(struct view *view)
{
struct output *output;
struct wlr_output_layout *layout = view->server->output_layout;
uint64_t new_outputs = 0;
wl_list_for_each(output, &view->server->outputs, link) {
if (output_is_usable(output) && wlr_output_layout_intersects(
layout, output->wlr_output, &view->current)) {
new_outputs |= (1ull << output->scene_output->index);
}
}
if (new_outputs != view->outputs) {
view->outputs = new_outputs;
if (view->toplevel.handle) {
foreign_toplevel_update_outputs(view);
}
desktop_update_top_layer_visiblity(view->server);
}
}
bool
view_on_output(struct view *view, struct output *output)
{
assert(view);
assert(output);
return output->scene_output
&& (view->outputs & (1ull << output->scene_output->index));
}
void
view_move(struct view *view, int x, int y)
{
assert(view);
view_move_resize(view, (struct wlr_box){
.x = x, .y = y,
.width = view->pending.width,
.height = view->pending.height
});
}
void
view_moved(struct view *view)
{
assert(view);
wlr_scene_node_set_position(&view->scene_tree->node,
view->current.x, view->current.y);
/*
* Only floating views change output when moved. Non-floating
* views (maximized/tiled/fullscreen) are tied to a particular
* output when they enter that state.
*/
if (view_is_floating(view)) {
view_discover_output(view, NULL);
}
view_update_outputs(view);
ssd_update_geometry(view->ssd);
cursor_update_focus(view->server);
2023-08-17 18:59:29 +02:00
if (rc.resize_indicator && view->server->grabbed_view == view) {
resize_indicator_update(view);
}
}
2020-12-23 18:52:46 +00:00
void
view_move_resize(struct view *view, struct wlr_box geo)
2020-12-23 18:52:46 +00:00
{
assert(view);
if (view->impl->configure) {
view->impl->configure(view, geo);
}
2020-12-23 18:52:46 +00:00
}
2023-06-27 21:20:04 +03:00
void
view_resize_relative(struct view *view, int left, int right, int top, int bottom)
{
assert(view);
if (view->fullscreen || view->maximized != VIEW_AXIS_NONE) {
return;
}
view_set_shade(view, false);
2023-06-27 21:20:04 +03:00
struct wlr_box newgeo = view->pending;
newgeo.x -= left;
newgeo.width += left + right;
newgeo.y -= top;
newgeo.height += top + bottom;
view_move_resize(view, newgeo);
view_set_untiled(view);
2023-06-27 21:20:04 +03:00
}
void
view_move_relative(struct view *view, int x, int y)
{
assert(view);
if (view->fullscreen) {
return;
}
view_maximize(view, VIEW_AXIS_NONE, /*store_natural_geometry*/ false);
if (view_is_tiled(view)) {
view_set_untiled(view);
view_restore_to(view, view->natural_geometry);
}
view_move(view, view->pending.x + x, view->pending.y + y);
}
2023-10-14 14:57:44 +02:00
void
view_move_to_cursor(struct view *view)
{
assert(view);
struct output *pending_output = output_nearest_to_cursor(view->server);
if (!output_is_usable(pending_output)) {
return;
}
view_set_fullscreen(view, false);
view_maximize(view, VIEW_AXIS_NONE, /*store_natural_geometry*/ false);
2023-10-14 14:57:44 +02:00
if (view_is_tiled(view)) {
view_set_untiled(view);
view_restore_to(view, view->natural_geometry);
}
struct border margin = ssd_thickness(view);
struct wlr_box geo = view->pending;
geo.width += margin.left + margin.right;
geo.height += margin.top + margin.bottom;
int x = view->server->seat.cursor->x - (geo.width / 2);
int y = view->server->seat.cursor->y - (geo.height / 2);
struct wlr_box usable = output_usable_area_in_layout_coords(pending_output);
/* Limit usable region to account for gap */
usable.x += rc.gap;
usable.y += rc.gap;
usable.width -= 2 * rc.gap;
usable.height -= 2 * rc.gap;
2023-10-14 14:57:44 +02:00
if (x + geo.width > usable.x + usable.width) {
x = usable.x + usable.width - geo.width;
}
x = MAX(x, usable.x) + margin.left;
2023-10-14 14:57:44 +02:00
if (y + geo.height > usable.y + usable.height) {
y = usable.y + usable.height - geo.height;
}
y = MAX(y, usable.y) + margin.top;
2023-10-14 14:57:44 +02:00
view_move(view, x, y);
}
struct view_size_hints
view_get_size_hints(struct view *view)
{
assert(view);
if (view->impl->get_size_hints) {
return view->impl->get_size_hints(view);
}
return (struct view_size_hints){0};
}
static void
substitute_nonzero(int *a, int *b)
{
if (!(*a)) {
*a = *b;
} else if (!(*b)) {
*b = *a;
}
}
static int
round_to_increment(int val, int base, int inc)
{
if (base < 0 || inc <= 0) {
return val;
}
return base + (val - base + inc / 2) / inc * inc;
}
void
view_adjust_size(struct view *view, int *w, int *h)
{
assert(view);
struct view_size_hints hints = view_get_size_hints(view);
/*
* "If a base size is not provided, the minimum size is to be
* used in its place and vice versa." (ICCCM 4.1.2.3)
*/
substitute_nonzero(&hints.min_width, &hints.base_width);
substitute_nonzero(&hints.min_height, &hints.base_height);
/*
* Snap width/height to requested size increments (if any).
* Typically, terminal emulators use these to make sure that the
* terminal is resized to a width/height evenly divisible by the
* cell (character) size.
*/
*w = round_to_increment(*w, hints.base_width, hints.width_inc);
*h = round_to_increment(*h, hints.base_height, hints.height_inc);
/*
* If a minimum width/height was not set, then use default.
* This is currently always the case for xdg-shell views.
*/
if (hints.min_width < 1) {
hints.min_width = LAB_MIN_VIEW_WIDTH;
}
if (hints.min_height < 1) {
hints.min_height = LAB_MIN_VIEW_HEIGHT;
}
*w = MAX(*w, hints.min_width);
*h = MAX(*h, hints.min_height);
}
static void
_minimize(struct view *view, bool minimized)
2020-09-08 20:51:33 +01:00
{
assert(view);
2021-08-05 13:00:34 +01:00
if (view->minimized == minimized) {
2020-09-08 20:51:33 +01:00
return;
}
2023-02-01 09:27:25 +01:00
if (view->toplevel.handle) {
2021-11-26 18:49:44 +00:00
wlr_foreign_toplevel_handle_v1_set_minimized(
2023-02-01 09:27:25 +01:00
view->toplevel.handle, minimized);
2021-08-05 13:00:34 +01:00
}
if (view->impl->minimize) {
view->impl->minimize(view, minimized);
}
2021-08-05 13:00:34 +01:00
view->minimized = minimized;
if (minimized) {
view->impl->unmap(view, /* client_request */ false);
2021-08-05 13:00:34 +01:00
} else {
view->impl->map(view);
}
2020-09-08 20:51:33 +01:00
}
2020-09-29 20:48:50 +01:00
static void
minimize_sub_views(struct view *view, bool minimized)
{
struct view **child;
struct wl_array children;
wl_array_init(&children);
view_append_children(view, &children);
wl_array_for_each(child, &children) {
_minimize(*child, minimized);
minimize_sub_views(*child, minimized);
}
wl_array_release(&children);
}
/*
* Minimize the whole view-hierarchy from top to bottom regardless of which one
* in the hierarchy requested the minimize. For example, if an 'About' or
* 'Open File' dialog is minimized, its toplevel is minimized also. And vice
* versa.
*/
void
view_minimize(struct view *view, bool minimized)
{
assert(view);
/*
* Minimize the root window first because some xwayland clients send a
* request-unmap to sub-windows at this point (for example gimp and its
* 'open file' dialog), so it saves trying to unmap them twice
*/
struct view *root = view_get_root(view);
_minimize(root, minimized);
minimize_sub_views(root, minimized);
/* Enable top-layer when full-screen views are minimized */
if (view->fullscreen && view->output) {
desktop_update_top_layer_visiblity(view->server);
}
}
bool
view_compute_centered_position(struct view *view, const struct wlr_box *ref,
int w, int h, int *x, int *y)
2021-07-09 21:39:20 +01:00
{
assert(view);
if (w <= 0 || h <= 0) {
wlr_log(WLR_ERROR, "view has empty geometry, not centering");
return false;
}
if (!output_is_usable(view->output)) {
wlr_log(WLR_ERROR, "view has no output, not centering");
return false;
2021-07-09 21:39:20 +01:00
}
struct border margin = ssd_get_margin(view->ssd);
struct wlr_box usable = output_usable_area_in_layout_coords(view->output);
int width = w + margin.left + margin.right;
int height = h + margin.top + margin.bottom;
/* If reference box is NULL then center to usable area */
if (!ref) {
ref = &usable;
}
*x = ref->x + (ref->width - width) / 2;
*y = ref->y + (ref->height - height) / 2;
/* Fit the view within the usable area */
2023-02-19 09:56:58 -05:00
if (*x < usable.x) {
*x = usable.x;
} else if (*x + width > usable.x + usable.width) {
*x = usable.x + usable.width - width;
}
2023-02-19 09:56:58 -05:00
if (*y < usable.y) {
*y = usable.y;
} else if (*y + height > usable.y + usable.height) {
*y = usable.y + usable.height - height;
}
*x += margin.left;
*y += margin.top;
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
return true;
}
static bool
adjust_floating_geometry(struct view *view, struct wlr_box *geometry,
bool midpoint_visibility)
{
assert(view);
if (!output_is_usable(view->output)) {
wlr_log(WLR_ERROR, "view has no output, not positioning");
return false;
}
/* Avoid moving panels out of their own reserved area ("strut") */
if (window_rules_get_property(view, "fixedPosition") == LAB_PROP_TRUE
|| view_has_strut_partial(view)) {
return false;
}
bool adjusted = false;
bool onscreen = false;
if (wlr_output_layout_intersects(view->server->output_layout,
view->output->wlr_output, geometry)) {
/* Always make sure the titlebar starts within the usable area */
struct border margin = ssd_get_margin(view->ssd);
struct wlr_box usable =
output_usable_area_in_layout_coords(view->output);
if (geometry->x < usable.x + margin.left) {
geometry->x = usable.x + margin.left;
adjusted = true;
}
if (geometry->y < usable.y + margin.top) {
geometry->y = usable.y + margin.top;
adjusted = true;
}
if (!midpoint_visibility) {
/*
* If midpoint visibility is not required, the view is
* on screen if at least one pixel is visible.
*/
onscreen = true;
} else {
/* Otherwise, make sure the midpoint is on screen */
int mx = geometry->x + geometry->width / 2;
int my = geometry->y + geometry->height / 2;
onscreen = mx <= usable.x + usable.width &&
my <= usable.y + usable.height;
}
}
if (onscreen) {
return adjusted;
}
/* Reposition offscreen automatically if configured to do so */
if (rc.placement_policy == LAB_PLACE_AUTOMATIC) {
if (placement_find_best(view, geometry)) {
return true;
}
}
/* If automatic placement failed or was not enabled, just center */
return view_compute_centered_position(view, NULL,
geometry->width, geometry->height,
&geometry->x, &geometry->y);
}
static void
set_fallback_geometry(struct view *view)
{
view->natural_geometry.width = LAB_FALLBACK_WIDTH;
view->natural_geometry.height = LAB_FALLBACK_HEIGHT;
view_compute_centered_position(view, NULL,
view->natural_geometry.width,
view->natural_geometry.height,
&view->natural_geometry.x,
&view->natural_geometry.y);
}
#undef LAB_FALLBACK_WIDTH
#undef LAB_FALLBACK_HEIGHT
void
view_store_natural_geometry(struct view *view)
{
assert(view);
if (!view_is_floating(view)) {
/* Do not overwrite the stored geometry with special cases */
return;
}
/**
* If an application was started maximized or fullscreened, its
* natural_geometry width/height may still be zero (or very small
* values) in which case we set some fallback values. This is the case
* with foot and some Qt/Tk applications.
*/
if (view->pending.width < LAB_MIN_VIEW_WIDTH
|| view->pending.height < LAB_MIN_VIEW_HEIGHT) {
set_fallback_geometry(view);
} else {
view->natural_geometry = view->pending;
}
}
int
view_effective_height(struct view *view, bool use_pending)
{
assert(view);
if (view->shaded) {
return 0;
}
return use_pending ? view->pending.height : view->current.height;
}
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
void
view_center(struct view *view, const struct wlr_box *ref)
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
{
assert(view);
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
int x, y;
if (view_compute_centered_position(view, ref, view->pending.width,
view->pending.height, &x, &y)) {
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
view_move(view, x, y);
}
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
}
void
view_place_by_policy(struct view *view, bool allow_cursor,
enum view_placement_policy policy)
{
if (allow_cursor && policy == LAB_PLACE_CURSOR) {
view_move_to_cursor(view);
return;
} else if (policy == LAB_PLACE_AUTOMATIC) {
struct wlr_box geometry = view->pending;
if (placement_find_best(view, &geometry)) {
view_move(view, geometry.x, geometry.y);
return;
}
}
view_center(view, NULL);
}
void
view_constrain_size_to_that_of_usable_area(struct view *view)
{
2024-01-22 23:52:33 +01:00
if (!view || !view->output || view->fullscreen) {
return;
}
struct wlr_box usable_area =
output_usable_area_in_layout_coords(view->output);
struct border margin = ssd_get_margin(view->ssd);
int available_width = usable_area.width - margin.left - margin.right;
int available_height = usable_area.height - margin.top - margin.bottom;
if (available_width <= 0 || available_height <= 0) {
return;
}
if (available_height >= view->pending.height &&
2024-01-22 23:14:25 +01:00
available_width >= view->pending.width) {
return;
}
int width = MIN(view->pending.width, available_width);
int height = MIN(view->pending.height, available_height);
int right_edge = usable_area.x + usable_area.width;
int bottom_edge = usable_area.y + usable_area.height;
int x =
MAX(usable_area.x + margin.left,
MIN(view->pending.x, right_edge - width - margin.right));
int y =
MAX(usable_area.y + margin.top,
MIN(view->pending.y, bottom_edge - height - margin.bottom));
struct wlr_box box = {
.x = x,
.y = y,
.width = width,
.height = height,
};
view_move_resize(view, box);
}
static void
view_apply_natural_geometry(struct view *view)
{
assert(view);
assert(view_is_floating(view));
struct wlr_box geometry = view->natural_geometry;
adjust_floating_geometry(view, &geometry,
/* midpoint_visibility */ false);
view_move_resize(view, geometry);
}
static void
view_apply_region_geometry(struct view *view)
{
assert(view);
assert(view->tiled_region || view->tiled_region_evacuate);
struct output *output = view->output;
assert(output_is_usable(output));
if (view->tiled_region_evacuate) {
/* View was evacuated from a destroying output */
/* 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;
2022-09-16 06:08:28 +02:00
/* Adjust for rc.gap */
if (rc.gap) {
2022-09-16 06:08:28 +02:00
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;
view_move_resize(view, geo);
}
static void
view_apply_tiled_geometry(struct view *view)
{
assert(view);
assert(view->tiled);
assert(output_is_usable(view->output));
view_move_resize(view, view_get_edge_snap_box(view,
view->output, view->tiled));
}
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
static void
view_apply_fullscreen_geometry(struct view *view)
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
{
assert(view);
assert(view->fullscreen);
assert(output_is_usable(view->output));
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
struct wlr_box box = { 0 };
wlr_output_effective_resolution(view->output->wlr_output,
&box.width, &box.height);
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
double ox = 0, oy = 0;
wlr_output_layout_output_coords(view->server->output_layout,
view->output->wlr_output, &ox, &oy);
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
box.x -= ox;
box.y -= oy;
view_move_resize(view, box);
}
static void
view_apply_maximized_geometry(struct view *view)
{
assert(view);
assert(view->maximized != VIEW_AXIS_NONE);
struct output *output = view->output;
assert(output_is_usable(output));
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
struct wlr_box box = output_usable_area_in_layout_coords(output);
2022-04-04 20:53:36 +01:00
if (box.height == output->wlr_output->height
&& output->wlr_output->scale != 1) {
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
box.height /= output->wlr_output->scale;
}
2022-04-04 20:53:36 +01:00
if (box.width == output->wlr_output->width
&& output->wlr_output->scale != 1) {
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
box.width /= output->wlr_output->scale;
}
/*
* If one axis (horizontal or vertical) is unmaximized, it
* should use the natural geometry. But if that geometry is not
* on-screen on the output where the view is maximized, then
* center the unmaximized axis.
*/
struct wlr_box natural = view->natural_geometry;
if (view->maximized != VIEW_AXIS_BOTH) {
struct wlr_box intersect;
wlr_box_intersection(&intersect, &box, &natural);
if (wlr_box_empty(&intersect)) {
view_compute_centered_position(view, NULL,
natural.width, natural.height,
&natural.x, &natural.y);
}
}
if (view->ssd_enabled) {
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
struct border border = ssd_thickness(view);
box.x += border.left;
box.y += border.top;
box.width -= border.right + border.left;
box.height -= border.top + border.bottom;
}
if (view->maximized == VIEW_AXIS_VERTICAL) {
box.x = natural.x;
box.width = natural.width;
} else if (view->maximized == VIEW_AXIS_HORIZONTAL) {
box.y = natural.y;
box.height = natural.height;
}
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
view_move_resize(view, box);
}
static void
view_apply_special_geometry(struct view *view)
{
assert(view);
assert(!view_is_floating(view));
if (!output_is_usable(view->output)) {
wlr_log(WLR_ERROR, "view has no output, not updating geometry");
return;
}
if (view->fullscreen) {
view_apply_fullscreen_geometry(view);
} else if (view->maximized != VIEW_AXIS_NONE) {
view_apply_maximized_geometry(view);
} else if (view->tiled) {
view_apply_tiled_geometry(view);
} else if (view->tiled_region || view->tiled_region_evacuate) {
view_apply_region_geometry(view);
} else {
assert(false); // not reached
}
}
/* For internal use only. Does not update geometry. */
static void
set_maximized(struct view *view, enum view_axis maximized)
{
if (view->impl->maximize) {
view->impl->maximize(view, (maximized == VIEW_AXIS_BOTH));
}
2023-02-01 09:27:25 +01:00
if (view->toplevel.handle) {
wlr_foreign_toplevel_handle_v1_set_maximized(
view->toplevel.handle, (maximized == VIEW_AXIS_BOTH));
}
view->maximized = maximized;
/*
* Ensure that follow-up actions like SnapToEdge / SnapToRegion
* use up-to-date SSD margin information. Otherwise we will end
* up using an outdated ssd->margin to calculate offsets.
*/
ssd_update_margin(view->ssd);
}
/*
* Un-maximize view and move it to specific geometry. Does not reset
2022-11-21 13:17:14 -05:00
* tiled state (use view_set_untiled() if you want that).
*/
void
view_restore_to(struct view *view, struct wlr_box geometry)
{
assert(view);
if (view->fullscreen) {
return;
}
if (view->maximized != VIEW_AXIS_NONE) {
set_maximized(view, VIEW_AXIS_NONE);
}
view_move_resize(view, geometry);
}
bool
view_is_tiled(struct view *view)
{
assert(view);
return (view->tiled || view->tiled_region
|| view->tiled_region_evacuate);
}
bool
view_is_tiled_and_notify_tiled(struct view *view)
{
switch (rc.snap_tiling_events_mode) {
case LAB_TILING_EVENTS_NEVER:
return false;
case LAB_TILING_EVENTS_REGION:
return view->tiled_region || view->tiled_region_evacuate;
case LAB_TILING_EVENTS_EDGE:
return view->tiled;
case LAB_TILING_EVENTS_ALWAYS:
return view_is_tiled(view);
}
return false;
}
bool
view_is_floating(struct view *view)
{
assert(view);
return !(view->fullscreen || (view->maximized != VIEW_AXIS_NONE)
|| view_is_tiled(view));
}
static void
view_notify_tiled(struct view *view)
{
assert(view);
if (view->impl->notify_tiled) {
view->impl->notify_tiled(view);
}
}
2022-11-21 13:17:14 -05:00
/* 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);
view_notify_tiled(view);
2022-11-21 13:17:14 -05:00
}
void
view_maximize(struct view *view, enum view_axis axis,
bool store_natural_geometry)
{
assert(view);
if (view->maximized == axis) {
2021-03-12 21:27:17 +00:00
return;
}
if (view->fullscreen) {
return;
}
view_set_shade(view, false);
if (axis != VIEW_AXIS_NONE) {
/*
* Maximize via keybind or client request cancels
* interactive move/resize since we can't move/resize
* a maximized view.
*/
interactive_cancel(view);
if (store_natural_geometry && view_is_floating(view)) {
view_store_natural_geometry(view);
view_invalidate_last_layout_geometry(view);
}
}
set_maximized(view, axis);
if (view_is_floating(view)) {
view_apply_natural_geometry(view);
} else {
view_apply_special_geometry(view);
}
}
2021-08-02 16:49:41 +01:00
void
view_toggle_maximize(struct view *view, enum view_axis axis)
2021-08-02 16:49:41 +01:00
{
assert(view);
switch (axis) {
case VIEW_AXIS_HORIZONTAL:
case VIEW_AXIS_VERTICAL:
/* Toggle one axis (XOR) */
view_maximize(view, view->maximized ^ axis,
/*store_natural_geometry*/ true);
break;
case VIEW_AXIS_BOTH:
/*
* Maximize in both directions if unmaximized or partially
* maximized, otherwise unmaximize.
*/
view_maximize(view, (view->maximized == VIEW_AXIS_BOTH) ?
VIEW_AXIS_NONE : VIEW_AXIS_BOTH,
/*store_natural_geometry*/ true);
break;
default:
break;
}
2021-08-02 16:49:41 +01:00
}
2024-04-19 13:35:16 +02:00
bool
view_wants_decorations(struct view *view)
{
/* Window-rules take priority if they exist for this view */
switch (window_rules_get_property(view, "serverDecoration")) {
case LAB_PROP_TRUE:
return true;
case LAB_PROP_FALSE:
return false;
default:
break;
}
/*
* view->ssd_preference may be set by the decoration implementation
* e.g. src/decorations/xdg-deco.c or src/decorations/kde-deco.c.
*/
switch (view->ssd_preference) {
case LAB_SSD_PREF_SERVER:
return true;
case LAB_SSD_PREF_CLIENT:
return false;
default:
/*
* We don't know anything about the client preference
* so fall back to core.decoration settings in rc.xml
*/
return rc.xdg_shell_server_side_deco;
}
}
2024-04-20 06:29:51 +02:00
void
view_set_decorations(struct view *view, enum ssd_mode mode, bool force_ssd)
{
assert(view);
if (force_ssd || view_wants_decorations(view)
2024-04-20 06:29:51 +02:00
|| mode < view_get_ssd_mode(view)) {
view_set_ssd_mode(view, mode);
}
}
void
view_toggle_decorations(struct view *view)
{
assert(view);
enum ssd_mode mode = view_get_ssd_mode(view);
if (rc.ssd_keep_border && mode == LAB_SSD_MODE_FULL) {
view_set_ssd_mode(view, LAB_SSD_MODE_BORDER);
} else if (mode != LAB_SSD_MODE_NONE) {
view_set_ssd_mode(view, LAB_SSD_MODE_NONE);
} else {
view_set_ssd_mode(view, LAB_SSD_MODE_FULL);
}
}
bool
view_is_always_on_top(struct view *view)
2022-04-09 01:16:09 +02:00
{
assert(view);
2022-04-09 01:16:09 +02:00
return view->scene_tree->node.parent ==
view->server->view_tree_always_on_top;
2022-04-09 01:16:09 +02:00
}
void
view_toggle_always_on_top(struct view *view)
{
assert(view);
if (view_is_always_on_top(view)) {
2022-06-15 02:02:50 +02:00
view->workspace = view->server->workspace_current;
wlr_scene_node_reparent(&view->scene_tree->node,
view->workspace->tree);
2022-04-09 01:16:09 +02:00
} else {
wlr_scene_node_reparent(&view->scene_tree->node,
view->server->view_tree_always_on_top);
2022-04-09 01:16:09 +02:00
}
}
bool
2023-05-11 22:26:41 +01:00
view_is_always_on_bottom(struct view *view)
{
assert(view);
return view->scene_tree->node.parent ==
view->server->view_tree_always_on_bottom;
}
void
view_toggle_always_on_bottom(struct view *view)
{
assert(view);
if (view_is_always_on_bottom(view)) {
view->workspace = view->server->workspace_current;
wlr_scene_node_reparent(&view->scene_tree->node,
view->workspace->tree);
} else {
wlr_scene_node_reparent(&view->scene_tree->node,
view->server->view_tree_always_on_bottom);
}
}
2023-11-25 17:54:36 -06:00
void
view_toggle_visible_on_all_workspaces(struct view *view)
{
assert(view);
view->visible_on_all_workspaces = !view->visible_on_all_workspaces;
}
void
view_move_to_workspace(struct view *view, struct workspace *workspace)
{
assert(view);
assert(workspace);
if (view->workspace != workspace) {
view->workspace = workspace;
wlr_scene_node_reparent(&view->scene_tree->node,
workspace->tree);
}
}
static void
decorate(struct view *view)
{
if (!view->ssd) {
view->ssd = ssd_create(view,
2023-12-19 17:45:11 +00:00
view == view->server->active_view);
}
}
static void
undecorate(struct view *view)
{
ssd_destroy(view->ssd);
view->ssd = NULL;
}
enum ssd_mode
view_get_ssd_mode(struct view *view)
{
assert(view);
if (!view->ssd_enabled) {
return LAB_SSD_MODE_NONE;
} else if (view->ssd_titlebar_hidden) {
return LAB_SSD_MODE_BORDER;
} else {
return LAB_SSD_MODE_FULL;
}
}
void
view_set_ssd_mode(struct view *view, enum ssd_mode mode)
{
assert(view);
if (view->shaded || view->fullscreen
|| mode == view_get_ssd_mode(view)) {
return;
}
/*
* Set these first since they are referenced
* within the call tree of ssd_create() and ssd_thickness()
*/
view->ssd_enabled = mode != LAB_SSD_MODE_NONE;
view->ssd_titlebar_hidden = mode != LAB_SSD_MODE_FULL;
if (view->ssd_enabled) {
decorate(view);
ssd_set_titlebar(view->ssd, !view->ssd_titlebar_hidden);
} else {
undecorate(view);
}
if (!view_is_floating(view)) {
view_apply_special_geometry(view);
}
}
void
view_toggle_fullscreen(struct view *view)
{
assert(view);
view_set_fullscreen(view, !view->fullscreen);
}
/* For internal use only. Does not update geometry. */
static void
set_fullscreen(struct view *view, bool fullscreen)
2021-08-23 22:05:30 +01:00
{
/* When going fullscreen, unshade the window */
if (fullscreen) {
view_set_shade(view, false);
}
/* Hide decorations when going fullscreen */
if (fullscreen && view->ssd_enabled) {
undecorate(view);
}
2021-08-23 22:05:30 +01:00
if (view->impl->set_fullscreen) {
view->impl->set_fullscreen(view, fullscreen);
2021-08-23 22:05:30 +01:00
}
2023-02-01 09:27:25 +01:00
if (view->toplevel.handle) {
2021-08-23 22:05:30 +01:00
wlr_foreign_toplevel_handle_v1_set_fullscreen(
2023-02-01 09:27:25 +01:00
view->toplevel.handle, fullscreen);
2021-08-23 22:05:30 +01:00
}
view->fullscreen = fullscreen;
/* Re-show decorations when no longer fullscreen */
if (!fullscreen && view->ssd_enabled) {
decorate(view);
}
/* Show fullscreen views above top-layer */
if (view->output) {
desktop_update_top_layer_visiblity(view->server);
}
}
void
view_set_fullscreen(struct view *view, bool fullscreen)
{
assert(view);
if (fullscreen == view->fullscreen) {
return;
}
2021-08-23 22:05:30 +01:00
if (fullscreen) {
if (!output_is_usable(view->output)) {
/* Prevent fullscreen with no available outputs */
return;
}
/*
* Fullscreen via keybind or client request cancels
* interactive move/resize since we can't move/resize
* a fullscreen view.
*/
interactive_cancel(view);
view_store_natural_geometry(view);
view_invalidate_last_layout_geometry(view);
}
set_fullscreen(view, fullscreen);
if (view_is_floating(view)) {
view_apply_natural_geometry(view);
} else {
view_apply_special_geometry(view);
2021-08-23 22:05:30 +01:00
}
set_adaptive_sync_fullscreen(view);
2021-08-23 22:05:30 +01:00
}
static bool
last_layout_geometry_is_valid(struct view *view)
{
return view->last_layout_geometry.width > 0
&& view->last_layout_geometry.height > 0;
}
static void
update_last_layout_geometry(struct view *view)
{
/*
* Only update an invalid last-layout geometry to prevent a series of
* successive layout changes from continually replacing the "preferred"
* location with whatever location the view currently holds. The
* "preferred" location should be whatever state was set by user
* interaction, not automatic responses to layout changes.
*/
if (last_layout_geometry_is_valid(view)) {
return;
}
if (view_is_floating(view)) {
view->last_layout_geometry = view->pending;
} else {
view->last_layout_geometry = view->natural_geometry;
}
}
static bool
apply_last_layout_geometry(struct view *view, bool force_update)
{
/* Only apply a valid last-layout geometry */
if (!last_layout_geometry_is_valid(view)) {
return false;
}
/*
* Unless forced, the last-layout geometry is only applied
* when the relevant view geometry is distinct.
*/
if (!force_update) {
struct wlr_box *relevant = view_is_floating(view) ?
&view->pending : &view->natural_geometry;
if (wlr_box_equal(relevant, &view->last_layout_geometry)) {
return false;
}
}
view->natural_geometry = view->last_layout_geometry;
adjust_floating_geometry(view, &view->natural_geometry,
/* midpoint_visibility */ true);
return true;
}
void
view_invalidate_last_layout_geometry(struct view *view)
{
assert(view);
view->last_layout_geometry.width = 0;
view->last_layout_geometry.height = 0;
}
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
void
view_adjust_for_layout_change(struct view *view)
{
assert(view);
bool was_fullscreen = view->fullscreen;
bool is_floating = view_is_floating(view);
if (!output_is_usable(view->output)) {
/* A view losing an output should have a last-layout geometry */
update_last_layout_geometry(view);
/* Exit fullscreen and re-assess floating status */
if (was_fullscreen) {
set_fullscreen(view, false);
is_floating = view_is_floating(view);
}
}
/* Restore any full-screen window to natural geometry */
bool use_natural = was_fullscreen;
/* Capture a pointer to the last-layout geometry (only if valid) */
struct wlr_box *last_geometry = NULL;
if (last_layout_geometry_is_valid(view)) {
last_geometry = &view->last_layout_geometry;
}
/*
* Check if an output change is required:
* - Floating views are always mapped to the nearest output
* - Any view without a usable output needs to be repositioned
* - Any view with a valid last-layout geometry might be better
* positioned on another output
*/
if (is_floating || last_geometry || !output_is_usable(view->output)) {
/* Move the view to an appropriate output, if needed */
bool output_changed = view_discover_output(view, last_geometry);
/*
* Try to apply the last-layout to the natural geometry
* (adjusting to ensure that it fits on the screen). This is
* forced if the output has changed, but will be done
* opportunistically even on the same output if the last-layout
* geometry is different from the view's governing geometry.
*/
if (apply_last_layout_geometry(view, output_changed)) {
use_natural = true;
}
/*
* Whether or not the view has moved, the layout has changed.
* Ensure that the view now has a valid last-layout geometry.
*/
update_last_layout_geometry(view);
}
if (!is_floating) {
view_apply_special_geometry(view);
} else if (use_natural) {
/*
* Move the window to its natural location, either because it
* was fullscreen or we are trying to restore a prior layout.
*/
view_apply_natural_geometry(view);
} else {
/* Otherwise, just ensure the view is on screen. */
struct wlr_box geometry = view->pending;
if (adjust_floating_geometry(view, &geometry,
/* midpoint_visibility */ true)) {
view_move_resize(view, geometry);
}
Adjust views to account for output layout changes labwc currently doesn't handle output layout changes very well: - Windows can end up "lost" completely offscreen - Maximized/fullscreen windows can end up spanning multiple outputs Currently, new_output_notify() and output_destroy_notify() contain logic to update the cursor and force a repaint when outputs are added or removed. This logic in fact needs to run on any output layout change, so consolidate it into a new function, output_update_for_layout_change(). Then add a second new function, view_adjust_for_layout_change(), which adjusts window placement to account for the new layout. The behavior is roughly as follows: - Normal windows that end up offscreen are centered on the closest output (making use of the existing view_center() logic) - Maximized windows are re-maximized on the closest output. Logic is also added to the unmaximize step to check that the original unmaximized position is still on-screen. - Fullscreen windows are re-fullscreened on the same output if possible; otherwise they are un-fullscreened. Minimized windows don't require any special handling. Their placement is adjusted just the same, but invisible to the user until they are later unminimized. There is some positioning glitch still with un-fullscreening a window whose output has been disconnected/disabled; it can end up in an unexpected position (but at least has the correct size and decoration). I don't think this is due to a bug in my change per se, but perhaps the change has exposed a bug elsewhere. Fixes: #177
2021-12-31 17:30:55 -05:00
}
view_update_outputs(view);
}
void
view_evacuate_region(struct view *view)
{
assert(view);
assert(view->tiled_region);
if (!view->tiled_region_evacuate) {
view->tiled_region_evacuate = xstrdup(view->tiled_region->name);
}
view->tiled_region = NULL;
}
void
view_on_output_destroy(struct view *view)
{
assert(view);
2023-02-28 11:46:48 -05:00
/*
* This is the only time we modify view->output for a fullscreen
* view. We expect view_adjust_for_layout_change() to be called
* shortly afterward, which will exit fullscreen.
*/
view->output = NULL;
}
static enum wlr_direction
opposite_direction(enum wlr_direction direction)
{
switch (direction) {
case WLR_DIRECTION_RIGHT:
return WLR_DIRECTION_LEFT;
case WLR_DIRECTION_LEFT:
return WLR_DIRECTION_RIGHT;
case WLR_DIRECTION_DOWN:
return WLR_DIRECTION_UP;
case WLR_DIRECTION_UP:
return WLR_DIRECTION_DOWN;
default:
return 0;
}
}
static enum wlr_direction
get_wlr_direction(enum view_edge edge)
{
switch (edge) {
case VIEW_EDGE_LEFT:
return WLR_DIRECTION_LEFT;
case VIEW_EDGE_RIGHT:
return WLR_DIRECTION_RIGHT;
case VIEW_EDGE_UP:
return WLR_DIRECTION_UP;
case VIEW_EDGE_DOWN:
return WLR_DIRECTION_DOWN;
case VIEW_EDGE_CENTER:
case VIEW_EDGE_INVALID:
default:
return 0;
}
}
2024-01-21 23:40:25 +01:00
struct output *
view_get_adjacent_output(struct view *view, enum view_edge edge, bool wrap)
{
assert(view);
struct output *output = view->output;
if (!output_is_usable(output)) {
wlr_log(WLR_ERROR,
"view has no output, cannot find adjacent output");
return NULL;
}
struct wlr_box box = output_usable_area_in_layout_coords(output);
int lx = box.x + box.width / 2;
int ly = box.y + box.height / 2;
/* Determine any adjacent output in the appropriate direction */
struct wlr_output *new_output = NULL;
struct wlr_output *current_output = output->wlr_output;
struct wlr_output_layout *layout = view->server->output_layout;
enum wlr_direction direction = get_wlr_direction(edge);
new_output = wlr_output_layout_adjacent_output(layout, direction,
current_output, lx, ly);
/*
* Optionally wrap around from top-to-bottom or left-to-right, and vice
* versa.
*/
if (wrap && !new_output) {
new_output = wlr_output_layout_farthest_output(layout,
opposite_direction(direction), current_output, lx, ly);
}
/*
* When "adjacent" output is the same as the original, there is no
* adjacent
*/
if (!new_output || new_output == current_output) {
return NULL;
}
output = output_from_wlr_output(view->server, new_output);
if (!output_is_usable(output)) {
wlr_log(WLR_ERROR, "invalid output in layout");
return NULL;
}
return output;
}
static int
shift_view_to_usable_1d(int size,
int cur_pos, int cur_lo, int cur_extent,
int next_pos, int next_lo, int next_extent,
int margin_lo, int margin_hi)
{
int cur_min = cur_lo + rc.gap + margin_lo;
int cur_max = cur_lo + cur_extent - rc.gap - margin_hi;
int next_min = next_lo + rc.gap + margin_lo;
int next_max = next_lo + next_extent - rc.gap - margin_hi;
/*
* If the view is fully within the usable area of its original display,
* ensure that it is also fully within the usable area of the target.
*/
if (cur_pos >= cur_min && cur_pos + size <= cur_max) {
if (next_pos >= next_min && next_pos + size > next_max) {
next_pos = next_max - size;
}
return MAX(next_pos, next_min);
}
/*
* If the view was not fully within the usable area of its original
* display, kick it onscreen if its midpoint will be off the target.
*/
int midpoint = next_pos + size / 2;
if (next_pos >= next_min && midpoint > next_lo + next_extent) {
next_pos = next_max - size;
}
return MAX(next_pos, next_min);
}
void
view_move_to_edge(struct view *view, enum view_edge direction, bool snap_to_windows)
{
assert(view);
if (!output_is_usable(view->output)) {
wlr_log(WLR_ERROR, "view has no output, not moving to edge");
return;
}
int dx = 0, dy = 0;
snap_move_to_edge(view, direction, snap_to_windows, &dx, &dy);
if (dx != 0 || dy != 0) {
/* Move the window if a change was discovered */
view_move(view, view->pending.x + dx, view->pending.y + dy);
return;
}
/* If the view is maximized, do not attempt to jump displays */
if (view->maximized != VIEW_AXIS_NONE) {
return;
}
/* Otherwise, move to edge of next adjacent display, if possible */
struct output *output =
view_get_adjacent_output(view, direction, /* wrap */ false);
if (!output) {
return;
}
/* When jumping to next output, attach to edge nearest the motion */
struct wlr_box usable = output_usable_area_in_layout_coords(output);
struct border margin = ssd_get_margin(view->ssd);
/* Bounds of the possible placement zone in this output */
int left = usable.x + rc.gap + margin.left;
int right = usable.x + usable.width - rc.gap - margin.right;
int top = usable.y + rc.gap + margin.top;
int bottom = usable.y + usable.height - rc.gap - margin.bottom;
/* Default target position on new output is current target position */
int destination_x = view->pending.x;
int destination_y = view->pending.y;
/* Compute the new position in the direction of motion */
direction = view_edge_invert(direction);
switch (direction) {
case VIEW_EDGE_LEFT:
destination_x = left;
break;
case VIEW_EDGE_RIGHT:
destination_x = right - view->pending.width;
break;
case VIEW_EDGE_UP:
destination_y = top;
break;
case VIEW_EDGE_DOWN:
destination_y = bottom
- view_effective_height(view, /* use_pending */ true);
break;
default:
return;
}
struct wlr_box original_usable =
output_usable_area_in_layout_coords(view->output);
/* Make sure the window is appropriately in view along the x direction */
destination_x = shift_view_to_usable_1d(view->pending.width,
view->pending.x, original_usable.x, original_usable.width,
destination_x, usable.x, usable.width, margin.left, margin.right);
/* Make sure the window is appropriately in view along the y direction */
int eff_height = view_effective_height(view, /* use_pending */ true);
destination_y = shift_view_to_usable_1d(eff_height,
view->pending.y, original_usable.y, original_usable.height,
destination_y, usable.y, usable.height, margin.top, margin.bottom);
view_set_untiled(view);
view_set_output(view, output);
view_move(view, destination_x, destination_y);
}
void
view_grow_to_edge(struct view *view, enum view_edge direction)
{
assert(view);
/* TODO: allow grow to edge if maximized along the other axis */
if (view->fullscreen || view->maximized != VIEW_AXIS_NONE) {
return;
}
if (!output_is_usable(view->output)) {
wlr_log(WLR_ERROR, "view has no output, not growing view");
return;
}
view_set_shade(view, false);
struct wlr_box geo;
snap_grow_to_next_edge(view, direction, &geo);
view_move_resize(view, geo);
}
void
view_shrink_to_edge(struct view *view, enum view_edge direction)
{
assert(view);
/* TODO: allow shrink to edge if maximized along the other axis */
if (view->fullscreen || view->maximized != VIEW_AXIS_NONE) {
return;
}
if (!output_is_usable(view->output)) {
wlr_log(WLR_ERROR, "view has no output, not shrinking view");
return;
}
view_set_shade(view, false);
struct wlr_box geo = view->pending;
snap_shrink_to_next_edge(view, direction, &geo);
view_move_resize(view, geo);
}
enum view_axis
view_axis_parse(const char *direction)
{
if (!direction) {
return VIEW_AXIS_NONE;
}
if (!strcasecmp(direction, "horizontal")) {
return VIEW_AXIS_HORIZONTAL;
} else if (!strcasecmp(direction, "vertical")) {
return VIEW_AXIS_VERTICAL;
} else if (!strcasecmp(direction, "both")) {
return VIEW_AXIS_BOTH;
} else {
return VIEW_AXIS_NONE;
}
}
enum view_edge
view_edge_parse(const char *direction)
{
if (!direction) {
return VIEW_EDGE_INVALID;
}
if (!strcasecmp(direction, "left")) {
return VIEW_EDGE_LEFT;
} else if (!strcasecmp(direction, "up")) {
return VIEW_EDGE_UP;
} else if (!strcasecmp(direction, "right")) {
return VIEW_EDGE_RIGHT;
} else if (!strcasecmp(direction, "down")) {
return VIEW_EDGE_DOWN;
2021-11-26 18:49:44 +00:00
} else if (!strcasecmp(direction, "center")) {
return VIEW_EDGE_CENTER;
} else {
return VIEW_EDGE_INVALID;
}
}
enum view_placement_policy
view_placement_parse(const char *policy)
{
if (!policy) {
return LAB_PLACE_CENTER;
}
if (!strcasecmp(policy, "automatic")) {
return LAB_PLACE_AUTOMATIC;
} else if (!strcasecmp(policy, "cursor")) {
return LAB_PLACE_CURSOR;
} else if (!strcasecmp(policy, "center")) {
return LAB_PLACE_CENTER;
}
return LAB_PLACE_INVALID;
}
void
view_snap_to_edge(struct view *view, enum view_edge edge,
bool across_outputs, bool store_natural_geometry)
{
assert(view);
if (view->fullscreen) {
return;
}
struct output *output = view->output;
if (!output_is_usable(output)) {
wlr_log(WLR_ERROR, "view has no output, not snapping to edge");
return;
}
view_set_shade(view, false);
if (across_outputs && view->tiled == edge && view->maximized == VIEW_AXIS_NONE) {
/* We are already tiled for this edge; try to switch outputs */
output = view_get_adjacent_output(view, edge, /* wrap */ false);
if (!output) {
/*
* No more output to move to
*
* We re-apply the tiled geometry without changing any
* state because the window might have been moved away
* (and thus got untiled) and then snapped back to the
* original edge.
*/
view_apply_tiled_geometry(view);
return;
}
/* When switching outputs, jump to the opposite edge */
edge = view_edge_invert(edge);
}
if (view->maximized != VIEW_AXIS_NONE) {
/* Unmaximize + keep using existing natural_geometry */
view_maximize(view, VIEW_AXIS_NONE,
/*store_natural_geometry*/ false);
} else if (store_natural_geometry) {
/* store current geometry as new natural_geometry */
view_store_natural_geometry(view);
view_invalidate_last_layout_geometry(view);
2022-07-01 20:44:40 +02:00
}
view_set_untiled(view);
2023-02-28 11:46:48 -05:00
view_set_output(view, output);
view->tiled = edge;
view_notify_tiled(view);
view_apply_tiled_geometry(view);
}
void
view_snap_to_region(struct view *view, struct region *region,
bool store_natural_geometry)
{
assert(view);
assert(region);
if (view->fullscreen) {
return;
}
/* view_apply_region_geometry() needs a usable output */
if (!output_is_usable(view->output)) {
wlr_log(WLR_ERROR, "view has no output, not snapping to region");
return;
}
view_set_shade(view, false);
if (view->maximized != VIEW_AXIS_NONE) {
/* Unmaximize + keep using existing natural_geometry */
view_maximize(view, VIEW_AXIS_NONE,
/*store_natural_geometry*/ false);
} else if (store_natural_geometry) {
/* store current geometry as new natural_geometry */
view_store_natural_geometry(view);
view_invalidate_last_layout_geometry(view);
}
view_set_untiled(view);
view->tiled_region = region;
view_notify_tiled(view);
view_apply_region_geometry(view);
}
2024-01-21 23:44:27 +01:00
void
view_move_to_output(struct view *view, struct output *output)
{
assert(view);
if (view->fullscreen) {
return;
}
view_invalidate_last_layout_geometry(view);
view_set_output(view, output);
if (view_is_floating(view)) {
struct wlr_box output_area = output_usable_area_in_layout_coords(output);
view->pending.x = output_area.x;
view->pending.y = output_area.y;
view_place_by_policy(view,
/* allow_cursor */ false, rc.placement_policy);
2024-01-21 23:44:27 +01:00
} else if (view->maximized != VIEW_AXIS_NONE) {
view_apply_maximized_geometry(view);
} else if (view->tiled) {
view_apply_tiled_geometry(view);
} else if (view->tiled_region) {
struct region *region = regions_from_name(view->tiled_region->name, output);
view_snap_to_region(view, region, /*store_natural_geometry*/ false);
}
}
static void
for_each_subview(struct view *view, void (*action)(struct view *))
{
struct wl_array subviews;
struct view **subview;
wl_array_init(&subviews);
view_append_children(view, &subviews);
wl_array_for_each(subview, &subviews) {
action(*subview);
}
wl_array_release(&subviews);
}
static void
move_to_front(struct view *view)
{
if (view->impl->move_to_front) {
view->impl->move_to_front(view);
}
view->server->last_raised_view = view;
}
static void
move_to_back(struct view *view)
{
if (view->impl->move_to_back) {
view->impl->move_to_back(view);
}
if (view == view->server->last_raised_view) {
view->server->last_raised_view = NULL;
}
}
/*
* In the view_move_to_{front,back} functions, a modal dialog is always
* shown above its parent window, and the two always move together, so
* other windows cannot come between them.
* This is consistent with GTK3/Qt5 applications on mutter and openbox.
*/
void
view_move_to_front(struct view *view)
{
assert(view);
/*
* This function is called often, generally on every mouse
* button press (more often for focus-follows-mouse). Avoid
* unnecessarily raising the same view over and over, or
* attempting to raise a root view above its own sub-view.
*/
struct view *last = view->server->last_raised_view;
if (view == last || (last && view == view_get_root(last))) {
return;
}
struct view *root = view_get_root(view);
assert(root);
move_to_front(root);
for_each_subview(root, move_to_front);
/* make sure view is in front of other sub-views */
if (view != root) {
move_to_front(view);
}
cursor_update_focus(view->server);
desktop_update_top_layer_visiblity(view->server);
}
void
view_move_to_back(struct view *view)
{
assert(view);
struct view *root = view_get_root(view);
assert(root);
for_each_subview(root, move_to_back);
move_to_back(root);
cursor_update_focus(view->server);
desktop_update_top_layer_visiblity(view->server);
}
struct view *
view_get_root(struct view *view)
{
assert(view);
if (view->impl->get_root) {
return view->impl->get_root(view);
}
return view;
}
void
view_append_children(struct view *view, struct wl_array *children)
{
assert(view);
if (view->impl->append_children) {
view->impl->append_children(view, children);
}
}
bool
view_has_strut_partial(struct view *view)
{
assert(view);
return view->impl->has_strut_partial &&
view->impl->has_strut_partial(view);
}
2021-10-18 20:01:10 +01:00
const char *
view_get_string_prop(struct view *view, const char *prop)
{
assert(view);
assert(prop);
2021-10-18 20:01:10 +01:00
if (view->impl->get_string_prop) {
2021-12-06 20:16:30 +00:00
return view->impl->get_string_prop(view, prop);
2021-10-18 20:01:10 +01:00
}
2021-10-18 20:06:47 +01:00
return "";
2021-10-18 20:01:10 +01:00
}
void
view_update_title(struct view *view)
{
assert(view);
2021-10-18 20:01:10 +01:00
const char *title = view_get_string_prop(view, "title");
2023-02-01 09:27:25 +01:00
if (!view->toplevel.handle || !title) {
return;
}
ssd_update_title(view->ssd);
2023-02-01 09:27:25 +01:00
wlr_foreign_toplevel_handle_v1_set_title(view->toplevel.handle, title);
}
void
view_update_app_id(struct view *view)
{
assert(view);
2021-10-18 20:01:10 +01:00
const char *app_id = view_get_string_prop(view, "app_id");
2023-02-01 09:27:25 +01:00
if (!view->toplevel.handle || !app_id) {
return;
}
wlr_foreign_toplevel_handle_v1_set_app_id(
2023-02-01 09:27:25 +01:00
view->toplevel.handle, app_id);
}
void
view_reload_ssd(struct view *view)
{
assert(view);
if (view->ssd_enabled && !view->fullscreen) {
undecorate(view);
decorate(view);
}
}
void
view_toggle_keybinds(struct view *view)
2023-03-05 10:35:56 +01:00
{
assert(view);
view->inhibits_keybinds = !view->inhibits_keybinds;
if (view->inhibits_keybinds) {
2023-03-05 10:35:56 +01:00
view->server->seat.nr_inhibited_keybind_views++;
} else {
view->server->seat.nr_inhibited_keybind_views--;
}
if (view->ssd_enabled) {
ssd_enable_keybind_inhibit_indicator(view->ssd,
view->inhibits_keybinds);
2023-03-05 10:35:56 +01:00
}
}
void
mappable_connect(struct mappable *mappable, struct wlr_surface *surface,
wl_notify_func_t notify_map, wl_notify_func_t notify_unmap)
{
assert(mappable);
assert(!mappable->connected);
mappable->map.notify = notify_map;
wl_signal_add(&surface->events.map, &mappable->map);
mappable->unmap.notify = notify_unmap;
wl_signal_add(&surface->events.unmap, &mappable->unmap);
mappable->connected = true;
}
void
mappable_disconnect(struct mappable *mappable)
{
assert(mappable);
assert(mappable->connected);
wl_list_remove(&mappable->map.link);
wl_list_remove(&mappable->unmap.link);
mappable->connected = false;
}
static void
handle_map(struct wl_listener *listener, void *data)
{
struct view *view = wl_container_of(listener, view, mappable.map);
view->impl->map(view);
}
static void
handle_unmap(struct wl_listener *listener, void *data)
{
struct view *view = wl_container_of(listener, view, mappable.unmap);
view->impl->unmap(view, /* client_request */ true);
}
/*
* TODO: after the release of wlroots 0.17, consider incorporating this
* function into a more general view_set_surface() function, which could
* connect other surface event handlers (like commit) as well.
*/
void
view_connect_map(struct view *view, struct wlr_surface *surface)
{
assert(view);
mappable_connect(&view->mappable, surface, handle_map, handle_unmap);
}
void
view_set_shade(struct view *view, bool shaded)
{
assert(view);
if (view->shaded == shaded) {
return;
}
/* Views without a title-bar or SSD cannot be shaded */
if (shaded && (!view->ssd || view->ssd_titlebar_hidden)) {
return;
}
/* If this window is being resized, cancel the resize when shading */
if (shaded && view->server->input_mode == LAB_INPUT_STATE_RESIZE) {
interactive_cancel(view);
}
view->shaded = shaded;
ssd_enable_shade(view->ssd, view->shaded);
wlr_scene_node_set_enabled(view->scene_node, !view->shaded);
if (view->impl->shade) {
view->impl->shade(view, shaded);
}
}
void
view_destroy(struct view *view)
{
assert(view);
struct server *server = view->server;
snap_constraints_invalidate(view);
if (view->mappable.connected) {
mappable_disconnect(&view->mappable);
}
wl_list_remove(&view->request_move.link);
wl_list_remove(&view->request_resize.link);
wl_list_remove(&view->request_minimize.link);
wl_list_remove(&view->request_maximize.link);
wl_list_remove(&view->request_fullscreen.link);
wl_list_remove(&view->set_title.link);
wl_list_remove(&view->destroy.link);
2023-02-01 09:27:25 +01:00
if (view->toplevel.handle) {
wlr_foreign_toplevel_handle_v1_destroy(view->toplevel.handle);
}
if (server->grabbed_view == view) {
/* Application got killed while moving around */
server->input_mode = LAB_INPUT_STATE_PASSTHROUGH;
server->grabbed_view = NULL;
overlay_hide(&server->seat);
}
2023-12-19 17:45:11 +00:00
if (server->active_view == view) {
server->active_view = NULL;
}
if (server->last_raised_view == view) {
server->last_raised_view = NULL;
}
if (server->seat.pressed.view == view) {
seat_reset_pressed(&server->seat);
}
if (view->tiled_region_evacuate) {
zfree(view->tiled_region_evacuate);
}
2023-03-05 10:35:56 +01:00
if (view->inhibits_keybinds) {
view->inhibits_keybinds = false;
server->seat.nr_inhibited_keybind_views--;
}
osd_on_view_destroy(view);
undecorate(view);
/*
* The layer-shell top-layer is disabled when an application is running
* in fullscreen mode, so if that's the case, we may have to re-enable
* it here.
*/
if (view->fullscreen && view->output) {
view->fullscreen = false;
desktop_update_top_layer_visiblity(server);
if (rc.adaptive_sync == LAB_ADAPTIVE_SYNC_FULLSCREEN) {
2024-01-05 20:43:56 +02:00
set_adaptive_sync_fullscreen(view);
}
}
/* If we spawned a window menu, close it */
if (server->menu_current
&& server->menu_current->triggered_by_view == view) {
menu_close_root(server);
}
/*
* Destroy the view's scene tree. View methods assume this is non-NULL,
* so we should avoid any calls to those between this and freeing the
* view.
*/
if (view->scene_tree) {
wlr_scene_node_destroy(&view->scene_tree->node);
view->scene_tree = NULL;
}
/* Remove view from server->views */
wl_list_remove(&view->link);
free(view);
cursor_update_focus(server);
}