labwc/src/view.c

1017 lines
24 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 "common/scene-helpers.h"
2021-03-20 14:36:40 +00:00
#include "labwc.h"
#include "menu/menu.h"
#include "regions.h"
2023-01-01 19:35:07 +01:00
#include "ssd.h"
#include "view.h"
2022-06-15 02:02:50 +02:00
#include "workspaces.h"
#include "xwayland.h"
#define LAB_MIN_VIEW_WIDTH 100
#define LAB_MIN_VIEW_HEIGHT 60
#define LAB_FALLBACK_WIDTH 640
#define LAB_FALLBACK_HEIGHT 480
/**
* 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_INVALID = 0,
VIEW_EDGE_LEFT,
VIEW_EDGE_RIGHT,
VIEW_EDGE_UP,
VIEW_EDGE_DOWN,
VIEW_EDGE_CENTER,
};
static 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)
{
struct wlr_box usable = output_usable_area_in_layout_coords(output);
if (usable.height == output->wlr_output->height
&& output->wlr_output->scale != 1) {
usable.height /= output->wlr_output->scale;
}
if (usable.width == output->wlr_output->width
&& output->wlr_output->scale != 1) {
usable.width /= output->wlr_output->scale;
}
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 void
_view_set_activated(struct view *view, bool activated)
{
ssd_set_active(view->ssd, activated);
if (view->impl->set_activated) {
view->impl->set_activated(view, activated);
}
if (view->toplevel_handle) {
wlr_foreign_toplevel_handle_v1_set_activated(
view->toplevel_handle, activated);
}
}
void
view_set_activated(struct view *view)
{
assert(view);
struct view *last = view->server->focused_view;
if (last == view) {
return;
}
if (last) {
_view_set_activated(last, false);
}
_view_set_activated(view, true);
view->server->focused_view = view;
}
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);
}
}
void
view_move(struct view *view, int x, int y)
{
assert(view);
if (view->impl->move) {
view->impl->move(view, x, y);
}
}
void
view_moved(struct view *view)
{
assert(view);
wlr_scene_node_set_position(&view->scene_tree->node, view->x, view->y);
view_discover_output(view);
ssd_update_geometry(view->ssd);
cursor_update_focus(view->server);
}
/* N.B. Use view_move() if not resizing. */
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
}
void
view_adjust_size(struct view *view, int *w, int *h)
{
assert(view);
#if HAVE_XWAYLAND
if (xwayland_apply_size_hints(view, w, h)) {
/* We don't want to cap the size to keep the aspect ratio */
return;
}
#endif
*w = MAX(*w, LAB_MIN_VIEW_WIDTH);
*h = MAX(*h, LAB_MIN_VIEW_HEIGHT);
}
void
2021-08-05 13:00:34 +01:00
view_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;
}
2021-08-05 13:00:34 +01:00
if (view->toplevel_handle) {
2021-11-26 18:49:44 +00:00
wlr_foreign_toplevel_handle_v1_set_minimized(
view->toplevel_handle, minimized);
2021-08-05 13:00:34 +01:00
}
view->minimized = minimized;
if (minimized) {
view->impl->unmap(view);
desktop_move_to_back(view);
_view_set_activated(view, false);
if (view == view->server->focused_view) {
/*
* Prevents clearing the active view when
* we don't actually have keyboard focus.
*
* This may happen when using a custom mouse
* focus configuration or by using the foreign
* toplevel protocol via some panel.
*/
view->server->focused_view = NULL;
}
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
/* view_wlr_output - return the output that a view is mostly on */
struct wlr_output *
view_wlr_output(struct view *view)
2021-10-18 19:59:11 -04:00
{
assert(view);
2021-10-19 18:15:56 -04:00
double closest_x, closest_y;
struct wlr_output *wlr_output = NULL;
wlr_output_layout_closest_point(view->server->output_layout, wlr_output,
view->x + view->w / 2, view->y + view->h / 2, &closest_x,
&closest_y);
wlr_output = wlr_output_layout_output_at(view->server->output_layout,
closest_x, closest_y);
return wlr_output;
2021-10-18 19:59:11 -04:00
}
static struct output *
view_output(struct view *view)
{
struct wlr_output *wlr_output = view_wlr_output(view);
return output_from_wlr_output(view->server, wlr_output);
}
bool
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_compute_centered_position(struct view *view, int w, int h, int *x, int *y)
2021-07-09 21:39:20 +01:00
{
struct output *output = view_output(view);
if (!output) {
return false;
}
struct wlr_output *wlr_output = output->wlr_output;
if (!wlr_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
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(output);
int width = w + margin.left + margin.right;
int height = h + margin.top + margin.bottom;
*x = usable.x + (usable.width - width) / 2;
*y = usable.y + (usable.height - height) / 2;
/* If view is bigger than usable area, just top/left align it */
if (*x < 0) {
*x = 0;
}
if (*y < 0) {
*y = 0;
}
#if HAVE_XWAYLAND
/* TODO: refactor xwayland.c functions to get rid of this */
if (view->type == LAB_XWAYLAND_VIEW) {
*x += margin.left;
*y += margin.top;
}
#endif
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 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,
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->maximized || view->tiled || view->tiled_region) {
/* 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 in which case we set
* some fallback values. This is the case with foot and Qt applications.
*/
if (!view->w || !view->h) {
set_fallback_geometry(view);
} else {
view->natural_geometry.x = view->x;
view->natural_geometry.y = view->y;
view->natural_geometry.width = view->w;
view->natural_geometry.height = view->h;
}
}
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)
{
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, view->w, view->h, &x, &y)) {
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
}
static void
view_apply_natural_geometry(struct view *view)
{
struct wlr_output_layout *layout = view->server->output_layout;
if (wlr_output_layout_intersects(layout, NULL, &view->natural_geometry)
|| wl_list_empty(&layout->outputs)) {
/* restore to original geometry */
view_move_resize(view, view->natural_geometry);
} else {
/* reposition if original geometry is offscreen */
struct wlr_box box = view->natural_geometry;
if (view_compute_centered_position(view, box.width, box.height,
&box.x, &box.y)) {
view_move_resize(view, box);
}
}
}
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)
{
assert(view->tiled);
if (!output) {
output = view_output(view);
}
if (!output) {
wlr_log(WLR_ERROR, "Can't tile: no output");
return;
}
struct wlr_box dst = view_get_edge_snap_box(view, output, view->tiled);
if (view->w == dst.width && view->h == dst.height) {
/* move horizontally/vertically without changing size */
view_move(view, dst.x, dst.y);
} else {
view_move_resize(view, dst);
}
}
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, struct wlr_output *wlr_output)
{
assert(wlr_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 output *output =
output_from_wlr_output(view->server, wlr_output);
struct wlr_box box = { 0 };
wlr_output_effective_resolution(wlr_output, &box.width, &box.height);
double ox = 0, oy = 0;
wlr_output_layout_output_coords(output->server->output_layout,
output->wlr_output, &ox, &oy);
box.x -= ox;
box.y -= oy;
view_move_resize(view, box);
}
static void
view_apply_maximized_geometry(struct view *view)
{
/*
* The same code handles both initial maximize and re-maximize
* to account for layout changes. In either case, view_output()
* gives the output closest to the current geometry (which may
* be different from the output originally maximized onto).
* view_output() can return NULL if all outputs are disabled.
*/
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 output *output = view_output(view);
if (!output) {
return;
}
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 (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;
}
view_move_resize(view, box);
}
static bool
view_apply_special_geometry(struct view *view)
{
if (view->maximized) {
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;
}
return true;
}
static void
set_maximized(struct view *view, bool maximized)
{
if (view->impl->maximize) {
view->impl->maximize(view, maximized);
}
if (view->toplevel_handle) {
wlr_foreign_toplevel_handle_v1_set_maximized(
view->toplevel_handle, maximized);
}
view->maximized = maximized;
}
/*
* 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) {
set_maximized(view, false);
}
view_move_resize(view, geometry);
}
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;
2022-11-21 13:17:14 -05:00
}
void
view_maximize(struct view *view, bool maximize, bool store_natural_geometry)
{
assert(view);
2021-03-12 21:27:17 +00:00
if (view->maximized == maximize) {
return;
}
if (view->fullscreen) {
return;
}
2021-03-12 21:27:17 +00:00
if (maximize) {
/*
* 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_store_natural_geometry(view);
}
}
set_maximized(view, maximize);
if (!view_apply_special_geometry(view)) {
view_apply_natural_geometry(view);
}
}
2021-08-02 16:49:41 +01:00
void
view_toggle_maximize(struct view *view)
{
assert(view);
view_maximize(view, !view->maximized,
/*store_natural_geometry*/ true);
2021-08-02 16:49:41 +01:00
}
void
view_toggle_decorations(struct view *view)
{
assert(view);
view_set_decorations(view, !view->ssd_enabled);
}
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
}
}
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,
view == view->server->focused_view);
}
}
static void
undecorate(struct view *view)
{
ssd_destroy(view->ssd);
view->ssd = NULL;
}
void
view_set_decorations(struct view *view, bool decorations)
{
assert(view);
if (view->ssd_enabled != decorations && !view->fullscreen) {
/*
* Set view->ssd_enabled first since it is referenced
* within the call tree of ssd_create()
*/
view->ssd_enabled = decorations;
if (decorations) {
decorate(view);
} else {
undecorate(view);
}
view_apply_special_geometry(view);
}
}
void
view_toggle_fullscreen(struct view *view)
{
assert(view);
view_set_fullscreen(view, !view->fullscreen, NULL);
}
2021-08-23 22:05:30 +01:00
void
view_set_fullscreen(struct view *view, bool fullscreen,
struct wlr_output *wlr_output)
{
assert(view);
2022-04-04 20:53:36 +01:00
if (fullscreen != !view->fullscreen) {
2021-08-23 22:05:30 +01:00
return;
}
if (!wlr_output) {
wlr_output = view_wlr_output(view);
if (!wlr_output && fullscreen) {
/* Prevent fullscreen with no available outputs */
return;
}
}
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
}
if (view->toplevel_handle) {
wlr_foreign_toplevel_handle_v1_set_fullscreen(
view->toplevel_handle, fullscreen);
}
if (fullscreen) {
/*
* 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);
/* Hide decorations when going fullscreen */
if (view->ssd_enabled) {
undecorate(view);
}
2021-08-23 22:05:30 +01:00
view->fullscreen = wlr_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
view_apply_fullscreen_geometry(view, view->fullscreen);
2021-08-23 22:05:30 +01:00
} else {
view->fullscreen = false;
/* Re-show decorations when no longer fullscreen */
if (view->ssd_enabled) {
decorate(view);
}
/* Restore non-fullscreen geometry */
if (!view_apply_special_geometry(view)) {
view_apply_natural_geometry(view);
}
2021-08-23 22:05:30 +01:00
}
/* Show fullscreen views above top-layer */
struct output *output =
output_from_wlr_output(view->server, wlr_output);
if (!output) {
return;
}
uint32_t top = ZWLR_LAYER_SHELL_V1_LAYER_TOP;
wlr_scene_node_set_enabled(&output->layer_tree[top]->node, !fullscreen);
2021-08-23 22:05:30 +01:00
}
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);
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_output_layout *layout = view->server->output_layout;
if (view->fullscreen) {
if (wlr_output_layout_get(layout, view->fullscreen)) {
/* recompute fullscreen geometry */
view_apply_fullscreen_geometry(view, view->fullscreen);
} else {
/* output is gone, exit fullscreen */
view_set_fullscreen(view, false, NULL);
}
} else if (!view_apply_special_geometry(view)) {
/* reposition view if it's offscreen */
struct wlr_box box = { view->x, view->y, view->w, view->h };
if (!wlr_output_layout_intersects(layout, NULL, &box)) {
view_center(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
}
}
static void
view_output_enter(struct view *view, struct wlr_output *wlr_output)
{
if (view->toplevel_handle) {
wlr_foreign_toplevel_handle_v1_output_enter(
view->toplevel_handle, wlr_output);
}
}
static void
view_output_leave(struct view *view, struct wlr_output *wlr_output)
{
if (view->toplevel_handle) {
wlr_foreign_toplevel_handle_v1_output_leave(
view->toplevel_handle, wlr_output);
}
}
/*
* At present, a view can only 'enter' one output at a time, although the view
* may span multiple outputs. Ideally we would handle multiple outputs, but
* this method is the simplest form of what we want.
*/
void
view_discover_output(struct view *view)
{
assert(view);
struct output *old_output = view->output;
struct output *new_output = view_output(view);
if (old_output != new_output) {
view->output = new_output;
if (new_output) {
view_output_enter(view, new_output->wlr_output);
}
if (old_output) {
view_output_leave(view, old_output->wlr_output);
}
}
}
void
view_on_output_destroy(struct view *view)
{
assert(view);
view_output_leave(view, view->output->wlr_output);
view->output = NULL;
}
void
2021-07-20 20:24:39 +01:00
view_move_to_edge(struct view *view, const char *direction)
{
assert(view);
struct output *output = view_output(view);
if (!output) {
wlr_log(WLR_ERROR, "no output");
return;
}
if (!direction) {
wlr_log(WLR_ERROR, "invalid edge");
return;
}
struct border margin = ssd_get_margin(view->ssd);
struct wlr_box usable = output_usable_area_in_layout_coords(output);
2022-04-04 20:53:36 +01:00
if (usable.height == output->wlr_output->height
&& output->wlr_output->scale != 1) {
usable.height /= output->wlr_output->scale;
}
2022-04-04 20:53:36 +01:00
if (usable.width == output->wlr_output->width
&& output->wlr_output->scale != 1) {
usable.width /= output->wlr_output->scale;
}
int x = 0, y = 0;
if (!strcasecmp(direction, "left")) {
x = usable.x + margin.left + rc.gap;
y = view->y;
} else if (!strcasecmp(direction, "up")) {
x = view->x;
y = usable.y + margin.top + rc.gap;
} else if (!strcasecmp(direction, "right")) {
x = usable.x + usable.width - view->w - margin.right
- rc.gap;
y = view->y;
} else if (!strcasecmp(direction, "down")) {
x = view->x;
y = usable.y + usable.height - view->h - margin.bottom
- rc.gap;
} else {
wlr_log(WLR_ERROR, "invalid edge");
return;
}
view_move(view, x, y);
}
static 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;
}
}
void
view_snap_to_edge(struct view *view, const char *direction,
bool store_natural_geometry)
{
assert(view);
if (view->fullscreen) {
return;
}
struct output *output = view_output(view);
if (!output) {
wlr_log(WLR_ERROR, "no output");
return;
}
enum view_edge edge = view_edge_parse(direction);
if (edge == VIEW_EDGE_INVALID) {
wlr_log(WLR_ERROR, "invalid edge");
return;
}
if (view->tiled == edge && !view->maximized) {
/* We are already tiled for this edge and thus should switch outputs */
struct wlr_output *new_output = NULL;
struct wlr_output *current_output = output->wlr_output;
struct wlr_output_layout *layout = view->server->output_layout;
switch (edge) {
2021-11-26 18:49:44 +00:00
case VIEW_EDGE_LEFT:
new_output = wlr_output_layout_adjacent_output(
layout, WLR_DIRECTION_LEFT, current_output, 1, 0);
2021-11-26 18:49:44 +00:00
break;
case VIEW_EDGE_RIGHT:
new_output = wlr_output_layout_adjacent_output(
layout, WLR_DIRECTION_RIGHT, current_output, 1, 0);
2021-11-26 18:49:44 +00:00
break;
case VIEW_EDGE_UP:
new_output = wlr_output_layout_adjacent_output(
layout, WLR_DIRECTION_UP, current_output, 0, 1);
2021-11-26 18:49:44 +00:00
break;
case VIEW_EDGE_DOWN:
new_output = wlr_output_layout_adjacent_output(
layout, WLR_DIRECTION_DOWN, current_output, 0, 1);
2021-11-26 18:49:44 +00:00
break;
default:
break;
}
if (new_output && new_output != current_output) {
/* Move to next output */
edge = view_edge_invert(edge);
output = output_from_wlr_output(view->server, new_output);
} else {
/*
* 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.
*
* TODO: The described pattern will cause another bug
* in multi monitor setups: it will snap the
* window to the inverted edge of the nearest
* output. This is the desired behavior when
* caused by a keybind but doesn't make sense
* when caused by mouse movement.
*/
view_apply_tiled_geometry(view, output);
return;
}
}
2022-07-01 20:44:40 +02:00
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);
2022-07-01 20:44:40 +02:00
}
view_set_untiled(view);
view->tiled = edge;
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);
}
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");
if (!view->toplevel_handle || !title) {
return;
}
ssd_update_title(view->ssd);
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");
if (!view->toplevel_handle || !app_id) {
return;
}
wlr_foreign_toplevel_handle_v1_set_app_id(
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_destroy(struct view *view)
{
assert(view);
struct server *server = view->server;
bool need_cursor_update = false;
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;
need_cursor_update = true;
}
if (server->focused_view == view) {
server->focused_view = NULL;
need_cursor_update = true;
}
if (server->seat.pressed.view == view) {
seat_reset_pressed(&server->seat);
}
osd_on_view_destroy(view);
undecorate(view);
if (view->scene_tree) {
wlr_scene_node_destroy(&view->scene_tree->node);
view->scene_tree = NULL;
}
/*
* The layer-shell top-layer is disabled when an application is running
* in fullscreen mode, so if that's the case, we have to re-enable it
* here.
*/
if (view->fullscreen) {
struct output *output =
2022-06-09 16:38:17 +02:00
output_from_wlr_output(server, view->fullscreen);
uint32_t top = ZWLR_LAYER_SHELL_V1_LAYER_TOP;
wlr_scene_node_set_enabled(&output->layer_tree[top]->node, true);
}
/* If we spawned a window menu, close it */
if (server->menu_current
&& server->menu_current->triggered_by_view == view) {
menu_close_root(server);
}
/* Remove view from server->views */
wl_list_remove(&view->link);
free(view);
if (need_cursor_update) {
cursor_update_focus(server);
}
}