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

commit3c3d84cd1305617dbaab99e471dd7d5e6e76a1e7
parent4f71a256da
authorIsaac Freund <[email protected]>
date2024-12-20 21:14
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,
     });