From 19ba61a834244020314926780d70e93abb0e5d72 Mon Sep 17 00:00:00 2001 From: stormshadow <190884359+st0rm-shad0w@users.noreply.github.com> Date: Wed, 6 May 2026 01:35:11 +0530 Subject: [PATCH 1/2] view: clamp position to prevent windows moving fully off-screen Add overshoot detection based on squared distance of window corners from the output layout. view_move_relative() now only permits movement that brings windows closer to being fully inside the layout, and recursively halves the step size to nudge windows flush against the edge. --- src/view.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/view.c b/src/view.c index bcbd366e..6c8b6406 100644 --- a/src/view.c +++ b/src/view.c @@ -609,6 +609,33 @@ view_resize_relative(struct view *view, int left, int right, int top, int bottom view_set_untiled(view); } +/* + * Returns the total squared distance of the box's four corners from the + * output layout. A return value of 0 means the box is fully inside. + */ +static double +box_layout_overshoot(struct wlr_box *box) +{ + struct wlr_output_layout *layout = server.output_layout; + double overshoot = 0; + double corners[4][2] = { + {box->x, box->y}, + {box->x + box->width - 1, box->y}, + {box->x, box->y + box->height - 1}, + {box->x + box->width - 1, box->y + box->height - 1}, + }; + for (int i = 0; i < 4; i++) { + double closest_x, closest_y; + wlr_output_layout_closest_point(layout, NULL, + corners[i][0], corners[i][1], + &closest_x, &closest_y); + double dx = corners[i][0] - closest_x; + double dy = corners[i][1] - closest_y; + overshoot += dx * dx + dy * dy; + } + return overshoot; +} + void view_move_relative(struct view *view, int x, int y) { @@ -621,6 +648,40 @@ view_move_relative(struct view *view, int x, int y) view_set_untiled(view); view_move_resize(view, view->natural_geometry); } + + /* + * Only proceed if movement brings the window closer to being fully + * inside the output layout. This allows a partially-outside window + * to be moved back inside, while still preventing a fully-inside + * window from being moved outside. + */ + struct border border = ssd_thickness(view); + struct wlr_box current_box = { + .x = view->pending.x - border.left, + .y = view->pending.y - border.top, + .width = view->pending.width + border.left + border.right, + .height = view->pending.height + border.top + border.bottom, + }; + struct wlr_box dest = current_box; + dest.x += x; + dest.y += y; + double current_overshoot = box_layout_overshoot(¤t_box); + double dest_overshoot = box_layout_overshoot(&dest); + if (dest_overshoot > 0 && dest_overshoot >= current_overshoot) { + if (abs(x) > 1 || abs(y) > 1) { + /* + * It is reasonable to assume that users will specify + * big numbers (like 20) for the MoveRelative x or y + * value. When moving towards a layout outer edge, we + * would not want the window to stop a distance away + * from the edge, so recursively nudge it nearer until + * it is touching. + */ + view_move_relative(view, x / 2, y / 2); + } + return; + } + view_move(view, view->pending.x + x, view->pending.y + y); } From 286d241c4335e65f238dc8c04f93e7c18efd0475 Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Fri, 8 May 2026 22:07:13 +0100 Subject: [PATCH 2/2] [fixup] including the following... 1. Use `struct point corners[]` instead of double corners[4][2] 2. Use ARRAY_SIZE(corners) 3. Ignore any corners inside the output-layout area 4. Allow movement when dest_overshoot == current_overshoot so that movement along an output edge can take place. --- src/view.c | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/src/view.c b/src/view.c index 6c8b6406..ec461c53 100644 --- a/src/view.c +++ b/src/view.c @@ -12,6 +12,7 @@ #include "buffer.h" #include "common/box.h" #include "common/list.h" +#include "common/macros.h" #include "common/match.h" #include "common/mem.h" #include "common/string-helpers.h" @@ -609,6 +610,10 @@ view_resize_relative(struct view *view, int left, int right, int top, int bottom view_set_untiled(view); } +struct point { + int x, y; +}; + /* * Returns the total squared distance of the box's four corners from the * output layout. A return value of 0 means the box is fully inside. @@ -618,19 +623,35 @@ box_layout_overshoot(struct wlr_box *box) { struct wlr_output_layout *layout = server.output_layout; double overshoot = 0; - double corners[4][2] = { - {box->x, box->y}, - {box->x + box->width - 1, box->y}, - {box->x, box->y + box->height - 1}, - {box->x + box->width - 1, box->y + box->height - 1}, + struct point corners[] = { { + .x = box->x, + .y = box->y, + }, { + .x = box->x + box->width - 1, + .y = box->y, + }, { + .x = box->x, + .y = box->y + box->height - 1, + }, { + .x = box->x + box->width - 1, + .y = box->y + box->height - 1, + }, }; - for (int i = 0; i < 4; i++) { + + for (size_t i = 0; i < ARRAY_SIZE(corners); i++) { + struct point corner = corners[i]; + + /* We only care about corners outside the output-layout */ + if (wlr_output_layout_output_at(layout, corner.x, corner.y)) { + continue; + } + double closest_x, closest_y; - wlr_output_layout_closest_point(layout, NULL, - corners[i][0], corners[i][1], + wlr_output_layout_closest_point(layout, NULL, corner.x, corner.y, &closest_x, &closest_y); - double dx = corners[i][0] - closest_x; - double dy = corners[i][1] - closest_y; + wlr_log(WLR_ERROR, "closest-x=%f; closest-y=%f", closest_x, closest_y); + double dx = corner.x - closest_x; + double dy = corner.y - closest_y; overshoot += dx * dx + dy * dy; } return overshoot; @@ -667,7 +688,7 @@ view_move_relative(struct view *view, int x, int y) dest.y += y; double current_overshoot = box_layout_overshoot(¤t_box); double dest_overshoot = box_layout_overshoot(&dest); - if (dest_overshoot > 0 && dest_overshoot >= current_overshoot) { + if (dest_overshoot > 0 && dest_overshoot > current_overshoot) { if (abs(x) > 1 || abs(y) > 1) { /* * It is reasonable to assume that users will specify