From f0b0606e9f6a9ffe4a05d26269508d860a97fc13 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sat, 10 Feb 2024 14:17:29 +0100 Subject: [PATCH] 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 | 112 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 78 insertions(+), 34 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; + 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); + } - 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 surface_clip: wlr.Box = undefined; + _ = surface_clip.intersection(box, &output_box); - 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); + // The clip is applied relative to the root node of the subsurface tree. + surface_clip.x -= box.x; + surface_clip.y -= box.y; - 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); + 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.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); + if (!view.surface_tree.children.empty()) { + view.surface_tree.node.subsurfaceTreeSetClip(&surface_clip); + } + } + + { + 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.