Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Scene: split out from Root
river/Cursor.zig | 14 ++---
river/DragIcon.zig | 2 +-
river/InputPopup.zig | 8 +--
river/LockManager.zig | 10 +--
river/LockSurface.zig | 2 +-
river/Output.zig | 6 +-
river/PointerConstraint.zig | 2 +-
river/Root.zig | 98 -----------------------------
river/Scene.zig | 125 +++++++++++++++++++++++++++++++++++++
river/Server.zig | 8 ++-
river/TabletTool.zig | 4 +-
river/Window.zig | 4 +-
river/XwaylandOverrideRedirect.zig | 2 +-
13 files changed, 158 insertions(+), 127 deletions(-)
diff --git a/river/Cursor.zig b/river/Cursor.zig
index 68bc102..1fff6ab 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -36,7 +36,7 @@ const InputDevice = @import("InputDevice.zig");
const LockSurface = @import("LockSurface.zig");
const Output = @import("Output.zig");
const PointerConstraint = @import("PointerConstraint.zig");
-const Root = @import("Root.zig");
+const Scene = @import("Scene.zig");
const Seat = @import("Seat.zig");
const Tablet = @import("Tablet.zig");
const TabletTool = @import("TabletTool.zig");
@@ -351,7 +351,7 @@ fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.P
return;
}
- if (server.root.at(cursor.wlr_cursor.x, cursor.wlr_cursor.y)) |result| {
+ if (server.scene.at(cursor.wlr_cursor.x, cursor.wlr_cursor.y)) |result| {
if (result.data == .window and cursor.handlePointerMapping(event, result.data.window)) {
// If a mapping is triggered don't send events to clients.
return;
@@ -377,7 +377,7 @@ fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.P
}
/// Requires a call to WindowManager.dirtyPending()
-fn updateKeyboardFocus(cursor: Cursor, result: Root.AtResult) void {
+fn updateKeyboardFocus(cursor: Cursor, result: Scene.AtResult) void {
switch (result.data) {
.window => |window| {
cursor.seat.focus(window);
@@ -486,7 +486,7 @@ fn handleTouchDown(
return;
};
- if (server.root.at(lx, ly)) |result| {
+ if (server.scene.at(lx, ly)) |result| {
cursor.updateKeyboardFocus(result);
if (result.surface) |surface| {
@@ -516,7 +516,7 @@ fn handleTouchMotion(
cursor.updateDragIcons();
- if (server.root.at(point.lx, point.ly)) |result| {
+ if (server.scene.at(point.lx, point.ly)) |result| {
cursor.seat.wlr_seat.touchNotifyMotion(event.time_msec, event.touch_id, result.sx, result.sy);
}
}
@@ -976,7 +976,7 @@ pub fn updateState(cursor: *Cursor) void {
fn passthrough(cursor: *Cursor, time: u32) void {
assert(cursor.mode == .passthrough);
- if (server.root.at(cursor.wlr_cursor.x, cursor.wlr_cursor.y)) |result| {
+ if (server.scene.at(cursor.wlr_cursor.x, cursor.wlr_cursor.y)) |result| {
if (result.data == .lock_surface) {
assert(server.lock_manager.state != .unlocked);
} else {
@@ -994,7 +994,7 @@ fn passthrough(cursor: *Cursor, time: u32) void {
}
fn updateDragIcons(cursor: *Cursor) void {
- var it = server.root.drag_icons.children.iterator(.forward);
+ var it = server.scene.drag_icons.children.iterator(.forward);
while (it.next()) |node| {
const icon = @as(*DragIcon, @ptrFromInt(node.data));
diff --git a/river/DragIcon.zig b/river/DragIcon.zig
index eae4059..99b6d56 100644
--- a/river/DragIcon.zig
+++ b/river/DragIcon.zig
@@ -32,7 +32,7 @@ scene_drag_icon: *wlr.SceneTree,
destroy: wl.Listener(*wlr.Drag.Icon) = wl.Listener(*wlr.Drag.Icon).init(handleDestroy),
pub fn create(wlr_drag_icon: *wlr.Drag.Icon, cursor: *Cursor) error{OutOfMemory}!void {
- const scene_drag_icon = try server.root.drag_icons.createSceneDragIcon(wlr_drag_icon);
+ const scene_drag_icon = try server.scene.drag_icons.createSceneDragIcon(wlr_drag_icon);
errdefer scene_drag_icon.node.destroy();
const drag_icon = try util.gpa.create(DragIcon);
diff --git a/river/InputPopup.zig b/river/InputPopup.zig
index 03aa24a..067ddc5 100644
--- a/river/InputPopup.zig
+++ b/river/InputPopup.zig
@@ -47,7 +47,7 @@ pub fn create(wlr_popup: *wlr.InputPopupSurfaceV2, input_relay: *InputRelay) !vo
.link = undefined,
.input_relay = input_relay,
.wlr_popup = wlr_popup,
- .surface_tree = try server.root.hidden_tree.createSceneSubsurfaceTree(wlr_popup.surface),
+ .surface_tree = try server.scene.hidden_tree.createSceneSubsurfaceTree(wlr_popup.surface),
};
input_relay.input_popups.append(input_popup);
@@ -82,7 +82,7 @@ fn handleMap(listener: *wl.Listener(void)) void {
fn handleUnmap(listener: *wl.Listener(void)) void {
const input_popup: *InputPopup = @fieldParentPtr("unmap", listener);
- input_popup.surface_tree.node.reparent(server.root.hidden_tree);
+ input_popup.surface_tree.node.reparent(server.scene.hidden_tree);
}
fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
@@ -93,7 +93,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
pub fn update(input_popup: *InputPopup) void {
const text_input = input_popup.input_relay.text_input orelse {
- input_popup.surface_tree.node.reparent(server.root.hidden_tree);
+ input_popup.surface_tree.node.reparent(server.scene.hidden_tree);
return;
};
@@ -116,7 +116,7 @@ pub fn update(input_popup: *InputPopup) void {
const popup_tree = switch (focused.data) {
.window => |window| window.popup_tree,
- .lock_surface => |_| server.root.layers.popups, // XXX Do we need per-lock-surface popup trees?
+ .lock_surface => |_| server.scene.layers.popups, // XXX Do we need per-lock-surface popup trees?
// Xwayland doesn't use the text-input protocol
.override_redirect => unreachable,
};
diff --git a/river/LockManager.zig b/river/LockManager.zig
index c52e8b0..7389a5a 100644
--- a/river/LockManager.zig
+++ b/river/LockManager.zig
@@ -99,7 +99,7 @@ fn handleLock(listener: *wl.Listener(*wlr.SessionLockV1), lock: *wlr.SessionLock
manager.state = .waiting_for_lock_surfaces;
if (build_options.xwayland) {
- server.root.layers.override_redirect.node.setEnabled(false);
+ server.scene.layers.override_redirect.node.setEnabled(false);
}
manager.lock_surfaces_timer.timerUpdate(200) catch {
@@ -189,11 +189,11 @@ fn handleUnlock(listener: *wl.Listener(void)) void {
log.info("session unlocked", .{});
- assert(!server.root.normal_tree.node.enabled);
- server.root.normal_tree.node.setEnabled(true);
+ assert(!server.scene.normal_tree.node.enabled);
+ server.scene.normal_tree.node.setEnabled(true);
- assert(server.root.locked_tree.node.enabled);
- server.root.locked_tree.node.setEnabled(true);
+ assert(server.scene.locked_tree.node.enabled);
+ server.scene.locked_tree.node.setEnabled(true);
{
var it = server.input_manager.seats.first;
diff --git a/river/LockSurface.zig b/river/LockSurface.zig
index af355b3..b146dea 100644
--- a/river/LockSurface.zig
+++ b/river/LockSurface.zig
@@ -46,7 +46,7 @@ pub fn create(wlr_lock_surface: *wlr.SessionLockSurfaceV1, lock: *wlr.SessionLoc
};
wlr_lock_surface.data = @intFromPtr(lock_surface);
- const tree = try server.root.locked_tree.createSceneSubsurfaceTree(wlr_lock_surface.surface);
+ const tree = try server.scene.locked_tree.createSceneSubsurfaceTree(wlr_lock_surface.surface);
errdefer tree.node.destroy();
try SceneNodeData.attach(&tree.node, .{ .lock_surface = lock_surface });
diff --git a/river/Output.zig b/river/Output.zig
index a99eab2..d9254d5 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -188,7 +188,7 @@ pub fn create(wlr_output: *wlr.Output) !void {
if (!wlr_output.initRender(server.allocator, server.renderer)) return error.InitRenderFailed;
- const scene_output = try server.root.scene.createSceneOutput(wlr_output);
+ const scene_output = try server.scene.wlr_scene.createSceneOutput(wlr_output);
errdefer comptime unreachable;
@@ -386,8 +386,8 @@ fn renderAndCommit(output: *Output) !void {
(server.lock_manager.state == .waiting_for_lock_surfaces and lock_surface_mapped) or
server.lock_manager.state == .waiting_for_blank)
{
- assert(!server.root.normal_tree.node.enabled);
- assert(server.root.locked_tree.node.enabled);
+ assert(!server.scene.normal_tree.node.enabled);
+ assert(server.scene.locked_tree.node.enabled);
switch (server.lock_manager.state) {
.unlocked => unreachable,
diff --git a/river/PointerConstraint.zig b/river/PointerConstraint.zig
index 7caeb54..dbf89e0 100644
--- a/river/PointerConstraint.zig
+++ b/river/PointerConstraint.zig
@@ -78,7 +78,7 @@ pub fn maybeActivate(constraint: *PointerConstraint) void {
if (seat.cursor.mode == .move or seat.cursor.mode == .resize) return;
- const result = server.root.at(seat.cursor.wlr_cursor.x, seat.cursor.wlr_cursor.y) orelse return;
+ const result = server.scene.at(seat.cursor.wlr_cursor.x, seat.cursor.wlr_cursor.y) orelse return;
if (result.surface != constraint.wlr_constraint.surface) return;
const sx: i32 = @intFromFloat(result.sx);
diff --git a/river/Root.zig b/river/Root.zig
index 914a046..e427c09 100644
--- a/river/Root.zig
+++ b/river/Root.zig
@@ -34,40 +34,6 @@ const SceneNodeData = @import("SceneNodeData.zig");
const Window = @import("Window.zig");
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
-scene: *wlr.Scene,
-/// All windows, status bars, drowdown menus, etc. that can recieve pointer events and similar.
-interactive_tree: *wlr.SceneTree,
-/// Drag icons, which cannot recieve e.g. pointer events and are therefore kept
-/// in a separate tree from the interactive tree.
-drag_icons: *wlr.SceneTree,
-/// Always disabled, used for staging changes
-/// TODO can this be refactored away?
-hidden_tree: *wlr.SceneTree,
-/// Direct child of interactive_tree, disabled when the session is locked
-normal_tree: *wlr.SceneTree,
-/// Direct child of interactive_tree, enabled when the session is locked
-locked_tree: *wlr.SceneTree,
-
-/// All direct children of the normal_tree scene node
-layers: struct {
- /// Background layer shell layer
- background: *wlr.SceneTree,
- /// Bottom layer shell layer
- bottom: *wlr.SceneTree,
- /// Windows and shell surfaces of the window manager
- wm: *wlr.SceneTree,
- /// Top layer shell layer
- top: *wlr.SceneTree,
- /// Overlay layer shell layer
- overlay: *wlr.SceneTree,
- /// Popups from xdg-shell and input-method-v2 clients
- popups: *wlr.SceneTree,
- /// Xwayland override redirect windows are a legacy wart that decide where
- /// to place themselves in layout coordinates. Unfortunately this is how
- /// X11 decided to make dropdown menus and the like possible.
- override_redirect: if (build_options.xwayland) *wlr.SceneTree else void,
-},
-
new_output: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleNewOutput),
output_layout: *wlr.OutputLayout,
@@ -96,33 +62,7 @@ pub fn init(root: *Root) !void {
const output_layout = try wlr.OutputLayout.create(server.wl_server);
errdefer output_layout.destroy();
- const scene = try wlr.Scene.create();
- errdefer scene.tree.node.destroy();
-
- const interactive_tree = try scene.tree.createSceneTree();
- const drag_icons = try scene.tree.createSceneTree();
- const hidden_tree = try scene.tree.createSceneTree();
- hidden_tree.node.setEnabled(false);
-
- const normal_tree = try interactive_tree.createSceneTree();
- const locked_tree = try interactive_tree.createSceneTree();
-
root.* = .{
- .scene = scene,
- .interactive_tree = interactive_tree,
- .drag_icons = drag_icons,
- .hidden_tree = hidden_tree,
- .normal_tree = normal_tree,
- .locked_tree = locked_tree,
- .layers = .{
- .background = try normal_tree.createSceneTree(),
- .bottom = try normal_tree.createSceneTree(),
- .wm = try normal_tree.createSceneTree(),
- .top = try normal_tree.createSceneTree(),
- .overlay = try normal_tree.createSceneTree(),
- .popups = try normal_tree.createSceneTree(),
- .override_redirect = if (build_options.xwayland) try normal_tree.createSceneTree(),
- },
.output_layout = output_layout,
.outputs = undefined,
@@ -146,44 +86,6 @@ pub fn deinit(root: *Root) void {
root.output_layout.destroy();
}
-pub const AtResult = struct {
- node: *wlr.SceneNode,
- surface: ?*wlr.Surface,
- sx: f64,
- sy: f64,
- data: SceneNodeData.Data,
-};
-
-/// Return information about what is currently rendered in the interactive_tree
-/// tree at the given layout coordinates, taking surface input regions into account.
-pub fn at(root: Root, lx: f64, ly: f64) ?AtResult {
- var sx: f64 = undefined;
- var sy: f64 = undefined;
- const node = root.interactive_tree.node.at(lx, ly, &sx, &sy) orelse return null;
-
- const surface: ?*wlr.Surface = blk: {
- if (node.type == .buffer) {
- const scene_buffer = wlr.SceneBuffer.fromNode(node);
- if (wlr.SceneSurface.tryFromBuffer(scene_buffer)) |scene_surface| {
- break :blk scene_surface.surface;
- }
- }
- break :blk null;
- };
-
- if (SceneNodeData.fromNode(node)) |scene_node_data| {
- return .{
- .node = node,
- .surface = surface,
- .sx = sx,
- .sy = sy,
- .data = scene_node_data.data,
- };
- } else {
- return null;
- }
-}
-
fn handleNewOutput(_: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
const log = std.log.scoped(.output_manager);
diff --git a/river/Scene.zig b/river/Scene.zig
new file mode 100644
index 0000000..764d58b
--- /dev/null
+++ b/river/Scene.zig
@@ -0,0 +1,125 @@
+// This file is part of river, a dynamic tiling wayland compositor.
+//
+// Copyright 2024 The River Developers
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+const Scene = @This();
+
+const build_options = @import("build_options");
+const wlr = @import("wlroots");
+
+const SceneNodeData = @import("SceneNodeData.zig");
+
+wlr_scene: *wlr.Scene,
+/// All windows, status bars, drowdown menus, etc. that can recieve pointer events and similar.
+interactive_tree: *wlr.SceneTree,
+/// Drag icons, which cannot recieve e.g. pointer events and are therefore kept
+/// in a separate tree from the interactive tree.
+drag_icons: *wlr.SceneTree,
+/// Always disabled, used for staging changes
+/// TODO can this be refactored away?
+hidden_tree: *wlr.SceneTree,
+/// Direct child of interactive_tree, disabled when the session is locked
+normal_tree: *wlr.SceneTree,
+/// Direct child of interactive_tree, enabled when the session is locked
+locked_tree: *wlr.SceneTree,
+
+/// All direct children of the normal_tree scene node
+layers: struct {
+ /// Background layer shell layer
+ background: *wlr.SceneTree,
+ /// Bottom layer shell layer
+ bottom: *wlr.SceneTree,
+ /// Windows and shell surfaces of the window manager
+ wm: *wlr.SceneTree,
+ /// Top layer shell layer
+ top: *wlr.SceneTree,
+ /// Overlay layer shell layer
+ overlay: *wlr.SceneTree,
+ /// Popups from xdg-shell and input-method-v2 clients
+ popups: *wlr.SceneTree,
+ /// Xwayland override redirect windows are a legacy wart that decide where
+ /// to place themselves in layout coordinates. Unfortunately this is how
+ /// X11 decided to make dropdown menus and the like possible.
+ override_redirect: if (build_options.xwayland) *wlr.SceneTree else void,
+},
+
+pub fn init(scene: *Scene) !void {
+ const wlr_scene = try wlr.Scene.create();
+ errdefer wlr_scene.tree.node.destroy();
+
+ const interactive_tree = try wlr_scene.tree.createSceneTree();
+ const drag_icons = try wlr_scene.tree.createSceneTree();
+ const hidden_tree = try wlr_scene.tree.createSceneTree();
+ hidden_tree.node.setEnabled(false);
+
+ const normal_tree = try interactive_tree.createSceneTree();
+ const locked_tree = try interactive_tree.createSceneTree();
+
+ scene.* = .{
+ .wlr_scene = wlr_scene,
+ .interactive_tree = interactive_tree,
+ .drag_icons = drag_icons,
+ .hidden_tree = hidden_tree,
+ .normal_tree = normal_tree,
+ .locked_tree = locked_tree,
+ .layers = .{
+ .background = try normal_tree.createSceneTree(),
+ .bottom = try normal_tree.createSceneTree(),
+ .wm = try normal_tree.createSceneTree(),
+ .top = try normal_tree.createSceneTree(),
+ .overlay = try normal_tree.createSceneTree(),
+ .popups = try normal_tree.createSceneTree(),
+ .override_redirect = if (build_options.xwayland) try normal_tree.createSceneTree(),
+ },
+ };
+}
+
+pub const AtResult = struct {
+ node: *wlr.SceneNode,
+ surface: ?*wlr.Surface,
+ sx: f64,
+ sy: f64,
+ data: SceneNodeData.Data,
+};
+
+/// Return information about what is currently rendered in the interactive_tree
+/// tree at the given layout coordinates, taking surface input regions into account.
+pub fn at(scene: *const Scene, lx: f64, ly: f64) ?AtResult {
+ var sx: f64 = undefined;
+ var sy: f64 = undefined;
+ const node = scene.interactive_tree.node.at(lx, ly, &sx, &sy) orelse return null;
+
+ const surface: ?*wlr.Surface = blk: {
+ if (node.type == .buffer) {
+ const scene_buffer = wlr.SceneBuffer.fromNode(node);
+ if (wlr.SceneSurface.tryFromBuffer(scene_buffer)) |scene_surface| {
+ break :blk scene_surface.surface;
+ }
+ }
+ break :blk null;
+ };
+
+ if (SceneNodeData.fromNode(node)) |scene_node_data| {
+ return .{
+ .node = node,
+ .surface = surface,
+ .sx = sx,
+ .sy = sy,
+ .data = scene_node_data.data,
+ };
+ } else {
+ return null;
+ }
+}
diff --git a/river/Server.zig b/river/Server.zig
index 079e2f5..e323cd3 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -32,8 +32,9 @@ const InputManager = @import("InputManager.zig");
const LockManager = @import("LockManager.zig");
const Output = @import("Output.zig");
const Root = @import("Root.zig");
-const Seat = @import("Seat.zig");
+const Scene = @import("Scene.zig");
const SceneNodeData = @import("SceneNodeData.zig");
+const Seat = @import("Seat.zig");
const TabletTool = @import("TabletTool.zig");
const WindowManager = @import("WindowManager.zig");
const XdgDecoration = @import("XdgDecoration.zig");
@@ -80,6 +81,7 @@ screencopy_manager: *wlr.ScreencopyManagerV1,
foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1,
+scene: Scene,
input_manager: InputManager,
root: Root,
config: Config,
@@ -150,6 +152,7 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
.config = try Config.init(),
+ .scene = undefined,
.root = undefined,
.input_manager = undefined,
.idle_inhibit_manager = undefined,
@@ -172,6 +175,7 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
server.xwayland.?.events.new_surface.add(&server.new_xsurface);
}
+ try server.scene.init();
try server.root.init();
try server.input_manager.init();
try server.idle_inhibit_manager.init();
@@ -210,7 +214,7 @@ pub fn deinit(server: *Server) void {
// The scene graph needs to be destroyed after the backend but before the renderer
// Output destruction requires the scene graph to still be around while the scene
// graph may require the renderer to still be around to destroy textures it seems.
- server.root.scene.tree.node.destroy();
+ server.scene.wlr_scene.tree.node.destroy();
server.renderer.destroy();
server.allocator.destroy();
diff --git a/river/TabletTool.zig b/river/TabletTool.zig
index af558a3..9c6c291 100644
--- a/river/TabletTool.zig
+++ b/river/TabletTool.zig
@@ -209,7 +209,7 @@ pub fn tip(tool: *TabletTool, tablet: *Tablet, event: *wlr.Tablet.event.Tip) voi
tool.wp_tool.notifyDown();
- if (server.root.at(tool.wlr_cursor.x, tool.wlr_cursor.y)) |result| {
+ if (server.scene.at(tool.wlr_cursor.x, tool.wlr_cursor.y)) |result| {
if (result.surface != null) {
tool.mode = .{
.down = .{
@@ -252,7 +252,7 @@ fn maybeExitDown(tool: *TabletTool, tablet: *Tablet) void {
/// If there is no surface under the cursor or the surface under the cursor
/// does not support the tablet v2 protocol, send a proximity_out event.
fn passthrough(tool: *TabletTool, tablet: *Tablet) void {
- if (server.root.at(tool.wlr_cursor.x, tool.wlr_cursor.y)) |result| {
+ if (server.scene.at(tool.wlr_cursor.x, tool.wlr_cursor.y)) |result| {
if (result.data == .lock_surface) {
assert(server.lock_manager.state != .unlocked);
} else {
diff --git a/river/Window.zig b/river/Window.zig
index c7635b2..16d5ff5 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -184,10 +184,10 @@ pub fn create(impl: Impl) error{OutOfMemory}!*Window {
const window = try util.gpa.create(Window);
errdefer util.gpa.destroy(window);
- const tree = try server.root.hidden_tree.createSceneTree();
+ const tree = try server.scene.hidden_tree.createSceneTree();
errdefer tree.node.destroy();
- const popup_tree = try server.root.hidden_tree.createSceneTree();
+ const popup_tree = try server.scene.hidden_tree.createSceneTree();
errdefer popup_tree.node.destroy();
window.* = .{
diff --git a/river/XwaylandOverrideRedirect.zig b/river/XwaylandOverrideRedirect.zig
index 0b4af70..639dc38 100644
--- a/river/XwaylandOverrideRedirect.zig
+++ b/river/XwaylandOverrideRedirect.zig
@@ -115,7 +115,7 @@ pub fn handleMap(listener: *wl.Listener(void)) void {
fn mapImpl(override_redirect: *XwaylandOverrideRedirect) error{OutOfMemory}!void {
const surface = override_redirect.xsurface.surface.?;
override_redirect.surface_tree =
- try server.root.layers.override_redirect.createSceneSubsurfaceTree(surface);
+ try server.scene.layers.override_redirect.createSceneSubsurfaceTree(surface);
try SceneNodeData.attach(&override_redirect.surface_tree.?.node, .{
.override_redirect = override_redirect,
});