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

commit15d67269e9a3c1281ef3654280893f4aed44e32e
parentc43937a78c
authorIsaac Freund <[email protected]>
date2020-05-24 15:18
Add callback to command request for error handling

 protocol/river-window-management-unstable-v1.xml | 29 ++++++++++++++---
 src/Command.zig                                  | 13 +++++++-
 src/WindowManager.zig                            | 40 ++++++++++++++++++++----
 src/riverctl.zig                                 | 38 +++++++++++++++++++---
 4 files changed, 105 insertions(+), 15 deletions(-)

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 @@
       </description>
       <arg name="command" type="array" summary="the command to run as a series
         of null-terminated strings"/>
+      <arg name="callback" type="new_id" interface="zriver_command_callback_v1"
+        summary="callback object to recieve success/error events"/>
     </request>
 
-    <enum name="error">
-      <entry name="invalid_command" value="0" summary="the command is invalid"/>
-    </enum>
-
     <event name="focus">
       <description summary="sent when a view gains focus">
       </description>
@@ -56,4 +54,27 @@
         summary="the current tags of each view on the output"/>
     </event>
   </interface>
+
+  <interface name="zriver_command_callback_v1" version="1">
+    <description summary="callback object">
+      Exactly one of the success or failure events will be sent.
+    </description>
+
+    <event name="success">
+      <description summary="command successful">
+        Send when the command has been successfully received and validated by
+        the server and will be carried out.
+      </description>
+    </event>
+
+    <event name="failure">
+      <description summary="command failed">
+        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.
+      </description>
+      <arg name="failure_message" type="string"
+        summary="a message explaining why failure occurred"/>
+    </event>
+  </interface>
 </protocol>
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);
+}