2020-06-16 12:48:30 +00:00
|
|
|
// This file is part of river, a dynamic tiling wayland compositor.
|
|
|
|
//
|
2021-04-14 22:28:39 +00:00
|
|
|
// Copyright 2020-2021 The River Developers
|
2020-06-16 12:48:30 +00:00
|
|
|
//
|
|
|
|
// 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 <https://www.gnu.org/licenses/>.
|
2020-10-02 13:53:08 +00:00
|
|
|
|
|
|
|
// This is an implementation of the default "tiled" layout of dwm and the
|
2021-04-14 22:28:39 +00:00
|
|
|
// 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.
|
2020-10-02 13:53:08 +00:00
|
|
|
//
|
2021-04-14 22:28:39 +00:00
|
|
|
// With 4 views and one main on the left, the layout looks something like this:
|
2020-10-02 13:53:08 +00:00
|
|
|
//
|
|
|
|
// +-----------------------+------------+
|
|
|
|
// | | |
|
|
|
|
// | | |
|
|
|
|
// | | |
|
|
|
|
// | +------------+
|
|
|
|
// | | |
|
|
|
|
// | | |
|
|
|
|
// | | |
|
|
|
|
// | +------------+
|
|
|
|
// | | |
|
|
|
|
// | | |
|
|
|
|
// | | |
|
|
|
|
// +-----------------------+------------+
|
2020-06-16 12:48:30 +00:00
|
|
|
|
|
|
|
const std = @import("std");
|
2021-04-14 22:28:39 +00:00
|
|
|
const mem = std.mem;
|
2021-04-26 19:03:28 +00:00
|
|
|
const math = std.math;
|
2021-04-14 22:28:39 +00:00
|
|
|
const assert = std.debug.assert;
|
|
|
|
|
2020-10-02 13:53:08 +00:00
|
|
|
const wayland = @import("wayland");
|
|
|
|
const wl = wayland.client.wl;
|
|
|
|
const river = wayland.client.river;
|
|
|
|
|
2021-04-26 21:41:30 +00:00
|
|
|
const Args = @import("args.zig").Args;
|
|
|
|
const FlagDef = @import("args.zig").FlagDef;
|
|
|
|
|
2021-05-01 10:49:49 +00:00
|
|
|
const usage =
|
|
|
|
\\Usage: rivertile [options]
|
|
|
|
\\
|
|
|
|
\\ -h, --help Print this help message and exit.
|
|
|
|
\\ -view-padding Set the padding around views in pixels. (Default 6)
|
|
|
|
\\ -outer-padding Set the padding around the edge of the layout area in
|
|
|
|
\\ pixels. (Default 6)
|
|
|
|
\\ -main-location Set the initial location of the main area in the
|
|
|
|
\\ layout. (Default left)
|
|
|
|
\\ -main-count Set the initial number of views in the main area of the
|
|
|
|
\\ layout. (Default 1)
|
|
|
|
\\ -main-factor Set the initial ratio of main area to total layout
|
|
|
|
\\ area. (Default: 0.6)
|
|
|
|
\\
|
|
|
|
;
|
|
|
|
|
2021-04-14 22:28:39 +00:00
|
|
|
const Location = enum {
|
|
|
|
top,
|
|
|
|
right,
|
|
|
|
bottom,
|
|
|
|
left,
|
|
|
|
};
|
|
|
|
|
2021-04-26 21:41:30 +00:00
|
|
|
// 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;
|
2021-04-14 22:28:39 +00:00
|
|
|
|
|
|
|
/// We don't free resources on exit, only when output globals are removed.
|
2020-10-02 13:53:08 +00:00
|
|
|
const gpa = std.heap.c_allocator;
|
|
|
|
|
|
|
|
const Context = struct {
|
2021-04-14 22:28:39 +00:00
|
|
|
initialized: bool = false,
|
2021-04-26 19:03:28 +00:00
|
|
|
layout_manager: ?*river.LayoutManagerV2 = null,
|
2020-10-02 13:53:08 +00:00
|
|
|
outputs: std.TailQueue(Output) = .{},
|
|
|
|
|
2021-04-14 22:28:39 +00:00
|
|
|
fn addOutput(context: *Context, registry: *wl.Registry, name: u32) !void {
|
|
|
|
const wl_output = try registry.bind(name, wl.Output, 3);
|
|
|
|
errdefer wl_output.release();
|
2020-10-02 13:53:08 +00:00
|
|
|
const node = try gpa.create(std.TailQueue(Output).Node);
|
2021-04-14 22:28:39 +00:00
|
|
|
errdefer gpa.destroy(node);
|
|
|
|
try node.data.init(context, wl_output, name);
|
|
|
|
context.outputs.append(node);
|
2020-10-02 13:53:08 +00:00
|
|
|
}
|
2021-04-14 22:28:39 +00:00
|
|
|
};
|
2020-06-16 12:48:30 +00:00
|
|
|
|
2020-10-02 13:53:08 +00:00
|
|
|
const Output = struct {
|
2021-04-14 22:28:39 +00:00
|
|
|
wl_output: *wl.Output,
|
|
|
|
name: u32,
|
2020-10-02 13:53:08 +00:00
|
|
|
|
2021-04-26 21:41:30 +00:00
|
|
|
main_location: Location,
|
|
|
|
main_count: u32,
|
|
|
|
main_factor: f64,
|
2021-04-26 19:03:28 +00:00
|
|
|
|
|
|
|
layout: *river.LayoutV2 = undefined,
|
2020-10-02 13:53:08 +00:00
|
|
|
|
2021-04-14 22:28:39 +00:00
|
|
|
fn init(output: *Output, context: *Context, wl_output: *wl.Output, name: u32) !void {
|
2021-04-26 21:41:30 +00:00
|
|
|
output.* = .{
|
|
|
|
.wl_output = wl_output,
|
|
|
|
.name = name,
|
|
|
|
.main_location = default_main_location,
|
|
|
|
.main_count = default_main_count,
|
|
|
|
.main_factor = default_main_factor,
|
|
|
|
};
|
2021-04-26 19:03:04 +00:00
|
|
|
if (context.initialized) try output.getLayout(context);
|
2020-10-02 13:53:08 +00:00
|
|
|
}
|
|
|
|
|
2021-04-26 19:03:04 +00:00
|
|
|
fn getLayout(output: *Output, context: *Context) !void {
|
2021-04-14 22:28:39 +00:00
|
|
|
assert(context.initialized);
|
|
|
|
output.layout = try context.layout_manager.?.getLayout(output.wl_output, "rivertile");
|
2021-04-27 10:24:30 +00:00
|
|
|
output.layout.setListener(*Output, layoutListener, output);
|
2020-10-02 13:53:08 +00:00
|
|
|
}
|
|
|
|
|
2021-04-14 22:28:39 +00:00
|
|
|
fn deinit(output: *Output) void {
|
|
|
|
output.wl_output.release();
|
|
|
|
output.layout.destroy();
|
2020-10-02 13:53:08 +00:00
|
|
|
}
|
|
|
|
|
2021-04-26 19:03:28 +00:00
|
|
|
fn layoutListener(layout: *river.LayoutV2, event: river.LayoutV2.Event, output: *Output) void {
|
2020-10-02 13:53:08 +00:00
|
|
|
switch (event) {
|
2021-04-14 22:28:39 +00:00
|
|
|
.namespace_in_use => fatal("namespace 'rivertile' already in use.", .{}),
|
2020-10-02 13:53:08 +00:00
|
|
|
|
2021-04-26 19:03:28 +00:00
|
|
|
.set_int_value => |ev| {
|
|
|
|
if (mem.eql(u8, mem.span(ev.name), "main_count")) {
|
|
|
|
if (ev.value > 0) output.main_count = @intCast(u32, ev.value);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
.mod_int_value => |ev| {
|
|
|
|
if (mem.eql(u8, mem.span(ev.name), "main_count")) {
|
|
|
|
const result = @as(i33, output.main_count) + ev.delta;
|
|
|
|
if (result > 0) output.main_count = @intCast(u32, result);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
.set_fixed_value => |ev| {
|
|
|
|
if (mem.eql(u8, mem.span(ev.name), "main_factor")) {
|
|
|
|
output.main_factor = math.clamp(ev.value.toDouble(), 0.1, 0.9);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
.mod_fixed_value => |ev| {
|
|
|
|
if (mem.eql(u8, mem.span(ev.name), "main_factor")) {
|
|
|
|
const new_value = ev.delta.toDouble() + output.main_factor;
|
|
|
|
output.main_factor = math.clamp(new_value, 0.1, 0.9);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
.set_string_value => |ev| {
|
|
|
|
if (mem.eql(u8, mem.span(ev.name), "main_location")) {
|
|
|
|
if (std.meta.stringToEnum(Location, mem.span(ev.value))) |new_location| {
|
|
|
|
output.main_location = new_location;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2021-04-14 22:28:39 +00:00
|
|
|
.layout_demand => |ev| {
|
2021-04-26 19:03:28 +00:00
|
|
|
const secondary_count = if (ev.view_count > output.main_count)
|
|
|
|
ev.view_count - output.main_count
|
2020-10-02 13:53:08 +00:00
|
|
|
else
|
|
|
|
0;
|
|
|
|
|
2021-04-26 19:03:28 +00:00
|
|
|
const usable_width = switch (output.main_location) {
|
2021-04-26 21:41:30 +00:00
|
|
|
.left, .right => ev.usable_width - 2 * outer_padding,
|
|
|
|
.top, .bottom => ev.usable_height - 2 * outer_padding,
|
2021-04-14 22:28:39 +00:00
|
|
|
};
|
2021-04-26 19:03:28 +00:00
|
|
|
const usable_height = switch (output.main_location) {
|
2021-04-26 21:41:30 +00:00
|
|
|
.left, .right => ev.usable_height - 2 * outer_padding,
|
|
|
|
.top, .bottom => ev.usable_width - 2 * outer_padding,
|
2021-04-14 22:28:39 +00:00
|
|
|
};
|
2020-10-02 13:53:08 +00:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2021-04-26 19:03:28 +00:00
|
|
|
if (output.main_count > 0 and secondary_count > 0) {
|
|
|
|
main_width = @floatToInt(u32, output.main_factor * @intToFloat(f64, usable_width));
|
|
|
|
main_height = usable_height / output.main_count;
|
|
|
|
main_height_rem = usable_height % output.main_count;
|
2020-10-02 13:53:08 +00:00
|
|
|
|
|
|
|
secondary_width = usable_width - main_width;
|
|
|
|
secondary_height = usable_height / secondary_count;
|
|
|
|
secondary_height_rem = usable_height % secondary_count;
|
2021-04-26 19:03:28 +00:00
|
|
|
} else if (output.main_count > 0) {
|
2020-10-02 13:53:08 +00:00
|
|
|
main_width = usable_width;
|
2021-04-26 19:03:28 +00:00
|
|
|
main_height = usable_height / output.main_count;
|
|
|
|
main_height_rem = usable_height % output.main_count;
|
2020-10-02 13:53:08 +00:00
|
|
|
} 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;
|
|
|
|
}
|
2020-06-16 12:48:30 +00:00
|
|
|
|
2020-10-02 13:53:08 +00:00
|
|
|
var i: u32 = 0;
|
2021-04-14 22:28:39 +00:00
|
|
|
while (i < ev.view_count) : (i += 1) {
|
2020-10-02 13:53:08 +00:00
|
|
|
var x: i32 = undefined;
|
|
|
|
var y: i32 = undefined;
|
|
|
|
var width: u32 = undefined;
|
|
|
|
var height: u32 = undefined;
|
|
|
|
|
2021-04-26 19:03:28 +00:00
|
|
|
if (i < output.main_count) {
|
2020-10-02 13:53:08 +00:00
|
|
|
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);
|
2021-04-26 19:03:28 +00:00
|
|
|
y = @intCast(i32, (i - output.main_count) * secondary_height +
|
|
|
|
if (i > output.main_count) secondary_height_rem else 0);
|
2020-10-02 13:53:08 +00:00
|
|
|
width = secondary_width;
|
2021-04-26 19:03:28 +00:00
|
|
|
height = secondary_height + if (i == output.main_count) secondary_height_rem else 0;
|
2020-10-02 13:53:08 +00:00
|
|
|
}
|
|
|
|
|
2021-04-26 21:41:30 +00:00
|
|
|
x += @intCast(i32, view_padding);
|
|
|
|
y += @intCast(i32, view_padding);
|
|
|
|
width -= 2 * view_padding;
|
|
|
|
height -= 2 * view_padding;
|
2020-10-02 13:53:08 +00:00
|
|
|
|
2021-04-26 19:03:28 +00:00
|
|
|
switch (output.main_location) {
|
2020-10-02 13:53:08 +00:00
|
|
|
.left => layout.pushViewDimensions(
|
2021-04-14 22:28:39 +00:00
|
|
|
ev.serial,
|
2021-04-26 21:41:30 +00:00
|
|
|
x + @intCast(i32, outer_padding),
|
|
|
|
y + @intCast(i32, outer_padding),
|
2020-10-02 13:53:08 +00:00
|
|
|
width,
|
|
|
|
height,
|
|
|
|
),
|
|
|
|
.right => layout.pushViewDimensions(
|
2021-04-14 22:28:39 +00:00
|
|
|
ev.serial,
|
2021-04-26 21:41:30 +00:00
|
|
|
@intCast(i32, usable_width - width) - x + @intCast(i32, outer_padding),
|
|
|
|
y + @intCast(i32, outer_padding),
|
2020-10-02 13:53:08 +00:00
|
|
|
width,
|
|
|
|
height,
|
|
|
|
),
|
|
|
|
.top => layout.pushViewDimensions(
|
2021-04-14 22:28:39 +00:00
|
|
|
ev.serial,
|
2021-04-26 21:41:30 +00:00
|
|
|
y + @intCast(i32, outer_padding),
|
|
|
|
x + @intCast(i32, outer_padding),
|
2020-10-02 13:53:08 +00:00
|
|
|
height,
|
|
|
|
width,
|
|
|
|
),
|
|
|
|
.bottom => layout.pushViewDimensions(
|
2021-04-14 22:28:39 +00:00
|
|
|
ev.serial,
|
2021-04-26 21:41:30 +00:00
|
|
|
y + @intCast(i32, outer_padding),
|
|
|
|
@intCast(i32, usable_width - width) - x + @intCast(i32, outer_padding),
|
2020-10-02 13:53:08 +00:00
|
|
|
height,
|
|
|
|
width,
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-14 22:28:39 +00:00
|
|
|
layout.commit(ev.serial);
|
2020-10-02 13:53:08 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
.advertise_view => {},
|
|
|
|
.advertise_done => {},
|
2020-06-16 12:48:30 +00:00
|
|
|
}
|
|
|
|
}
|
2020-10-02 13:53:08 +00:00
|
|
|
};
|
2020-06-16 12:48:30 +00:00
|
|
|
|
2020-10-02 13:53:08 +00:00
|
|
|
pub fn main() !void {
|
2021-04-26 21:41:30 +00:00
|
|
|
// https://github.com/ziglang/zig/issues/7807
|
|
|
|
const argv: [][*:0]const u8 = std.os.argv;
|
|
|
|
const args = Args(0, &[_]FlagDef{
|
2021-05-01 10:49:49 +00:00
|
|
|
.{ .name = "-h", .kind = .boolean },
|
|
|
|
.{ .name = "--help", .kind = .boolean },
|
2021-04-26 21:41:30 +00:00
|
|
|
.{ .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..]);
|
|
|
|
|
2021-05-01 10:49:49 +00:00
|
|
|
if (args.boolFlag("-h") or args.boolFlag("--help")) {
|
2021-05-01 10:54:57 +00:00
|
|
|
try std.io.getStdOut().writeAll(usage);
|
2021-05-01 10:49:49 +00:00
|
|
|
std.os.exit(0);
|
|
|
|
}
|
2021-04-26 21:41:30 +00:00
|
|
|
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});
|
|
|
|
}
|
|
|
|
|
2020-10-02 13:53:08 +00:00
|
|
|
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 = .{};
|
2020-06-16 15:06:24 +00:00
|
|
|
|
2020-10-02 13:53:08 +00:00
|
|
|
const registry = try display.getRegistry();
|
2021-04-27 10:24:30 +00:00
|
|
|
registry.setListener(*Context, registryListener, &context);
|
2020-10-02 13:53:08 +00:00
|
|
|
_ = try display.roundtrip();
|
2020-06-16 15:06:24 +00:00
|
|
|
|
2020-10-02 13:53:08 +00:00
|
|
|
if (context.layout_manager == null) {
|
2021-05-05 12:48:47 +00:00
|
|
|
fatal("wayland compositor does not support river-layout-v2.\n", .{});
|
2020-10-02 13:53:08 +00:00
|
|
|
}
|
2021-04-14 22:28:39 +00:00
|
|
|
|
|
|
|
context.initialized = true;
|
2020-10-02 13:53:08 +00:00
|
|
|
|
2021-04-14 22:28:39 +00:00
|
|
|
var it = context.outputs.first;
|
|
|
|
while (it) |node| : (it = node.next) {
|
|
|
|
const output = &node.data;
|
2021-04-26 19:03:04 +00:00
|
|
|
try output.getLayout(&context);
|
2020-10-02 13:53:08 +00:00
|
|
|
}
|
2021-04-14 22:28:39 +00:00
|
|
|
|
|
|
|
while (true) _ = try display.dispatch();
|
2020-10-02 13:53:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *Context) void {
|
|
|
|
switch (event) {
|
|
|
|
.global => |global| {
|
2021-04-26 19:03:28 +00:00
|
|
|
if (std.cstr.cmp(global.interface, river.LayoutManagerV2.getInterface().name) == 0) {
|
|
|
|
context.layout_manager = registry.bind(global.name, river.LayoutManagerV2, 1) catch return;
|
2020-10-02 13:53:08 +00:00
|
|
|
} else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) {
|
2021-04-14 22:28:39 +00:00
|
|
|
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;
|
|
|
|
}
|
2020-10-02 13:53:08 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
2020-06-16 15:06:24 +00:00
|
|
|
}
|
2021-04-14 22:28:39 +00:00
|
|
|
|
2021-04-26 21:41:30 +00:00
|
|
|
pub fn fatal(comptime format: []const u8, args: anytype) noreturn {
|
|
|
|
const stderr = std.io.getStdErr().writer();
|
|
|
|
stderr.print("err: " ++ format ++ "\n", args) catch {};
|
2021-04-14 22:28:39 +00:00
|
|
|
std.os.exit(1);
|
|
|
|
}
|