diff --git a/river/Output.zig b/river/Output.zig index e65d4d0..155ce62 100644 --- a/river/Output.zig +++ b/river/Output.zig @@ -72,6 +72,10 @@ attach_mode: AttachMode = .top, /// List of status tracking objects relaying changes to this output to clients. status_trackers: std.SinglyLinkedList(OutputStatus) = .{}, +/// Whether or not the output is active +/// An active output can have focus (e.g. an output turned off by dpms is active) +active: bool = false, + // All listeners for this output, in alphabetical order listen_destroy: c.wl_listener = undefined, listen_frame: c.wl_listener = undefined, @@ -503,45 +507,10 @@ fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { const root = self.root; log.debug(.server, "output '{}' destroyed", .{self.wlr_output.name}); - - // Use the first output in the list that is not the one being destroyed. - // If there is no other real output, use the noop output. - var output_it = root.outputs.first; - const fallback_output = while (output_it) |output_node| : (output_it = output_node.next) { - if (&output_node.data != self) { - break &output_node.data; - } - } else &root.noop_output; - - // Move all views from the destroyed output to the fallback one - while (self.views.last) |node| { - const view = &node.view; - view.sendToOutput(fallback_output); - } - - // Close all layer surfaces on the destroyed output - for (self.layers) |*layer, layer_idx| { - while (layer.pop()) |node| { - const layer_surface = &node.data; - // We need to move the closing layer surface to the noop output - // since it may not be immediately destoryed. This just a request - // to close which will trigger unmap and destroy events in - // response, and the LayerSurface needs a valid output to - // handle them. - root.noop_output.layers[layer_idx].prepend(node); - layer_surface.output = &root.noop_output; - c.wlr_layer_surface_v1_close(layer_surface.wlr_layer_surface); - } - } - - // If any seat has the destroyed output focused, focus the fallback one - var seat_it = root.server.input_manager.seats.first; - while (seat_it) |seat_node| : (seat_it = seat_node.next) { - const seat = &seat_node.data; - if (seat.focused_output == self) { - seat.focusOutput(fallback_output); - seat.focus(null); - } + // Remove the destroyed output from root if it wasn't already removed + const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); + if (self.active) { + root.removeOutput(node); } // Remove all listeners @@ -549,20 +518,12 @@ fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { c.wl_list_remove(&self.listen_frame.link); c.wl_list_remove(&self.listen_mode.link); - // Clean up the wlr_output - self.wlr_output.data = null; - // Free the layout command util.gpa.free(self.layout); - // Remove the destroyed output from the list - const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); - root.outputs.remove(node); + // Clean up the wlr_output + self.wlr_output.data = null; util.gpa.destroy(node); - - // Arrange the root in case evacuated views affect the layout - fallback_output.arrangeViews(); - root.startTransaction(); } fn handleFrame(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { diff --git a/river/Root.zig b/river/Root.zig index 0c6f6e8..b707143 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -35,6 +35,11 @@ const DragIcon = @import("DragIcon.zig"); server: *Server, wlr_output_layout: *c.wlr_output_layout, + +/// A list of all outputs +all_outputs: std.TailQueue(*Output) = .{}, + +/// A list of all active outputs. See Output.active outputs: std.TailQueue(Output) = .{}, /// This output is used internally when no real outputs are available. @@ -91,10 +96,58 @@ pub fn deinit(self: *Self) void { if (c.wl_event_source_remove(self.transaction_timer) < 0) unreachable; } +/// Removes the output in node.data from self.outputs +/// The node is not freed +pub fn removeOutput(self: *Self, node: *std.TailQueue(Output).Node) void { + const output = &node.data; + self.outputs.remove(node); + output.active = false; + + // Use the first output in the list as fallback. + // If there is no other real output, use the noop output. + const fallback_output = if (self.outputs.first) |output_node| &output_node.data else &self.noop_output; + + // Move all views from the destroyed output to the fallback one + while (output.views.last) |view_node| { + const view = &view_node.view; + view.sendToOutput(fallback_output); + } + + // Close all layer surfaces on the removed output + for (output.layers) |*layer, layer_idx| { + while (layer.pop()) |layer_node| { + const layer_surface = &layer_node.data; + // We need to move the closing layer surface to the noop output + // since it may not be immediately destoryed. This just a request + // to close which will trigger unmap and destroy events in + // response, and the LayerSurface needs a valid output to + // handle them. + self.noop_output.layers[layer_idx].prepend(layer_node); + layer_surface.output = &self.noop_output; + c.wlr_layer_surface_v1_close(layer_surface.wlr_layer_surface); + } + } + + // If any seat has the removed output focused, focus the fallback one + var seat_it = self.server.input_manager.seats.first; + while (seat_it) |seat_node| : (seat_it = seat_node.next) { + const seat = &seat_node.data; + if (seat.focused_output == output) { + seat.focusOutput(fallback_output); + seat.focus(null); + } + } + + // Arrange the root in case evacuated views affect the layout + fallback_output.arrangeViews(); + self.startTransaction(); +} + /// Adds the output in node.data to self.outputs /// The Output in node.data must be initalized pub fn addOutput(self: *Self, node: *std.TailQueue(Output).Node) void { self.outputs.append(node); + node.data.active = true; // Add the new output to the layout. The add_auto function arranges outputs // from left-to-right in the order they appear. A more sophisticated