~novakane/rivercarro

c2394042fe664e9aa3ad1fad432e2b7c07c157b6 — Hugo Machet 4 months ago 22ea006
Cleanup before 0.1.2 realease
3 files changed, 99 insertions(+), 87 deletions(-)

M README.md
M doc/rivercarro.1.scd
M src/main.zig
M README.md => README.md +19 -8
@@ 6,8 6,8 @@ A slightly modified version of _rivertile_ layout generator for
Compared to _rivertile_, _rivercarro_ adds:

-   Monocle layout, views will takes all the usable area on the screen.
-   Gaps instead of padding around views or layout area. Change gaps size
    at runtime.
-   Gaps instead of padding around views or layout area.
-   Modify gaps size at runtime.
-   Smart gaps, if there is only one view, gaps will be disable.
-   Limit the width of the usable area of the screen.



@@ 36,7 36,8 @@ set `PKG_CONFIG_PATH` in your shell rc, e.g.
## Usage

Works exactly as _rivertile_, you can just replace _rivertile_ name by
_rivercarro_ in your config, and read `rivercarro(1)` man page
_rivercarro_ in your config, and read `rivercarro(1)` man page for commands
specific to rivercarro.

`e.g.` In your **river** init (usually `$XDG_CONFIG_HOME/river/init`)



@@ 57,6 58,15 @@ riverctl map normal $mod Left  send-layout-cmd rivercarro "main-location left"
# And for monocle
riverctl map normal $mod M     send-layout-cmd rivercarro "main-location monocle"

# Add others rivercarrro's commands the same way with the keybinds you'd like.
# e.g.
# riverctl map normal $mod <keys> send-layout-cmd rivercarro "inner-gaps -1"
# riverctl map normal $mod <keys> send-layout-cmd rivercarro "inner-gaps +1"
# riverctl map normal $mod <keys> send-layout-cmd rivercarro "outer-gaps -1"
# riverctl map normal $mod <keys> send-layout-cmd rivercarro "outer-gaps +1"
# riverctl map normal $mod <keys> send-layout-cmd rivercarro "width-ratio -0.1"
# riverctl map normal $mod <keys> send-layout-cmd rivercarro "width-ratio +0.1"

# Set and exec into the default layout generator, rivercarro.
# River will send the process group of the init executable SIGTERM on exit.
riverctl default-layout rivercarro


@@ 66,8 76,8 @@ exec rivercarro
### Command line options

```
$ rivercarro -help
Usage: rivercarro [options]
$ rivercarro -h
Usage: rivercarro [options...]

  -h              Print this help message and exit.
  -version        Print the version number and exit.


@@ 114,13 124,14 @@ You can also found me on IRC `irc.libera.chat` as `novakane`, mostly on

## Thanks

Almost all credits go to [Isaac Freund] and [Leon Henrik Plickat]
Thanks to [Isaac Freund] and [Leon Henrik Plickat] for river obviously, for
rivertile, most of rivercarro code comes from them, and for always answering
my many questions!

**Thanks to them!**

## License

[GNU General Public License v3.0 or later]
rivercarro is licensed under the [GNU General Public License v3.0 or later]

[river]: https://github.com/ifreund/river
[stacktile]: https://sr.ht/~leon_plickat/stacktile/

M doc/rivercarro.1.scd => doc/rivercarro.1.scd +7 -7
@@ 19,8 19,8 @@ help of *riverctl*(1).
In addition to *rivertile*(1), *rivercarro* provides:

- Monocle layout, views will takes all the usable area on the screen.
- Gaps instead of padding around views or layout area. Change gaps size
  at runtime.
- Gaps instead of padding around views or layout area.
- Modify gaps size at runtime.
- Smart gaps, if there is only one view, gaps will be disable.
- Limit the width of the usable area of the screen.



@@ 74,16 74,16 @@ These commands may be sent to rivercarro at runtime with the help of
*riverctl*(1).

*inner-gaps* <pixels>
	Set the gaps around views in pixels. If _value_ is prefixed by
	a +/- sign, _value_ is added/subtracted from the current _value_. If
	there is no sign, inner-gaps is set to _value_.
	Set the gaps around views in pixels. If _pixels_ is prefixed by
	a +/- sign, _pixels_ is added/subtracted from the current value. If
	there is no sign, inner-gaps is set to _pixels_.

	Default: 6

*outer-gaps* <pixels>
	Set the gaps around the edge of the layout area in pixels. If
	_value_ is prefixed by a +/- sign, _value_ is added/subtracted from the
	current _value_. If there is no sign, outer-gaps is set to _value_.
	_pixels_ is prefixed by a +/- sign, _pixels_ is added/subtracted from the
	current value. If there is no sign, outer-gaps is set to _pixels_.

	Default: 6


M src/main.zig => src/main.zig +73 -72
@@ 19,6 19,7 @@ const build_options = @import("build_options");

const std = @import("std");
const assert = std.debug.assert;
const fmt = std.fmt;
const mem = std.mem;
const math = std.math;
const os = std.os;


@@ 32,7 33,7 @@ const flags = @import("flags.zig");
const log = std.log.scoped(.rivercarro);

const usage =
    \\Usage: rivercarro [options]
    \\Usage: rivercarro [options...]
    \\
    \\  -h              Print this help message and exit.
    \\  -version        Print the version number and exit.


@@ 73,7 74,7 @@ const Location = enum {
    monocle,
};

// Configured through command line options
// Configured through command line options.
var default_inner_gaps: u32 = 6;
var default_outer_gaps: u32 = 6;
var smart_gaps: bool = true;


@@ 88,17 89,18 @@ var only_one_view: bool = false;
const gpa = std.heap.c_allocator;

const Context = struct {
    initialized: bool = false,
    layout_manager: ?*river.LayoutManagerV3 = null,
    outputs: std.TailQueue(Output) = .{},

    fn addOutput(context: *Context, registry: *wl.Registry, name: u32) !void {
    initialized: bool = false,

    fn addOutput(ctx: *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);
        errdefer gpa.destroy(node);
        try node.data.init(context, wl_output, name);
        context.outputs.append(node);
        try node.data.init(ctx, wl_output, name);
        ctx.outputs.append(node);
    }
};



@@ 106,34 108,27 @@ const Output = struct {
    wl_output: *wl.Output,
    name: u32,

    inner_gaps: u32,
    outer_gaps: u32,
    main_location: Location,
    main_count: u32,
    main_ratio: f64,
    width_ratio: f64,

    inner_gaps: u32,
    outer_gaps: u32,

    layout: *river.LayoutV3 = undefined,

    fn init(output: *Output, context: *Context, wl_output: *wl.Output, name: u32) !void {
    fn init(output: *Output, ctx: *Context, wl_output: *wl.Output, name: u32) !void {
        output.* = .{
            .wl_output = wl_output,
            .name = name,
            .inner_gaps = default_inner_gaps,
            .outer_gaps = default_outer_gaps,
            .main_location = default_main_location,
            .main_count = default_main_count,
            .main_ratio = default_main_ratio,
            .width_ratio = default_width_ratio,
            .inner_gaps = default_inner_gaps,
            .outer_gaps = default_outer_gaps,
        };
        if (context.initialized) try output.getLayout(context);
    }

    fn getLayout(output: *Output, context: *Context) !void {
        assert(context.initialized);
        output.layout = try context.layout_manager.?.getLayout(output.wl_output, "rivercarro");
        output.layout.setListener(*Output, layoutListener, output);
        if (ctx.initialized) try output.getLayout(ctx);
    }

    fn deinit(output: *Output) void {


@@ 141,6 136,12 @@ const Output = struct {
        output.layout.destroy();
    }

    fn getLayout(output: *Output, ctx: *Context) !void {
        assert(ctx.initialized);
        output.layout = try ctx.layout_manager.?.getLayout(output.wl_output, "rivercarro");
        output.layout.setListener(*Output, layoutListener, output);
    }

    fn layoutListener(layout: *river.LayoutV3, event: river.LayoutV3.Event, output: *Output) void {
        switch (event) {
            .namespace_in_use => fatal("namespace 'rivercarro' already in use.", .{}),


@@ 148,25 149,25 @@ const Output = struct {
            .user_command => |ev| {
                var it = mem.tokenize(u8, mem.span(ev.command), " ");
                const raw_cmd = it.next() orelse {
                    log.err("not enough arguments", .{});
                    log.err("Not enough arguments", .{});
                    return;
                };
                const raw_arg = it.next() orelse {
                    log.err("not enough arguments", .{});
                    log.err("Not enough arguments", .{});
                    return;
                };
                if (it.next() != null) {
                    log.err("too many arguments", .{});
                    log.err("Too many arguments", .{});
                    return;
                }
                const cmd = std.meta.stringToEnum(Command, raw_cmd) orelse {
                    log.err("unknown command: {s}", .{raw_cmd});
                    log.err("Unknown command: {s}", .{raw_cmd});
                    return;
                };
                switch (cmd) {
                    .@"inner-gaps" => {
                        const arg = std.fmt.parseInt(i32, raw_arg, 10) catch |err| {
                            log.err("failed to parse argument: {}", .{err});
                        const arg = fmt.parseInt(i32, raw_arg, 10) catch |err| {
                            log.err("Failed to parse argument: {}", .{err});
                            return;
                        };
                        switch (raw_arg[0]) {


@@ 179,8 180,8 @@ const Output = struct {
                        }
                    },
                    .@"outer-gaps" => {
                        const arg = std.fmt.parseInt(i32, raw_arg, 10) catch |err| {
                            log.err("failed to parse argument: {}", .{err});
                        const arg = fmt.parseInt(i32, raw_arg, 10) catch |err| {
                            log.err("Failed to parse argument: {}", .{err});
                            return;
                        };
                        switch (raw_arg[0]) {


@@ 194,13 195,13 @@ const Output = struct {
                    },
                    .@"main-location" => {
                        output.main_location = std.meta.stringToEnum(Location, raw_arg) orelse {
                            log.err("unknown location: {s}", .{raw_arg});
                            log.err("Unknown location: {s}", .{raw_arg});
                            return;
                        };
                    },
                    .@"main-count" => {
                        const arg = std.fmt.parseInt(i32, raw_arg, 10) catch |err| {
                            log.err("failed to parse argument: {}", .{err});
                        const arg = fmt.parseInt(i32, raw_arg, 10) catch |err| {
                            log.err("Failed to parse argument: {}", .{err});
                            return;
                        };
                        switch (raw_arg[0]) {


@@ 213,8 214,8 @@ const Output = struct {
                        }
                    },
                    .@"main-ratio" => {
                        const arg = std.fmt.parseFloat(f64, raw_arg) catch |err| {
                            log.err("failed to parse argument: {}", .{err});
                        const arg = fmt.parseFloat(f64, raw_arg) catch |err| {
                            log.err("Failed to parse argument: {}", .{err});
                            return;
                        };
                        switch (raw_arg[0]) {


@@ 225,8 226,8 @@ const Output = struct {
                        }
                    },
                    .@"width-ratio" => {
                        const arg = std.fmt.parseFloat(f64, raw_arg) catch |err| {
                            log.err("failed to parse argument: {}", .{err});
                        const arg = fmt.parseFloat(f64, raw_arg) catch |err| {
                            log.err("Failed to parse argument: {}", .{err});
                            return;
                        };
                        switch (raw_arg[0]) {


@@ 241,17 242,17 @@ const Output = struct {

            .layout_demand => |ev| {
                const main_count = math.clamp(output.main_count, 1, ev.view_count);
                const secondary_count = if (ev.view_count > main_count)
                    ev.view_count - main_count
                else
                    0;
                const secondary_count = blk: {
                    if (ev.view_count > main_count) break :blk ev.view_count - main_count;
                    break :blk 0;
                };

                only_one_view = if (ev.view_count == 1 or output.main_location == .monocle)
                    true
                else
                    false;
                only_one_view = blk: {
                    if (ev.view_count == 1 or output.main_location == .monocle) break :blk true;
                    break :blk false;
                };

                // Don't add gaps if there is only one view
                // Don't add gaps if there is only one view.
                if (only_one_view and smart_gaps) {
                    default_outer_gaps = 0;
                    default_inner_gaps = 0;


@@ 263,7 264,7 @@ const Output = struct {
                const usable_width = switch (output.main_location) {
                    .left, .right, .monocle => @floatToInt(
                        u32,
                        (@intToFloat(f64, ev.usable_width)) * output.width_ratio,
                        @intToFloat(f64, ev.usable_width) * output.width_ratio,
                    ) - 2 * default_outer_gaps,
                    .top, .bottom => ev.usable_height - 2 * default_outer_gaps,
                };


@@ 271,12 272,12 @@ const Output = struct {
                    .left, .right, .monocle => ev.usable_height - 2 * default_outer_gaps,
                    .top, .bottom => @floatToInt(
                        u32,
                        (@intToFloat(f64, ev.usable_width)) * output.width_ratio,
                        @intToFloat(f64, ev.usable_width) * output.width_ratio,
                    ) - 2 * default_outer_gaps,
                };

                // to make things pixel-perfect, we make the first main and first secondary
                // view slightly larger if the height is not evenly divisible
                // 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;


@@ 403,18 404,18 @@ pub fn main() !void {
    const result = flags.parse(argv[1..], &[_]flags.Flag{
        .{ .name = "-h", .kind = .boolean },
        .{ .name = "-version", .kind = .boolean },
        .{ .name = "-no-smart-gaps", .kind = .boolean },
        .{ .name = "-inner-gaps", .kind = .arg },
        .{ .name = "-outer-gaps", .kind = .arg },
        .{ .name = "-main-location", .kind = .arg },
        .{ .name = "-main-count", .kind = .arg },
        .{ .name = "-main-ratio", .kind = .arg },
        .{ .name = "-width-ratio", .kind = .arg },
        .{ .name = "-no-smart-gaps", .kind = .boolean },
    }) catch {
        try std.io.getStdErr().writeAll(usage);
        os.exit(1);
    };
    if (result.args.len != 0) fatalPrintUsage("unknown option '{s}'", .{result.args[0]});
    if (result.args.len != 0) fatalPrintUsage("Unknown option '{s}'", .{result.args[0]});

    if (result.boolFlag("-h")) {
        try std.io.getStdOut().writeAll(usage);


@@ 428,28 429,28 @@ pub fn main() !void {
        smart_gaps = false;
    }
    if (result.argFlag("-inner-gaps")) |raw| {
        default_inner_gaps = std.fmt.parseUnsigned(u32, raw, 10) catch
            fatalPrintUsage("invalid value '{s}' provided to -inner-gaps", .{raw});
        default_inner_gaps = fmt.parseUnsigned(u32, raw, 10) catch
            fatalPrintUsage("Invalid value '{s}' provided to -inner-gaps", .{raw});
    }
    if (result.argFlag("-outer-gaps")) |raw| {
        default_outer_gaps = std.fmt.parseUnsigned(u32, raw, 10) catch
            fatalPrintUsage("invalid value '{s}' provided to -outer-gaps", .{raw});
        default_outer_gaps = fmt.parseUnsigned(u32, raw, 10) catch
            fatalPrintUsage("Invalid value '{s}' provided to -outer-gaps", .{raw});
    }
    if (result.argFlag("-main-location")) |raw| {
        default_main_location = std.meta.stringToEnum(Location, raw) orelse
            fatalPrintUsage("invalid value '{s}' provided to -main-location", .{raw});
            fatalPrintUsage("Invalid value '{s}' provided to -main-location", .{raw});
    }
    if (result.argFlag("-main-count")) |raw| {
        default_main_count = std.fmt.parseUnsigned(u32, raw, 10) catch
            fatalPrintUsage("invalid value '{s}' provided to -main-count", .{raw});
        default_main_count = fmt.parseUnsigned(u32, raw, 10) catch
            fatalPrintUsage("Invalid value '{s}' provided to -main-count", .{raw});
    }
    if (result.argFlag("-main-ratio")) |raw| {
        default_main_ratio = std.fmt.parseFloat(f64, raw) catch
            fatalPrintUsage("invalid value '{s}' provided to -main-ratio", .{raw});
        default_main_ratio = fmt.parseFloat(f64, raw) catch
            fatalPrintUsage("Invalid value '{s}' provided to -main-ratio", .{raw});
    }
    if (result.argFlag("-width-ratio")) |raw| {
        default_width_ratio = std.fmt.parseFloat(f64, raw) catch
            fatalPrintUsage("invalid value '{s}' provided to -width-ratio", .{raw});
        default_width_ratio = fmt.parseFloat(f64, raw) catch
            fatalPrintUsage("Invalid value '{s}' provided to -width-ratio", .{raw});
    }

    const display = wl.Display.connect(null) catch {


@@ 458,43 459,43 @@ pub fn main() !void {
    };
    defer display.disconnect();

    var context: Context = .{};
    var ctx: Context = .{};

    const registry = try display.getRegistry();
    registry.setListener(*Context, registryListener, &context);
    registry.setListener(*Context, registryListener, &ctx);
    _ = try display.roundtrip();

    if (context.layout_manager == null) {
    if (ctx.layout_manager == null) {
        fatal("Wayland compositor does not support river_layout_v3.\n", .{});
    }

    context.initialized = true;
    ctx.initialized = true;

    var it = context.outputs.first;
    var it = ctx.outputs.first;
    while (it) |node| : (it = node.next) {
        const output = &node.data;
        try output.getLayout(&context);
        try output.getLayout(&ctx);
    }

    while (true) _ = try display.dispatch();
}

fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *Context) void {
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, ctx: *Context) void {
    switch (event) {
        .global => |global| {
            if (std.cstr.cmp(global.interface, river.LayoutManagerV3.getInterface().name) == 0) {
                context.layout_manager = registry.bind(global.name, river.LayoutManagerV3, 1) catch return;
                ctx.layout_manager = registry.bind(global.name, river.LayoutManagerV3, 1) catch return;
            } else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) {
                context.addOutput(registry, global.name) catch |err|
                    fatal("failed to bind output: {}", .{err});
                ctx.addOutput(registry, global.name) catch |err|
                    fatal("Failed to bind output: {}", .{err});
            }
        },
        .global_remove => |ev| {
            var it = context.outputs.first;
            var it = ctx.outputs.first;
            while (it) |node| : (it = node.next) {
                const output = &node.data;
                if (output.name == ev.name) {
                    context.outputs.remove(node);
                    ctx.outputs.remove(node);
                    output.deinit();
                    gpa.destroy(node);
                    break;