diff --git a/doc/rivertile.1.scd b/doc/rivertile.1.scd index e94b39c..751e327 100644 --- a/doc/rivertile.1.scd +++ b/doc/rivertile.1.scd @@ -6,13 +6,30 @@ rivertile - Tiled layout generator for river # SYNOPSIS -*rivertile* +*rivertile* [_options_] # DESCRIPTION *rivertile* is a layout client for river. It provides a simple tiled layout split main/secondary stacks. +# OPTIONS + +*-view-padding* _pixels_ + Set the padding around views in pixels. + +*-outer-padding* _pixels_ + Set the padding around the edge of the layout area in pixels. + +*-main-location* [*top*|*bottom*|*left*|*right*] + Set the default location of the main area in the layout. + +*-main-count* _count_ + Set the default number of views in the main area of the layout. + +*-main-factor* _ratio_ + Set the default ratio of main area to total layout area. + # VALUES _main_location_ (string: top, bottom, left, or right) diff --git a/rivertile/args.zig b/rivertile/args.zig new file mode 100644 index 0000000..31b57bc --- /dev/null +++ b/rivertile/args.zig @@ -0,0 +1,126 @@ +// This file is part of river, a dynamic tiling wayland compositor. +// +// Copyright 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +const std = @import("std"); +const mem = std.mem; +const cstr = std.cstr; + +const root = @import("root"); + +pub const FlagDef = struct { + name: [*:0]const u8, + kind: enum { boolean, arg }, +}; + +pub fn Args(comptime num_positionals: comptime_int, comptime flag_defs: []const FlagDef) type { + return struct { + const Self = @This(); + + positionals: [num_positionals][*:0]const u8, + flags: [flag_defs.len]struct { + name: [*:0]const u8, + value: union { + boolean: bool, + arg: ?[*:0]const u8, + }, + }, + + pub fn parse(argv: [][*:0]const u8) Self { + var ret: Self = undefined; + + // Init all flags in the flags array to false/null + inline for (flag_defs) |flag_def, flag_idx| { + switch (flag_def.kind) { + .boolean => ret.flags[flag_idx] = .{ + .name = flag_def.name, + .value = .{ .boolean = false }, + }, + .arg => ret.flags[flag_idx] = .{ + .name = flag_def.name, + .value = .{ .arg = null }, + }, + } + } + + // Parse the argv in to the positionals and flags arrays + var arg_idx: usize = 0; + var positional_idx: usize = 0; + outer: while (arg_idx < argv.len) : (arg_idx += 1) { + var should_continue = false; + inline for (flag_defs) |flag_def, flag_idx| { + if (cstr.cmp(flag_def.name, argv[arg_idx]) == 0) { + switch (flag_def.kind) { + .boolean => ret.flags[flag_idx].value.boolean = true, + .arg => { + arg_idx += 1; + ret.flags[flag_idx].value.arg = if (arg_idx < argv.len) + argv[arg_idx] + else + root.fatal("flag '" ++ flag_def.name ++ + "' requires an argument but none was provided!", .{}); + }, + } + // TODO: this variable exists as a workaround for the fact that + // using continue :outer here crashes the stage1 compiler. + should_continue = true; + } + } + if (should_continue) continue; + + if (positional_idx == num_positionals) { + root.fatal( + "{} positional arguments expected but more were provided!", + .{num_positionals}, + ); + } + + // This check should not be needed as this code is unreachable + // if num_positionals is 0. Howevere the stage1 zig compiler does + // not seem to be smart enough to realize this. + if (num_positionals > 0) { + ret.positionals[positional_idx] = argv[arg_idx]; + } else { + unreachable; + } + positional_idx += 1; + } + + if (positional_idx < num_positionals) { + root.fatal( + "{} positional arguments expected but only {} were provided!", + .{ num_positionals, positional_idx }, + ); + } + + return ret; + } + + pub fn boolFlag(self: Self, flag_name: [*:0]const u8) bool { + for (self.flags) |flag| { + if (cstr.cmp(flag.name, flag_name) == 0) return flag.value.boolean; + } + unreachable; + } + + pub fn argFlag(self: Self, flag_name: [*:0]const u8) ?[*:0]const u8 { + for (self.flags) |flag| { + if (cstr.cmp(flag.name, flag_name) == 0) return flag.value.arg; + } + unreachable; + } + }; +} diff --git a/rivertile/main.zig b/rivertile/main.zig index 881abc3..29d9665 100644 --- a/rivertile/main.zig +++ b/rivertile/main.zig @@ -45,6 +45,9 @@ const wayland = @import("wayland"); const wl = wayland.client.wl; const river = wayland.client.river; +const Args = @import("args.zig").Args; +const FlagDef = @import("args.zig").FlagDef; + const Location = enum { top, right, @@ -52,9 +55,12 @@ const Location = enum { left, }; -// TODO: expose these as command line options -const default_view_padding = 6; -const default_outer_padding = 6; +// Configured through command line options +var view_padding: u32 = 6; +var outer_padding: u32 = 6; +var default_main_location: Location = .left; +var default_main_count: u32 = 1; +var default_main_factor: f64 = 0.6; /// We don't free resources on exit, only when output globals are removed. const gpa = std.heap.c_allocator; @@ -78,14 +84,20 @@ const Output = struct { wl_output: *wl.Output, name: u32, - main_location: Location = .left, - main_count: u32 = 1, - main_factor: f64 = 0.6, + main_location: Location, + main_count: u32, + main_factor: f64, layout: *river.LayoutV2 = undefined, fn init(output: *Output, context: *Context, wl_output: *wl.Output, name: u32) !void { - output.* = .{ .wl_output = wl_output, .name = name }; + output.* = .{ + .wl_output = wl_output, + .name = name, + .main_location = default_main_location, + .main_count = default_main_count, + .main_factor = default_main_factor, + }; if (context.initialized) try output.getLayout(context); } @@ -143,12 +155,12 @@ const Output = struct { 0; const usable_width = switch (output.main_location) { - .left, .right => ev.usable_width - 2 * default_outer_padding, - .top, .bottom => ev.usable_height - 2 * default_outer_padding, + .left, .right => ev.usable_width - 2 * outer_padding, + .top, .bottom => ev.usable_height - 2 * outer_padding, }; const usable_height = switch (output.main_location) { - .left, .right => ev.usable_height - 2 * default_outer_padding, - .top, .bottom => ev.usable_width - 2 * default_outer_padding, + .left, .right => ev.usable_height - 2 * outer_padding, + .top, .bottom => ev.usable_width - 2 * outer_padding, }; // to make things pixel-perfect, we make the first main and first secondary @@ -200,37 +212,37 @@ const Output = struct { height = secondary_height + if (i == output.main_count) secondary_height_rem else 0; } - x += @intCast(i32, default_view_padding); - y += @intCast(i32, default_view_padding); - width -= 2 * default_view_padding; - height -= 2 * default_view_padding; + x += @intCast(i32, view_padding); + y += @intCast(i32, view_padding); + width -= 2 * view_padding; + height -= 2 * view_padding; switch (output.main_location) { .left => layout.pushViewDimensions( ev.serial, - x + @intCast(i32, default_outer_padding), - y + @intCast(i32, default_outer_padding), + x + @intCast(i32, outer_padding), + y + @intCast(i32, outer_padding), width, height, ), .right => layout.pushViewDimensions( ev.serial, - @intCast(i32, usable_width - width) - x + @intCast(i32, default_outer_padding), - y + @intCast(i32, default_outer_padding), + @intCast(i32, usable_width - width) - x + @intCast(i32, outer_padding), + y + @intCast(i32, outer_padding), width, height, ), .top => layout.pushViewDimensions( ev.serial, - y + @intCast(i32, default_outer_padding), - x + @intCast(i32, default_outer_padding), + y + @intCast(i32, outer_padding), + x + @intCast(i32, outer_padding), height, width, ), .bottom => layout.pushViewDimensions( ev.serial, - y + @intCast(i32, default_outer_padding), - @intCast(i32, usable_width - width) - x + @intCast(i32, default_outer_padding), + y + @intCast(i32, outer_padding), + @intCast(i32, usable_width - width) - x + @intCast(i32, outer_padding), height, width, ), @@ -247,6 +259,37 @@ const Output = struct { }; pub fn main() !void { + // https://github.com/ziglang/zig/issues/7807 + const argv: [][*:0]const u8 = std.os.argv; + const args = Args(0, &[_]FlagDef{ + .{ .name = "-view-padding", .kind = .arg }, + .{ .name = "-outer-padding", .kind = .arg }, + .{ .name = "-main-location", .kind = .arg }, + .{ .name = "-main-count", .kind = .arg }, + .{ .name = "-main-factor", .kind = .arg }, + }).parse(argv[1..]); + + if (args.argFlag("-view-padding")) |raw| { + view_padding = std.fmt.parseUnsigned(u32, mem.span(raw), 10) catch + fatal("invalid value '{s}' provided to -view-padding", .{raw}); + } + if (args.argFlag("-outer-padding")) |raw| { + outer_padding = std.fmt.parseUnsigned(u32, mem.span(raw), 10) catch + fatal("invalid value '{s}' provided to -outer-padding", .{raw}); + } + if (args.argFlag("-main-location")) |raw| { + default_main_location = std.meta.stringToEnum(Location, mem.span(raw)) orelse + fatal("invalid value '{s}' provided to -main-location", .{raw}); + } + if (args.argFlag("-main-count")) |raw| { + default_main_count = std.fmt.parseUnsigned(u32, mem.span(raw), 10) catch + fatal("invalid value '{s}' provided to -main-count", .{raw}); + } + if (args.argFlag("-main-factor")) |raw| { + default_main_factor = std.fmt.parseFloat(f64, mem.span(raw)) catch + fatal("invalid value '{s}' provided to -main-factor", .{raw}); + } + const display = wl.Display.connect(null) catch { std.debug.warn("Unable to connect to Wayland server.\n", .{}); std.os.exit(1); @@ -298,7 +341,8 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: * } } -fn fatal(comptime format: []const u8, args: anytype) noreturn { - std.log.err(format, args); +pub fn fatal(comptime format: []const u8, args: anytype) noreturn { + const stderr = std.io.getStdErr().writer(); + stderr.print("err: " ++ format ++ "\n", args) catch {}; std.os.exit(1); }