From 0605ce507e9eeef62f7047a76c4a8b586f8ff60a Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sun, 25 Feb 2024 14:37:34 +0100 Subject: [PATCH] Root: fix auto-layout output coordinates Outputs using the wlr_output_layout auto-layout feature may have their coordinates update any time an output is added/removed to the layout or the position of another output in the layout is set. River currently doesn't keep the scene node coordinates of such outputs in sync with their position in the output layout, which leads to bugs with e.g. the cursor not working properly for such outputs. --- river/Output.zig | 2 +- river/Root.zig | 75 ++++++++++++++++++++++++++---------------------- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/river/Output.zig b/river/Output.zig index dcd6dfb..d208cb6 100644 --- a/river/Output.zig +++ b/river/Output.zig @@ -409,7 +409,7 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void { util.gpa.destroy(output); - server.root.sendOutputManagerConfig(); + server.root.handleOutputConfigChange() catch std.log.err("out of memory", .{}); server.root.applyPending(); } diff --git a/river/Root.zig b/river/Root.zig index f4bf0fe..e91ea1f 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -81,6 +81,7 @@ views: wl.list.Head(View, .link), new_output: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleNewOutput), output_layout: *wlr.OutputLayout, +layout_change: wl.Listener(*wlr.OutputLayout) = wl.Listener(*wlr.OutputLayout).init(handleLayoutChange), output_manager: *wlr.OutputManagerV1, manager_apply: wl.Listener(*wlr.OutputConfigurationV1) = @@ -185,6 +186,7 @@ pub fn init(self: *Self) !void { server.backend.events.new_output.add(&self.new_output); self.output_manager.events.apply.add(&self.manager_apply); self.output_manager.events.@"test".add(&self.manager_test); + self.output_layout.events.change.add(&self.layout_change); self.power_manager.events.set_mode.add(&self.power_manager_set_mode); self.gamma_control_manager.events.set_gamma.add(&self.gamma_control_set_gamma); } @@ -246,7 +248,7 @@ fn handleNewOutput(_: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) void { return; }; - server.root.sendOutputManagerConfig(); + server.root.handleOutputConfigChange() catch log.err("out of memory", .{}); server.input_manager.reconfigureDevices(); } @@ -359,7 +361,7 @@ pub fn activateOutput(root: *Self, output: *Output) void { // This arranges outputs from left-to-right in the order they appear. The // wlr-output-management protocol may be used to modify this arrangement. // This also creates a wl_output global which is advertised to clients. - const layout_output = root.output_layout.addAuto(output.wlr_output) catch { + _ = root.output_layout.addAuto(output.wlr_output) catch { // This would currently be very awkward to handle well and this output // handling code needs to be heavily refactored soon anyways for double // buffered state application as part of the transaction system. @@ -367,9 +369,6 @@ pub fn activateOutput(root: *Self, output: *Output) void { // possible to handle after updating to 0.17. @panic("TODO handle allocation failure here"); }; - output.tree.node.setEnabled(true); - output.tree.node.setPosition(layout_output.x, layout_output.y); - output.scene_output.setPosition(layout_output.x, layout_output.y); // If we previously had no outputs, move all views to the new output and focus it. if (first) { @@ -712,12 +711,37 @@ fn commitTransaction(root: *Self) void { } } -/// Send the current output configuration to all wlr-output-manager clients -pub fn sendOutputManagerConfig(self: *Self) void { - const config = self.currentOutputConfig() catch { - std.log.scoped(.output_manager).err("out of memory", .{}); - return; - }; +// We need this listener to deal with outputs that have their position auto-configured +// by the wlr_output_layout. +fn handleLayoutChange(listener: *wl.Listener(*wlr.OutputLayout), _: *wlr.OutputLayout) void { + const self = @fieldParentPtr(Self, "layout_change", listener); + + self.handleOutputConfigChange() catch std.log.err("out of memory", .{}); +} + +/// Sync up the output scene node state with the output_layout and +/// send the current output configuration to all wlr-output-manager clients. +pub fn handleOutputConfigChange(self: *Self) !void { + const config = try wlr.OutputConfigurationV1.create(); + // this destroys all associated config heads as well + errdefer config.destroy(); + + var it = self.all_outputs.iterator(.forward); + while (it.next()) |output| { + // If the output is not part of the layout (and thus disabled) + // the box will be zeroed out. + var box: wlr.Box = undefined; + self.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); + + const head = try wlr.OutputConfigurationV1.Head.create(config, output.wlr_output); + head.state.x = box.x; + head.state.y = box.y; + } + self.output_manager.setConfiguration(config); } @@ -732,8 +756,7 @@ fn handleManagerApply( self.processOutputConfig(config, .apply); - // Send the config that was actually applied - self.sendOutputManagerConfig(); + self.handleOutputConfigChange() catch std.log.err("out of memory", .{}); } fn handleManagerTest( @@ -751,6 +774,10 @@ fn processOutputConfig( config: *wlr.OutputConfigurationV1, action: enum { test_only, apply }, ) void { + // Ignore layout change events this function generates while applying the config + self.layout_change.link.remove(); + defer self.output_layout.events.change.add(&self.layout_change); + var success = true; var it = config.heads.iterator(.forward); @@ -787,8 +814,6 @@ fn processOutputConfig( // applyState() will always add the output to the layout on success, which means // that this function cannot fail as it does not need to allocate a new layout output. _ = self.output_layout.add(output.wlr_output, head.state.x, head.state.y) catch unreachable; - output.tree.node.setPosition(head.state.x, head.state.y); - output.scene_output.setPosition(head.state.x, head.state.y); } }, } @@ -803,26 +828,6 @@ fn processOutputConfig( } } -fn currentOutputConfig(self: *Self) !*wlr.OutputConfigurationV1 { - const config = try wlr.OutputConfigurationV1.create(); - // this destroys all associated config heads as well - errdefer config.destroy(); - - var it = self.all_outputs.iterator(.forward); - while (it.next()) |output| { - const head = try wlr.OutputConfigurationV1.Head.create(config, output.wlr_output); - - // If the output is not part of the layout (and thus disabled) - // the box will be zeroed out. - var box: wlr.Box = undefined; - self.output_layout.getBox(output.wlr_output, &box); - head.state.x = box.x; - head.state.y = box.y; - } - - return config; -} - fn handlePowerManagerSetMode( _: *wl.Listener(*wlr.OutputPowerManagerV1.event.SetMode), event: *wlr.OutputPowerManagerV1.event.SetMode,