368 lines
14 KiB
Diff
368 lines
14 KiB
Diff
--- a/sway/desktop/transaction.c
|
|
+++ b/sway/desktop/transaction.c
|
|
@@ -1,3 +1,4 @@
|
|
+#include <math.h>
|
|
#include <assert.h>
|
|
#include <stdbool.h>
|
|
#include <limits.h>
|
|
@@ -869,12 +870,37 @@
|
|
width, height, ws->gaps_inner);
|
|
}
|
|
|
|
+// Camera conversion is validated before any double-to-integer cast.
|
|
+static int probe_camera_scroll(double scroll) {
|
|
+ if (!isfinite(scroll) || scroll < 0) return 0;
|
|
+ return scroll > INT_MAX ? INT_MAX : (int)scroll;
|
|
+}
|
|
+
|
|
+static void probe_workspace_clip(struct sway_workspace *ws,
|
|
+ struct wlr_box *area, struct side_gaps *gaps) {
|
|
+ if (ws->current.layout != L_MOSAIC || !ws->mosaic) {
|
|
+ wlr_scene_tree_set_clip(ws->layers.tiling, NULL);
|
|
+ return;
|
|
+ }
|
|
+ struct mosaic_row *row = ws->mosaic->rows->items[0];
|
|
+ int64_t width = (int64_t)area->width - gaps->left - gaps->right;
|
|
+ int64_t height = (int64_t)area->height - gaps->top - gaps->bottom;
|
|
+ struct wlr_box clip = {0};
|
|
+ if (isfinite(row->state.scroll_x) && row->state.scroll_x >= 0 &&
|
|
+ row->state.scroll_x <= INT_MAX && width > 0 && width <= INT_MAX &&
|
|
+ height > 0 && height <= INT_MAX) {
|
|
+ clip = (struct wlr_box){probe_camera_scroll(row->state.scroll_x), 0,
|
|
+ (int)width, (int)height};
|
|
+ }
|
|
+ wlr_scene_tree_set_clip(ws->layers.tiling, &clip);
|
|
+}
|
|
+
|
|
static int workspace_tiling_scene_x(struct sway_workspace *ws,
|
|
struct wlr_box *area, struct side_gaps *gaps) {
|
|
int64_t x = (int64_t)area->x + gaps->left;
|
|
if (ws->current.layout == L_MOSAIC && ws->mosaic) {
|
|
struct mosaic_row *row = ws->mosaic->rows->items[0];
|
|
- x -= (int64_t)row->state.scroll_x;
|
|
+ x -= probe_camera_scroll(row->state.scroll_x);
|
|
}
|
|
// The camera is kept in logical pixels; this conversion is the sole
|
|
// output-scene mapping and must never overflow wlroots' int positions.
|
|
@@ -974,6 +1000,7 @@
|
|
wlr_scene_node_set_position(&child->layers.tiling->node,
|
|
workspace_tiling_scene_x(child, area, gaps),
|
|
gaps->top + area->y);
|
|
+ probe_workspace_clip(child, area, gaps);
|
|
|
|
arrange_workspace_tiling(child,
|
|
area->width - gaps->left - gaps->right,
|
|
@@ -992,6 +1019,8 @@
|
|
workspace_tiling_scene_x(child, area, gaps),
|
|
gaps->top + area->y);
|
|
|
|
+ probe_workspace_clip(child, area, gaps);
|
|
+
|
|
arrange_workspace_tiling(child,
|
|
area->width - gaps->left - gaps->right,
|
|
area->height - gaps->top - gaps->bottom);
|
|
@@ -1006,6 +1035,118 @@
|
|
output->prev_active_workspace = new_active;
|
|
}
|
|
|
|
+// WHY: disjoint extreme endpoints must be rejected before subtraction.
|
|
+// CONTRACT: int64 intermediates; zero on empty; output may alias either input.
|
|
+// INVARIANT: no overflow is delegated to the historical wlroots box helper.
|
|
+static bool probe_clip_box_intersection(struct wlr_box *dest,
|
|
+ const struct wlr_box *a, const struct wlr_box *b) {
|
|
+ int64_t x1 = a->x > b->x ? a->x : b->x;
|
|
+ int64_t y1 = a->y > b->y ? a->y : b->y;
|
|
+ int64_t ax2 = (int64_t)a->x + a->width, bx2 = (int64_t)b->x + b->width;
|
|
+ int64_t ay2 = (int64_t)a->y + a->height, by2 = (int64_t)b->y + b->height;
|
|
+ int64_t x2 = ax2 < bx2 ? ax2 : bx2, y2 = ay2 < by2 ? ay2 : by2;
|
|
+ if (a->width <= 0 || a->height <= 0 || b->width <= 0 || b->height <= 0 ||
|
|
+ x2 <= x1 || y2 <= y1 || x2 - x1 > INT_MAX || y2 - y1 > INT_MAX) {
|
|
+ *dest = (struct wlr_box){0};
|
|
+ return false;
|
|
+ }
|
|
+ *dest = (struct wlr_box){(int)x1, (int)y1, (int)(x2-x1), (int)(y2-y1)};
|
|
+ return true;
|
|
+}
|
|
+
|
|
+// Experimental: derive a root popup clip from its actual tiled owner and the
|
|
+// already-applied Mosaic tiling clip. This avoids pending XDG geometry as a
|
|
+// source for the visible viewport; no owner pointer is stored on the clip.
|
|
+static void probe_clip_popup(struct wlr_scene_node *node,
|
|
+ struct sway_popup_desc *popup) {
|
|
+ struct wlr_scene_tree *popup_tree = wlr_scene_tree_from_node(node);
|
|
+ struct sway_container *con = popup->view ? popup->view->container : NULL;
|
|
+ struct sway_workspace *ws = con ? con->current.workspace : NULL;
|
|
+ if (!ws || ws->current.layout != L_MOSAIC || !ws->mosaic) {
|
|
+ wlr_scene_tree_set_clip(popup_tree, NULL);
|
|
+ return;
|
|
+ }
|
|
+ struct sway_container *owner = con;
|
|
+ bool fullscreen = false;
|
|
+ for (struct sway_container *ancestor = con; ancestor;
|
|
+ ancestor = ancestor->current.parent) {
|
|
+ fullscreen |= ancestor->current.fullscreen_mode != 0;
|
|
+ owner = ancestor;
|
|
+ }
|
|
+ if (!ws->current.tiling || list_find(ws->current.tiling, owner) < 0 || fullscreen) {
|
|
+ wlr_scene_tree_set_clip(popup_tree, NULL);
|
|
+ return;
|
|
+ }
|
|
+ struct wlr_box local_viewport;
|
|
+ struct sway_output *output = ws->current.output;
|
|
+ int tile_x, tile_y, popup_x, popup_y;
|
|
+ if (!output || output->current.active_workspace != ws ||
|
|
+ !wlr_scene_tree_get_clip(ws->layers.tiling, &local_viewport) ||
|
|
+ !wlr_scene_node_coords(&ws->layers.tiling->node, &tile_x, &tile_y) ||
|
|
+ !wlr_scene_node_coords(node, &popup_x, &popup_y)) {
|
|
+ struct wlr_box empty = {0};
|
|
+ wlr_scene_tree_set_clip(popup_tree, &empty);
|
|
+ return;
|
|
+ }
|
|
+ struct wlr_box global = local_viewport;
|
|
+ int64_t gx = (int64_t)tile_x + global.x;
|
|
+ int64_t gy = (int64_t)tile_y + global.y;
|
|
+ if (gx < INT_MIN || gx > INT_MAX || gy < INT_MIN || gy > INT_MAX) {
|
|
+ struct wlr_box empty = {0};
|
|
+ wlr_scene_tree_set_clip(popup_tree, &empty);
|
|
+ return;
|
|
+ }
|
|
+ global.x = (int)gx;
|
|
+ global.y = (int)gy;
|
|
+ struct wlr_box output_box = {
|
|
+ .x = output->lx, .y = output->ly,
|
|
+ .width = output->width, .height = output->height,
|
|
+ };
|
|
+ if (!probe_clip_box_intersection(&global, &global, &output_box)) {
|
|
+ struct wlr_box empty = {0};
|
|
+ wlr_scene_tree_set_clip(popup_tree, &empty);
|
|
+ return;
|
|
+ }
|
|
+ // EXPERIMENTAL INVARIANT: the viewport alone does not imply that its
|
|
+ // popup owner is visible. Use the already-applied scene position and size,
|
|
+ // not pending layout geometry, to suppress a fully hidden owner's popup.
|
|
+ int owner_x = INT_MIN, owner_y = INT_MIN;
|
|
+ struct wlr_box owner_visible;
|
|
+ if (!wlr_scene_node_coords(&con->scene_tree->node, &owner_x, &owner_y) ||
|
|
+ con->animation_state.current_width <= 0 ||
|
|
+ con->animation_state.current_height <= 0 ||
|
|
+ !probe_clip_box_intersection(&owner_visible, &global,
|
|
+ &(struct wlr_box){
|
|
+ .x = owner_x, .y = owner_y,
|
|
+ .width = con->animation_state.current_width,
|
|
+ .height = con->animation_state.current_height,
|
|
+ })) {
|
|
+ struct wlr_box empty = {0};
|
|
+ wlr_scene_tree_set_clip(popup_tree, &empty);
|
|
+ // Sampled applied geometry only; logging timing neutrality NOT_PROVEN.
|
|
+ sway_log(SWAY_INFO, "PROBE_POPUP_HIDDEN_OWNER owner=%p applied=%d,%d %dx%d viewport=%d,%d %dx%d",
|
|
+ (void *)con, owner_x, owner_y,
|
|
+ con->animation_state.current_width,
|
|
+ con->animation_state.current_height,
|
|
+ global.x, global.y, global.width, global.height);
|
|
+ return;
|
|
+ }
|
|
+ int64_t px = (int64_t)global.x - popup_x;
|
|
+ int64_t py = (int64_t)global.y - popup_y;
|
|
+ if (px < INT_MIN || px > INT_MAX || py < INT_MIN || py > INT_MAX) {
|
|
+ struct wlr_box empty = {0};
|
|
+ wlr_scene_tree_set_clip(popup_tree, &empty);
|
|
+ return;
|
|
+ }
|
|
+ global.x = (int)px;
|
|
+ global.y = (int)py;
|
|
+ wlr_scene_tree_set_clip(popup_tree, &global);
|
|
+ // WHY/CONTRACT: preserve sampled applied clip evidence, no GPU identity.
|
|
+ // INVARIANT: this trace does not prove generation or timing neutrality.
|
|
+ sway_log(SWAY_INFO, "PROBE_POPUP_CLIP owner=%p local=%d,%d %dx%d",
|
|
+ (void *)con, global.x, global.y, global.width, global.height);
|
|
+}
|
|
+
|
|
void arrange_popups(struct wlr_scene_tree *popups) {
|
|
struct wlr_scene_node *node;
|
|
wl_list_for_each(node, &popups->children, link) {
|
|
@@ -1016,6 +1157,7 @@
|
|
int lx, ly;
|
|
wlr_scene_node_coords(popup->relative, &lx, &ly);
|
|
wlr_scene_node_set_position(node, lx, ly);
|
|
+ probe_clip_popup(node, popup);
|
|
}
|
|
}
|
|
}
|
|
--- a/sway/desktop/xdg_shell.c
|
|
+++ b/sway/desktop/xdg_shell.c
|
|
@@ -117,6 +117,10 @@
|
|
|
|
popup->image_capture_tree = wlr_scene_xdg_surface_create(image_capture_parent, xdg_surface);
|
|
if (popup->image_capture_tree == NULL) {
|
|
+ // WHY: helper admission can fail after primary tree/descriptor creation.
|
|
+ // CONTRACT/INVARIANT: rollback owns both primary subtree and popup.
|
|
+ wlr_scene_node_destroy(&popup->scene_tree->node);
|
|
+ free(popup);
|
|
return NULL;
|
|
}
|
|
|
|
@@ -380,6 +384,7 @@
|
|
int lx, ly;
|
|
wlr_scene_node_coords(&popup->view->content_tree->node, &lx, &ly);
|
|
wlr_scene_node_set_position(&popup->scene_tree->node, lx, ly);
|
|
+ arrange_popups(root->layers.popup);
|
|
}
|
|
|
|
static void handle_request_maximize(struct wl_listener *listener, void *data) {
|
|
--- a/sway/input/seat.c
|
|
+++ b/sway/input/seat.c
|
|
@@ -520,7 +520,8 @@
|
|
seat->scene_tree = alloc_scene_tree(root->layers.seat, &failed);
|
|
seat->drag_icons = alloc_scene_tree(seat->scene_tree, &failed);
|
|
if (failed) {
|
|
- wlr_scene_node_destroy(&seat->scene_tree->node);
|
|
+ // Factory admission may fail before the top-level tree exists.
|
|
+ wlr_scene_node_destroy(seat->scene_tree ? &seat->scene_tree->node : NULL);
|
|
free(seat);
|
|
return NULL;
|
|
}
|
|
--- a/sway/input/text_input.c
|
|
+++ b/sway/input/text_input.c
|
|
@@ -368,6 +368,7 @@
|
|
static void input_popup_set_focus(struct sway_input_popup *popup,
|
|
struct wlr_surface *surface) {
|
|
wl_list_remove(&popup->focused_surface_unmap.link);
|
|
+ wl_list_init(&popup->focused_surface_unmap.link);
|
|
|
|
if (!popup->scene_tree) {
|
|
wl_list_init(&popup->focused_surface_unmap.link);
|
|
@@ -380,6 +381,13 @@
|
|
popup->desc.relative = NULL;
|
|
}
|
|
|
|
+ // WHY: replacement may fail after old relative node/descriptor retirement.
|
|
+ // CONTRACT: failed refocus leaves a disabled wrapper without stale focus.
|
|
+ // INVARIANT: only a complete relative node and descriptor re-enable it.
|
|
+ wlr_scene_node_set_enabled(&popup->scene_tree->node, false);
|
|
+ popup->desc.view = NULL;
|
|
+ popup->fixed_output = NULL;
|
|
+
|
|
if (surface == NULL) {
|
|
wl_list_init(&popup->focused_surface_unmap.link);
|
|
wlr_scene_node_set_enabled(&popup->scene_tree->node, false);
|
|
@@ -439,12 +447,23 @@
|
|
}
|
|
|
|
struct wlr_scene_tree *relative = wlr_scene_tree_create(relative_parent);
|
|
+ if (relative == NULL) {
|
|
+ wl_list_remove(&popup->focused_surface_unmap.link);
|
|
+ wl_list_init(&popup->focused_surface_unmap.link);
|
|
+ popup->desc.view = NULL;
|
|
+ popup->fixed_output = NULL;
|
|
+ return;
|
|
+ }
|
|
|
|
popup->desc.relative = &relative->node;
|
|
if (!scene_descriptor_assign(&popup->scene_tree->node,
|
|
SWAY_SCENE_DESC_POPUP, &popup->desc)) {
|
|
- wlr_scene_node_destroy(&popup->scene_tree->node);
|
|
- popup->scene_tree = NULL;
|
|
+ wlr_scene_node_destroy(&relative->node);
|
|
+ popup->desc.relative = NULL;
|
|
+ popup->desc.view = NULL;
|
|
+ popup->fixed_output = NULL;
|
|
+ wl_list_remove(&popup->focused_surface_unmap.link);
|
|
+ wl_list_init(&popup->focused_surface_unmap.link);
|
|
return;
|
|
}
|
|
|
|
@@ -515,7 +534,6 @@
|
|
|
|
popup->relay = relay;
|
|
popup->popup_surface = data;
|
|
- popup->popup_surface->data = popup;
|
|
|
|
popup->scene_tree = wlr_scene_tree_create(root->layers.popup);
|
|
if (!popup->scene_tree) {
|
|
@@ -532,6 +550,10 @@
|
|
return;
|
|
}
|
|
|
|
+ // WHY: failed factory/helper admission frees popup before listeners exist.
|
|
+ // CONTRACT/INVARIANT: publish protocol back-pointer only after admission.
|
|
+ popup->popup_surface->data = popup;
|
|
+
|
|
wl_signal_add(&popup->popup_surface->events.destroy, &popup->popup_destroy);
|
|
popup->popup_destroy.notify = handle_im_popup_destroy;
|
|
wl_signal_add(&popup->popup_surface->surface->events.commit, &popup->popup_surface_commit);
|
|
--- a/sway/tree/container.c
|
|
+++ b/sway/tree/container.c
|
|
@@ -93,7 +93,7 @@
|
|
|
|
c->dim_rect = alloc_rect_node(c->border.tree, &failed);
|
|
// Disable rect input
|
|
- c->dim_rect->accepts_input = false;
|
|
+ if (c->dim_rect) c->dim_rect->accepts_input = false;
|
|
}
|
|
|
|
if (!failed && !scene_descriptor_assign(&c->scene_tree->node,
|
|
@@ -102,7 +102,8 @@
|
|
}
|
|
|
|
if (failed) {
|
|
- wlr_scene_node_destroy(&c->scene_tree->node);
|
|
+ // Failed factory admission may leave the top-level tree NULL.
|
|
+ wlr_scene_node_destroy(c->scene_tree ? &c->scene_tree->node : NULL);
|
|
free(c);
|
|
return NULL;
|
|
}
|
|
--- a/sway/tree/output.c
|
|
+++ b/sway/tree/output.c
|
|
@@ -89,19 +89,19 @@
|
|
}
|
|
|
|
static void destroy_scene_layers(struct sway_output *output) {
|
|
- wlr_scene_node_destroy(&output->fullscreen_background->node);
|
|
+ wlr_scene_node_destroy(output->fullscreen_background ? &output->fullscreen_background->node : NULL);
|
|
|
|
scene_node_disown_children(output->layers.tiling);
|
|
scene_node_disown_children(output->layers.fullscreen);
|
|
|
|
- wlr_scene_node_destroy(&output->layers.shell_background->node);
|
|
- wlr_scene_node_destroy(&output->layers.shell_bottom->node);
|
|
- wlr_scene_node_destroy(&output->layers.blur_layer->node);
|
|
- wlr_scene_node_destroy(&output->layers.tiling->node);
|
|
- wlr_scene_node_destroy(&output->layers.fullscreen->node);
|
|
- wlr_scene_node_destroy(&output->layers.shell_top->node);
|
|
- wlr_scene_node_destroy(&output->layers.shell_overlay->node);
|
|
- wlr_scene_node_destroy(&output->layers.session_lock->node);
|
|
+ wlr_scene_node_destroy(output->layers.shell_background ? &output->layers.shell_background->node : NULL);
|
|
+ wlr_scene_node_destroy(output->layers.shell_bottom ? &output->layers.shell_bottom->node : NULL);
|
|
+ wlr_scene_node_destroy(output->layers.blur_layer ? &output->layers.blur_layer->node : NULL);
|
|
+ wlr_scene_node_destroy(output->layers.tiling ? &output->layers.tiling->node : NULL);
|
|
+ wlr_scene_node_destroy(output->layers.fullscreen ? &output->layers.fullscreen->node : NULL);
|
|
+ wlr_scene_node_destroy(output->layers.shell_top ? &output->layers.shell_top->node : NULL);
|
|
+ wlr_scene_node_destroy(output->layers.shell_overlay ? &output->layers.shell_overlay->node : NULL);
|
|
+ wlr_scene_node_destroy(output->layers.session_lock ? &output->layers.session_lock->node : NULL);
|
|
}
|
|
|
|
struct sway_output *output_create(struct wlr_output *wlr_output) {
|
|
--- a/sway/tree/view.c
|
|
+++ b/sway/tree/view.c
|
|
@@ -114,7 +114,7 @@
|
|
return true;
|
|
|
|
err:
|
|
- wlr_scene_node_destroy(&view->scene_tree->node);
|
|
+ wlr_scene_node_destroy(view->scene_tree ? &view->scene_tree->node : NULL);
|
|
return false;
|
|
}
|
|
|
|
--- a/sway/tree/workspace.c
|
|
+++ b/sway/tree/workspace.c
|
|
@@ -208,8 +208,8 @@
|
|
ws->layers.fullscreen = alloc_scene_tree(root->staging, &failed);
|
|
|
|
if (failed) {
|
|
- wlr_scene_node_destroy(&ws->layers.tiling->node);
|
|
- wlr_scene_node_destroy(&ws->layers.fullscreen->node);
|
|
+ wlr_scene_node_destroy(ws->layers.tiling ? &ws->layers.tiling->node : NULL);
|
|
+ wlr_scene_node_destroy(ws->layers.fullscreen ? &ws->layers.fullscreen->node : NULL);
|
|
wlr_ext_workspace_handle_v1_destroy(ws->ext_workspace);
|
|
free(ws);
|
|
return NULL;
|