view stack: rework iteration for flexibility

There is now a single iter() function which accepts a filter and context
allowing users of the api to filter the views in any arbitrary way. This
change allowed for a good amount of code cleanup, and this commit also
ensures that the correct properties are checked in each case, including
the new View.destroying field added in the previous commit. This fixes
at least one crash involving switching focus to a destroying view.
This commit is contained in:
Isaac Freund 2020-08-21 15:08:28 +02:00
parent fa08d85c58
commit db416eb119
9 changed files with 185 additions and 217 deletions

View file

@ -548,16 +548,20 @@ fn layerSurfaceAt(
/// Find the topmost visible view surface (incl. popups) at ox,oy.
fn viewSurfaceAt(output: Output, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface {
// Focused views are rendered on top, so look for them first.
var it = ViewStack(View).iterator(output.views.first, output.current.tags);
while (it.next()) |node| {
if (node.view.current.focus == 0) continue;
if (node.view.surfaceAt(ox, oy, sx, sy)) |found| return found;
var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter);
while (it.next()) |view| {
if (view.current.focus == 0) continue;
if (view.surfaceAt(ox, oy, sx, sy)) |found| return found;
}
it = ViewStack(View).iterator(output.views.first, output.current.tags);
while (it.next()) |node| {
if (node.view.surfaceAt(ox, oy, sx, sy)) |found| return found;
it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, surfaceAtFilter);
while (it.next()) |view| {
if (view.surfaceAt(ox, oy, sx, sy)) |found| return found;
}
return null;
}
fn surfaceAtFilter(view: *View, filter_tags: u32) bool {
return !view.destroying and view.current.tags & filter_tags != 0;
}

View file

@ -97,7 +97,7 @@ pub fn init(self: *Self, root: *Root, wlr_output: *c.wlr_output) !void {
layer.* = std.TailQueue(LayerSurface).init();
}
self.views.init();
self.views = ViewStack(View){};
self.current = .{
.tags = 1 << 0,
@ -184,15 +184,12 @@ fn layoutFull(self: *Self, visible_count: u32) void {
.height = self.usable_box.height - (2 * xy_offset),
};
var it = ViewStack(View).pendingIterator(self.views.first, self.pending.tags);
while (it.next()) |node| {
const view = &node.view;
if (!view.pending.float and !view.pending.fullscreen) {
var it = ViewStack(View).iter(self.views.first, .forward, self.pending.tags, arrangeFilter);
while (it.next()) |view| {
view.pending.box = full_box;
view.applyConstraints();
}
}
}
const LayoutError = error{
BadExitCode,
@ -285,15 +282,16 @@ fn layoutExternal(self: *Self, visible_count: u32) !void {
// Apply window configuration to views
var i: u32 = 0;
var view_it = ViewStack(View).pendingIterator(self.views.first, self.pending.tags);
while (view_it.next()) |node| {
const view = &node.view;
if (!view.pending.float and !view.pending.fullscreen and !view.destroying) {
var view_it = ViewStack(View).iter(self.views.first, .forward, self.pending.tags, arrangeFilter);
while (view_it.next()) |view| : (i += 1) {
view.pending.box = view_boxen.items[i];
view.applyConstraints();
i += 1;
}
}
fn arrangeFilter(view: *View, filter_tags: u32) bool {
return !view.destroying and !view.pending.float and
!view.pending.fullscreen and view.pending.tags & filter_tags != 0;
}
/// Arrange all views on the output for the current layout. Modifies only
@ -302,15 +300,10 @@ fn layoutExternal(self: *Self, visible_count: u32) !void {
pub fn arrangeViews(self: *Self) void {
if (self == &self.root.noop_output) return;
const full_area = Box.fromWlrBox(c.wlr_output_layout_get_box(self.root.wlr_output_layout, self.wlr_output).*);
// Count up views that will be arranged by the layout
var layout_count: u32 = 0;
var it = ViewStack(View).pendingIterator(self.views.first, self.pending.tags);
while (it.next()) |node| {
const view = &node.view;
if (!view.pending.float and !view.pending.fullscreen and !view.destroying) layout_count += 1;
}
var it = ViewStack(View).iter(self.views.first, .forward, self.pending.tags, arrangeFilter);
while (it.next() != null) layout_count += 1;
// If the usable area has a zero dimension, trying to arrange the layout
// would cause an underflow and is pointless anyway.

View file

@ -60,13 +60,14 @@ pub fn sendViewTags(self: Self) void {
var view_tags = std.ArrayList(u32).init(util.gpa);
defer view_tags.deinit();
var it = ViewStack(View).iterator(self.output.views.first, std.math.maxInt(u32));
while (it.next()) |node|
var it = self.output.views.first;
while (it) |node| : (it = node.next) {
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;
};
}
var wl_array = c.wl_array{
.size = view_tags.items.len * @sizeOf(u32),

View file

@ -125,12 +125,11 @@ pub fn startTransaction(self: *Self) void {
// to reset the pending count to 0 and clear serials from the views
self.pending_configures = 0;
// Iterate over all layout views of all outputs
// 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(View).iterator(output.views.first, std.math.maxInt(u32));
while (view_it.next()) |view_node| {
while (output_it) |output_node| : (output_it = output_node.next) {
var view_it = output_node.data.views.first;
while (view_it) |view_node| : (view_it = view_node.next) {
const view = &view_node.view;
if (view.destroying) {

View file

@ -88,7 +88,7 @@ pub fn init(self: *Self, input_manager: *InputManager, name: [*:0]const u8) !voi
self.focused = .none;
self.focus_stack.init();
self.focus_stack = ViewStack(*View){};
self.status_trackers = std.SinglyLinkedList(SeatStatus).init();
@ -111,41 +111,43 @@ pub fn deinit(self: *Self) void {
/// Set the current focus. If a visible view is passed it will be focused.
/// If null is passed, the first visible view in the focus stack will be focused.
pub fn focus(self: *Self, _view: ?*View) void {
var view = _view;
pub fn focus(self: *Self, _target: ?*View) void {
var target = _target;
// While a layer surface is focused, views may not recieve focus
if (self.focused == .layer) return;
// If the view is not currently visible, behave as if null was passed
if (view) |v| {
if (v.output != self.focused_output or
v.pending.tags & self.focused_output.pending.tags == 0) view = null;
if (target) |view| {
if (view.output != self.focused_output or
view.pending.tags & self.focused_output.pending.tags == 0) target = null;
}
// If the target view is not fullscreen or null, then a fullscreen view
// will grab focus if visible.
if (if (view) |v| !v.pending.fullscreen else true) {
var it = ViewStack(*View).pendingIterator(self.focus_stack.first, self.focused_output.pending.tags);
view = while (it.next()) |node| {
if (node.view.output == self.focused_output and node.view.pending.fullscreen) break node.view;
} else view;
if (if (target) |v| !v.pending.fullscreen else true) {
const tags = self.focused_output.pending.tags;
var it = ViewStack(*View).iter(self.focus_stack.first, .forward, tags, pendingFilter);
target = while (it.next()) |view| {
if (view.output == self.focused_output and view.pending.fullscreen) break view;
} else target;
}
if (view == null) {
if (target == null) {
// Set view to the first currently visible view in the focus stack if any
var it = ViewStack(*View).pendingIterator(self.focus_stack.first, self.focused_output.pending.tags);
view = while (it.next()) |node| {
if (node.view.output == self.focused_output) break node.view;
const tags = self.focused_output.pending.tags;
var it = ViewStack(*View).iter(self.focus_stack.first, .forward, tags, pendingFilter);
target = while (it.next()) |view| {
if (view.output == self.focused_output) break view;
} else null;
}
if (view) |view_to_focus| {
if (target) |view| {
// Find or allocate a new node in the focus stack for the target view
var it = self.focus_stack.first;
while (it) |node| : (it = node.next) {
// If the view is found, move it to the top of the stack
if (node.view == view_to_focus) {
if (node.view == view) {
const new_focus_node = self.focus_stack.remove(node);
self.focus_stack.push(node);
break;
@ -153,18 +155,22 @@ pub fn focus(self: *Self, _view: ?*View) void {
} else {
// The view is not in the stack, so allocate a new node and prepend it
const new_focus_node = util.gpa.create(ViewStack(*View).Node) catch return;
new_focus_node.view = view_to_focus;
new_focus_node.view = view;
self.focus_stack.push(new_focus_node);
}
// Focus the target view
self.setFocusRaw(.{ .view = view_to_focus });
self.setFocusRaw(.{ .view = view });
} else {
// Otherwise clear the focus
self.setFocusRaw(.{ .none = {} });
}
}
fn pendingFilter(view: *View, filter_tags: u32) bool {
return !view.destroying and view.pending.tags & filter_tags != 0;
}
/// Switch focus to the target, handling unfocus and input inhibition
/// properly. This should only be called directly if dealing with layers.
pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {

View file

@ -44,15 +44,15 @@ pub fn focusView(
// If there is a currently focused view, focus the next visible view in the stack.
const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", seat.focused.view);
var it = switch (direction) {
.next => ViewStack(View).iterator(focused_node, output.current.tags),
.previous => ViewStack(View).reverseIterator(focused_node, output.current.tags),
.next => ViewStack(View).iter(focused_node, .forward, output.pending.tags, filter),
.previous => ViewStack(View).iter(focused_node, .reverse, output.pending.tags, filter),
};
// Skip past the focused node
_ = it.next();
// Focus the next visible node if there is one
if (it.next()) |node| {
seat.focus(&node.view);
if (it.next()) |view| {
seat.focus(view);
output.root.startTransaction();
return;
}
@ -61,10 +61,14 @@ pub fn focusView(
// There is either no currently focused view or the last visible view in the
// stack is focused and we need to wrap.
var it = switch (direction) {
.next => ViewStack(View).iterator(output.views.first, output.current.tags),
.previous => ViewStack(View).reverseIterator(output.views.last, output.current.tags),
.next => ViewStack(View).iter(output.views.first, .forward, output.pending.tags, filter),
.previous => ViewStack(View).iter(output.views.last, .reverse, output.pending.tags, filter),
};
seat.focus(if (it.next()) |node| &node.view else null);
seat.focus(it.next());
output.root.startTransaction();
}
fn filter(view: *View, filter_tags: u32) bool {
return !view.destroying and view.pending.tags & filter_tags != 0;
}

View file

@ -39,19 +39,14 @@ pub fn zoom(
// If the first view that is part of the layout is focused, zoom
// the next view in the layout. Otherwise zoom the focused view.
const output = seat.focused_output;
var it = ViewStack(View).iterator(output.views.first, output.current.tags);
const layout_first = while (it.next()) |node| {
if (!node.view.pending.float and !node.view.pending.fullscreen) break node;
} else unreachable;
var it = ViewStack(View).iter(output.views.first, .forward, output.pending.tags, filter);
const layout_first = @fieldParentPtr(ViewStack(View).Node, "view", it.next().?);
const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", seat.focused.view);
const zoom_node = if (focused_node == layout_first) blk: {
while (it.next()) |node| {
if (!node.view.pending.float and !node.view.pending.fullscreen) break :blk node;
} else {
break :blk null;
}
} else focused_node;
const zoom_node = if (focused_node == layout_first)
if (it.next()) |view| @fieldParentPtr(ViewStack(View).Node, "view", view) else null
else
focused_node;
if (zoom_node) |to_bump| {
output.views.remove(to_bump);
@ -62,3 +57,8 @@ pub fn zoom(
}
}
}
fn filter(view: *View, filter_tags: u32) bool {
return !view.destroying and !view.pending.float and
!view.pending.fullscreen and view.pending.tags & filter_tags != 0;
}

View file

@ -57,9 +57,9 @@ pub fn renderOutput(output: *Output) void {
c.wlr_renderer_begin(wlr_renderer, width, height);
// Find the first visible fullscreen view in the stack if there is one
var it = ViewStack(View).iterator(output.views.first, output.current.tags);
const fullscreen_view = while (it.next()) |node| {
if (node.view.current.fullscreen) break &node.view;
var it = ViewStack(View).iter(output.views.first, .forward, output.current.tags, renderFilter);
const fullscreen_view = while (it.next()) |view| {
if (view.current.fullscreen) break view;
} else null;
// If we have a fullscreen view to render, render it.
@ -76,14 +76,8 @@ pub fn renderOutput(output: *Output) void {
renderLayer(output.*, output.layers[c.ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &now);
// The first view in the list is "on top" so iterate in reverse.
it = ViewStack(View).reverseIterator(output.views.last, output.current.tags);
while (it.next()) |node| {
const view = &node.view;
// 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;
it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter);
while (it.next()) |view| {
// Focused views are rendered on top of normal views, skip them for now
if (view.current.focus != 0) continue;
@ -92,14 +86,8 @@ pub fn renderOutput(output: *Output) void {
}
// Render focused views
it = ViewStack(View).reverseIterator(output.views.last, output.current.tags);
while (it.next()) |node| {
const view = &node.view;
// 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;
it = ViewStack(View).iter(output.views.last, .reverse, output.current.tags, renderFilter);
while (it.next()) |view| {
// Skip unfocused views since we already rendered them
if (view.current.focus == 0) continue;
@ -130,6 +118,14 @@ pub fn renderOutput(output: *Output) void {
_ = c.wlr_output_commit(output.wlr_output);
}
fn renderFilter(view: *View, filter_tags: u32) bool {
// 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)
return false;
return view.current.tags & filter_tags != 0;
}
/// Render all surfaces on the passed layer
fn renderLayer(output: Output, layer: std.TailQueue(LayerSurface), now: *c.timespec) void {
var it = layer.first;

View file

@ -41,14 +41,8 @@ pub fn ViewStack(comptime T: type) type {
};
/// Top/bottom nodes in the stack
first: ?*Node,
last: ?*Node,
/// Initialize an undefined stack
pub fn init(self: *Self) void {
self.first = null;
self.last = null;
}
first: ?*Node = null,
last: ?*Node = null,
/// Add a node to the top of the stack.
pub fn push(self: *Self, new_node: *Node) void {
@ -114,60 +108,42 @@ pub fn ViewStack(comptime T: type) type {
}
}
const Iterator = struct {
const Direction = enum {
forward,
reverse,
};
fn Iter(comptime Context: type) type {
return struct {
it: ?*Node,
tags: u32,
reverse: bool,
pending: bool,
dir: Direction,
context: Context,
filter: fn (*View, Context) bool,
/// Returns the next node in iteration order, or null if done.
/// This function is horribly ugly, but it's well tested below.
pub fn next(self: *Iterator) ?*Node {
while (self.it) |node| : (self.it = if (self.reverse) node.prev else node.next) {
if (if (self.pending)
self.tags & node.view.pending.tags != 0
else
self.tags & node.view.current.tags != 0) {
self.it = if (self.reverse) node.prev else node.next;
return node;
/// Returns the next node in iteration order which passes the
/// filter, or null if done.
pub fn next(self: *@This()) ?*View {
return while (self.it) |node| : (self.it = if (self.dir == .forward) node.next else node.prev) {
const view = if (T == View) &node.view else node.view;
if (self.filter(view, self.context)) {
self.it = if (self.dir == .forward) node.next else node.prev;
break view;
}
} else null;
}
return null;
}
};
/// Returns an iterator starting at the passed node and filtered by
/// checking the passed tags against the current tags of each view.
pub fn iterator(start: ?*Node, tags: u32) Iterator {
return Iterator{
.it = start,
.tags = tags,
.reverse = false,
.pending = false,
};
}
/// Returns a reverse iterator starting at the passed node and filtered by
/// checking the passed tags against the current tags of each view.
pub fn reverseIterator(start: ?*Node, tags: u32) Iterator {
return Iterator{
.it = start,
.tags = tags,
.reverse = true,
.pending = false,
};
}
/// Returns an iterator starting at the passed node and filtered by
/// checking the passed tags against the pending tags of each view.
/// If a view has no pending tags, the current tags are used.
pub fn pendingIterator(start: ?*Node, tags: u32) Iterator {
return Iterator{
.it = start,
.tags = tags,
.reverse = false,
.pending = true,
};
/// Return a filtered iterator over the stack given a start node,
/// iteration direction, and filter function. Views for which the
/// filter function returns false will be skipped.
pub fn iter(
start: ?*Node,
dir: Direction,
context: var,
filter: fn (*View, @TypeOf(context)) bool,
) Iter(@TypeOf(context)) {
return .{ .it = start, .dir = dir, .context = context, .filter = filter };
}
};
}
@ -177,8 +153,7 @@ test "push/remove (*View)" {
const allocator = testing.allocator;
var views: ViewStack(*View) = undefined;
views.init();
var views = ViewStack(*View){};
const one = try allocator.create(ViewStack(*View).Node);
defer allocator.destroy(one);
@ -309,8 +284,21 @@ test "iteration (View)" {
const allocator = testing.allocator;
var views: ViewStack(View) = undefined;
views.init();
const filters = struct {
fn all(view: *View, context: void) bool {
return true;
}
fn none(view: *View, context: void) bool {
return false;
}
fn current(view: *View, filter_tags: u32) bool {
return view.current.tags & filter_tags != 0;
}
};
var views = ViewStack(View){};
const one_a_pb = try allocator.create(ViewStack(View).Node);
defer allocator.destroy(one_a_pb);
@ -343,94 +331,71 @@ test "iteration (View)" {
views.push(five_b); // {5, 4, 1, 3}
views.push(two_a); // {2, 5, 4, 1, 3}
// Iteration over all tags
// Iteration over all views
{
var it = ViewStack(View).iterator(views.first, std.math.maxInt(u32));
testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view);
testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view);
testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view);
testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view);
testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view);
var it = ViewStack(View).iter(views.first, .forward, {}, filters.all);
testing.expect(it.next() == &two_a.view);
testing.expect(it.next() == &five_b.view);
testing.expect(it.next() == &four_b.view);
testing.expect(it.next() == &one_a_pb.view);
testing.expect(it.next() == &three_b_pa.view);
testing.expect(it.next() == null);
}
// Iteration over no views
{
var it = ViewStack(View).iter(views.first, .forward, {}, filters.none);
testing.expect(it.next() == null);
}
// Iteration over 'a' tags
{
var it = ViewStack(View).iterator(views.first, 1 << 0);
testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view);
testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view);
var it = ViewStack(View).iter(views.first, .forward, @as(u32, 1 << 0), filters.current);
testing.expect(it.next() == &two_a.view);
testing.expect(it.next() == &one_a_pb.view);
testing.expect(it.next() == null);
}
// Iteration over 'b' tags
{
var it = ViewStack(View).iterator(views.first, 1 << 1);
testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view);
testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view);
testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view);
var it = ViewStack(View).iter(views.first, .forward, @as(u32, 1 << 1), filters.current);
testing.expect(it.next() == &five_b.view);
testing.expect(it.next() == &four_b.view);
testing.expect(it.next() == &three_b_pa.view);
testing.expect(it.next() == null);
}
// Iteration over tags that aren't present
// Reverse iteration over all views
{
var it = ViewStack(View).iterator(views.first, 1 << 2);
var it = ViewStack(View).iter(views.last, .reverse, {}, filters.all);
testing.expect(it.next() == &three_b_pa.view);
testing.expect(it.next() == &one_a_pb.view);
testing.expect(it.next() == &four_b.view);
testing.expect(it.next() == &five_b.view);
testing.expect(it.next() == &two_a.view);
testing.expect(it.next() == null);
}
// Reverse iteration over all tags
// Reverse iteration over no views
{
var it = ViewStack(View).reverseIterator(views.last, std.math.maxInt(u32));
testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view);
testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view);
testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view);
testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view);
testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view);
var it = ViewStack(View).iter(views.last, .reverse, {}, filters.none);
testing.expect(it.next() == null);
}
// Reverse iteration over 'a' tags
{
var it = ViewStack(View).reverseIterator(views.last, 1 << 0);
testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view);
testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view);
var it = ViewStack(View).iter(views.last, .reverse, @as(u32, 1 << 0), filters.current);
testing.expect(it.next() == &one_a_pb.view);
testing.expect(it.next() == &two_a.view);
testing.expect(it.next() == null);
}
// Reverse iteration over 'b' tags
{
var it = ViewStack(View).reverseIterator(views.last, 1 << 1);
testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view);
testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view);
testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view);
testing.expect(it.next() == null);
}
// Reverse iteration over tags that aren't present
{
var it = ViewStack(View).reverseIterator(views.first, 1 << 2);
testing.expect(it.next() == null);
}
// Iteration over (pending) 'a' tags
{
var it = ViewStack(View).pendingIterator(views.first, 1 << 0);
testing.expect((if (it.next()) |node| &node.view else null) == &two_a.view);
testing.expect((if (it.next()) |node| &node.view else null) == &three_b_pa.view);
testing.expect(it.next() == null);
}
// Iteration over (pending) 'b' tags
{
var it = ViewStack(View).pendingIterator(views.first, 1 << 1);
testing.expect((if (it.next()) |node| &node.view else null) == &five_b.view);
testing.expect((if (it.next()) |node| &node.view else null) == &four_b.view);
testing.expect((if (it.next()) |node| &node.view else null) == &one_a_pb.view);
testing.expect(it.next() == null);
}
// Iteration over (pending) tags that aren't present
{
var it = ViewStack(View).pendingIterator(views.first, 1 << 2);
var it = ViewStack(View).iter(views.last, .reverse, @as(u32, 1 << 1), filters.current);
testing.expect(it.next() == &three_b_pa.view);
testing.expect(it.next() == &four_b.view);
testing.expect(it.next() == &five_b.view);
testing.expect(it.next() == null);
}
}