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); +}