diff --git a/protocol/river-window-management-unstable-v1.xml b/protocol/river-window-management-unstable-v1.xml
index 71b37b6..816e49d 100644
--- a/protocol/river-window-management-unstable-v1.xml
+++ b/protocol/river-window-management-unstable-v1.xml
@@ -29,12 +29,10 @@
+
-
-
-
-
@@ -56,4 +54,27 @@
summary="the current tags of each view on the output"/>
+
+
+
+ Exactly one of the success or failure events will be sent.
+
+
+
+
+ Send when the command has been successfully received and validated by
+ the server and will be carried out.
+
+
+
+
+
+ Sent when the command could not be carried out. This could be due to
+ sending a non-existent command, no command, not enough arguments, too
+ many arguments, invalid arguments, etc.
+
+
+
+
diff --git a/src/Command.zig b/src/Command.zig
index 58c58d3..0632e5c 100644
--- a/src/Command.zig
+++ b/src/Command.zig
@@ -117,10 +117,21 @@ const str_to_read_fn = [_]Definition{
};
// zig fmt: on
+pub const Error = error{
+ NoCommand,
+ UnknownCommand,
+ NotEnoughArguments,
+ TooManyArguments,
+ Overflow,
+ InvalidCharacter,
+ InvalidDirection,
+ OutOfMemory,
+};
+
impl: ImplFn,
arg: Arg,
-pub fn init(args: []const []const u8, allocator: *std.mem.Allocator) !Self {
+pub fn init(args: []const []const u8, allocator: *std.mem.Allocator) Error!Self {
if (args.len == 0) return error.NoCommand;
const name = args[0];
diff --git a/src/WindowManager.zig b/src/WindowManager.zig
index c95aa23..02bf90a 100644
--- a/src/WindowManager.zig
+++ b/src/WindowManager.zig
@@ -74,7 +74,12 @@ fn resourceDestroy(wl_resource: ?*c.wl_resource) callconv(.C) void {
// TODO
}
-fn runCommand(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource, wl_array: ?*c.wl_array) callconv(.C) void {
+fn runCommand(
+ wl_client: ?*c.wl_client,
+ wl_resource: ?*c.wl_resource,
+ wl_array: ?*c.wl_array,
+ callback_id: u32,
+) callconv(.C) void {
const self = @ptrCast(*Self, @alignCast(@alignOf(*Self), c.wl_resource_get_user_data(wl_resource)));
const allocator = self.server.allocator;
@@ -89,11 +94,34 @@ fn runCommand(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource, wl_array:
i += slice.len + 1;
}
- for (args.items) |x| {
- std.debug.warn("{}\n", .{x});
- }
+ const callback_resource = c.wl_resource_create(
+ wl_client,
+ &c.zriver_command_callback_v1_interface,
+ protocol_version,
+ callback_id,
+ ) orelse {
+ c.wl_client_post_no_memory(wl_client);
+ return;
+ };
- // TODO: send the error event on failure instead of crashing
- const command = Command.init(args.items, allocator) catch unreachable;
+ c.wl_resource_set_implementation(callback_resource, null, null, null);
+
+ const command = Command.init(args.items, allocator) catch |err| {
+ c.zriver_command_callback_v1_send_failure(
+ callback_resource,
+ switch (err) {
+ Command.Error.NoCommand => "no command given",
+ Command.Error.UnknownCommand => "unknown command",
+ Command.Error.NotEnoughArguments => "not enough arguments",
+ Command.Error.TooManyArguments => "too many arguments",
+ Command.Error.Overflow => "value out of bounds",
+ Command.Error.InvalidCharacter => "invalid character in argument",
+ Command.Error.InvalidDirection => "invalid direction. Must be 'next' or 'previous'",
+ Command.Error.OutOfMemory => unreachable,
+ },
+ );
+ return;
+ };
+ c.zriver_command_callback_v1_send_success(callback_resource);
command.run(self.server.input_manager.default_seat);
}
diff --git a/src/riverctl.zig b/src/riverctl.zig
index 5f6dd54..d5d45af 100644
--- a/src/riverctl.zig
+++ b/src/riverctl.zig
@@ -27,14 +27,20 @@ const wl_registry_listener = c.wl_registry_listener{
.global_remove = handleGlobalRemove,
};
+const command_callback_listener = c.zriver_command_callback_v1_listener{
+ .success = handleSuccess,
+ .failure = handleFailure,
+};
+
var river_window_manager: ?*c.zriver_window_manager_v1 = null;
pub fn main() !void {
const wl_display = c.wl_display_connect(null) orelse return error.CantConnectToDisplay;
const wl_registry = c.wl_display_get_registry(wl_display);
- _ = c.wl_registry_add_listener(wl_registry, &wl_registry_listener, null);
- if (c.wl_display_roundtrip(wl_display) == -1) return error.RoundtripFailed;
+ if (c.wl_registry_add_listener(wl_registry, &wl_registry_listener, null) < 0)
+ return error.FailedToAddListener;
+ if (c.wl_display_roundtrip(wl_display) < 0) return error.RoundtripFailed;
const wm = river_window_manager orelse return error.RiverWMNotAdvertised;
@@ -51,8 +57,15 @@ pub fn main() !void {
ptr[arg.len] = 0;
}
- c.zriver_window_manager_v1_run_command(wm, &command);
- if (c.wl_display_roundtrip(wl_display) == -1) return error.RoundtripFailed;
+ const command_callback = c.zriver_window_manager_v1_run_command(wm, &command);
+ if (c.zriver_command_callback_v1_add_listener(
+ command_callback,
+ &command_callback_listener,
+ null,
+ ) < 0) return error.FailedToAddListener;
+
+ // Loop until our callback is called and we exit.
+ while (true) if (c.wl_display_dispatch(wl_display) < 0) return error.DispatchFailed;
}
fn handleGlobal(
@@ -77,3 +90,20 @@ fn handleGlobal(
/// Ignore the event
fn handleGlobalRemove(data: ?*c_void, wl_registry: ?*c.wl_registry, name: u32) callconv(.C) void {}
+
+/// On success we simply exit with a clean exit code
+fn handleSuccess(data: ?*c_void, callback: ?*c.zriver_command_callback_v1) callconv(.C) void {
+ std.os.exit(0);
+}
+
+/// Print the failure message and exit non-zero
+fn handleFailure(
+ data: ?*c_void,
+ callback: ?*c.zriver_command_callback_v1,
+ failure_message: ?[*:0]const u8,
+) callconv(.C) void {
+ if (failure_message) |message| {
+ std.debug.warn("Error: {}\n", .{failure_message});
+ }
+ std.os.exit(1);
+}