Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
protocol: add river_window_v1.set_content_clip_box
protocol/river-window-management-v1.xml | 28 +++++
river/Decoration.zig | 2 +-
river/Scene.zig | 56 ++++++----
river/ShellSurface.zig | 2 +-
river/Window.zig | 192 +++++++++++++++++++-------------
5 files changed, 181 insertions(+), 99 deletions(-)
diff --git a/protocol/river-window-management-v1.xml b/protocol/river-window-management-v1.xml
index 60296ac..d005671 100644
--- a/protocol/river-window-management-v1.xml
+++ b/protocol/river-window-management-v1.xml
@@ -965,6 +965,8 @@
Setting a clip box with 0 width or height disables clipping.
+ Both set_clip_box and set_content_clip_box may be enabled simultaneously.
+
This request modifies rendering state and may only be made as part of a
render sequence, see the river_window_manager_v1 description.
</description>
@@ -989,6 +991,32 @@
</description>
<arg name="unreliable_pid" type="int"/>
</event>
+
+ <request name="set_content_clip_box" since="3">
+ <description summary="clip the window content to a given box">
+ Clip the content of the window, excluding borders and decoration
+ surfaces, to the box specified by the x, y, width, and height arguments.
+ The x/y position of the box is relative to the top left corner of the
+ window.
+
+ Borders drawn by the compositor (see set_borders) are placed around the
+ intersection of the window content (as defined by the dimensions event)
+ and the content clip box when content clipping is enabled.
+
+ The width and height arguments must be greater than or equal to 0.
+
+ Setting a clip box with 0 width or height disables content clipping.
+
+ Both set_clip_box and set_content_clip_box may be enabled simultaneously.
+
+ This request modifies rendering state and may only be made as part of a
+ render sequence, see the river_window_manager_v1 description.
+ </description>
+ <arg name="x" type="int"/>
+ <arg name="y" type="int"/>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
</interface>
<interface name="river_decoration_v1" version="3">
diff --git a/river/Decoration.zig b/river/Decoration.zig
index 4e432ce..34538d0 100644
--- a/river/Decoration.zig
+++ b/river/Decoration.zig
@@ -142,7 +142,7 @@ pub fn renderFinish(decoration: *Decoration, window_clip: *const wlr.Box) void {
if (rendering_requested.sync_next_commit) {
rendering_requested.sync_next_commit = false;
- if (!decoration.surfaces.saved.node.enabled) {
+ if (!decoration.surfaces.saved) {
if (decoration.object) |object| {
object.postError(.no_commit,
\\no wl_surface.commit after sync_next_commit and before update_rendering_finish
diff --git a/river/Scene.zig b/river/Scene.zig
index feb3794..f2359e0 100644
--- a/river/Scene.zig
+++ b/river/Scene.zig
@@ -133,36 +133,50 @@ pub fn layerSurfaceTree(scene: *Scene, layer: zwlr.LayerShellV1.Layer) *wlr.Scen
}
pub const SaveableSurfaces = struct {
+ enabled: bool,
+ saved: bool,
tree: *wlr.SceneTree,
- saved: *wlr.SceneTree,
+ saved_tree: *wlr.SceneTree,
pub fn init(parent: *wlr.SceneTree) !SaveableSurfaces {
const surfaces: SaveableSurfaces = .{
+ .enabled = true,
+ .saved = false,
.tree = try parent.createSceneTree(),
- .saved = try parent.createSceneTree(),
+ .saved_tree = try parent.createSceneTree(),
};
- surfaces.saved.node.setEnabled(false);
+ surfaces.syncEnabled();
return surfaces;
}
- pub fn save(surfaces: SaveableSurfaces) void {
- if (surfaces.saved.node.enabled) return;
- assert(surfaces.tree.node.enabled);
- assert(surfaces.saved.children.empty());
+ fn syncEnabled(surfaces: *const SaveableSurfaces) void {
+ surfaces.tree.node.setEnabled(surfaces.enabled and !surfaces.saved);
+ surfaces.saved_tree.node.setEnabled(surfaces.enabled and surfaces.saved);
+ }
- surfaces.tree.node.forEachBuffer(*wlr.SceneTree, saveSurfaceTreeIter, surfaces.saved);
+ pub fn setEnabled(surfaces: *SaveableSurfaces, enabled: bool) void {
+ if (enabled == surfaces.enabled) return;
+ surfaces.enabled = enabled;
+ surfaces.syncEnabled();
+ }
- surfaces.tree.node.setEnabled(false);
- surfaces.saved.node.setEnabled(true);
+ pub fn save(surfaces: *SaveableSurfaces) void {
+ if (surfaces.saved) return;
+ assert(surfaces.tree.node.enabled == surfaces.enabled);
+ assert(!surfaces.saved_tree.node.enabled);
+ assert(surfaces.saved_tree.children.empty());
+ surfaces.tree.node.forEachBuffer(*wlr.SceneTree, saveSurfaceTreeIter, surfaces.saved_tree);
+ surfaces.saved = true;
+ surfaces.syncEnabled();
}
fn saveSurfaceTreeIter(
buffer: *wlr.SceneBuffer,
sx: c_int,
sy: c_int,
- saved: *wlr.SceneTree,
+ saved_tree: *wlr.SceneTree,
) void {
- const scene_buffer = saved.createSceneBuffer(buffer.buffer) catch {
+ const scene_buffer = saved_tree.createSceneBuffer(buffer.buffer) catch {
std.log.err("out of memory", .{});
return;
};
@@ -172,15 +186,15 @@ pub const SaveableSurfaces = struct {
scene_buffer.setTransform(buffer.transform);
}
- pub fn dropSaved(surfaces: SaveableSurfaces) void {
- if (!surfaces.saved.node.enabled) return;
-
+ pub fn dropSaved(surfaces: *SaveableSurfaces) void {
+ if (!surfaces.saved) return;
assert(!surfaces.tree.node.enabled);
-
- var it = surfaces.saved.children.safeIterator(.forward);
- while (it.next()) |node| node.destroy();
-
- surfaces.saved.node.setEnabled(false);
- surfaces.tree.node.setEnabled(true);
+ assert(surfaces.saved_tree.node.enabled == surfaces.enabled);
+ {
+ var it = surfaces.saved_tree.children.safeIterator(.forward);
+ while (it.next()) |node| node.destroy();
+ }
+ surfaces.saved = false;
+ surfaces.syncEnabled();
}
};
diff --git a/river/ShellSurface.zig b/river/ShellSurface.zig
index 7f8e4e7..5398a51 100644
--- a/river/ShellSurface.zig
+++ b/river/ShellSurface.zig
@@ -148,7 +148,7 @@ pub fn renderFinish(shell_surface: *ShellSurface) void {
if (rendering_requested.sync_next_commit) {
rendering_requested.sync_next_commit = false;
- if (!shell_surface.surfaces.saved.node.enabled) {
+ if (!shell_surface.surfaces.saved) {
shell_surface.object.postError(.no_commit,
\\no wl_surface.commit after sync_next_commit and before update_rendering_finish
);
diff --git a/river/Window.zig b/river/Window.zig
index 3ce5615..9b15cf5 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -211,6 +211,7 @@ rendering_requested: struct {
hidden: bool = false,
border: Border = .{},
clip: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
+ content_clip: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
} = .{},
/// The currently rendered position/dimensions of the window in the scene graph
@@ -630,6 +631,19 @@ fn handleRequest(
.height = args.height,
};
},
+ .set_content_clip_box => |args| {
+ if (!server.wm.ensureRendering()) return;
+ if (args.width < 0 or args.height < 0) {
+ window_v1.postError(.invalid_clip_box, "width/height must be greater than or equal to 0 ");
+ return;
+ }
+ rendering_requested.content_clip = .{
+ .x = args.x,
+ .y = args.y,
+ .width = args.width,
+ .height = args.height,
+ };
+ },
}
}
@@ -774,12 +788,14 @@ pub fn renderStart(window: *Window) void {
}
pub fn renderFinish(window: *Window) void {
+ const requested = &window.rendering_requested;
+
window.tree.node.setEnabled(!window.rendering_requested.hidden);
window.popup_tree.node.setEnabled(!window.rendering_requested.hidden);
window.box = .{
- .x = window.rendering_requested.x,
- .y = window.rendering_requested.y,
+ .x = requested.x,
+ .y = requested.y,
.width = window.rendering_sent.width,
.height = window.rendering_sent.height,
};
@@ -797,96 +813,120 @@ pub fn renderFinish(window: *Window) void {
window.tree.node.setPosition(window.box.x, window.box.y);
window.popup_tree.node.setPosition(window.box.x, window.box.y);
- {
- var surface_clip = window.rendering_requested.clip;
- switch (window.impl) {
- .toplevel => |toplevel| {
- surface_clip.x += toplevel.geometry.x;
- surface_clip.y += toplevel.geometry.y;
- },
- .xwayland, .destroying => {},
- }
- // wlroots asserts that a subsurface tree is present.
- if (!window.surfaces.tree.children.empty()) {
- window.surfaces.tree.node.subsurfaceTreeSetClip(&surface_clip);
- }
- }
+ window.applySurfaceClip();
- // f32 cannot represent all u32 values exactly, therefore we must initially use f64
- // (which can) and then cast to f32, potentially losing precision.
- const border = &window.rendering_requested.border;
- const color: [4]f32 = .{
- @floatCast(@as(f64, @floatFromInt(border.r)) / math.maxInt(u32)),
- @floatCast(@as(f64, @floatFromInt(border.g)) / math.maxInt(u32)),
- @floatCast(@as(f64, @floatFromInt(border.b)) / math.maxInt(u32)),
- @floatCast(@as(f64, @floatFromInt(border.a)) / math.maxInt(u32)),
- };
-
- var left: wlr.Box = .{
- .x = -@as(i32, border.width),
- .y = 0,
- .width = border.width,
- .height = window.box.height,
- };
- var right: wlr.Box = .{
- .x = window.box.width,
- .y = 0,
- .width = border.width,
- .height = window.box.height,
- };
- var top: wlr.Box = .{
- .x = 0,
- .y = -@as(i32, border.width),
- .width = window.box.width,
- .height = border.width,
- };
- var bottom: wlr.Box = .{
+ var content: wlr.Box = .{
.x = 0,
- .y = window.box.height,
+ .y = 0,
.width = window.box.width,
- .height = border.width,
+ .height = window.box.height,
};
+ if (requested.content_clip.empty() or
+ content.intersection(&content, &requested.content_clip))
+ {
+ // f32 cannot represent all u32 values exactly, therefore we must initially use f64
+ // (which can) and then cast to f32, potentially losing precision.
+ const border = &requested.border;
+ const color: [4]f32 = .{
+ @floatCast(@as(f64, @floatFromInt(border.r)) / math.maxInt(u32)),
+ @floatCast(@as(f64, @floatFromInt(border.g)) / math.maxInt(u32)),
+ @floatCast(@as(f64, @floatFromInt(border.b)) / math.maxInt(u32)),
+ @floatCast(@as(f64, @floatFromInt(border.a)) / math.maxInt(u32)),
+ };
+
+ var left: wlr.Box = .{
+ .x = -@as(i32, border.width),
+ .y = 0,
+ .width = border.width,
+ .height = content.height,
+ };
+ var right: wlr.Box = .{
+ .x = content.width,
+ .y = 0,
+ .width = border.width,
+ .height = content.height,
+ };
+ var top: wlr.Box = .{
+ .x = 0,
+ .y = -@as(i32, border.width),
+ .width = content.width,
+ .height = border.width,
+ };
+ var bottom: wlr.Box = .{
+ .x = 0,
+ .y = content.height,
+ .width = content.width,
+ .height = border.width,
+ };
+
+ // Use left and right scene rects to draw the corners if needed
+ if (border.edges.top) {
+ left.y -= border.width;
+ left.height += border.width;
+ right.y -= border.width;
+ right.height += border.width;
+ }
+ if (border.edges.bottom) {
+ left.height += border.width;
+ right.height += border.width;
+ }
- // Use left and right scene rects to draw the corners if needed
- if (border.edges.top) {
- left.y -= border.width;
- left.height += border.width;
- right.y -= border.width;
- right.height += border.width;
- }
- if (border.edges.bottom) {
- left.height += border.width;
- right.height += border.width;
- }
-
- inline for (.{
- .{ .name = "left", .box = &left },
- .{ .name = "right", .box = &right },
- .{ .name = "top", .box = &top },
- .{ .name = "bottom", .box = &bottom },
- }) |edge| {
- if (!window.rendering_requested.clip.empty()) {
- if (!edge.box.intersection(edge.box, &window.rendering_requested.clip)) {
- // TODO(wlroots): remove this redundant code after fixed upstream
- // https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5084
- edge.box.* = .{ .x = 0, .y = 0, .width = 0, .height = 0 };
+ inline for (.{
+ .{ .name = "left", .box = &left },
+ .{ .name = "right", .box = &right },
+ .{ .name = "top", .box = &top },
+ .{ .name = "bottom", .box = &bottom },
+ }) |edge| {
+ if (!requested.clip.empty()) {
+ if (!edge.box.intersection(edge.box, &requested.clip)) {
+ // TODO(wlroots): remove this redundant code after fixed upstream
+ // https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5084
+ edge.box.* = .{ .x = 0, .y = 0, .width = 0, .height = 0 };
+ }
}
+ const rect = @field(window.border, edge.name);
+ rect.node.setEnabled(@field(border.edges, edge.name));
+ rect.node.setPosition(edge.box.x, edge.box.y);
+ rect.setSize(edge.box.width, edge.box.height);
+ rect.setColor(&color);
}
- const rect = @field(window.border, edge.name);
- rect.node.setEnabled(@field(border.edges, edge.name));
- rect.node.setPosition(edge.box.x, edge.box.y);
- rect.setSize(edge.box.width, edge.box.height);
- rect.setColor(&color);
}
inline for (.{ &window.decorations_above, &window.decorations_below }) |decorations| {
var it = decorations.iterator(.forward);
while (it.next()) |decoration| {
- decoration.renderFinish(&window.rendering_requested.clip);
+ decoration.renderFinish(&requested.clip);
}
}
}
+fn applySurfaceClip(window: *Window) void {
+ const requested = &window.rendering_requested;
+ var surface_clip: wlr.Box = requested.clip;
+ if (!requested.clip.empty() and !requested.content_clip.empty()) {
+ if (!surface_clip.intersection(&requested.clip, &requested.content_clip)) {
+ // Clip boxes are both non-empty but don't intersect, all window
+ // content is clipped away.
+ window.surfaces.setEnabled(false);
+ return;
+ }
+ } else if (!requested.content_clip.empty()) {
+ surface_clip = requested.content_clip;
+ }
+ window.surfaces.setEnabled(true);
+ switch (window.impl) {
+ .toplevel => |toplevel| {
+ surface_clip.x += toplevel.geometry.x;
+ surface_clip.y += toplevel.geometry.y;
+ },
+ .xwayland, .destroying => {},
+ }
+ // wlroots asserts that a subsurface tree is present.
+ if (!window.surfaces.tree.children.empty()) {
+ window.surfaces.tree.node.subsurfaceTreeSetClip(&surface_clip);
+ }
+}
+
/// Returns null if the window is currently being destroyed and no longer has
/// an associated surface.
/// May also return null for Xwayland windows that are not currently mapped.