river: rework core data structures & transactions

This commit is contained in:
Isaac Freund
2023-02-24 19:28:37 +01:00
parent f5dc67cfc1
commit be4330288d
34 changed files with 1027 additions and 1543 deletions

View File

@ -33,8 +33,7 @@ pub fn borderWidth(
if (args.len > 2) return Error.TooManyArguments;
server.config.border_width = try fmt.parseInt(u31, args[1], 10);
server.root.arrangeAll();
server.root.startTransaction();
server.root.applyPending();
}
pub fn backgroundColor(
@ -62,7 +61,7 @@ pub fn borderColorFocused(
if (args.len > 2) return Error.TooManyArguments;
server.config.border_color_focused = try parseRgba(args[1]);
server.root.startTransaction();
server.root.applyPending();
}
pub fn borderColorUnfocused(
@ -74,7 +73,7 @@ pub fn borderColorUnfocused(
if (args.len > 2) return Error.TooManyArguments;
server.config.border_color_unfocused = try parseRgba(args[1]);
server.root.startTransaction();
server.root.applyPending();
}
pub fn borderColorUrgent(
@ -86,7 +85,7 @@ pub fn borderColorUrgent(
if (args.len > 2) return Error.TooManyArguments;
server.config.border_color_urgent = try parseRgba(args[1]);
server.root.startTransaction();
server.root.applyPending();
}
pub fn setCursorWarp(

View File

@ -15,14 +15,15 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const assert = std.debug.assert;
const server = &@import("../main.zig").server;
const Direction = @import("../command.zig").Direction;
const Error = @import("../command.zig").Error;
const Output = @import("../Output.zig");
const Seat = @import("../Seat.zig");
const View = @import("../View.zig");
const ViewStack = @import("../view_stack.zig").ViewStack;
/// Focus either the next or the previous visible view, depending on the enum
/// passed. Does nothing if there are 1 or 0 views in the stack.
@ -35,40 +36,45 @@ pub fn focusView(
if (args.len > 2) return Error.TooManyArguments;
const direction = std.meta.stringToEnum(Direction, args[1]) orelse return Error.InvalidDirection;
const output = seat.focused_output;
const output = seat.focused_output orelse return;
if (seat.focused == .view) {
// If the focused view is fullscreen, do nothing
if (seat.focused.view.current.fullscreen) return;
if (seat.focused != .view) return;
if (seat.focused.view.pending.fullscreen) return;
// 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).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()) |view| {
seat.focus(view);
server.root.startTransaction();
return;
}
if (focusViewTarget(seat, output, direction)) |target| {
assert(!target.pending.fullscreen);
seat.focus(target);
server.root.applyPending();
}
// 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).iter(output.views.first, .forward, output.pending.tags, filter),
.previous => ViewStack(View).iter(output.views.last, .reverse, output.pending.tags, filter),
};
seat.focus(it.next());
server.root.startTransaction();
}
fn filter(view: *View, filter_tags: u32) bool {
return view.tree.node.enabled and view.pending.tags & filter_tags != 0;
fn focusViewTarget(seat: *Seat, output: *Output, direction: Direction) ?*View {
switch (direction) {
inline else => |dir| {
const it_dir = comptime switch (dir) {
.next => .forward,
.previous => .reverse,
};
var it = output.pending.wm_stack.iterator(it_dir);
while (it.next()) |view| {
if (view == seat.focused.view) break;
} else {
unreachable;
}
// Return the next view in the stack matching the tags if any.
while (it.next()) |view| {
if (output.pending.tags & view.pending.tags != 0) return view;
}
// Wrap and return the first view in the stack matching the tags if
// any is found before completing the loop back to the focused view.
while (it.next()) |view| {
if (view == seat.focused.view) return null;
if (output.pending.tags & view.pending.tags != 0) return view;
}
unreachable;
},
}
}

View File

@ -32,7 +32,7 @@ pub fn outputLayout(
if (args.len < 2) return Error.NotEnoughArguments;
if (args.len > 2) return Error.TooManyArguments;
const output = seat.focused_output;
const output = seat.focused_output orelse return;
const old_layout_namespace = output.layout_namespace;
output.layout_namespace = try util.gpa.dupe(u8, args[1]);
if (old_layout_namespace) |old| util.gpa.free(old);
@ -69,7 +69,7 @@ pub fn sendLayoutCmd(
if (args.len < 3) return Error.NotEnoughArguments;
if (args.len > 3) return Error.TooManyArguments;
const output = seat.focused_output;
const output = seat.focused_output orelse return;
const target_namespace = args[1];
var it = output.layouts.first;
@ -82,5 +82,5 @@ pub fn sendLayoutCmd(
layout.layout.sendUserCommandTags(output.pending.tags);
}
layout.layout.sendUserCommand(args[2]);
if (layout == output.current.layout) output.arrangeViews();
if (layout == output.layout) server.root.applyPending();
}

View File

@ -60,10 +60,11 @@ pub fn snap(
return Error.InvalidPhysicalDirection;
const view = getView(seat) orelse return;
const output = view.pending.output orelse return;
const border_width = server.config.border_width;
var output_width: i32 = undefined;
var output_height: i32 = undefined;
view.output.wlr_output.effectiveResolution(&output_width, &output_height);
output.wlr_output.effectiveResolution(&output_width, &output_height);
switch (direction) {
.up => view.pending.box.y = border_width,
.down => view.pending.box.y = output_height - view.pending.box.height - border_width,
@ -87,14 +88,16 @@ pub fn resize(
return Error.InvalidOrientation;
const view = getView(seat) orelse return;
var output_width: i32 = undefined;
var output_height: i32 = undefined;
view.output.wlr_output.effectiveResolution(&output_width, &output_height);
var output_width: c_int = math.maxInt(c_int);
var output_height: c_int = math.maxInt(c_int);
if (view.pending.output) |output| {
output.wlr_output.effectiveResolution(&output_width, &output_height);
}
switch (orientation) {
.horizontal => {
const prev_width = view.pending.box.width;
view.pending.box.width += delta;
view.applyConstraints();
view.applyConstraints(&view.pending.box);
// Get width difference after applying view constraints, so that the
// move reflects the actual size difference, but before applying the
// output size constraints, to allow growing a view even if it is
@ -110,7 +113,7 @@ pub fn resize(
.vertical => {
const prev_height = view.pending.box.height;
view.pending.box.height += delta;
view.applyConstraints();
view.applyConstraints(&view.pending.box);
const diff_height = prev_height - view.pending.box.height;
// Do not grow bigger than the output
view.pending.box.height = math.min(
@ -129,12 +132,11 @@ fn apply(view: *View) void {
// dimensions are set by a layout generator. If however the views are
// unarranged, leave them as non-floating so the next active layout can
// affect them.
if (view.output.pending.layout != null)
if (view.pending.output == null or view.pending.output.?.layout != null) {
view.pending.float = true;
}
view.float_box = view.pending.box;
view.applyPending();
server.root.applyPending();
}
fn getView(seat: *Seat) ?*View {

View File

@ -37,14 +37,14 @@ pub fn focusOutput(
if (args.len > 2) return Error.TooManyArguments;
// If the noop output is focused, there are no other outputs to switch to
if (seat.focused_output == &server.root.noop_output) {
if (seat.focused_output == null) {
assert(server.root.outputs.len == 0);
return;
}
seat.focusOutput((try getOutput(seat, args[1])) orelse return);
seat.focus(null);
server.root.startTransaction();
server.root.applyPending();
}
pub fn sendToOutput(
@ -56,22 +56,21 @@ pub fn sendToOutput(
if (args.len > 2) return Error.TooManyArguments;
// If the noop output is focused, there is nowhere to send the view
if (seat.focused_output == &server.root.noop_output) {
if (seat.focused_output == null) {
assert(server.root.outputs.len == 0);
return;
}
if (seat.focused == .view) {
const destination_output = (try getOutput(seat, args[1])) orelse return;
// If the view is already on destination_output, do nothing
if (seat.focused.view.output == destination_output) return;
seat.focused.view.sendToOutput(destination_output);
if (seat.focused.view.pending.output == destination_output) return;
seat.focused.view.setPendingOutput(destination_output);
// Handle the change and focus whatever's next in the focus stack
seat.focus(null);
seat.focused_output.arrangeViews();
destination_output.arrangeViews();
server.root.startTransaction();
server.root.applyPending();
}
}
@ -80,19 +79,19 @@ pub fn sendToOutput(
fn getOutput(seat: *Seat, str: []const u8) !?*Output {
if (std.meta.stringToEnum(Direction, str)) |direction| { // Logical direction
// Return the next/prev output in the list if there is one, else wrap
const focused_node = @fieldParentPtr(std.TailQueue(Output).Node, "data", seat.focused_output);
const focused_node = @fieldParentPtr(std.TailQueue(Output).Node, "data", seat.focused_output.?);
return switch (direction) {
.next => if (focused_node.next) |node| &node.data else &server.root.outputs.first.?.data,
.previous => if (focused_node.prev) |node| &node.data else &server.root.outputs.last.?.data,
};
} else if (std.meta.stringToEnum(wlr.OutputLayout.Direction, str)) |direction| { // Spacial direction
var focus_box: wlr.Box = undefined;
server.root.output_layout.getBox(seat.focused_output.wlr_output, &focus_box);
server.root.output_layout.getBox(seat.focused_output.?.wlr_output, &focus_box);
if (focus_box.empty()) return null;
const wlr_output = server.root.output_layout.adjacentOutput(
direction,
seat.focused_output.wlr_output,
seat.focused_output.?.wlr_output,
@intToFloat(f64, focus_box.x + @divTrunc(focus_box.width, 2)),
@intToFloat(f64, focus_box.y + @divTrunc(focus_box.height, 2)),
) orelse return null;

View File

@ -15,14 +15,15 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const assert = std.debug.assert;
const server = &@import("../main.zig").server;
const Error = @import("../command.zig").Error;
const Direction = @import("../command.zig").Direction;
const Error = @import("../command.zig").Error;
const Output = @import("../Output.zig");
const Seat = @import("../Seat.zig");
const View = @import("../View.zig");
const ViewStack = @import("../view_stack.zig").ViewStack;
/// Swap the currently focused view with either the view higher or lower in the visible stack
pub fn swap(
@ -33,51 +34,47 @@ pub fn swap(
if (args.len < 2) return Error.NotEnoughArguments;
if (args.len > 2) return Error.TooManyArguments;
if (seat.focused != .view)
return;
const direction = std.meta.stringToEnum(Direction, args[1]) orelse return Error.InvalidDirection;
const output = seat.focused_output orelse return;
// Filter out everything that is not part of the current layout
if (seat.focused != .view) return;
if (seat.focused.view.pending.float or seat.focused.view.pending.fullscreen) return;
const direction = std.meta.stringToEnum(Direction, args[1]) orelse return Error.InvalidDirection;
const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", seat.focused.view);
const output = seat.focused_output;
var it = ViewStack(View).iter(
focused_node,
if (direction == .next) .forward else .reverse,
output.pending.tags,
filter,
);
var it_wrap = ViewStack(View).iter(
if (direction == .next) output.views.first else output.views.last,
if (direction == .next) .forward else .reverse,
output.pending.tags,
filter,
);
// skip the first node which is focused_node
_ = it.next().?;
const to_swap = @fieldParentPtr(
ViewStack(View).Node,
"view",
// Wrap around if needed
if (it.next()) |next| next else it_wrap.next().?,
);
// Dont swap when only the focused view is part of the layout
if (focused_node == to_swap) {
return;
if (swapTarget(seat, output, direction)) |target| {
assert(!target.pending.float);
assert(!target.pending.fullscreen);
seat.focused.view.pending_wm_stack_link.swapWith(&target.pending_wm_stack_link);
server.root.applyPending();
}
output.views.swap(focused_node, to_swap);
output.arrangeViews();
server.root.startTransaction();
}
fn filter(view: *View, filter_tags: u32) bool {
return view.tree.node.enabled and !view.pending.float and
!view.pending.fullscreen and view.pending.tags & filter_tags != 0;
fn swapTarget(seat: *Seat, output: *Output, direction: Direction) ?*View {
switch (direction) {
inline else => |dir| {
const it_dir = comptime switch (dir) {
.next => .forward,
.previous => .reverse,
};
var it = output.pending.wm_stack.iterator(it_dir);
while (it.next()) |view| {
if (view == seat.focused.view) break;
} else {
unreachable;
}
// Return the next view in the stack matching the tags if any.
while (it.next()) |view| {
if (output.pending.tags & view.pending.tags != 0 and !view.pending.float) return view;
}
// Wrap and return the first view in the stack matching the tags if
// any is found before completing the loop back to the focused view.
while (it.next()) |view| {
if (view == seat.focused.view) return null;
if (output.pending.tags & view.pending.tags != 0 and !view.pending.float) return view;
}
unreachable;
},
}
}

View File

@ -30,12 +30,12 @@ pub fn setFocusedTags(
out: *?[]const u8,
) Error!void {
const tags = try parseTags(args, out);
if (seat.focused_output.pending.tags != tags) {
seat.focused_output.previous_tags = seat.focused_output.pending.tags;
seat.focused_output.pending.tags = tags;
seat.focused_output.arrangeViews();
const output = seat.focused_output orelse return;
if (output.pending.tags != tags) {
output.previous_tags = output.pending.tags;
output.pending.tags = tags;
seat.focus(null);
server.root.startTransaction();
server.root.applyPending();
}
}
@ -59,7 +59,7 @@ pub fn setViewTags(
const view = seat.focused.view;
view.pending.tags = tags;
seat.focus(null);
view.applyPending();
server.root.applyPending();
}
}
@ -70,14 +70,13 @@ pub fn toggleFocusedTags(
out: *?[]const u8,
) Error!void {
const tags = try parseTags(args, out);
const output = seat.focused_output;
const output = seat.focused_output orelse return;
const new_focused_tags = output.pending.tags ^ tags;
if (new_focused_tags != 0) {
output.previous_tags = output.pending.tags;
output.pending.tags = new_focused_tags;
output.arrangeViews();
seat.focus(null);
server.root.startTransaction();
server.root.applyPending();
}
}
@ -94,7 +93,7 @@ pub fn toggleViewTags(
const view = seat.focused.view;
view.pending.tags = new_tags;
seat.focus(null);
view.applyPending();
server.root.applyPending();
}
}
}
@ -106,13 +105,13 @@ pub fn focusPreviousTags(
_: *?[]const u8,
) Error!void {
if (args.len > 1) return error.TooManyArguments;
const previous_tags = seat.focused_output.previous_tags;
if (seat.focused_output.pending.tags != previous_tags) {
seat.focused_output.previous_tags = seat.focused_output.pending.tags;
seat.focused_output.pending.tags = previous_tags;
seat.focused_output.arrangeViews();
const output = seat.focused_output orelse return;
const previous_tags = output.previous_tags;
if (output.pending.tags != previous_tags) {
output.previous_tags = output.pending.tags;
output.pending.tags = previous_tags;
seat.focus(null);
server.root.startTransaction();
server.root.applyPending();
}
}
@ -123,12 +122,13 @@ pub fn sendToPreviousTags(
_: *?[]const u8,
) Error!void {
if (args.len > 1) return error.TooManyArguments;
const previous_tags = seat.focused_output.previous_tags;
const output = seat.focused_output orelse return;
if (seat.focused == .view) {
const view = seat.focused.view;
view.pending.tags = previous_tags;
view.pending.tags = output.previous_tags;
seat.focus(null);
view.applyPending();
server.root.applyPending();
}
}

View File

@ -36,13 +36,12 @@ pub fn toggleFloat(
// If views are unarranged, don't allow changing the views float status.
// It would just lead to confusing because this state would not be
// visible immediately, only after a layout is connected.
if (view.output.pending.layout == null)
return;
if (view.pending.output == null or view.pending.output.?.layout == null) return;
// Don't float fullscreen views
if (view.pending.fullscreen) return;
view.pending.float = !view.pending.float;
view.applyPending();
server.root.applyPending();
}
}

View File

@ -33,6 +33,6 @@ pub fn toggleFullscreen(
const view = seat.focused.view;
view.pending.fullscreen = !view.pending.fullscreen;
view.applyPending();
server.root.applyPending();
}
}

View File

@ -15,13 +15,13 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const assert = std.debug.assert;
const server = &@import("../main.zig").server;
const Error = @import("../command.zig").Error;
const Seat = @import("../Seat.zig");
const View = @import("../View.zig");
const ViewStack = @import("../view_stack.zig").ViewStack;
/// Bump the focused view to the top of the stack. If the view on the top of
/// the stack is focused, bump the second view to the top.
@ -32,33 +32,50 @@ pub fn zoom(
) Error!void {
if (args.len > 1) return Error.TooManyArguments;
if (seat.focused == .view) {
// Only zoom views that are part of the layout
if (seat.focused.view.pending.float or seat.focused.view.pending.fullscreen) return;
if (seat.focused != .view) return;
if (seat.focused.view.pending.float or seat.focused.view.pending.fullscreen) return;
// 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).iter(output.views.first, .forward, output.pending.tags, filter);
const layout_first = @fieldParentPtr(ViewStack(View).Node, "view", it.next().?);
const output = seat.focused_output orelse return;
const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", seat.focused.view);
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);
output.views.push(to_bump);
seat.focus(&to_bump.view);
output.arrangeViews();
server.root.startTransaction();
const layout_first = blk: {
var it = output.pending.wm_stack.iterator(.forward);
while (it.next()) |view| {
if (view.pending.tags & output.pending.tags != 0 and !view.pending.float) break :blk view;
} else {
// If we are focusing a view that is not fullscreen or floating
// it must be visible and in the layout.
unreachable;
}
};
// If the first view that is part of the layout is focused, zoom
// the next view in the layout if any. Otherwise zoom the focused view.
const zoom_target = blk: {
if (seat.focused.view == layout_first) {
var it = output.pending.wm_stack.iterator(.forward);
while (it.next()) |view| {
if (view == seat.focused.view) break;
} else {
unreachable;
}
while (it.next()) |view| {
if (view.pending.tags & output.pending.tags != 0 and !view.pending.float) break :blk view;
} else {
break :blk null;
}
} else {
break :blk seat.focused.view;
}
};
if (zoom_target) |target| {
assert(!target.pending.float);
assert(!target.pending.fullscreen);
target.pending_wm_stack_link.remove();
output.pending.wm_stack.prepend(target);
seat.focus(target);
server.root.applyPending();
}
}
fn filter(view: *View, filter_tags: u32) bool {
return view.tree.node.enabled and !view.pending.float and
!view.pending.fullscreen and view.pending.tags & filter_tags != 0;
}