git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit9e84d1ccf94cb95e0bb56e2fc603ef5fa2db65c4
parent9a9f597862
authorIsaac Freund <[email protected]>
date2021-02-07 19:11
riverctl: add -focused-output for option commands

This is more convenient for interactive usage and makes using the same
bindings across multiple outputs easy.

 doc/riverctl.1.scd   | 11 +++++----
 riverctl/args.zig    |  6 ++++-
 riverctl/main.zig    |  7 +++++-
 riverctl/options.zig | 64 +++++++++++++++++++++++++++++++++++++++++++++++-----
 4 files changed, 75 insertions(+), 13 deletions(-)

diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd
index 3424ea4..cbb3896 100644
--- a/doc/riverctl.1.scd
+++ b/doc/riverctl.1.scd
@@ -284,19 +284,20 @@ A complete list may be found in _/usr/include/linux/input-event-codes.h_
 
 River has various options that are saved in a typed key-value store. It also
 allows users to store arbitrary custom options in the store. Options are
-scoped either globally or per-output if the -output flag is passed with the
-name of the output as obtained from the xdg-output protocol.
+scoped either globally or per-output if the *-output* flag is passed with the
+name of the output as obtained from the xdg-output protocol. Alternatively,
+the currently focused output may be targeted with the *-focused-output* flag.
 
-*declare-option* [-output _output_name_] _name_ _type_ _value_
+*declare-option* [*-output* _output_name_|*-focused-output*] _name_ _type_ _value_
 	Declare a new option with the given _type_ and inital _value_. If
 	the option already exists with the given _type_, it is still set
 	to _value_. If the option already exists with a different type,
 	nothing happens.
 
-*get-option* [-output _output_name_] _name_
+*get-option* [*-output* _output_name_|*-focused-output*] _name_
 	Print the current value of the given option to stdout.
 
-*set-option* [-output _output_name_] _name_ _value_
+*set-option* [*-output* _output_name_|*-focused-output*] _name_ _value_
 	Set the value of the specified option to _value_.
 
 River declares certain default options for all outputs.
diff --git a/riverctl/args.zig b/riverctl/args.zig
index 3240a97..29ca0f9 100644
--- a/riverctl/args.zig
+++ b/riverctl/args.zig
@@ -60,6 +60,7 @@ pub fn Args(comptime num_positionals: comptime_int, comptime flag_defs: []const
             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) {
@@ -73,9 +74,12 @@ pub fn Args(comptime num_positionals: comptime_int, comptime flag_defs: []const
                                         "' requires an argument but none was provided!", .{});
                             },
                         }
-                        continue :outer;
+                        // 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.printErrorExit(
diff --git a/riverctl/main.zig b/riverctl/main.zig
index 4c3f1c8..91e24c4 100644
--- a/riverctl/main.zig
+++ b/riverctl/main.zig
@@ -18,6 +18,7 @@
 const std = @import("std");
 const mem = std.mem;
 const os = std.os;
+const assert = std.debug.assert;
 
 const wayland = @import("wayland");
 const wl = wayland.client.wl;
@@ -36,6 +37,7 @@ pub const Output = struct {
 pub const Globals = struct {
     control: ?*zriver.ControlV1 = null,
     options_manager: ?*zriver.OptionsManagerV1 = null,
+    status_manager: ?*zriver.StatusManagerV1 = null,
     seat: ?*wl.Seat = null,
     output_manager: ?*zxdg.OutputManagerV1 = null,
     outputs: std.ArrayList(Output) = std.ArrayList(Output).init(gpa),
@@ -77,12 +79,15 @@ pub fn main() !void {
 fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *Globals) void {
     switch (event) {
         .global => |global| {
-            if (globals.seat == null and std.cstr.cmp(global.interface, wl.Seat.getInterface().name) == 0) {
+            if (std.cstr.cmp(global.interface, wl.Seat.getInterface().name) == 0) {
+                assert(globals.seat == null); // TODO: support multiple seats
                 globals.seat = registry.bind(global.name, wl.Seat, 1) catch @panic("out of memory");
             } else if (std.cstr.cmp(global.interface, zriver.ControlV1.getInterface().name) == 0) {
                 globals.control = registry.bind(global.name, zriver.ControlV1, 1) catch @panic("out of memory");
             } else if (std.cstr.cmp(global.interface, zriver.OptionsManagerV1.getInterface().name) == 0) {
                 globals.options_manager = registry.bind(global.name, zriver.OptionsManagerV1, 1) catch @panic("out of memory");
+            } else if (std.cstr.cmp(global.interface, zriver.StatusManagerV1.getInterface().name) == 0) {
+                globals.status_manager = registry.bind(global.name, zriver.StatusManagerV1, 1) catch @panic("out of memory");
             } else if (std.cstr.cmp(global.interface, zxdg.OutputManagerV1.getInterface().name) == 0 and global.version >= 2) {
                 globals.output_manager = registry.bind(global.name, zxdg.OutputManagerV1, 2) catch @panic("out of memory");
             } else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) {
diff --git a/riverctl/options.zig b/riverctl/options.zig
index 41e3223..18df45b 100644
--- a/riverctl/options.zig
+++ b/riverctl/options.zig
@@ -49,12 +49,22 @@ const Context = struct {
 pub fn declareOption(display: *wl.Display, globals: *Globals) !void {
     // https://github.com/ziglang/zig/issues/7807
     const argv: [][*:0]const u8 = os.argv;
-    const args = Args(3, &[_]FlagDef{.{ .name = "-output", .kind = .arg }}).parse(argv[2..]);
+    const args = Args(3, &[_]FlagDef{
+        .{ .name = "-output", .kind = .arg },
+        .{ .name = "-focused-output", .kind = .boolean },
+    }).parse(argv[2..]);
+
     const key = args.positionals[0];
     const value_type = std.meta.stringToEnum(ValueType, mem.span(args.positionals[1])) orelse
         root.printErrorExit("'{}' is not a valid type, must be int, uint, fixed, or string", .{args.positionals[1]});
     const raw_value = args.positionals[2];
-    const output = if (args.argFlag("-output")) |o| try parseOutputName(display, globals, o) else null;
+
+    const output = if (args.argFlag("-output")) |o|
+        try parseOutputName(display, globals, o)
+    else if (args.boolFlag("-focused-output"))
+        try getFocusedOutput(display, globals)
+    else
+        null;
 
     const options_manager = globals.options_manager orelse return error.RiverOptionsManagerNotAdvertised;
     const handle = try options_manager.getOptionHandle(key, if (output) |o| o.wl_output else null);
@@ -86,12 +96,23 @@ fn setFixedValueRaw(handle: *zriver.OptionHandleV1, raw_value: [*:0]const u8) vo
 pub fn getOption(display: *wl.Display, globals: *Globals) !void {
     // https://github.com/ziglang/zig/issues/7807
     const argv: [][*:0]const u8 = os.argv;
-    const args = Args(1, &[_]FlagDef{.{ .name = "-output", .kind = .arg }}).parse(argv[2..]);
+    const args = Args(1, &[_]FlagDef{
+        .{ .name = "-output", .kind = .arg },
+        .{ .name = "-focused-output", .kind = .boolean },
+    }).parse(argv[2..]);
+
+    const output = if (args.argFlag("-output")) |o|
+        try parseOutputName(display, globals, o)
+    else if (args.boolFlag("-focused-output"))
+        try getFocusedOutput(display, globals)
+    else
+        null;
+
     const ctx = Context{
         .display = display,
         .key = args.positionals[0],
         .raw_value = undefined,
-        .output = if (args.argFlag("-output")) |o| try parseOutputName(display, globals, o) else null,
+        .output = output,
     };
 
     const options_manager = globals.options_manager orelse return error.RiverOptionsManagerNotAdvertised;
@@ -105,12 +126,23 @@ pub fn getOption(display: *wl.Display, globals: *Globals) !void {
 pub fn setOption(display: *wl.Display, globals: *Globals) !void {
     // https://github.com/ziglang/zig/issues/7807
     const argv: [][*:0]const u8 = os.argv;
-    const args = Args(2, &[_]FlagDef{.{ .name = "-output", .kind = .arg }}).parse(argv[2..]);
+    const args = Args(2, &[_]FlagDef{
+        .{ .name = "-output", .kind = .arg },
+        .{ .name = "-focused-output", .kind = .boolean },
+    }).parse(argv[2..]);
+
+    const output = if (args.argFlag("-output")) |o|
+        try parseOutputName(display, globals, o)
+    else if (args.boolFlag("-focused-output"))
+        try getFocusedOutput(display, globals)
+    else
+        null;
+
     const ctx = Context{
         .display = display,
         .key = args.positionals[0],
         .raw_value = args.positionals[1],
-        .output = if (args.argFlag("-output")) |o| try parseOutputName(display, globals, o) else null,
+        .output = output,
     };
 
     const options_manager = globals.options_manager orelse return error.RiverOptionsManagerNotAdvertised;
@@ -142,6 +174,26 @@ fn xdgOutputListener(xdg_output: *zxdg.OutputV1, event: zxdg.OutputV1.Event, out
     }
 }
 
+fn getFocusedOutput(display: *wl.Display, globals: *Globals) !*Output {
+    const status_manager = globals.status_manager orelse return error.RiverStatusManagerNotAdvertised;
+    const seat = globals.seat orelse return error.SeatNotAdverstised;
+    const seat_status = try status_manager.getRiverSeatStatus(seat);
+    var result: ?*wl.Output = null;
+    seat_status.setListener(*?*wl.Output, seatStatusListener, &result) catch unreachable;
+    _ = try display.roundtrip();
+    const wl_output = if (result) |output| output else return error.NoOutputFocused;
+    for (globals.outputs.items) |*output| {
+        if (output.wl_output == wl_output) return output;
+    } else unreachable;
+}
+
+fn seatStatusListener(seat_status: *zriver.SeatStatusV1, event: zriver.SeatStatusV1.Event, result: *?*wl.Output) void {
+    switch (event) {
+        .focused_output => |ev| result.* = ev.output,
+        .unfocused_output, .focused_view => {},
+    }
+}
+
 fn getOptionListener(
     handle: *zriver.OptionHandleV1,
     event: zriver.OptionHandleV1.Event,