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.
This commit is contained in:
Isaac Freund 2020-06-27 22:43:15 +02:00
parent 89d0fb012d
commit c04112b81a
No known key found for this signature in database
GPG Key ID: 86DED400DDFD7A11
12 changed files with 105 additions and 124 deletions

View File

@ -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); var it = ViewStack(View).pendingIterator(self.views.first, output_tags);
while (it.next()) |node| { while (it.next()) |node| {
const view = &node.view; 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); var view_it = ViewStack(View).pendingIterator(self.views.first, output_tags);
while (view_it.next()) |node| { while (view_it.next()) |node| {
const view = &node.view; const view = &node.view;
if (view.mode == .layout) { if (view.pending.mode == .layout) {
view.pending_box = view_boxen.items[i]; view.pending.box = view_boxen.items[i];
i += 1; i += 1;
} }
} }
@ -310,7 +310,7 @@ pub fn arrangeViews(self: *Self) void {
var count: u32 = 0; var count: u32 = 0;
var it = ViewStack(View).pendingIterator(self.views.first, output_tags); var it = ViewStack(View).pendingIterator(self.views.first, output_tags);
while (it.next()) |node| { while (it.next()) |node| {
if (node.view.mode == .layout) count += 1; if (node.view.pending.mode == .layout) count += 1;
} }
break :blk count; break :blk count;
}; };

View File

@ -62,7 +62,7 @@ pub fn sendViewTags(self: Self) void {
var it = ViewStack(View).iterator(self.output.views.first, std.math.maxInt(u32)); var it = ViewStack(View).iterator(self.output.views.first, std.math.maxInt(u32));
while (it.next()) |node| 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); c.wl_resource_post_no_memory(self.wl_resource);
log.crit(.river_status, "out of memory", .{}); log.crit(.river_status, "out of memory", .{});
return; return;

View File

@ -175,7 +175,7 @@ fn startTransaction(self: *Self) void {
fn handleTimeout(data: ?*c_void) callconv(.C) c_int { fn handleTimeout(data: ?*c_void) callconv(.C) c_int {
const self = util.voidCast(Self, data.?); 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(); self.commitTransaction();
@ -225,19 +225,10 @@ fn commitTransaction(self: *Self) void {
var view_it = ViewStack(View).iterator(output.views.first, std.math.maxInt(u32)); var view_it = ViewStack(View).iterator(output.views.first, std.math.maxInt(u32));
while (view_it.next()) |view_node| { while (view_it.next()) |view_node| {
const view = &view_node.view; const view = &view_node.view;
// Ensure that all pending state is cleared // Apply pending state
view.pending_serial = null; view.pending_serial = null;
if (view.pending_box) |state| { if (view.pending.tags != view.current.tags) view_tags_changed = true;
view.current_box = state; view.current = view.pending;
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;
}
view.dropSavedBuffers(); view.dropSavedBuffers();
} }
@ -247,7 +238,5 @@ fn commitTransaction(self: *Self) void {
// Iterate over all seats and update focus // Iterate over all seats and update focus
var it = self.server.input_manager.seats.first; var it = self.server.input_manager.seats.first;
while (it) |seat_node| : (it = seat_node.next) { while (it) |seat_node| : (it = seat_node.next) seat_node.data.focus(null);
seat_node.data.focus(null);
}
} }

View File

@ -125,7 +125,7 @@ pub fn focus(self: *Self, _view: ?*View) void {
// If view is null or not currently visible // If view is null or not currently visible
if (if (view) |v| if (if (view) |v|
v.output != self.focused_output or 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 else
true) { true) {
// Set view to the first currently visible view on in the focus stack if any // Set view to the first currently visible view on in the focus stack if any

View File

@ -41,6 +41,18 @@ const Mode = enum {
float, 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 { const SavedBuffer = struct {
wlr_buffer: *c.wlr_buffer, wlr_buffer: *c.wlr_buffer,
box: Box, box: Box,
@ -56,19 +68,15 @@ output: *Output,
/// This is non-null exactly when the view is mapped /// This is non-null exactly when the view is mapped
wlr_surface: ?*c.wlr_surface, 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 /// True if the view is currently focused by at least one seat
focused: bool, focused: bool,
/// The current output-relative coordinates and dimensions of the view. The /// The double-buffered state of the view
/// surface itself may have other dimensions which are stored in the current: State,
/// surface_box member. pending: State,
current_box: Box,
/// Pending dimensions of the view during a transaction /// The serial sent with the currently pending configure event
pending_box: ?Box, pending_serial: ?u32,
/// The currently commited geometry of the surface. The x/y may be negative if /// 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. /// 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_width: u32,
natural_height: 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 { pub fn init(self: *Self, output: *Output, tags: u32, surface: var) void {
self.output = output; self.output = output;
self.wlr_surface = null; self.wlr_surface = null;
self.mode = .layout;
self.focused = false; self.focused = false;
self.current_box = Box{ self.current = .{
.x = 0, .box = .{
.y = 0, .x = 0,
.height = 0, .y = 0,
.width = 0, .height = 0,
.width = 0,
},
.tags = tags,
.mode = .layout,
}; };
self.pending_box = null; self.pending = self.current;
self.current_tags = tags;
self.pending_tags = null;
self.pending_serial = null; self.pending_serial = null;
@ -135,13 +138,9 @@ pub fn needsConfigure(self: Self) bool {
} }
pub fn configure(self: Self) void { pub fn configure(self: Self) void {
if (self.pending_box) |pending_box| { switch (self.impl) {
switch (self.impl) { .xdg_toplevel => |xdg_toplevel| xdg_toplevel.configure(self.pending.box),
.xdg_toplevel => |xdg_toplevel| xdg_toplevel.configure(pending_box), .xwayland_view => |xwayland_view| xwayland_view.configure(self.pending.box),
.xwayland_view => |xwayland_view| xwayland_view.configure(pending_box),
}
} else {
log.err(.transaction, "configure called on a View with no 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 { pub fn setFocused(self: *Self, focused: bool) void {
self.focused = focused; self.focused = focused;
switch (self.impl) { 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 /// Move a view from one output to another, sending the required enter/leave
/// events. /// events.
pub fn sendToOutput(self: *Self, destination_output: *Output) void { pub fn sendToOutput(self: *Self, destination_output: *Output) void {

View File

@ -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 /// Returns true if a configure must be sent to ensure the dimensions of the
/// pending_box are applied. /// pending_box are applied.
pub fn needsConfigure(self: Self) bool { 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( const wlr_xdg_toplevel: *c.wlr_xdg_toplevel = @field(
self.wlr_xdg_surface, self.wlr_xdg_surface,
c.wlr_xdg_surface_union, 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 // sync with the current dimensions or be the dimensions sent with the
// most recent configure. In both cases server_pending has the values we // most recent configure. In both cases server_pending has the values we
// want to check against. // want to check against.
return pending_box.width != wlr_xdg_toplevel.server_pending.width or return self.view.pending.box.width != wlr_xdg_toplevel.server_pending.width or
pending_box.height != wlr_xdg_toplevel.server_pending.height; self.view.pending.box.height != wlr_xdg_toplevel.server_pending.height;
} }
/// Send a configure event, applying the width/height of the pending box. /// 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; const view = self.view;
return c.wlr_xdg_surface_surface_at( return c.wlr_xdg_surface_surface_at(
self.wlr_xdg_surface, self.wlr_xdg_surface,
ox - @intToFloat(f64, view.current_box.x - view.surface_box.x), ox - @intToFloat(f64, view.current.box.x - view.surface_box.x),
oy - @intToFloat(f64, view.current_box.y - view.surface_box.y), oy - @intToFloat(f64, view.current.box.y - view.surface_box.y),
sx, sx,
sy, 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| { for (root.server.config.float_filter.items) |filter_app_id| {
// Make views with app_ids listed in the float filter float // 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))) { 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; break;
} }
} else if ((wlr_xdg_toplevel.parent != null) or } 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))) (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 // 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 // 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 // This will free itself on destroy
var xdg_popup = util.gpa.create(XdgPopup) catch unreachable; 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);
} }

View File

@ -57,11 +57,8 @@ pub fn init(self: *Self, view: *View, wlr_xwayland_surface: *c.wlr_xwayland_surf
} }
pub fn needsConfigure(self: Self) bool { pub fn needsConfigure(self: Self) bool {
const view = self.view; return self.view.current.box.width != self.view.pending.box.width or
if (view.pending_box) |pending_box| self.view.current.box.height != self.view.pending.box.height;
return view.current_box.width != pending_box.width or
view.current_box.height != pending_box.height;
return false;
} }
/// Tell the client to take a new size /// 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 { pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface {
return c.wlr_surface_surface_at( return c.wlr_surface_surface_at(
self.wlr_xwayland_surface.surface, self.wlr_xwayland_surface.surface,
ox - @intToFloat(f64, self.view.current_box.x), ox - @intToFloat(f64, self.view.current.box.x),
oy - @intToFloat(f64, self.view.current_box.y), oy - @intToFloat(f64, self.view.current.box.y),
sx, sx,
sy, sy,
); );

View File

@ -43,10 +43,8 @@ pub fn setViewTags(
) Error!void { ) Error!void {
const tags = try parseTags(allocator, args, out); const tags = try parseTags(allocator, args, out);
if (seat.focused_view) |view| { if (seat.focused_view) |view| {
if (view.current_tags != tags) { view.pending.tags = tags;
view.pending_tags = tags; view.output.root.arrange();
seat.input_manager.server.root.arrange();
}
} }
} }
@ -75,10 +73,10 @@ pub fn toggleViewTags(
) Error!void { ) Error!void {
const tags = try parseTags(allocator, args, out); const tags = try parseTags(allocator, args, out);
if (seat.focused_view) |view| { if (seat.focused_view) |view| {
const new_tags = view.current_tags ^ tags; const new_tags = view.current.tags ^ tags;
if (new_tags != 0) { if (new_tags != 0) {
view.pending_tags = new_tags; view.pending.tags = new_tags;
seat.input_manager.server.root.arrange(); view.output.root.arrange();
} }
} }
} }

View File

@ -30,9 +30,19 @@ pub fn toggleFloat(
) Error!void { ) Error!void {
if (args.len > 1) return Error.TooManyArguments; if (args.len > 1) return Error.TooManyArguments;
if (seat.focused_view) |view| { if (seat.focused_view) |view| {
switch (view.mode) { switch (view.current.mode) {
.layout => view.setMode(.float), .layout => {
.float => view.setMode(.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(); view.output.root.arrange();
} }

View File

@ -37,7 +37,7 @@ pub fn zoom(
const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", current_focus); const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", current_focus);
// Only zoom views that are part of the layout // 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); var it = ViewStack(View).iterator(output.views.first, output.current_focused_tags);
const zoom_node = if (focused_node == it.next()) const zoom_node = if (focused_node == it.next())

View File

@ -68,7 +68,7 @@ pub fn renderOutput(output: *Output) void {
// This check prevents a race condition when a frame is requested // This check prevents a race condition when a frame is requested
// between mapping of a view and the first configure being handled. // 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 // Focused views are rendered on top of normal views, skip them for now
if (view.focused) continue; 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 // This check prevents a race condition when a frame is requested
// between mapping of a view and the first configure being handled. // 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 // Skip unfocused views since we already rendered them
if (!view.focused) continue; if (!view.focused) continue;
@ -144,8 +144,8 @@ fn renderView(output: Output, view: *View, now: *c.timespec) void {
output, output,
saved_buffer.wlr_buffer.texture, saved_buffer.wlr_buffer.texture,
.{ .{
.x = saved_buffer.box.x + view.current_box.x - view.saved_surface_box.x, .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, .y = saved_buffer.box.y + view.current.box.y - view.saved_surface_box.y,
.width = @intCast(c_int, saved_buffer.box.width), .width = @intCast(c_int, saved_buffer.box.width),
.height = @intCast(c_int, saved_buffer.box.height), .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. // a transaction and may simply render each toplevel surface.
var rdata = SurfaceRenderData{ var rdata = SurfaceRenderData{
.output = &output, .output = &output,
.output_x = view.current_box.x - view.surface_box.x, .output_x = view.current.box.x - view.surface_box.x,
.output_y = view.current_box.y - view.surface_box.y, .output_y = view.current.box.y - view.surface_box.y,
.when = now, .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; const border_width = output.root.server.config.border_width;
// left and right, covering the corners as well // 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.width = border_width;
border.height = view.current_box.height + border_width * 2; border.height = view.current.box.height + border_width * 2;
// left // 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); renderRect(output, border, color);
// right // 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); renderRect(output, border, color);
// top and bottom // top and bottom
border.x = view.current_box.x; border.x = view.current.box.x;
border.width = view.current_box.width; border.width = view.current.box.width;
border.height = border_width; border.height = border_width;
// top // 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); renderRect(output, border, color);
// bottom border // 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); renderRect(output, border, color);
} }

View File

@ -93,12 +93,9 @@ pub fn ViewStack(comptime T: type) type {
pub fn next(self: *Iterator) ?*Node { pub fn next(self: *Iterator) ?*Node {
while (self.it) |node| : (self.it = if (self.reverse) node.prev else node.next) { while (self.it) |node| : (self.it = if (self.reverse) node.prev else node.next) {
if (if (self.pending) if (if (self.pending)
if (node.view.pending_tags) |pending_tags| self.tags & node.view.pending.tags != 0
self.tags & pending_tags != 0
else
self.tags & node.view.current_tags != 0
else 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; self.it = if (self.reverse) node.prev else node.next;
return node; return node;
} }