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,