aboutsummaryrefslogtreecommitdiff
path: root/rivertile/main.zig
diff options
context:
space:
mode:
Diffstat (limited to 'rivertile/main.zig')
-rw-r--r--rivertile/main.zig482
1 files changed, 373 insertions, 109 deletions
diff --git a/rivertile/main.zig b/rivertile/main.zig
index d1149b1..c020101 100644
--- a/rivertile/main.zig
+++ b/rivertile/main.zig
@@ -14,129 +14,393 @@
//
// 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.
+//
+// With 4 views and one main, the left layout looks something like this:
+//
+// +-----------------------+------------+
+// | | |
+// | | |
+// | | |
+// | +------------+
+// | | |
+// | | |
+// | | |
+// | +------------+
+// | | |
+// | | |
+// | | |
+// +-----------------------+------------+
+//
const std = @import("std");
+const wayland = @import("wayland");
+const wl = wayland.client.wl;
+const zriver = wayland.client.zriver;
+const river = wayland.client.river;
+
+const gpa = std.heap.c_allocator;
+
+const Context = struct {
+ running: bool = true,
+ layout_manager: ?*river.LayoutManagerV1 = null,
+ options_manager: ?*zriver.OptionsManagerV1 = 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);
+ const node = try gpa.create(std.TailQueue(Output).Node);
+ node.data.init(self, output);
+ self.outputs.append(node);
+ }
-const Orientation = enum {
- left,
- right,
- top,
- bottom,
+ pub fn destroyAllOutputs(self: *Context) void {
+ while (self.outputs.pop()) |node| {
+ node.data.deinit();
+ gpa.destroy(node);
+ }
+ }
+
+ pub fn configureAllOutputs(self: *Context) void {
+ var it = self.outputs.first;
+ while (it) |node| : (it = node.next) {
+ node.data.configure(self);
+ }
+ }
};
-/// 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.
-///
-/// With 4 views and one main view, the left layout looks something like this:
-///
-/// +-----------------------+------------+
-/// | | |
-/// | | |
-/// | | |
-/// | +------------+
-/// | | |
-/// | | |
-/// | | |
-/// | +------------+
-/// | | |
-/// | | |
-/// | | |
-/// +-----------------------+------------+
-pub fn main() !void {
- const args = std.os.argv;
- if (args.len != 7) printUsageAndExit();
+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)),
+ 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()"),
+ }
+ }
+};
+
+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);
+ }
+
+ pub fn deinit(self: *Output) void {
+ self.output.release();
+
+ if (self.configured) {
+ self.top.deinit();
+ self.right.deinit();
+ self.bottom.deinit();
+ self.left.deinit();
+
+ self.main_amount.deinit();
+ self.main_factor.deinit();
+ self.view_padding.deinit();
+ self.outer_padding.deinit();
+ }
+ }
+
+ 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;
- // first arg must be left, right, top, or bottom
- const main_location = std.meta.stringToEnum(Orientation, std.mem.spanZ(args[1])) orelse
- printUsageAndExit();
+ 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 {};
+ }
+};
- // the other 5 are passed by river and described in river-layouts(7)
- const num_views = try std.fmt.parseInt(u32, std.mem.spanZ(args[2]), 10);
- const main_count = try std.fmt.parseInt(u32, std.mem.spanZ(args[3]), 10);
- const main_factor = try std.fmt.parseFloat(f64, std.mem.spanZ(args[4]));
+const Layout = struct {
+ output: *Output,
+ layout: ?*river.LayoutV1,
+ orientation: Orientation,
- const width_arg: u32 = switch (main_location) {
- .left, .right => 5,
- .top, .bottom => 6,
+ const Orientation = enum {
+ top,
+ right,
+ bottom,
+ left,
};
- const height_arg: u32 = if (width_arg == 5) 6 else 5;
-
- const output_width = try std.fmt.parseInt(u32, std.mem.spanZ(args[width_arg]), 10);
- const output_height = try std.fmt.parseInt(u32, std.mem.spanZ(args[height_arg]), 10);
-
- const secondary_count = if (num_views > main_count) num_views - main_count else 0;
-
- // to make things pixel-perfect, we make the first main and first secondary
- // view slightly larger if the height is not evenly divisible
- var main_width: u32 = undefined;
- var main_height: u32 = undefined;
- var main_height_rem: u32 = undefined;
-
- var secondary_width: u32 = undefined;
- var secondary_height: u32 = undefined;
- var secondary_height_rem: u32 = undefined;
-
- if (main_count > 0 and secondary_count > 0) {
- main_width = @floatToInt(u32, main_factor * @intToFloat(f64, output_width));
- main_height = output_height / main_count;
- main_height_rem = output_height % main_count;
-
- secondary_width = output_width - main_width;
- secondary_height = output_height / secondary_count;
- secondary_height_rem = output_height % secondary_count;
- } else if (main_count > 0) {
- main_width = output_width;
- main_height = output_height / main_count;
- main_height_rem = output_height % main_count;
- } else if (secondary_width > 0) {
- main_width = 0;
- secondary_width = output_width;
- secondary_height = output_height / secondary_count;
- secondary_height_rem = output_height % secondary_count;
- }
-
- // Buffering the output makes things faster
- var stdout_buf = std.io.bufferedOutStream(std.io.getStdOut().outStream());
- const stdout = stdout_buf.outStream();
-
- var i: u32 = 0;
- while (i < num_views) : (i += 1) {
- var x: u32 = undefined;
- var y: u32 = undefined;
- var width: u32 = undefined;
- var height: u32 = undefined;
-
- if (i < main_count) {
- x = 0;
- y = 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 = main_width;
- y = (i - main_count) * secondary_height + if (i > main_count) secondary_height_rem else 0;
- width = secondary_width;
- height = secondary_height + if (i == main_count) secondary_height_rem else 0;
+
+ 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 getNamespace(self: *Layout) [*:0]const u8 {
+ return switch (self.orientation) {
+ .top => "tile-top",
+ .right => "tile-right",
+ .bottom => "tile-bottom",
+ .left => "tile-left",
+ };
+ }
+
+ pub fn deinit(self: *Layout) void {
+ if (self.layout) |layout| {
+ layout.destroy();
+ self.layout = null;
}
+ }
+
+ fn layoutListener(layout: *river.LayoutV1, event: river.LayoutV1.Event, self: *Layout) 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);
+
+ const secondary_count = if (data.view_count > main_amount)
+ data.view_count - main_amount
+ 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);
+
+ // to make things pixel-perfect, we make the first main and first secondary
+ // view slightly larger if the height is not evenly divisible
+ var main_width: u32 = undefined;
+ var main_height: u32 = undefined;
+ var main_height_rem: u32 = undefined;
+
+ var secondary_width: u32 = undefined;
+ 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;
+
+ secondary_width = usable_width - main_width;
+ secondary_height = usable_height / secondary_count;
+ secondary_height_rem = usable_height % secondary_count;
+ } else if (main_amount > 0) {
+ main_width = usable_width;
+ main_height = usable_height / main_amount;
+ main_height_rem = usable_height % main_amount;
+ } else if (secondary_width > 0) {
+ main_width = 0;
+ secondary_width = usable_width;
+ secondary_height = usable_height / secondary_count;
+ secondary_height_rem = usable_height % secondary_count;
+ }
- switch (main_location) {
- .left => try stdout.print("{} {} {} {}\n", .{ x, y, width, height }),
- .right => try stdout.print("{} {} {} {}\n", .{ output_width - x - width, y, width, height }),
- .top => try stdout.print("{} {} {} {}\n", .{ y, x, height, width }),
- .bottom => try stdout.print("{} {} {} {}\n", .{ y, output_width - x - width, height, width }),
+ var i: u32 = 0;
+ while (i < data.view_count) : (i += 1) {
+ var x: i32 = undefined;
+ var y: i32 = undefined;
+ var width: u32 = undefined;
+ var height: u32 = undefined;
+
+ if (i < main_amount) {
+ 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);
+ width = secondary_width;
+ height = secondary_height + if (i == main_amount) secondary_height_rem else 0;
+ }
+
+ x += @intCast(i32, view_padding);
+ y += @intCast(i32, view_padding);
+ width -= 2 * view_padding;
+ height -= 2 * view_padding;
+
+ switch (self.orientation) {
+ .left => layout.pushViewDimensions(
+ data.serial,
+ x + @intCast(i32, outer_padding),
+ y + @intCast(i32, outer_padding),
+ width,
+ height,
+ ),
+ .right => layout.pushViewDimensions(
+ data.serial,
+ @intCast(i32, usable_width - width) - x + @intCast(i32, outer_padding),
+ y + @intCast(i32, outer_padding),
+ width,
+ height,
+ ),
+ .top => layout.pushViewDimensions(
+ data.serial,
+ y + @intCast(i32, outer_padding),
+ x + @intCast(i32, outer_padding),
+ height,
+ width,
+ ),
+ .bottom => layout.pushViewDimensions(
+ data.serial,
+ y + @intCast(i32, outer_padding),
+ @intCast(i32, usable_width - width) - x + @intCast(i32, outer_padding),
+ height,
+ width,
+ ),
+ }
+ }
+
+ layout.commit(data.serial);
+ },
+
+ .advertise_view => {},
+ .advertise_done => {},
}
}
+};
- try stdout_buf.flush();
-}
+pub fn main() !void {
+ const display = wl.Display.connect(null) catch {
+ std.debug.warn("Unable to connect to Wayland server.\n", .{});
+ std.os.exit(1);
+ };
+ defer display.disconnect();
+
+ var context: Context = .{};
-fn printUsageAndExit() noreturn {
- const usage: []const u8 =
- \\Usage: rivertile left|right|top|bottom [args passed by river]
- \\
- ;
+ const registry = try display.getRegistry();
+ try registry.setListener(*Context, registryListener, &context);
+ _ = try display.roundtrip();
- std.debug.warn(usage, .{});
- std.os.exit(1);
+ if (context.layout_manager == null) {
+ std.debug.warn("Wayland server does not support river_layout_unstable_v1.\n", .{});
+ std.os.exit(1);
+ }
+
+ if (context.options_manager == null) {
+ std.debug.warn("Wayland server does not support river_options_unstable_v1.\n", .{});
+ std.os.exit(1);
+ }
+
+ context.configureAllOutputs();
+ defer context.destroyAllOutputs();
+
+ while (context.running) {
+ _ = try display.dispatch();
+ }
+}
+
+fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *Context) void {
+ switch (event) {
+ .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, wl.Output.getInterface().name) == 0) {
+ context.addOutput(registry, global.name) catch {
+ std.debug.warn("Failed to bind output.\n", .{});
+ context.running = false;
+ };
+ }
+ },
+ .global_remove => |global| {},
+ }
}