diff --git a/src/command.zig b/src/command.zig index ab290c8..1f2b483 100644 --- a/src/command.zig +++ b/src/command.zig @@ -33,22 +33,24 @@ pub fn focusPrevView(server: *Server, arg: Arg) void { /// Modify the number of master views pub fn modifyMasterCount(server: *Server, arg: Arg) void { const delta = arg.int; - server.root.master_count = @intCast(u32, std.math.max( - 0, - @intCast(i32, server.root.master_count) + delta, - )); + const output = server.root.focusedOutput(); + output.master_count = @intCast( + u32, + std.math.max(0, @intCast(i32, output.master_count) + delta), + ); server.root.arrange(); } /// Modify the percent of the width of the screen that the master views occupy. pub fn modifyMasterFactor(server: *Server, arg: Arg) void { const delta = arg.float; + const output = server.root.focusedOutput(); const new_master_factor = std.math.min( - std.math.max(server.root.master_factor + delta, 0.05), + std.math.max(output.master_factor + delta, 0.05), 0.95, ); - if (new_master_factor != server.root.master_factor) { - server.root.master_factor = new_master_factor; + if (new_master_factor != output.master_factor) { + output.master_factor = new_master_factor; server.root.arrange(); } } @@ -57,10 +59,11 @@ pub fn modifyMasterFactor(server: *Server, arg: Arg) void { /// TODO: if the top of the stack is focused, bump the next visible view. pub fn zoom(server: *Server, arg: Arg) void { if (server.root.focused_view) |current_focus| { + const output = server.root.focusedOutput(); const node = @fieldParentPtr(ViewStack.Node, "view", current_focus); - if (node != server.root.views.first) { - server.root.views.remove(node); - server.root.views.push(node); + if (node != output.views.first) { + output.views.remove(node); + output.views.push(node); server.root.arrange(); } } @@ -69,16 +72,18 @@ pub fn zoom(server: *Server, arg: Arg) void { /// Switch focus to the passed tags. pub fn focusTags(server: *Server, arg: Arg) void { const tags = arg.uint; - server.root.pending_focused_tags = tags; + const output = server.root.focusedOutput(); + output.pending_focused_tags = tags; server.root.arrange(); } /// Toggle focus of the passsed tags. pub fn toggleTags(server: *Server, arg: Arg) void { const tags = arg.uint; - const new_focused_tags = server.root.current_focused_tags ^ tags; + const output = server.root.focusedOutput(); + const new_focused_tags = output.current_focused_tags ^ tags; if (new_focused_tags != 0) { - server.root.pending_focused_tags = new_focused_tags; + output.pending_focused_tags = new_focused_tags; server.root.arrange(); } } diff --git a/src/output.zig b/src/output.zig index 1888a44..634a062 100644 --- a/src/output.zig +++ b/src/output.zig @@ -4,7 +4,9 @@ const render = @import("render.zig"); const Box = @import("box.zig").Box; const LayerSurface = @import("layer_surface.zig").LayerSurface; +const Log = @import("log.zig").Log; const Root = @import("root.zig").Root; +const ViewStack = @import("view_stack.zig").ViewStack; pub const Output = struct { const Self = @This(); @@ -12,8 +14,26 @@ pub const Output = struct { root: *Root, wlr_output: *c.wlr_output, + /// All layer surfaces on the output, indexed by the layer enum. layers: [4]std.TailQueue(LayerSurface), + /// The area left for views and other layer surfaces after applying the + /// exclusive zones of exclusive layer surfaces. + usable_box: Box, + + /// The top of the stack is the "most important" view. + views: ViewStack, + + /// A bit field of focused tags + current_focused_tags: u32, + pending_focused_tags: ?u32, + + /// Number of views in "master" section of the screen. + master_count: u32, + + /// Percentage of the total screen that the master section takes up. + master_factor: f64, + listen_frame: c.wl_listener, pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void { @@ -41,6 +61,17 @@ pub const Output = struct { layer.* = std.TailQueue(LayerSurface).init(); } + self.usable_box = undefined; + + self.views.init(); + + self.current_focused_tags = 1 << 0; + self.pending_focused_tags = null; + + self.master_count = 1; + + self.master_factor = 0.6; + // Sets up a listener for the frame notify event. self.listen_frame.notify = handleFrame; c.wl_signal_add(&wlr_output.events.frame, &self.listen_frame); @@ -57,6 +88,14 @@ pub const Output = struct { c.wlr_output_create_global(wlr_output); } + /// Add a new view to the output. arrangeViews() will be called by the view + /// when it is mapped. + pub fn addView(self: *Self, wlr_xdg_surface: *c.wlr_xdg_surface) void { + const node = self.root.server.allocator.create(ViewStack.Node) catch unreachable; + node.view.init(self, wlr_xdg_surface, self.current_focused_tags); + self.views.push(node); + } + /// Add a newly created layer surface to the output. pub fn addLayerSurface(self: *Self, wlr_layer_surface: *c.wlr_layer_surface_v1) !void { const layer = wlr_layer_surface.client_pending.layer; @@ -66,6 +105,86 @@ pub const Output = struct { self.arrangeLayers(); } + pub fn arrange(self: *Self) void { + // TODO: properly handle output events instead of calling arrangeLayers() here + self.arrangeLayers(); + self.arrangeViews(); + } + + /// Arrange all views on the output for the current layout. Modifies only + /// pending state, the changes are not appplied until a transaction is started + /// and completed. + fn arrangeViews(self: *Self) void { + const output_tags = if (self.pending_focused_tags) |tags| + tags + else + self.current_focused_tags; + + const visible_count = blk: { + var count: u32 = 0; + var it = ViewStack.pendingIterator(self.views.first, output_tags); + while (it.next() != null) count += 1; + break :blk count; + }; + + const master_count = std.math.min(self.master_count, visible_count); + const slave_count = if (master_count >= visible_count) 0 else visible_count - master_count; + + const outer_padding = self.root.server.config.outer_padding; + + const layout_width = @intCast(u32, self.usable_box.width) - outer_padding * 2; + const layout_height = @intCast(u32, self.usable_box.height) - outer_padding * 2; + + var master_column_width: u32 = undefined; + var slave_column_width: u32 = undefined; + if (master_count > 0 and slave_count > 0) { + // If both master and slave views are present + master_column_width = @floatToInt(u32, @round(@intToFloat(f64, layout_width) * self.master_factor)); + slave_column_width = layout_width - master_column_width; + } else if (master_count > 0) { + master_column_width = layout_width; + slave_column_width = 0; + } else { + slave_column_width = layout_width; + master_column_width = 0; + } + + var i: u32 = 0; + var it = ViewStack.pendingIterator(self.views.first, output_tags); + while (it.next()) |view| { + if (i < master_count) { + // Add the remainder to the first master to ensure every pixel of height is used + const master_height = @divTrunc(layout_height, master_count); + const master_height_rem = layout_height % master_count; + + view.pending_box = Box{ + .x = @intCast(i32, outer_padding), + .y = @intCast(i32, outer_padding + i * master_height + + if (i > 0) master_height_rem else 0), + + .width = master_column_width, + .height = master_height + if (i == 0) master_height_rem else 0, + }; + } else { + // Add the remainder to the first slave to ensure every pixel of height is used + const slave_height = @divTrunc(layout_height, slave_count); + const slave_height_rem = layout_height % slave_count; + + view.pending_box = Box{ + .x = @intCast(i32, outer_padding + master_column_width), + .y = @intCast(i32, outer_padding + (i - master_count) * slave_height + + if (i > master_count) slave_height_rem else 0), + + .width = slave_column_width, + .height = slave_height + + if (i == master_count) slave_height_rem else 0, + }; + } + + i += 1; + } + } + /// Arrange all layer surfaces of this output and addjust the usable aread pub fn arrangeLayers(self: *Self) void { // TODO: handle exclusive zones @@ -85,6 +204,8 @@ pub const Output = struct { self.arrangeLayer(layer, bounds); } + self.usable_box = bounds; + // TODO: handle seat focus } diff --git a/src/render.zig b/src/render.zig index 5eb324c..a330a5b 100644 --- a/src/render.zig +++ b/src/render.zig @@ -44,10 +44,7 @@ pub fn renderOutput(output: *Output) void { renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &now, ox, oy); // The first view in the list is "on top" so iterate in reverse. - var it = ViewStack.reverseIterator( - output.root.views.last, - output.root.current_focused_tags, - ); + var it = ViewStack.reverseIterator(output.views.last, output.current_focused_tags); while (it.next()) |view| { // This check prevents a race condition when a frame is requested // between mapping of a view and the first configure being handled. @@ -166,8 +163,8 @@ fn renderView(output: Output, view: *View, now: *c.struct_timespec, ox: f64, oy: // If we have a stashed buffer, we are in the middle of a transaction // and need to render that buffer until the transaction is complete. if (view.stashed_buffer) |buffer| { - const border_width = view.root.server.config.border_width; - const view_padding = view.root.server.config.view_padding; + const border_width = view.output.root.server.config.border_width; + const view_padding = view.output.root.server.config.view_padding; var box = c.wlr_box{ .x = view.current_box.x + @intCast(i32, border_width + view_padding), .y = view.current_box.y + @intCast(i32, border_width + view_padding), @@ -231,8 +228,8 @@ fn renderSurface(_surface: ?*c.wlr_surface, sx: c_int, sy: c_int, data: ?*c_void return; } - const border_width = view.root.server.config.border_width; - const view_padding = view.root.server.config.view_padding; + const border_width = view.output.root.server.config.border_width; + const view_padding = view.output.root.server.config.view_padding; var box = c.wlr_box{ .x = @floatToInt(c_int, rdata.ox) + view.current_box.x + sx + @intCast(c_int, border_width + view_padding), diff --git a/src/root.zig b/src/root.zig index 6dccf42..2ac8074 100644 --- a/src/root.zig +++ b/src/root.zig @@ -19,22 +19,10 @@ pub const Root = struct { wlr_output_layout: *c.wlr_output_layout, outputs: std.TailQueue(Output), - /// The top of the stack is the "most important" view. - views: ViewStack, - /// The view that has seat focus, if any. + /// TODO: move this to Seat focused_view: ?*View, - /// A bit field of focused tags - current_focused_tags: u32, - pending_focused_tags: ?u32, - - /// Number of views in "master" section of the screen. - master_count: u32, - - /// Percentage of the total screen that the master section takes up. - master_factor: f64, - /// Number of pending configures sent in the current transaction. /// A value of 0 means there is no current transaction. pending_configures: u32, @@ -53,17 +41,8 @@ pub const Root = struct { self.outputs = std.TailQueue(Output).init(); - self.views.init(); - self.focused_view = null; - self.current_focused_tags = 1 << 0; - self.pending_focused_tags = null; - - self.master_count = 1; - - self.master_factor = 0.6; - self.pending_configures = 0; self.transaction_timer = null; @@ -80,19 +59,23 @@ pub const Root = struct { self.outputs.append(node); } - pub fn addView(self: *Self, wlr_xdg_surface: *c.wlr_xdg_surface) void { - const node = self.server.allocator.create(ViewStack.Node) catch unreachable; - node.view.init(self, wlr_xdg_surface, self.current_focused_tags); - self.views.push(node); + /// TODO: move this to seat, it's just a stop gap hack + pub fn focusedOutput(self: Self) *Output { + return &self.outputs.first.?.data; } /// Finds the topmost view under the output layout coordinates lx, ly /// returns the view if found, and a pointer to the wlr_surface as well as the surface coordinates pub fn viewAt(self: Self, lx: f64, ly: f64, surface: *?*c.wlr_surface, sx: *f64, sy: *f64) ?*View { - var it = ViewStack.iterator(self.views.first, 0xFFFFFFFF); - while (it.next()) |view| { - if (view.isAt(lx, ly, surface, sx, sy)) { - return view; + // Iterate over all views of all outputs + var output_it = self.outputs.first; + while (output_it) |node| : (output_it = node.next) { + const output = &node.data; + var view_it = ViewStack.iterator(output.views.first, 0xFFFFFFFF); + while (view_it.next()) |view| { + if (view.isAt(lx, ly, surface, sx, sy)) { + return view; + } } } return null; @@ -109,10 +92,11 @@ pub const Root = struct { /// Focus the next visible view in the stack, wrapping if needed. Does /// nothing if there is only one view in the stack. pub fn focusNextView(self: *Self) void { + const output = self.focusedOutput(); if (self.focused_view) |current_focus| { // If there is a currently focused view, focus the next visible view in the stack. const current_node = @fieldParentPtr(ViewStack.Node, "view", current_focus); - var it = ViewStack.iterator(current_node, self.current_focused_tags); + var it = ViewStack.iterator(current_node, output.current_focused_tags); // Skip past the current node _ = it.next(); // Focus the next visible node if there is one @@ -124,7 +108,7 @@ pub const Root = struct { // There is either no currently focused view or the last visible view in the // stack is focused and we need to wrap. - var it = ViewStack.iterator(self.views.first, self.current_focused_tags); + var it = ViewStack.iterator(output.views.first, output.current_focused_tags); if (it.next()) |view| { view.focus(view.wlr_xdg_surface.surface); } else { @@ -136,10 +120,11 @@ pub const Root = struct { /// Focus the previous view in the stack, wrapping if needed. Does nothing /// if there is only one view in the stack. pub fn focusPrevView(self: *Self) void { + const output = self.focusedOutput(); if (self.focused_view) |current_focus| { // If there is a currently focused view, focus the previous visible view in the stack. const current_node = @fieldParentPtr(ViewStack.Node, "view", current_focus); - var it = ViewStack.reverseIterator(current_node, self.current_focused_tags); + var it = ViewStack.reverseIterator(current_node, output.current_focused_tags); // Skip past the current node _ = it.next(); // Focus the previous visible node if there is one @@ -151,7 +136,7 @@ pub const Root = struct { // There is either no currently focused view or the first visible view in the // stack is focused and we need to wrap. - var it = ViewStack.reverseIterator(self.views.last, self.current_focused_tags); + var it = ViewStack.reverseIterator(output.views.last, output.current_focused_tags); if (it.next()) |view| { view.focus(view.wlr_xdg_surface.surface); } else { @@ -160,79 +145,13 @@ pub const Root = struct { } } + /// Arrange all outputs and then a transaction. pub fn arrange(self: *Self) void { - const root_tags = if (self.pending_focused_tags) |tags| - tags - else - self.current_focused_tags; - - const visible_count = blk: { - var count: u32 = 0; - var it = ViewStack.pendingIterator(self.views.first, root_tags); - while (it.next() != null) count += 1; - break :blk count; - }; - - const master_count = util.min(u32, self.master_count, visible_count); - const slave_count = if (master_count >= visible_count) 0 else visible_count - master_count; - - // This can't return null if we pass null as the reference - const output_box: *c.wlr_box = c.wlr_output_layout_get_box(self.wlr_output_layout, null); - - const outer_padding = self.server.config.outer_padding; - - const layout_width = @intCast(u32, output_box.width) - outer_padding * 2; - const layout_height = @intCast(u32, output_box.height) - outer_padding * 2; - - var master_column_width: u32 = undefined; - var slave_column_width: u32 = undefined; - if (master_count > 0 and slave_count > 0) { - // If both master and slave views are present - master_column_width = @floatToInt(u32, @round(@intToFloat(f64, layout_width) * self.master_factor)); - slave_column_width = layout_width - master_column_width; - } else if (master_count > 0) { - master_column_width = layout_width; - slave_column_width = 0; - } else { - slave_column_width = layout_width; - master_column_width = 0; + var it = self.outputs.first; + while (it) |node| : (it = node.next) { + const output = &node.data; + output.arrange(); } - - var i: u32 = 0; - var it = ViewStack.pendingIterator(self.views.first, root_tags); - while (it.next()) |view| { - if (i < master_count) { - // Add the remainder to the first master to ensure every pixel of height is used - const master_height = @divTrunc(layout_height, master_count); - const master_height_rem = layout_height % master_count; - - view.pending_box = Box{ - .x = @intCast(i32, outer_padding), - .y = @intCast(i32, outer_padding + i * master_height + - if (i > 0) master_height_rem else 0), - - .width = master_column_width, - .height = master_height + if (i == 0) master_height_rem else 0, - }; - } else { - // Add the remainder to the first slave to ensure every pixel of height is used - const slave_height = @divTrunc(layout_height, slave_count); - const slave_height_rem = layout_height % slave_count; - - view.pending_box = Box{ - .x = @intCast(i32, outer_padding + master_column_width), - .y = @intCast(i32, outer_padding + (i - master_count) * slave_height + - if (i > master_count) slave_height_rem else 0), - - .width = slave_column_width, - .height = slave_height + - if (i == master_count) slave_height_rem else 0, - }; - } - - i += 1; - } - self.startTransaction(); } @@ -243,25 +162,30 @@ pub const Root = struct { // to reset the pending count to 0 and clear serials from the views self.pending_configures = 0; - var it = ViewStack.iterator(self.views.first, 0xFFFFFFFF); - while (it.next()) |view| { - // Clear the serial in case this transaction is interrupting a prior one. - view.pending_serial = null; + // Iterate over all views of all outputs + var output_it = self.outputs.first; + while (output_it) |node| : (output_it = node.next) { + const output = &node.data; + var view_it = ViewStack.iterator(output.views.first, 0xFFFFFFFF); + while (view_it.next()) |view| { + // Clear the serial in case this transaction is interrupting a prior one. + view.pending_serial = null; - if (view.needsConfigure()) { - view.configurePending(); - self.pending_configures += 1; + if (view.needsConfigure()) { + view.configurePending(); + self.pending_configures += 1; - // We save the current buffer, so we can send an early - // frame done event to give the client a head start on - // redrawing. - view.sendFrameDone(); - } + // We save the current buffer, so we can send an early + // frame done event to give the client a head start on + // redrawing. + view.sendFrameDone(); + } - // If there is a saved buffer present, then this transaction is interrupting - // a previous transaction and we should keep the old buffer. - if (view.stashed_buffer == null) { - view.stashBuffer(); + // If there is a saved buffer present, then this transaction is interrupting + // a previous transaction and we should keep the old buffer. + if (view.stashed_buffer == null) { + view.stashBuffer(); + } } } @@ -317,45 +241,51 @@ pub const Root = struct { // Ensure this is set to 0 to avoid entering invalid state (e.g. if called due to timeout) self.pending_configures = 0; - // If there were pending focused tags, make them the current focus - if (self.pending_focused_tags) |tags| { - Log.Debug.log( - "changing current focus: {b:0>10} to {b:0>10}", - .{ self.current_focused_tags, tags }, - ); - self.current_focused_tags = tags; - self.pending_focused_tags = null; + // Iterate over all views of all outputs + var output_it = self.outputs.first; + while (output_it) |node| : (output_it = node.next) { + const output = &node.data; - self.focused_view = null; - self.focusNextView(); - } + // If there were pending focused tags, make them the current focus + if (output.pending_focused_tags) |tags| { + Log.Debug.log( + "changing current focus: {b:0>10} to {b:0>10}", + .{ output.current_focused_tags, tags }, + ); + output.current_focused_tags = tags; + output.pending_focused_tags = null; - var it = ViewStack.iterator(self.views.first, 0xFFFFFFFF); - while (it.next()) |view| { - // Ensure that all pending state is cleared - view.pending_serial = null; - if (view.pending_box) |state| { - view.current_box = state; - view.pending_box = null; + self.focused_view = null; + self.focusNextView(); } - // Apply possible pending tags - if (view.pending_tags) |tags| { - view.current_tags = tags; - view.pending_tags = null; + var view_it = ViewStack.iterator(output.views.first, 0xFFFFFFFF); + while (view_it.next()) |view| { + // Ensure that all pending state is cleared + view.pending_serial = null; + if (view.pending_box) |state| { + view.current_box = state; + view.pending_box = null; + } - // If the pending tags caused the currently focused view to no - // longer be visible, focus the next view. - if (self.focused_view) |focus| { - if (focus == view and - view.current_tags & self.current_focused_tags == 0) - { - self.focusNextView(); + // Apply possible pending tags + if (view.pending_tags) |tags| { + view.current_tags = tags; + view.pending_tags = null; + + // If the pending tags caused the currently focused view to no + // longer be visible, focus the next view. + if (self.focused_view) |focus| { + if (focus == view and + view.current_tags & output.current_focused_tags == 0) + { + self.focusNextView(); + } } } - } - view.dropStashedBuffer(); + view.dropStashedBuffer(); + } } } }; diff --git a/src/server.zig b/src/server.zig index 8f1f1d3..8a1db8b 100644 --- a/src/server.zig +++ b/src/server.zig @@ -144,8 +144,7 @@ pub const Server = struct { return; } - // toplevel surfaces are tracked and managed by the root - server.root.addView(wlr_xdg_surface); + server.root.focusedOutput().addView(wlr_xdg_surface); } /// This event is raised when the layer_shell recieves a new surface from a client. diff --git a/src/view.zig b/src/view.zig index b8368da..9ff03ec 100644 --- a/src/view.zig +++ b/src/view.zig @@ -2,13 +2,14 @@ const std = @import("std"); const c = @import("c.zig"); const Box = @import("box.zig").Box; +const Output = @import("output.zig").Output; const Root = @import("root.zig").Root; const ViewStack = @import("view_stack.zig").ViewStack; pub const View = struct { const Self = @This(); - root: *Root, + output: *Output, wlr_xdg_surface: *c.wlr_xdg_surface, mapped: bool, @@ -31,8 +32,8 @@ pub const View = struct { // listen_request_move: c.wl_listener, // listen_request_resize: c.wl_listener, - pub fn init(self: *Self, root: *Root, wlr_xdg_surface: *c.wlr_xdg_surface, tags: u32) void { - self.root = root; + pub fn init(self: *Self, output: *Output, wlr_xdg_surface: *c.wlr_xdg_surface, tags: u32) void { + self.output = output; self.wlr_xdg_surface = wlr_xdg_surface; // Inform the xdg toplevel that it is tiled. @@ -85,8 +86,8 @@ pub const View = struct { pub fn configurePending(self: *Self) void { if (self.pending_box) |pending_box| { - const border_width = self.root.server.config.border_width; - const view_padding = self.root.server.config.view_padding; + const border_width = self.output.root.server.config.border_width; + const view_padding = self.output.root.server.config.view_padding; self.pending_serial = c.wlr_xdg_toplevel_set_size( self.wlr_xdg_surface, pending_box.width - border_width * 2 - view_padding * 2, @@ -132,12 +133,12 @@ pub const View = struct { const view = @fieldParentPtr(View, "listen_map", listener.?); view.mapped = true; view.focus(view.wlr_xdg_surface.surface); - view.root.arrange(); + view.output.root.arrange(); } fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { const view = @fieldParentPtr(View, "listen_unmap", listener.?); - const root = view.root; + const root = view.output.root; view.mapped = false; if (root.focused_view) |current_focus| { @@ -153,18 +154,18 @@ pub const View = struct { fn handleDestroy(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { const view = @fieldParentPtr(View, "listen_destroy", listener.?); - const root = view.root; + const output = view.output; const node = @fieldParentPtr(ViewStack.Node, "view", view); - root.views.remove(node); - root.server.allocator.destroy(node); + output.views.remove(node); + output.root.server.allocator.destroy(node); } fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { const view = @fieldParentPtr(View, "listen_commit", listener.?); if (view.pending_serial) |s| { if (s == view.wlr_xdg_surface.configure_serial) { - view.root.notifyConfigured(); + view.output.root.notifyConfigured(); view.pending_serial = null; } } @@ -180,7 +181,7 @@ pub const View = struct { // } fn focus(self: *Self, surface: *c.wlr_surface) void { - const root = self.root; + const root = self.output.root; const wlr_seat = root.server.seat.wlr_seat; const prev_surface = wlr_seat.keyboard_state.focused_surface;