git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit52a041a3fad1b4ae7711cdf18003144a1fcd2ea3
parent0093d9b03d
authorIsaac Freund <[email protected]>
date2024-02-10 14:17
View: clip scene tree to output

There are some cases in which a view can end up with a size/position
that places some part of it outside its output. For example, a window
with a large minimum size in a tiled layout that is placed near the
right or bottom edge of the output may extend past the output bounds.

The problem with this is that part of the view outside the output bounds
may be rendered on another output where it does not belong in a multi-
monitor setup.

To fix this, clip the surfaces of the view and the borders to the output
bounds.

 river/View.zig | 114 +++++++++++++++++++++++++++++++++++++++------------------
 1 file changed, 79 insertions(+), 35 deletions(-)

diff --git a/river/View.zig b/river/View.zig
index fdccecd..c3ca0b7 100644
--- a/river/View.zig
+++ b/river/View.zig
@@ -123,12 +123,8 @@ link: wl.list.Link,
 tree: *wlr.SceneTree,
 surface_tree: *wlr.SceneTree,
 saved_surface_tree: *wlr.SceneTree,
-borders: struct {
-    left: *wlr.SceneRect,
-    right: *wlr.SceneRect,
-    top: *wlr.SceneRect,
-    bottom: *wlr.SceneRect,
-},
+/// Order is left, right, top, bottom
+borders: [4]*wlr.SceneRect,
 popup_tree: *wlr.SceneTree,
 
 /// Bounds on the width/height of the view, set by the xdg_toplevel/xwayland_view implementation.
@@ -190,10 +186,10 @@ pub fn create(impl: Impl) error{OutOfMemory}!*Self {
         .surface_tree = try tree.createSceneTree(),
         .saved_surface_tree = try tree.createSceneTree(),
         .borders = .{
-            .left = try tree.createSceneRect(0, 0, &server.config.border_color_unfocused),
-            .right = try tree.createSceneRect(0, 0, &server.config.border_color_unfocused),
-            .top = try tree.createSceneRect(0, 0, &server.config.border_color_unfocused),
-            .bottom = try tree.createSceneRect(0, 0, &server.config.border_color_unfocused),
+            try tree.createSceneRect(0, 0, &server.config.border_color_unfocused),
+            try tree.createSceneRect(0, 0, &server.config.border_color_unfocused),
+            try tree.createSceneRect(0, 0, &server.config.border_color_unfocused),
+            try tree.createSceneRect(0, 0, &server.config.border_color_unfocused),
         },
         .popup_tree = popup_tree,
 
@@ -317,38 +313,86 @@ pub fn updateCurrent(view: *Self) void {
     view.current = view.inflight;
     view.dropSavedSurfaceTree();
 
-    const color = blk: {
-        if (view.current.urgent) break :blk &config.border_color_urgent;
-        if (view.current.focus != 0) break :blk &config.border_color_focused;
-        break :blk &config.border_color_unfocused;
-    };
-
     const box = &view.current.box;
     view.tree.node.setPosition(box.x, box.y);
     view.popup_tree.node.setPosition(box.x, box.y);
 
-    const enable_borders = view.current.ssd and !view.current.fullscreen;
-
-    const border_width: c_int = config.border_width;
-    view.borders.left.node.setEnabled(enable_borders);
-    view.borders.left.node.setPosition(-border_width, -border_width);
-    view.borders.left.setSize(border_width, box.height + 2 * border_width);
-    view.borders.left.setColor(color);
+    var output_box: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 };
+    if (view.current.output) |output| {
+        output.wlr_output.effectiveResolution(&output_box.width, &output_box.height);
+    }
 
-    view.borders.right.node.setEnabled(enable_borders);
-    view.borders.right.node.setPosition(box.width, -border_width);
-    view.borders.right.setSize(border_width, box.height + 2 * border_width);
-    view.borders.right.setColor(color);
+    {
+        var surface_clip: wlr.Box = undefined;
+        _ = surface_clip.intersection(box, &output_box);
+
+        // The clip is applied relative to the root node of the subsurface tree.
+        surface_clip.x -= box.x;
+        surface_clip.y -= box.y;
+
+        switch (view.impl) {
+            .xdg_toplevel => |xdg_toplevel| {
+                surface_clip.x += xdg_toplevel.geometry.x;
+                surface_clip.y += xdg_toplevel.geometry.y;
+            },
+            .xwayland_view, .none => {},
+        }
 
-    view.borders.top.node.setEnabled(enable_borders);
-    view.borders.top.node.setPosition(0, -border_width);
-    view.borders.top.setSize(box.width, border_width);
-    view.borders.top.setColor(color);
+        if (!view.surface_tree.children.empty()) {
+            view.surface_tree.node.subsurfaceTreeSetClip(&surface_clip);
+        }
+    }
 
-    view.borders.bottom.node.setEnabled(enable_borders);
-    view.borders.bottom.node.setPosition(0, box.height);
-    view.borders.bottom.setSize(box.width, border_width);
-    view.borders.bottom.setColor(color);
+    {
+        const border_width: c_int = config.border_width;
+        const border_color = blk: {
+            if (view.current.urgent) break :blk &config.border_color_urgent;
+            if (view.current.focus != 0) break :blk &config.border_color_focused;
+            break :blk &config.border_color_unfocused;
+        };
+
+        // Order is left, right, top, bottom
+        // left and right borders include the corners, top and bottom do not.
+        var border_boxes = [4]wlr.Box{
+            .{
+                .x = -border_width,
+                .y = -border_width,
+                .width = border_width,
+                .height = box.height + 2 * border_width,
+            },
+            .{
+                .x = box.width,
+                .y = -border_width,
+                .width = border_width,
+                .height = box.height + 2 * border_width,
+            },
+            .{
+                .x = 0,
+                .y = -border_width,
+                .width = box.width,
+                .height = border_width,
+            },
+            .{
+                .x = 0,
+                .y = box.height,
+                .width = box.width,
+                .height = border_width,
+            },
+        };
+
+        for (&view.borders, &border_boxes) |border, *border_box| {
+            border_box.x += box.x;
+            border_box.y += box.y;
+            _ = border_box.intersection(border_box, &output_box);
+            border_box.x -= box.x;
+            border_box.y -= box.y;
+
+            border.node.setEnabled(view.current.ssd and !view.current.fullscreen);
+            border.node.setPosition(border_box.x, border_box.y);
+            border.setSize(border_box.width, border_box.height);
+            border.setColor(border_color);
+        }
+    }
 }
 
 /// Returns true if the configure should be waited for by the transaction system.