diff options
Diffstat (limited to 'rivertile/main.zig')
| -rw-r--r-- | rivertile/main.zig | 429 |
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); +} |
