// This file is part of river, a dynamic tiling wayland compositor. // // Copyright 2020-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, version 3. // // 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 io = std.io; const os = std.os; const assert = std.debug.assert; const builtin = @import("builtin"); const wayland = @import("wayland"); const wl = wayland.client.wl; const zriver = wayland.client.zriver; const flags = @import("flags"); const usage = \\usage: riverctl [options] \\ \\ -h Print this help message and exit. \\ -version Print the version number and exit. \\ \\Complete documentation of the recognized commands may be found in \\the riverctl(1) man page. \\ ; const gpa = std.heap.c_allocator; pub const Globals = struct { control: ?*zriver.ControlV1 = null, seat: ?*wl.Seat = null, }; pub fn main() !void { _main() catch |err| { if (builtin.mode == .Debug) return err; switch (err) { error.RiverControlNotAdvertised => fatal( \\The Wayland server does not support river-control-unstable-v1. \\Do your versions of river and riverctl match? , .{}), error.SeatNotAdverstised => fatal( \\The Wayland server did not advertise any seat. , .{}), else => return err, } }; } fn _main() !void { const result = flags.parser([*:0]const u8, &.{ .{ .name = "h", .kind = .boolean }, .{ .name = "version", .kind = .boolean }, }).parse(os.argv[1..]) catch { try io.getStdErr().writeAll(usage); os.exit(1); }; if (result.flags.h) { try io.getStdOut().writeAll(usage); os.exit(0); } if (result.flags.version) { try io.getStdOut().writeAll(@import("build_options").version ++ "\n"); os.exit(0); } const display = try wl.Display.connect(null); const registry = try display.getRegistry(); var globals = Globals{}; registry.setListener(*Globals, registryListener, &globals); if (display.roundtrip() != .SUCCESS) fatal("initial roundtrip failed", .{}); const control = globals.control orelse return error.RiverControlNotAdvertised; const seat = globals.seat orelse return error.SeatNotAdverstised; for (result.args) |arg| control.addArgument(arg); const callback = try control.runCommand(seat); callback.setListener(?*anyopaque, callbackListener, null); // Loop until our callback is called and we exit. while (true) { if (display.dispatch() != .SUCCESS) fatal("failed to dispatch wayland events", .{}); } } fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *Globals) void { switch (event) { .global => |global| { 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"); } }, .global_remove => {}, } } fn callbackListener(_: *zriver.CommandCallbackV1, event: zriver.CommandCallbackV1.Event, _: ?*anyopaque) void { switch (event) { .success => |success| { if (mem.len(success.output) > 0) { const stdout = io.getStdOut().writer(); stdout.print("{s}\n", .{success.output}) catch @panic("failed to write to stdout"); } os.exit(0); }, .failure => |failure| { // A small hack to provide usage text when river reports an unknown command. if (std.cstr.cmp(failure.failure_message, "unknown command") == 0) { std.log.err("unknown command", .{}); io.getStdErr().writeAll(usage) catch {}; os.exit(1); } fatal("{s}", .{failure.failure_message}); }, } } fn fatal(comptime format: []const u8, args: anytype) noreturn { std.log.err(format, args); os.exit(1); }