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

commit9ac55c943110fabde12d01be38280c6d4a105671
parente8e5421174
authorIsaac Freund <[email protected]>
date2020-06-27 22:43
view: introduce state struct to simplify code

The state struct holds all of the state that is double-buffered and
applied through transactions. This more explicit handling simplifies
much of the code, and will allow for easier implementation of new
feature such as fullscreen.

 river/Output.zig               |  8 ++--
 river/OutputStatus.zig         |  2 +-
 river/Root.zig                 | 21 +++-------
 river/Seat.zig                 |  2 +-
 river/View.zig                 | 88 +++++++++++++++---------------------------
 river/XdgToplevel.zig          | 32 ++++++++++-----
 river/XwaylandView.zig         | 11 ++----
 river/command/tags.zig         | 12 +++---
 river/command/toggle_float.zig | 16 ++++++--
 river/command/zoom.zig         |  2 +-
 river/render.zig               | 28 +++++++-------
 river/view_stack.zig           |  7 +---
 12 files changed, 105 insertions(+), 124 deletions(-)

diff --git a/river/Output.zig b/river/Output.zig
index f1f4419..7dc10cf 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -181,7 +181,7 @@ fn layoutFull(self: *Self, visible_count: u32, output_tags: u32) void {
     var it = ViewStack(View).pendingIterator(self.views.first, output_tags);
     while (it.next()) |node| {
         const view = &node.view;
-        if (view.mode == .layout) view.pending_box = full_box;
+        if (view.pending.mode == .layout) view.pending.box = full_box;
     }
 }
 
@@ -286,8 +286,8 @@ fn layoutExternal(self: *Self, visible_count: u32, output_tags: u32) !void {
     var view_it = ViewStack(View).pendingIterator(self.views.first, output_tags);
     while (view_it.next()) |node| {
         const view = &node.view;
-        if (view.mode == .layout) {
-            view.pending_box = view_boxen.items[i];
+        if (view.pending.mode == .layout) {
+            view.pending.box = view_boxen.items[i];
             i += 1;
         }
     }
@@ -310,7 +310,7 @@ pub fn arrangeViews(self: *Self) void {
         var count: u32 = 0;
         var it = ViewStack(View).pendingIterator(self.views.first, output_tags);
         while (it.next()) |node| {
-            if (node.view.mode == .layout) count += 1;
+            if (node.view.pending.mode == .layout) count += 1;
         }
         break :blk count;
     };
diff --git a/river/OutputStatus.zig b/river/OutputStatus.zig
index 980dbe8..be0568f 100644
--- a/river/OutputStatus.zig
+++ b/river/OutputStatus.zig
@@ -62,7 +62,7 @@ pub fn sendViewTags(self: Self) void {
 
     var it = ViewStack(View).iterator(self.output.views.first, std.math.maxInt(u32));
     while (it.next()) |node|
-        view_tags.append(node.view.current_tags) catch {
+        view_tags.append(node.view.current.tags) catch {
         c.wl_resource_post_no_memory(self.wl_resource);
         log.crit(.river_status, "out of memory", .{});
         return;
diff --git a/river/Root.zig b/river/Root.zig
index 7bcb83e..81478c8 100644
--- a/river/Root.zig
+++ b/river/Root.zig
@@ -175,7 +175,7 @@ fn startTransaction(self: *Self) void {
 fn handleTimeout(data: ?*c_void) callconv(.C) c_int {
     const self = util.voidCast(Self, data.?);
 
-    log.err(.transaction, "time out occurred, some imperfect frames may be shown", .{});
+    log.err(.transaction, "timeout occurred, some imperfect frames may be shown", .{});
 
     self.commitTransaction();
 
@@ -225,19 +225,10 @@ fn commitTransaction(self: *Self) void {
         var view_it = ViewStack(View).iterator(output.views.first, std.math.maxInt(u32));
         while (view_it.next()) |view_node| {
             const view = &view_node.view;
-            // Ensure that all pending state is cleared
+            // Apply pending state
             view.pending_serial = null;
-            if (view.pending_box) |state| {
-                view.current_box = state;
-                view.pending_box = null;
-            }
-
-            // Apply possible pending tags
-            if (view.pending_tags) |tags| {
-                view.current_tags = tags;
-                view.pending_tags = null;
-                view_tags_changed = true;
-            }
+            if (view.pending.tags != view.current.tags) view_tags_changed = true;
+            view.current = view.pending;
 
             view.dropSavedBuffers();
         }
@@ -247,7 +238,5 @@ fn commitTransaction(self: *Self) void {
 
     // Iterate over all seats and update focus
     var it = self.server.input_manager.seats.first;
-    while (it) |seat_node| : (it = seat_node.next) {
-        seat_node.data.focus(null);
-    }
+    while (it) |seat_node| : (it = seat_node.next) seat_node.data.focus(null);
 }
diff --git a/river/Seat.zig b/river/Seat.zig
index cdc08ed..f67fbae 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -125,7 +125,7 @@ pub fn focus(self: *Self, _view: ?*View) void {
     // If view is null or not currently visible
     if (if (view) |v|
         v.output != self.focused_output or
-            v.current_tags & self.focused_output.current_focused_tags == 0
+            v.current.tags & self.focused_output.current_focused_tags == 0
     else
         true) {
         // Set view to the first currently visible view on in the focus stack if any
diff --git a/river/View.zig b/river/View.zig
index a50957e..ffa7e4e 100644
--- a/river/View.zig
+++ b/river/View.zig
@@ -41,6 +41,18 @@ const Mode = enum {
     float,
 };
 
+const State = struct {
+    /// The output-relative coordinates and dimensions of the view. The
+    /// surface itself may have other dimensions which are stored in the
+    /// surface_box member.
+    box: Box,
+
+    /// The tags of the view, as a bitmask
+    tags: u32,
+
+    mode: Mode,
+};
+
 const SavedBuffer = struct {
     wlr_buffer: *c.wlr_buffer,
     box: Box,
@@ -56,19 +68,15 @@ output: *Output,
 /// This is non-null exactly when the view is mapped
 wlr_surface: ?*c.wlr_surface,
 
-/// The current mode of the view
-mode: Mode,
-
 /// True if the view is currently focused by at least one seat
 focused: bool,
 
-/// The current output-relative coordinates and dimensions of the view. The
-/// surface itself may have other dimensions which are stored in the
-/// surface_box member.
-current_box: Box,
+/// The double-buffered state of the view
+current: State,
+pending: State,
 
-/// Pending dimensions of the view during a transaction
-pending_box: ?Box,
+/// The serial sent with the currently pending configure event
+pending_serial: ?u32,
 
 /// The currently commited geometry of the surface. The x/y may be negative if
 /// for example the client has decided to draw CSD shadows a la GTK.
@@ -85,29 +93,24 @@ saved_buffers: std.ArrayList(SavedBuffer),
 natural_width: u32,
 natural_height: u32,
 
-current_tags: u32,
-pending_tags: ?u32,
-
-pending_serial: ?u32,
-
 pub fn init(self: *Self, output: *Output, tags: u32, surface: var) void {
     self.output = output;
 
     self.wlr_surface = null;
-    self.mode = .layout;
 
     self.focused = false;
 
-    self.current_box = Box{
-        .x = 0,
-        .y = 0,
-        .height = 0,
-        .width = 0,
+    self.current = .{
+        .box = .{
+            .x = 0,
+            .y = 0,
+            .height = 0,
+            .width = 0,
+        },
+        .tags = tags,
+        .mode = .layout,
     };
-    self.pending_box = null;
-
-    self.current_tags = tags;
-    self.pending_tags = null;
+    self.pending = self.current;
 
     self.pending_serial = null;
 
@@ -135,13 +138,9 @@ pub fn needsConfigure(self: Self) bool {
 }
 
 pub fn configure(self: Self) void {
-    if (self.pending_box) |pending_box| {
-        switch (self.impl) {
-            .xdg_toplevel => |xdg_toplevel| xdg_toplevel.configure(pending_box),
-            .xwayland_view => |xwayland_view| xwayland_view.configure(pending_box),
-        }
-    } else {
-        log.err(.transaction, "configure called on a View with no pending box", .{});
+    switch (self.impl) {
+        .xdg_toplevel => |xdg_toplevel| xdg_toplevel.configure(self.pending.box),
+        .xwayland_view => |xwayland_view| xwayland_view.configure(self.pending.box),
     }
 }
 
@@ -190,7 +189,8 @@ fn saveBuffersIterator(
     }
 }
 
-/// Set the focued bool and the active state of the view if it is a toplevel
+/// Set the focused bool and the active state of the view if it is a toplevel
+/// TODO: This is insufficient for multi-seat, probably need a focus counter.
 pub fn setFocused(self: *Self, focused: bool) void {
     self.focused = focused;
     switch (self.impl) {
@@ -199,30 +199,6 @@ pub fn setFocused(self: *Self, focused: bool) void {
     }
 }
 
-/// Set the mode of the view to the given mode
-pub fn setMode(self: *Self, mode: Mode) void {
-    switch (self.mode) {
-        .layout => switch (mode) {
-            .layout => {},
-            .float => {
-                self.mode = .float;
-                self.pending_box = Box{
-                    .x = std.math.max(0, @divTrunc(@intCast(i32, self.output.usable_box.width) -
-                        @intCast(i32, self.natural_width), 2)),
-                    .y = std.math.max(0, @divTrunc(@intCast(i32, self.output.usable_box.height) -
-                        @intCast(i32, self.natural_height), 2)),
-                    .width = self.natural_width,
-                    .height = self.natural_height,
-                };
-            },
-        },
-        .float => switch (mode) {
-            .float => {},
-            .layout => self.mode = .layout,
-        },
-    }
-}
-
 /// Move a view from one output to another, sending the required enter/leave
 /// events.
 pub fn sendToOutput(self: *Self, destination_output: *Output) void {
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index dbbcb9d..b08e8bd 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -62,8 +62,6 @@ pub fn init(self: *Self, view: *View, wlr_xdg_surface: *c.wlr_xdg_surface) void
 /// Returns true if a configure must be sent to ensure the dimensions of the
 /// pending_box are applied.
 pub fn needsConfigure(self: Self) bool {
-    const pending_box = self.view.pending_box orelse return false;
-
     const wlr_xdg_toplevel: *c.wlr_xdg_toplevel = @field(
         self.wlr_xdg_surface,
         c.wlr_xdg_surface_union,
@@ -73,8 +71,8 @@ pub fn needsConfigure(self: Self) bool {
     // sync with the current dimensions or be the dimensions sent with the
     // most recent configure. In both cases server_pending has the values we
     // want to check against.
-    return pending_box.width != wlr_xdg_toplevel.server_pending.width or
-        pending_box.height != wlr_xdg_toplevel.server_pending.height;
+    return self.view.pending.box.width != wlr_xdg_toplevel.server_pending.width or
+        self.view.pending.box.height != wlr_xdg_toplevel.server_pending.height;
 }
 
 /// Send a configure event, applying the width/height of the pending box.
@@ -109,8 +107,8 @@ pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surfa
     const view = self.view;
     return c.wlr_xdg_surface_surface_at(
         self.wlr_xdg_surface,
-        ox - @intToFloat(f64, view.current_box.x - view.surface_box.x),
-        oy - @intToFloat(f64, view.current_box.y - view.surface_box.y),
+        ox - @intToFloat(f64, view.current.box.x - view.surface_box.x),
+        oy - @intToFloat(f64, view.current.box.y - view.surface_box.y),
         sx,
         sy,
     );
@@ -171,7 +169,15 @@ fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
     for (root.server.config.float_filter.items) |filter_app_id| {
         // Make views with app_ids listed in the float filter float
         if (std.mem.eql(u8, std.mem.span(app_id), std.mem.span(filter_app_id))) {
-            view.setMode(.float);
+            view.pending.mode = .float;
+            view.pending.box = .{
+                .x = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.width) -
+                    @intCast(i32, view.natural_width), 2)),
+                .y = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.height) -
+                    @intCast(i32, view.natural_height), 2)),
+                .width = view.natural_width,
+                .height = view.natural_height,
+            };
             break;
         }
     } else if ((wlr_xdg_toplevel.parent != null) or
@@ -179,7 +185,15 @@ fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
         (state.min_width == state.max_width or state.min_height == state.max_height)))
     {
         // If the toplevel has a parent or is of fixed size make it float
-        view.setMode(.float);
+        view.pending.mode = .float;
+        view.pending.box = .{
+            .x = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.width) -
+                @intCast(i32, view.natural_width), 2)),
+            .y = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.height) -
+                @intCast(i32, view.natural_height), 2)),
+            .width = view.natural_width,
+            .height = view.natural_height,
+        };
     }
 
     // If the toplevel has no parent, inform it that it is tiled. This
@@ -246,5 +260,5 @@ fn handleNewPopup(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
 
     // This will free itself on destroy
     var xdg_popup = util.gpa.create(XdgPopup) catch unreachable;
-    xdg_popup.init(self.view.output, &self.view.current_box, wlr_xdg_popup);
+    xdg_popup.init(self.view.output, &self.view.current.box, wlr_xdg_popup);
 }
diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig
index 6ac5840..1cd0143 100644
--- a/river/XwaylandView.zig
+++ b/river/XwaylandView.zig
@@ -57,11 +57,8 @@ pub fn init(self: *Self, view: *View, wlr_xwayland_surface: *c.wlr_xwayland_surf
 }
 
 pub fn needsConfigure(self: Self) bool {
-    const view = self.view;
-    if (view.pending_box) |pending_box|
-        return view.current_box.width != pending_box.width or
-            view.current_box.height != pending_box.height;
-    return false;
+    return self.view.current.box.width != self.view.pending.box.width or
+        self.view.current.box.height != self.view.pending.box.height;
 }
 
 /// Tell the client to take a new size
@@ -105,8 +102,8 @@ pub fn forEachSurface(
 pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface {
     return c.wlr_surface_surface_at(
         self.wlr_xwayland_surface.surface,
-        ox - @intToFloat(f64, self.view.current_box.x),
-        oy - @intToFloat(f64, self.view.current_box.y),
+        ox - @intToFloat(f64, self.view.current.box.x),
+        oy - @intToFloat(f64, self.view.current.box.y),
         sx,
         sy,
     );
diff --git a/river/command/tags.zig b/river/command/tags.zig
index 48aaa28..5c14aaa 100644
--- a/river/command/tags.zig
+++ b/river/command/tags.zig
@@ -43,10 +43,8 @@ pub fn setViewTags(
 ) Error!void {
     const tags = try parseTags(allocator, args, out);
     if (seat.focused_view) |view| {
-        if (view.current_tags != tags) {
-            view.pending_tags = tags;
-            seat.input_manager.server.root.arrange();
-        }
+        view.pending.tags = tags;
+        view.output.root.arrange();
     }
 }
 
@@ -75,10 +73,10 @@ pub fn toggleViewTags(
 ) Error!void {
     const tags = try parseTags(allocator, args, out);
     if (seat.focused_view) |view| {
-        const new_tags = view.current_tags ^ tags;
+        const new_tags = view.current.tags ^ tags;
         if (new_tags != 0) {
-            view.pending_tags = new_tags;
-            seat.input_manager.server.root.arrange();
+            view.pending.tags = new_tags;
+            view.output.root.arrange();
         }
     }
 }
diff --git a/river/command/toggle_float.zig b/river/command/toggle_float.zig
index 4ade683..b918043 100644
--- a/river/command/toggle_float.zig
+++ b/river/command/toggle_float.zig
@@ -30,9 +30,19 @@ pub fn toggleFloat(
 ) Error!void {
     if (args.len > 1) return Error.TooManyArguments;
     if (seat.focused_view) |view| {
-        switch (view.mode) {
-            .layout => view.setMode(.float),
-            .float => view.setMode(.layout),
+        switch (view.current.mode) {
+            .layout => {
+                view.pending.mode = .float;
+                view.pending.box = .{
+                    .x = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.width) -
+                        @intCast(i32, view.natural_width), 2)),
+                    .y = std.math.max(0, @divTrunc(@intCast(i32, view.output.usable_box.height) -
+                        @intCast(i32, view.natural_height), 2)),
+                    .width = view.natural_width,
+                    .height = view.natural_height,
+                };
+            },
+            .float => view.pending.mode = .layout,
         }
         view.output.root.arrange();
     }
diff --git a/river/command/zoom.zig b/river/command/zoom.zig
index e21e038..783570f 100644
--- a/river/command/zoom.zig
+++ b/river/command/zoom.zig
@@ -37,7 +37,7 @@ pub fn zoom(
         const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", current_focus);
 
         // Only zoom views that are part of the layout
-        if (current_focus.mode != .layout) return;
+        if (current_focus.current.mode != .layout) return;
 
         var it = ViewStack(View).iterator(output.views.first, output.current_focused_tags);
         const zoom_node = if (focused_node == it.next())
diff --git a/river/render.zig b/river/render.zig
index 4a2256e..6847ffe 100644
--- a/river/render.zig
+++ b/river/render.zig
@@ -68,7 +68,7 @@ pub fn renderOutput(output: *Output) void {
 
         // This check prevents a race condition when a frame is requested
         // between mapping of a view and the first configure being handled.
-        if (view.current_box.width == 0 or view.current_box.height == 0) continue;
+        if (view.current.box.width == 0 or view.current.box.height == 0) continue;
 
         // Focused views are rendered on top of normal views, skip them for now
         if (view.focused) continue;
@@ -84,7 +84,7 @@ pub fn renderOutput(output: *Output) void {
 
         // This check prevents a race condition when a frame is requested
         // between mapping of a view and the first configure being handled.
-        if (view.current_box.width == 0 or view.current_box.height == 0) continue;
+        if (view.current.box.width == 0 or view.current.box.height == 0) continue;
 
         // Skip unfocused views since we already rendered them
         if (!view.focused) continue;
@@ -144,8 +144,8 @@ fn renderView(output: Output, view: *View, now: *c.timespec) void {
                 output,
                 saved_buffer.wlr_buffer.texture,
                 .{
-                    .x = saved_buffer.box.x + view.current_box.x - view.saved_surface_box.x,
-                    .y = saved_buffer.box.y + view.current_box.y - view.saved_surface_box.y,
+                    .x = saved_buffer.box.x + view.current.box.x - view.saved_surface_box.x,
+                    .y = saved_buffer.box.y + view.current.box.y - view.saved_surface_box.y,
                     .width = @intCast(c_int, saved_buffer.box.width),
                     .height = @intCast(c_int, saved_buffer.box.height),
                 },
@@ -156,8 +156,8 @@ fn renderView(output: Output, view: *View, now: *c.timespec) void {
         // a transaction and may simply render each toplevel surface.
         var rdata = SurfaceRenderData{
             .output = &output,
-            .output_x = view.current_box.x - view.surface_box.x,
-            .output_y = view.current_box.y - view.surface_box.y,
+            .output_x = view.current.box.x - view.surface_box.x,
+            .output_y = view.current.box.y - view.surface_box.y,
             .when = now,
         };
 
@@ -248,29 +248,29 @@ fn renderBorders(output: Output, view: *View, now: *c.timespec) void {
     const border_width = output.root.server.config.border_width;
 
     // left and right, covering the corners as well
-    border.y = view.current_box.y - @intCast(i32, border_width);
+    border.y = view.current.box.y - @intCast(i32, border_width);
     border.width = border_width;
-    border.height = view.current_box.height + border_width * 2;
+    border.height = view.current.box.height + border_width * 2;
 
     // left
-    border.x = view.current_box.x - @intCast(i32, border_width);
+    border.x = view.current.box.x - @intCast(i32, border_width);
     renderRect(output, border, color);
 
     // right
-    border.x = view.current_box.x + @intCast(i32, view.current_box.width);
+    border.x = view.current.box.x + @intCast(i32, view.current.box.width);
     renderRect(output, border, color);
 
     // top and bottom
-    border.x = view.current_box.x;
-    border.width = view.current_box.width;
+    border.x = view.current.box.x;
+    border.width = view.current.box.width;
     border.height = border_width;
 
     // top
-    border.y = view.current_box.y - @intCast(i32, border_width);
+    border.y = view.current.box.y - @intCast(i32, border_width);
     renderRect(output, border, color);
 
     // bottom border
-    border.y = view.current_box.y + @intCast(i32, view.current_box.height);
+    border.y = view.current.box.y + @intCast(i32, view.current.box.height);
     renderRect(output, border, color);
 }
 
diff --git a/river/view_stack.zig b/river/view_stack.zig
index 36be14b..2082591 100644
--- a/river/view_stack.zig
+++ b/river/view_stack.zig
@@ -93,12 +93,9 @@ pub fn ViewStack(comptime T: type) type {
             pub fn next(self: *Iterator) ?*Node {
                 while (self.it) |node| : (self.it = if (self.reverse) node.prev else node.next) {
                     if (if (self.pending)
-                        if (node.view.pending_tags) |pending_tags|
-                            self.tags & pending_tags != 0
-                        else
-                            self.tags & node.view.current_tags != 0
+                        self.tags & node.view.pending.tags != 0
                     else
-                        self.tags & node.view.current_tags != 0) {
+                        self.tags & node.view.current.tags != 0) {
                         self.it = if (self.reverse) node.prev else node.next;
                         return node;
                     }