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

commit704a9ca0ae8b5907f138429a6a862b6e306178ac
parent1c8a47b2ce
authorIsaac Freund <[email protected]>
date2024-08-10 18:55
Root: restructure scene graph for rwm protocol

 river/InputPopup.zig   |   2 +-
 river/LayerSurface.zig |  22 ++++-----
 river/LockManager.zig  |  33 +++++---------
 river/LockSurface.zig  |   7 +--
 river/Output.zig       | 120 ++++++-------------------------------------------
 river/Root.zig         |  87 +++++++++++++++++++++--------------
 6 files changed, 89 insertions(+), 182 deletions(-)

diff --git a/river/InputPopup.zig b/river/InputPopup.zig
index b4cb096..bee0802 100644
--- a/river/InputPopup.zig
+++ b/river/InputPopup.zig
@@ -118,7 +118,7 @@ pub fn update(input_popup: *InputPopup) void {
     const popup_tree = switch (focused.data) {
         .window => |window| window.popup_tree,
         .layer_surface => |layer_surface| layer_surface.popup_tree,
-        .lock_surface => |lock_surface| lock_surface.getOutput().layers.popups,
+        .lock_surface => |_| server.root.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/LayerSurface.zig b/river/LayerSurface.zig
index 9fe11a9..42765d7 100644
--- a/river/LayerSurface.zig
+++ b/river/LayerSurface.zig
@@ -47,13 +47,13 @@ pub fn create(wlr_layer_surface: *wlr.LayerSurfaceV1) error{OutOfMemory}!void {
     const layer_surface = try util.gpa.create(LayerSurface);
     errdefer util.gpa.destroy(layer_surface);
 
-    const layer_tree = output.layerSurfaceTree(wlr_layer_surface.current.layer);
+    const layer_tree = server.root.layerSurfaceTree(wlr_layer_surface.current.layer);
 
     layer_surface.* = .{
         .output = output,
         .wlr_layer_surface = wlr_layer_surface,
         .scene_layer_surface = try layer_tree.createSceneLayerSurfaceV1(wlr_layer_surface),
-        .popup_tree = try output.layers.popups.createSceneTree(),
+        .popup_tree = try server.root.layers.popups.createSceneTree(),
     };
 
     try SceneNodeData.attach(&layer_surface.scene_layer_surface.tree.node, .{ .layer_surface = layer_surface });
@@ -103,10 +103,7 @@ fn handleMap(listener: *wl.Listener(void)) void {
 
     const consider = wlr_surface.current.keyboard_interactive == .on_demand and
         (wlr_surface.current.layer == .top or wlr_surface.current.layer == .overlay);
-    handleKeyboardInteractiveExclusive(
-        layer_surface.output,
-        if (consider) layer_surface else null,
-    );
+    handleKeyboardInteractiveExclusive(if (consider) layer_surface else null);
 
     server.wm.dirtyPending();
 }
@@ -117,7 +114,7 @@ fn handleUnmap(listener: *wl.Listener(void)) void {
     log.debug("layer surface '{s}' unmapped", .{layer_surface.wlr_layer_surface.namespace});
 
     layer_surface.output.arrangeLayers();
-    handleKeyboardInteractiveExclusive(layer_surface.output, null);
+    handleKeyboardInteractiveExclusive(null);
     server.wm.dirtyPending();
 }
 
@@ -129,7 +126,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
 
     // If the layer was changed, move the LayerSurface to the proper tree.
     if (wlr_layer_surface.current.committed.layer) {
-        const tree = layer_surface.output.layerSurfaceTree(wlr_layer_surface.current.layer);
+        const tree = server.root.layerSurfaceTree(wlr_layer_surface.current.layer);
         layer_surface.scene_layer_surface.tree.node.reparent(tree);
     }
 
@@ -137,7 +134,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
         @as(u32, @bitCast(wlr_layer_surface.current.committed)) != 0)
     {
         layer_surface.output.arrangeLayers();
-        handleKeyboardInteractiveExclusive(layer_surface.output, null);
+        handleKeyboardInteractiveExclusive(null);
         server.wm.dirtyPending();
     }
 }
@@ -145,13 +142,13 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
 /// Focus topmost keyboard-interactivity-exclusive layer surface above normal
 /// content, or if none found, focus the surface given as `consider`.
 /// Requires a call to WindowManager.dirtyPending()
-fn handleKeyboardInteractiveExclusive(output: *Output, consider: ?*LayerSurface) void {
+fn handleKeyboardInteractiveExclusive(consider: ?*LayerSurface) void {
     if (server.lock_manager.state != .unlocked) return;
 
     // Find the topmost layer surface (if any) in the top or overlay layers which
     // requests exclusive keyboard interactivity.
     const to_focus = outer: for ([_]zwlr.LayerShellV1.Layer{ .overlay, .top }) |layer| {
-        const tree = output.layerSurfaceTree(layer);
+        const tree = server.root.layerSurfaceTree(layer);
         // Iterate in reverse to match rendering order.
         var it = tree.children.iterator(.reverse);
         while (it.next()) |node| {
@@ -179,8 +176,7 @@ fn handleKeyboardInteractiveExclusive(output: *Output, consider: ?*LayerSurface)
         if (true) @panic("TODO");
         if (false) {
             if (to_focus) |s| {
-                // If we found a surface on the output that requires focus, grab the focus of all
-                // seats that are focusing that output.
+                // If we found a surface that requires focus, grab the focus of all seats.
                 seat.setFocusRaw(.{ .layer = s });
                 continue;
             }
diff --git a/river/LockManager.zig b/river/LockManager.zig
index c40b124..5c50b10 100644
--- a/river/LockManager.zig
+++ b/river/LockManager.zig
@@ -135,13 +135,7 @@ fn handleLockSurfacesTimeout(manager: *LockManager) c_int {
     assert(manager.state == .waiting_for_lock_surfaces);
     manager.state = .waiting_for_blank;
 
-    {
-        var it = server.root.active_outputs.iterator(.forward);
-        while (it.next()) |output| {
-            output.normal_content.node.setEnabled(false);
-            output.locked_content.node.setEnabled(true);
-        }
-    }
+    if (true) @panic("XXX blank all outputs");
 
     // This call is necessary in the case that all outputs in the layout are disabled.
     manager.maybeLock();
@@ -193,20 +187,11 @@ fn handleUnlock(listener: *wl.Listener(void)) void {
 
     log.info("session unlocked", .{});
 
-    {
-        var it = server.root.active_outputs.iterator(.forward);
-        while (it.next()) |output| {
-            assert(!output.normal_content.node.enabled);
-            output.normal_content.node.setEnabled(true);
-
-            assert(output.locked_content.node.enabled);
-            output.locked_content.node.setEnabled(false);
-        }
-    }
+    assert(!server.root.normal_tree.node.enabled);
+    server.root.normal_tree.node.setEnabled(true);
 
-    if (build_options.xwayland) {
-        server.root.layers.override_redirect.node.setEnabled(true);
-    }
+    assert(server.root.locked_tree.node.enabled);
+    server.root.locked_tree.node.setEnabled(true);
 
     {
         var it = server.input_manager.seats.first;
@@ -254,14 +239,16 @@ fn handleSurface(
     };
 }
 
-pub fn updateLockSurfaceSize(manager: *LockManager, output: *Output) void {
-    const lock = manager.lock orelse return;
+pub fn lockSurfaceFromOutput(manager: *LockManager, output: *Output) ?*LockSurface {
+    const lock = manager.lock orelse return null;
 
     var it = lock.surfaces.iterator(.forward);
     while (it.next()) |wlr_lock_surface| {
         const lock_surface: *LockSurface = @ptrFromInt(wlr_lock_surface.data);
         if (output == lock_surface.getOutput()) {
-            lock_surface.configure();
+            return lock_surface;
         }
     }
+
+    return null;
 }
diff --git a/river/LockSurface.zig b/river/LockSurface.zig
index 60f0aa8..fb6e14c 100644
--- a/river/LockSurface.zig
+++ b/river/LockSurface.zig
@@ -46,8 +46,7 @@ pub fn create(wlr_lock_surface: *wlr.SessionLockSurfaceV1, lock: *wlr.SessionLoc
     };
     wlr_lock_surface.data = @intFromPtr(lock_surface);
 
-    const output = lock_surface.getOutput();
-    const tree = try output.locked_content.createSceneSubsurfaceTree(wlr_lock_surface.surface);
+    const tree = try server.root.locked_tree.createSceneSubsurfaceTree(wlr_lock_surface.surface);
     errdefer tree.node.destroy();
 
     try SceneNodeData.attach(&tree.node, .{ .lock_surface = lock_surface });
@@ -104,10 +103,6 @@ pub fn configure(lock_surface: *LockSurface) void {
 
 fn handleMap(listener: *wl.Listener(void)) void {
     const lock_surface: *LockSurface = @fieldParentPtr("map", listener);
-    const output = lock_surface.getOutput();
-
-    output.normal_content.node.setEnabled(false);
-    output.locked_content.node.setEnabled(true);
 
     // Unfortunately the surface commit handlers for the scene subsurface tree corresponding to
     // this lock surface won't be called until after this function returns, which means that we cannot
diff --git a/river/Output.zig b/river/Output.zig
index b73cffb..79a7f0a 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -52,31 +52,6 @@ active_link: wl.list.Link,
 /// TODO: this should be part of the output's State
 usable_box: wlr.Box,
 
-/// Scene node representing the entire output.
-/// Position must be updated when the output is moved in the layout.
-tree: *wlr.SceneTree,
-normal_content: *wlr.SceneTree,
-locked_content: *wlr.SceneTree,
-
-/// Child nodes of normal_content
-layers: struct {
-    background_color_rect: *wlr.SceneRect,
-    /// 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,
-    /// Fullscreen windows
-    fullscreen: *wlr.SceneTree,
-    /// Overlay layer shell layer
-    overlay: *wlr.SceneTree,
-    /// Popups from xdg-shell and input-method-v2 clients
-    popups: *wlr.SceneTree,
-},
-
 /// Tracks the currently presented frame on the output as it pertains to ext-session-lock.
 /// The output is initially considered blanked:
 /// If using the DRM backend it will be blanked with the initial modeset.
@@ -102,20 +77,6 @@ lock_render_state: enum {
 /// This request is handled while rendering the next frame in handleFrame().
 gamma_dirty: bool = false,
 
-/// The state most recently sent to the layout generator and clients.
-/// This state is immutable until all clients have replied and the transaction
-/// is completed, at which point this inflight state is copied to current.
-inflight: struct {
-    /// The window to be made fullscreen, if any.
-    fullscreen: ?*Window = null,
-} = .{},
-
-/// The current state represented by the scene graph.
-current: struct {
-    /// The currently fullscreen window, if any.
-    fullscreen: ?*Window = null,
-} = .{},
-
 destroy: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleDestroy),
 request_state: wl.Listener(*wlr.Output.event.RequestState) = wl.Listener(*wlr.Output.event.RequestState).init(handleRequestState),
 frame: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleFrame),
@@ -175,31 +136,11 @@ pub fn create(wlr_output: *wlr.Output) !void {
 
     const scene_output = try server.root.scene.createSceneOutput(wlr_output);
 
-    const tree = try server.root.layers.outputs.createSceneTree();
-    const normal_content = try tree.createSceneTree();
-
     output.* = .{
         .wlr_output = wlr_output,
         .scene_output = scene_output,
         .all_link = undefined,
         .active_link = undefined,
-        .tree = tree,
-        .normal_content = normal_content,
-        .locked_content = try tree.createSceneTree(),
-        .layers = .{
-            .background_color_rect = try normal_content.createSceneRect(
-                width,
-                height,
-                &server.config.background_color,
-            ),
-            .background = try normal_content.createSceneTree(),
-            .bottom = try normal_content.createSceneTree(),
-            .wm = try normal_content.createSceneTree(),
-            .top = try normal_content.createSceneTree(),
-            .fullscreen = try normal_content.createSceneTree(),
-            .overlay = try normal_content.createSceneTree(),
-            .popups = try normal_content.createSceneTree(),
-        },
         .usable_box = .{
             .x = 0,
             .y = 0,
@@ -209,9 +150,6 @@ pub fn create(wlr_output: *wlr.Output) !void {
     };
     wlr_output.data = @intFromPtr(output);
 
-    _ = try output.layers.fullscreen.createSceneRect(width, height, &[_]f32{ 0, 0, 0, 1.0 });
-    output.layers.fullscreen.node.setEnabled(false);
-
     wlr_output.events.destroy.add(&output.destroy);
     wlr_output.events.request_state.add(&output.request_state);
     wlr_output.events.frame.add(&output.frame);
@@ -225,16 +163,6 @@ pub fn create(wlr_output: *wlr.Output) !void {
     output.handleEnableDisable();
 }
 
-pub fn layerSurfaceTree(output: Output, layer: zwlr.LayerShellV1.Layer) *wlr.SceneTree {
-    const trees = [_]*wlr.SceneTree{
-        output.layers.background,
-        output.layers.bottom,
-        output.layers.top,
-        output.layers.overlay,
-    };
-    return trees[@intCast(@intFromEnum(layer))];
-}
-
 /// Arrange all layer surfaces of this output and adjust the usable area.
 /// Will arrange windows as well if the usable area changes.
 /// Requires a call to WindowManager.dirtyPending()
@@ -264,6 +192,7 @@ fn sendLayerConfigures(
     usable_box: *wlr.Box,
     mode: enum { exclusive, non_exclusive },
 ) void {
+    if (true) @panic("XXX");
     for ([_]zwlr.LayerShellV1.Layer{ .background, .bottom, .top, .overlay }) |layer| {
         const tree = output.layerSurfaceTree(layer);
         var it = tree.children.safeIterator(.forward);
@@ -321,8 +250,6 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
     output.frame.link.remove();
     output.present.link.remove();
 
-    output.tree.node.destroy();
-
     output.wlr_output.data = 0;
 
     util.gpa.destroy(output);
@@ -362,9 +289,8 @@ pub fn applyState(output: *Output, state: *wlr.Output.State) error{CommitFailed}
     }
 
     if (state.committed.mode) {
-        output.updateBackgroundRect();
         output.arrangeLayers();
-        server.lock_manager.updateLockSurfaceSize(output);
+        if (server.lock_manager.lockSurfaceFromOutput(output)) |s| s.configure();
     }
 }
 
@@ -382,40 +308,14 @@ fn handleEnableDisable(output: *Output) void {
 }
 
 pub fn updateLockRenderStateOnEnableDisable(output: *Output) void {
-    // We can't assert the current state of normal_content/locked_content
-    // here as this output may be newly created.
     if (output.wlr_output.enabled) {
-        switch (server.lock_manager.state) {
-            .unlocked => {
-                assert(output.lock_render_state == .blanked);
-                output.normal_content.node.setEnabled(true);
-                output.locked_content.node.setEnabled(false);
-            },
-            .waiting_for_lock_surfaces, .waiting_for_blank, .locked => {
-                assert(output.lock_render_state == .blanked);
-                output.normal_content.node.setEnabled(false);
-                output.locked_content.node.setEnabled(true);
-            },
-        }
+        assert(output.lock_render_state == .blanked);
     } else {
         // Disabling and re-enabling an output always blanks it.
         output.lock_render_state = .blanked;
-        output.normal_content.node.setEnabled(false);
-        output.locked_content.node.setEnabled(true);
     }
 }
 
-pub fn updateBackgroundRect(output: *Output) void {
-    var width: c_int = undefined;
-    var height: c_int = undefined;
-    output.wlr_output.effectiveResolution(&width, &height);
-    output.layers.background_color_rect.setSize(width, height);
-
-    var it = output.layers.fullscreen.children.iterator(.forward);
-    const fullscreen_background: *wlr.SceneRect = @fieldParentPtr("node", it.next().?);
-    fullscreen_background.setSize(width, height);
-}
-
 fn handleFrame(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
     const output: *Output = @fieldParentPtr("frame", listener);
     const scene_output = server.root.scene.getSceneOutput(output.wlr_output).?;
@@ -457,12 +357,20 @@ fn renderAndCommit(output: *Output, scene_output: *wlr.SceneOutput) !void {
         if (!scene_output.commit(null)) return error.CommitFailed;
     }
 
+    const lock_surface_mapped = blk: {
+        if (server.lock_manager.lockSurfaceFromOutput(output)) |lock_surface| {
+            break :blk lock_surface.wlr_lock_surface.surface.mapped;
+        } else {
+            break :blk false;
+        }
+    };
+
     if (server.lock_manager.state == .locked or
-        (server.lock_manager.state == .waiting_for_lock_surfaces and output.locked_content.node.enabled) or
+        (server.lock_manager.state == .waiting_for_lock_surfaces and lock_surface_mapped) or
         server.lock_manager.state == .waiting_for_blank)
     {
-        assert(!output.normal_content.node.enabled);
-        assert(output.locked_content.node.enabled);
+        assert(!server.root.normal_tree.node.enabled);
+        assert(server.root.locked_tree.node.enabled);
 
         switch (server.lock_manager.state) {
             .unlocked => unreachable,
diff --git a/river/Root.zig b/river/Root.zig
index 0f6fb10..4ecf190 100644
--- a/river/Root.zig
+++ b/river/Root.zig
@@ -37,23 +37,38 @@ const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
 
 scene: *wlr.Scene,
 /// All windows, status bars, drowdown menus, etc. that can recieve pointer events and similar.
-interactive_content: *wlr.SceneTree,
-/// Drag icons, which cannot recieve e.g. pointer events and are therefore kept in a separate tree.
+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 interactive_content scene node
+/// All direct children of the normal_tree scene node
 layers: struct {
-    /// Parent tree for output trees which have their position updated when
-    /// outputs are moved in the layout.
-    outputs: *wlr.SceneTree,
+    /// 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,
 },
 
-hidden_tree: *wlr.SceneTree,
-
 new_output: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleNewOutput),
 
 output_layout: *wlr.OutputLayout,
@@ -90,28 +105,34 @@ pub fn init(root: *Root) !void {
     const scene = try wlr.Scene.create();
     errdefer scene.tree.node.destroy();
 
-    const interactive_content = try scene.tree.createSceneTree();
+    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 outputs = try interactive_content.createSceneTree();
-    const override_redirect = if (build_options.xwayland) try interactive_content.createSceneTree();
+    const normal_tree = try interactive_tree.createSceneTree();
+    const locked_tree = try interactive_tree.createSceneTree();
 
     root.* = .{
         .scene = scene,
-        .interactive_content = interactive_content,
+        .interactive_tree = interactive_tree,
         .drag_icons = drag_icons,
+        .hidden_tree = hidden_tree,
+        .normal_tree = normal_tree,
+        .locked_tree = locked_tree,
         .layers = .{
-            .outputs = outputs,
-            .override_redirect = override_redirect,
+            .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,
         .all_outputs = undefined,
         .active_outputs = undefined,
 
-        .hidden_tree = hidden_tree,
-
         .presentation = try wlr.Presentation.create(server.wl_server, server.backend),
         .xdg_output_manager = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout),
         .output_manager = try wlr.OutputManagerV1.create(server.wl_server),
@@ -142,12 +163,12 @@ pub const AtResult = struct {
     data: SceneNodeData.Data,
 };
 
-/// Return information about what is currently rendered in the interactive_content
+/// 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_content.node.at(lx, ly, &sx, &sy) orelse return null;
+    const node = root.interactive_tree.node.at(lx, ly, &sx, &sy) orelse return null;
 
     const surface: ?*wlr.Surface = blk: {
         if (node.type == .buffer) {
@@ -172,6 +193,16 @@ pub fn at(root: Root, lx: f64, ly: f64) ?AtResult {
     }
 }
 
+pub fn layerSurfaceTree(root: Root, layer: zwlr.LayerShellV1.Layer) *wlr.SceneTree {
+    const trees = [_]*wlr.SceneTree{
+        root.layers.background,
+        root.layers.bottom,
+        root.layers.top,
+        root.layers.overlay,
+    };
+    return trees[@intCast(@intFromEnum(layer))];
+}
+
 fn handleNewOutput(_: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void {
     const log = std.log.scoped(.output_manager);
 
@@ -203,22 +234,11 @@ pub fn deactivateOutput(root: *Root, output: *Output) void {
     }
 
     root.output_layout.remove(output.wlr_output);
-    output.tree.node.setEnabled(false);
 
     output.active_link.remove();
     output.active_link.init();
 
-    // Close all layer surfaces on the removed output
-    for ([_]zwlr.LayerShellV1.Layer{ .overlay, .top, .bottom, .background }) |layer| {
-        const tree = output.layerSurfaceTree(layer);
-        var it = tree.children.safeIterator(.forward);
-        while (it.next()) |scene_node| {
-            assert(scene_node.type == .tree);
-            if (@as(?*SceneNodeData, @ptrFromInt(scene_node.data))) |node_data| {
-                node_data.data.layer_surface.wlr_layer_surface.destroy();
-            }
-        }
-    }
+    // XXX Close all layer surfaces on the removed output
 
     // We must call reconfigureDevices here to unmap devices that might be mapped to this output
     // in order to prevent a segfault in wlroots.
@@ -271,9 +291,10 @@ pub fn handleOutputConfigChange(root: *Root) !void {
         var box: wlr.Box = undefined;
         root.output_layout.getBox(output.wlr_output, &box);
 
-        output.tree.node.setEnabled(!box.empty());
-        output.tree.node.setPosition(box.x, box.y);
-        output.scene_output.setPosition(box.x, box.y);
+        // XXX
+        //output.tree.node.setEnabled(!box.empty());
+        //output.tree.node.setPosition(box.x, box.y);
+        //output.scene_output.setPosition(box.x, box.y);
 
         const head = try wlr.OutputConfigurationV1.Head.create(config, output.wlr_output);
         head.state.x = box.x;