aboutsummaryrefslogtreecommitdiff
path: root/rivertile/main.zig
diff options
context:
space:
mode:
Diffstat (limited to 'rivertile/main.zig')
-rw-r--r--rivertile/main.zig429
1 files changed, 191 insertions, 238 deletions
diff --git a/rivertile/main.zig b/rivertile/main.zig
index c020101..b33d9b0 100644
--- a/rivertile/main.zig
+++ b/rivertile/main.zig
@@ -1,6 +1,6 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
-// Copyright 2020 The River Developers
+// Copyright 2020-2021 The River Developers
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -14,15 +14,13 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-//
-//
// This is an implementation of the default "tiled" layout of dwm and the
-// 3 other orientations thereof. This code is written with the left
-// orientation in mind and then the input/output values are adjusted to apply
-// the necessary transformations to derive the other 3.
+// 3 other orientations thereof. This code is written for the main stack
+// to the left and then the input/output values are adjusted to apply
+// the necessary transformations to derive the other orientations.
//
-// With 4 views and one main, the left layout looks something like this:
+// With 4 views and one main on the left, the layout looks something like this:
//
// +-----------------------+------------+
// | | |
@@ -37,229 +35,164 @@
// | | |
// | | |
// +-----------------------+------------+
-//
const std = @import("std");
+const mem = std.mem;
+const assert = std.debug.assert;
+
const wayland = @import("wayland");
const wl = wayland.client.wl;
-const zriver = wayland.client.zriver;
const river = wayland.client.river;
+const Location = enum {
+ top,
+ right,
+ bottom,
+ left,
+};
+
+const default_main_location: Location = .left;
+const default_main_count = 1;
+const default_main_factor = 0.6;
+const default_view_padding = 6;
+const default_outer_padding = 6;
+
+/// We don't free resources on exit, only when output globals are removed.
const gpa = std.heap.c_allocator;
const Context = struct {
- running: bool = true,
+ initialized: bool = false,
layout_manager: ?*river.LayoutManagerV1 = null,
- options_manager: ?*zriver.OptionsManagerV1 = null,
+ options_manager: ?*river.OptionsManagerV2 = null,
outputs: std.TailQueue(Output) = .{},
- pub fn addOutput(self: *Context, registry: *wl.Registry, name: u32) !void {
- const output = try registry.bind(name, wl.Output, 3);
+ fn addOutput(context: *Context, registry: *wl.Registry, name: u32) !void {
+ const wl_output = try registry.bind(name, wl.Output, 3);
+ errdefer wl_output.release();
const node = try gpa.create(std.TailQueue(Output).Node);
- node.data.init(self, output);
- self.outputs.append(node);
+ errdefer gpa.destroy(node);
+ try node.data.init(context, wl_output, name);
+ context.outputs.append(node);
}
+};
- pub fn destroyAllOutputs(self: *Context) void {
- while (self.outputs.pop()) |node| {
- node.data.deinit();
- gpa.destroy(node);
+fn Option(comptime key: [:0]const u8, comptime T: type, comptime default: T) type {
+ return struct {
+ const Self = @This();
+ output: *Output,
+ handle: *river.OptionHandleV2,
+ value: T = default,
+
+ fn init(option: *Self, context: *Context, output: *Output) !void {
+ option.* = .{
+ .output = output,
+ .handle = try context.options_manager.?.getOptionHandle(key, output.wl_output),
+ };
+ option.handle.setListener(*Self, optionListener, option) catch unreachable;
}
- }
- pub fn configureAllOutputs(self: *Context) void {
- var it = self.outputs.first;
- while (it) |node| : (it = node.next) {
- node.data.configure(self);
+ fn deinit(option: *Self) void {
+ option.handle.destroy();
+ option.* = undefined;
}
- }
-};
-
-const Option = struct {
- pub const Value = union(enum) {
- unset: void,
- double: f64,
- uint: u32,
- };
-
- handle: ?*zriver.OptionHandleV1 = null,
- value: Value = .unset,
- output: *Output = undefined,
-
- pub fn init(self: *Option, output: *Output, comptime key: [*:0]const u8, initial: Value) !void {
- self.* = .{
- .value = initial,
- .output = output,
- .handle = try output.context.options_manager.?.getOptionHandle(
- key,
- output.output,
- ),
- };
- self.handle.?.setListener(*Option, optionListener, self) catch |err| {
- self.handle.?.destroy();
- self.handle = null;
- return err;
- };
- }
-
- pub fn deinit(self: *Option) void {
- if (self.handle) |handle| handle.destroy();
- }
- fn optionListener(handle: *zriver.OptionHandleV1, event: zriver.OptionHandleV1.Event, self: *Option) void {
- switch (event) {
- .unset => switch (self.value) {
- .uint => handle.setUintValue(self.value.uint),
- .double => handle.setFixedValue(wl.Fixed.fromDouble(self.value.double)),
+ fn optionListener(handle: *river.OptionHandleV2, event: river.OptionHandleV2.Event, option: *Self) void {
+ const prev_value = option.value;
+ assert(event != .undeclared); // We declare all options used in main()
+ switch (T) {
+ u32 => switch (event) {
+ .uint_value => |ev| option.value = ev.value,
+ else => std.log.err("expected value of uint type for " ++ key ++
+ " option, falling back to default", .{}),
+ },
+ f64 => switch (event) {
+ .fixed_value => |ev| option.value = ev.value.toDouble(),
+ else => std.log.err("expected value of fixed type for " ++ key ++
+ " option, falling back to default", .{}),
+ },
+ Location => switch (event) {
+ .string_value => |ev| if (ev.value) |value| {
+ if (std.meta.stringToEnum(Location, mem.span(value))) |location| {
+ option.value = location;
+ } else {
+ std.log.err(
+ \\invalid main_location "{s}", must be "top", "bottom", "left", or "right"
+ , .{value});
+ }
+ },
+ else => std.log.err("expected value of string type for " ++ key ++
+ " option, falling back to default", .{}),
+ },
else => unreachable,
- },
- .int_value => {},
- .uint_value => |data| self.value = .{ .uint = data.value },
- .fixed_value => |data| self.value = .{ .double = data.value.toDouble() },
- .string_value => {},
- }
- if (self.output.top.layout) |layout| layout.parametersChanged();
- if (self.output.right.layout) |layout| layout.parametersChanged();
- if (self.output.bottom.layout) |layout| layout.parametersChanged();
- if (self.output.left.layout) |layout| layout.parametersChanged();
- }
-
- pub fn getValueOrElse(self: *Option, comptime T: type, comptime otherwise: T) T {
- switch (T) {
- u32 => return if (self.value == .uint) self.value.uint else otherwise,
- f64 => return if (self.value == .double) self.value.double else otherwise,
- else => @compileError("Unsupported type for Option.getValueOrElse()"),
+ }
+ if (option.value != prev_value) option.output.layout.parametersChanged();
}
- }
-};
+ };
+}
const Output = struct {
- context: *Context,
- output: *wl.Output,
-
- top: Layout = undefined,
- right: Layout = undefined,
- bottom: Layout = undefined,
- left: Layout = undefined,
-
- main_amount: Option = .{},
- main_factor: Option = .{},
- view_padding: Option = .{},
- outer_padding: Option = .{},
-
- configured: bool = false,
-
- pub fn init(self: *Output, context: *Context, wl_output: *wl.Output) void {
- self.* = .{
- .output = wl_output,
- .context = context,
- };
- self.configure(context);
- }
+ wl_output: *wl.Output,
+ name: u32,
- pub fn deinit(self: *Output) void {
- self.output.release();
+ main_location: Option("main_location", Location, default_main_location) = undefined,
+ main_count: Option("main_count", u32, default_main_count) = undefined,
+ main_factor: Option("main_factor", f64, default_main_factor) = undefined,
+ view_padding: Option("view_padding", u32, default_view_padding) = undefined,
+ outer_padding: Option("outer_padding", u32, default_outer_padding) = undefined,
- if (self.configured) {
- self.top.deinit();
- self.right.deinit();
- self.bottom.deinit();
- self.left.deinit();
+ layout: *river.LayoutV1 = undefined,
- self.main_amount.deinit();
- self.main_factor.deinit();
- self.view_padding.deinit();
- self.outer_padding.deinit();
- }
+ fn init(output: *Output, context: *Context, wl_output: *wl.Output, name: u32) !void {
+ output.* = .{ .wl_output = wl_output, .name = name };
+ if (context.initialized) try output.initOptionsAndLayout(context);
}
- pub fn configure(self: *Output, context: *Context) void {
- if (self.configured) return;
- if (context.layout_manager == null) return;
- if (context.options_manager == null) return;
-
- self.configured = true;
-
- self.main_amount.init(self, "main_amount", .{ .uint = 1 }) catch {};
- self.main_factor.init(self, "main_factor", .{ .double = 0.6 }) catch {};
- self.view_padding.init(self, "view_padding", .{ .uint = 10 }) catch {};
- self.outer_padding.init(self, "outer_padding", .{ .uint = 10 }) catch {};
-
- self.top.init(self, .top) catch {};
- self.right.init(self, .right) catch {};
- self.bottom.init(self, .bottom) catch {};
- self.left.init(self, .left) catch {};
+ fn initOptionsAndLayout(output: *Output, context: *Context) !void {
+ assert(context.initialized);
+ try output.main_location.init(context, output);
+ errdefer output.main_location.deinit();
+ try output.main_count.init(context, output);
+ errdefer output.main_count.deinit();
+ try output.main_factor.init(context, output);
+ errdefer output.main_factor.deinit();
+ try output.view_padding.init(context, output);
+ errdefer output.view_padding.deinit();
+ try output.outer_padding.init(context, output);
+ errdefer output.outer_padding.deinit();
+
+ output.layout = try context.layout_manager.?.getLayout(output.wl_output, "rivertile");
+ output.layout.setListener(*Output, layoutListener, output) catch unreachable;
}
-};
-
-const Layout = struct {
- output: *Output,
- layout: ?*river.LayoutV1,
- orientation: Orientation,
-
- const Orientation = enum {
- top,
- right,
- bottom,
- left,
- };
- pub fn init(self: *Layout, output: *Output, orientation: Orientation) !void {
- self.output = output;
- self.orientation = orientation;
- self.layout = try output.context.layout_manager.?.getLayout(
- self.output.output,
- self.getNamespace(),
- );
- self.layout.?.setListener(*Layout, layoutListener, self) catch |err| {
- self.layout.?.destroy();
- self.layout = null;
- return err;
- };
- }
+ fn deinit(output: *Output) void {
+ output.wl_output.release();
- fn getNamespace(self: *Layout) [*:0]const u8 {
- return switch (self.orientation) {
- .top => "tile-top",
- .right => "tile-right",
- .bottom => "tile-bottom",
- .left => "tile-left",
- };
- }
+ output.main_count.deinit();
+ output.main_factor.deinit();
+ output.view_padding.deinit();
+ output.outer_padding.deinit();
- pub fn deinit(self: *Layout) void {
- if (self.layout) |layout| {
- layout.destroy();
- self.layout = null;
- }
+ output.layout.destroy();
}
- fn layoutListener(layout: *river.LayoutV1, event: river.LayoutV1.Event, self: *Layout) void {
+ fn layoutListener(layout: *river.LayoutV1, event: river.LayoutV1.Event, output: *Output) void {
switch (event) {
- .namespace_in_use => {
- std.debug.warn("{}: Namespace already in use.\n", .{self.getNamespace()});
- self.deinit();
- },
-
- .layout_demand => |data| {
- const main_amount = self.output.main_amount.getValueOrElse(u32, 1);
- const main_factor = std.math.clamp(self.output.main_factor.getValueOrElse(f64, 0.6), 0.1, 0.9);
- const view_padding = self.output.view_padding.getValueOrElse(u32, 0);
- const outer_padding = self.output.outer_padding.getValueOrElse(u32, 0);
+ .namespace_in_use => fatal("namespace 'rivertile' already in use.", .{}),
- const secondary_count = if (data.view_count > main_amount)
- data.view_count - main_amount
+ .layout_demand => |ev| {
+ const secondary_count = if (ev.view_count > output.main_count.value)
+ ev.view_count - output.main_count.value
else
0;
- const usable_width = if (self.orientation == .left or self.orientation == .right)
- data.usable_width - (2 * outer_padding)
- else
- data.usable_height - (2 * outer_padding);
- const usable_height = if (self.orientation == .left or self.orientation == .right)
- data.usable_height - (2 * outer_padding)
- else
- data.usable_width - (2 * outer_padding);
+ const usable_width = switch (output.main_location.value) {
+ .left, .right => ev.usable_width - (2 * output.outer_padding.value),
+ .top, .bottom => ev.usable_height - (2 * output.outer_padding.value),
+ };
+ const usable_height = switch (output.main_location.value) {
+ .left, .right => ev.usable_height - (2 * output.outer_padding.value),
+ .top, .bottom => ev.usable_width - (2 * output.outer_padding.value),
+ };
// to make things pixel-perfect, we make the first main and first secondary
// view slightly larger if the height is not evenly divisible
@@ -271,18 +204,18 @@ const Layout = struct {
var secondary_height: u32 = undefined;
var secondary_height_rem: u32 = undefined;
- if (main_amount > 0 and secondary_count > 0) {
- main_width = @floatToInt(u32, main_factor * @intToFloat(f64, usable_width));
- main_height = usable_height / main_amount;
- main_height_rem = usable_height % main_amount;
+ if (output.main_count.value > 0 and secondary_count > 0) {
+ main_width = @floatToInt(u32, output.main_factor.value * @intToFloat(f64, usable_width));
+ main_height = usable_height / output.main_count.value;
+ main_height_rem = usable_height % output.main_count.value;
secondary_width = usable_width - main_width;
secondary_height = usable_height / secondary_count;
secondary_height_rem = usable_height % secondary_count;
- } else if (main_amount > 0) {
+ } else if (output.main_count.value > 0) {
main_width = usable_width;
- main_height = usable_height / main_amount;
- main_height_rem = usable_height % main_amount;
+ main_height = usable_height / output.main_count.value;
+ main_height_rem = usable_height % output.main_count.value;
} else if (secondary_width > 0) {
main_width = 0;
secondary_width = usable_width;
@@ -291,63 +224,63 @@ const Layout = struct {
}
var i: u32 = 0;
- while (i < data.view_count) : (i += 1) {
+ while (i < ev.view_count) : (i += 1) {
var x: i32 = undefined;
var y: i32 = undefined;
var width: u32 = undefined;
var height: u32 = undefined;
- if (i < main_amount) {
+ if (i < output.main_count.value) {
x = 0;
y = @intCast(i32, (i * main_height) + if (i > 0) main_height_rem else 0);
width = main_width;
height = main_height + if (i == 0) main_height_rem else 0;
} else {
x = @intCast(i32, main_width);
- y = @intCast(i32, (i - main_amount) * secondary_height +
- if (i > main_amount) secondary_height_rem else 0);
+ y = @intCast(i32, (i - output.main_count.value) * secondary_height +
+ if (i > output.main_count.value) secondary_height_rem else 0);
width = secondary_width;
- height = secondary_height + if (i == main_amount) secondary_height_rem else 0;
+ height = secondary_height + if (i == output.main_count.value) secondary_height_rem else 0;
}
- x += @intCast(i32, view_padding);
- y += @intCast(i32, view_padding);
- width -= 2 * view_padding;
- height -= 2 * view_padding;
+ x += @intCast(i32, output.view_padding.value);
+ y += @intCast(i32, output.view_padding.value);
+ width -= 2 * output.view_padding.value;
+ height -= 2 * output.view_padding.value;
- switch (self.orientation) {
+ switch (output.main_location.value) {
.left => layout.pushViewDimensions(
- data.serial,
- x + @intCast(i32, outer_padding),
- y + @intCast(i32, outer_padding),
+ ev.serial,
+ x + @intCast(i32, output.outer_padding.value),
+ y + @intCast(i32, output.outer_padding.value),
width,
height,
),
.right => layout.pushViewDimensions(
- data.serial,
- @intCast(i32, usable_width - width) - x + @intCast(i32, outer_padding),
- y + @intCast(i32, outer_padding),
+ ev.serial,
+ @intCast(i32, usable_width - width) - x + @intCast(i32, output.outer_padding.value),
+ y + @intCast(i32, output.outer_padding.value),
width,
height,
),
.top => layout.pushViewDimensions(
- data.serial,
- y + @intCast(i32, outer_padding),
- x + @intCast(i32, outer_padding),
+ ev.serial,
+ y + @intCast(i32, output.outer_padding.value),
+ x + @intCast(i32, output.outer_padding.value),
height,
width,
),
.bottom => layout.pushViewDimensions(
- data.serial,
- y + @intCast(i32, outer_padding),
- @intCast(i32, usable_width - width) - x + @intCast(i32, outer_padding),
+ ev.serial,
+ y + @intCast(i32, output.outer_padding.value),
+ @intCast(i32, usable_width - width) - x + @intCast(i32, output.outer_padding.value),
height,
width,
),
}
}
- layout.commit(data.serial);
+ layout.commit(ev.serial);
},
.advertise_view => {},
@@ -366,25 +299,32 @@ pub fn main() !void {
var context: Context = .{};
const registry = try display.getRegistry();
- try registry.setListener(*Context, registryListener, &context);
+ registry.setListener(*Context, registryListener, &context) catch unreachable;
_ = try display.roundtrip();
if (context.layout_manager == null) {
- std.debug.warn("Wayland server does not support river_layout_unstable_v1.\n", .{});
- std.os.exit(1);
+ fatal("wayland compositor does not support river_layout_v1.\n", .{});
}
-
if (context.options_manager == null) {
- std.debug.warn("Wayland server does not support river_options_unstable_v1.\n", .{});
- std.os.exit(1);
+ fatal("wayland compositor does not support river_options_v2.\n", .{});
}
- context.configureAllOutputs();
- defer context.destroyAllOutputs();
+ // TODO: should be @tagName(default_main_location), https://github.com/ziglang/zig/issues/3779
+ context.options_manager.?.declareStringOption("main_location", "left");
+ context.options_manager.?.declareUintOption("main_count", default_main_count);
+ context.options_manager.?.declareFixedOption("main_factor", wl.Fixed.fromDouble(default_main_factor));
+ context.options_manager.?.declareUintOption("view_padding", default_view_padding);
+ context.options_manager.?.declareUintOption("outer_padding", default_outer_padding);
+
+ context.initialized = true;
- while (context.running) {
- _ = try display.dispatch();
+ var it = context.outputs.first;
+ while (it) |node| : (it = node.next) {
+ const output = &node.data;
+ try output.initOptionsAndLayout(&context);
}
+
+ while (true) _ = try display.dispatch();
}
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *Context) void {
@@ -392,15 +332,28 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *
.global => |global| {
if (std.cstr.cmp(global.interface, river.LayoutManagerV1.getInterface().name) == 0) {
context.layout_manager = registry.bind(global.name, river.LayoutManagerV1, 1) catch return;
- } else if (std.cstr.cmp(global.interface, zriver.OptionsManagerV1.getInterface().name) == 0) {
- context.options_manager = registry.bind(global.name, zriver.OptionsManagerV1, 1) catch return;
+ } else if (std.cstr.cmp(global.interface, river.OptionsManagerV2.getInterface().name) == 0) {
+ context.options_manager = registry.bind(global.name, river.OptionsManagerV2, 1) catch return;
} else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) {
- context.addOutput(registry, global.name) catch {
- std.debug.warn("Failed to bind output.\n", .{});
- context.running = false;
- };
+ context.addOutput(registry, global.name) catch |err| fatal("failed to bind output: {}", .{err});
+ }
+ },
+ .global_remove => |ev| {
+ var it = context.outputs.first;
+ while (it) |node| : (it = node.next) {
+ const output = &node.data;
+ if (output.name == ev.name) {
+ context.outputs.remove(node);
+ output.deinit();
+ gpa.destroy(node);
+ break;
+ }
}
},
- .global_remove => |global| {},
}
}
+
+fn fatal(comptime format: []const u8, args: anytype) noreturn {
+ std.log.err(format, args);
+ std.os.exit(1);
+}