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

commit489e0b6be40c5731071532025d8d2b8cf5fab800
parentb1014ed657
authorAlexander Courtis <[email protected]>
date2022-09-13 12:05
map-pointer: allow running arbitrary commands

 completions/fish/riverctl.fish |  2 +-
 completions/zsh/_riverctl      |  2 +-
 doc/riverctl.1.scd             |  8 ++++---
 example/init                   |  3 +++
 river/Cursor.zig               |  4 ++++
 river/PointerMapping.zig       | 53 +++++++++++++++++++++++++++++++++++++++++-
 river/Seat.zig                 |  9 +++++--
 river/command/map.zig          | 33 +++++++++++++-------------
 8 files changed, 89 insertions(+), 25 deletions(-)

diff --git a/completions/fish/riverctl.fish b/completions/fish/riverctl.fish
index aabf99c..f24f32a 100644
--- a/completions/fish/riverctl.fish
+++ b/completions/fish/riverctl.fish
@@ -44,7 +44,7 @@ complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'send-to-previous
 complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'declare-mode'           -d 'Create a new mode'
 complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'enter-mode'             -d 'Switch to given mode if it exists'
 complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'map'                    -d 'Run command when key is pressed while modifiers are held down and in the specified mode'
-complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'map-pointer'            -d 'Move or resize views when button and modifers are held down while in the specified mode'
+complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'map-pointer'            -d 'Move or resize views or run command when button and modifers are held down while in the specified mode'
 complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'map-switch '            -d 'Run command when river receives a switch event in the specified mode'
 complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'unmap'                  -d 'Remove the mapping defined by the arguments'
 complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'unmap-pointer'          -d 'Remove the pointer mapping defined by the arguments'
diff --git a/completions/zsh/_riverctl b/completions/zsh/_riverctl
index f5cd125..f303e5e 100644
--- a/completions/zsh/_riverctl
+++ b/completions/zsh/_riverctl
@@ -38,7 +38,7 @@ _riverctl_subcommands()
         'declare-mode:Create a new mode'
         'enter-mode:Switch to given mode if it exists'
         'map:Run command when key is pressed while modifiers are held down and in the specified mode'
-        'map-pointer:Move or resize views when button and modifiers are held down while in the specified mode'
+        'map-pointer:Move or resize views or run command when button and modifiers are held down while in the specified mode'
         'map-switch:Run command when river receives a switch event in the specified mode'
         'unmap:Remove the mapping defined by the arguments'
         'unmap-pointer:Remove the pointer mapping defined by the arguments'
diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd
index d936308..b75fcae 100644
--- a/doc/riverctl.1.scd
+++ b/doc/riverctl.1.scd
@@ -212,9 +212,10 @@ A complete list may be found in _/usr/include/linux/input-event-codes.h_
 	- _key_: an XKB keysym name as described above
 	- _command_: any command that may be run with riverctl
 
-*map-pointer* _mode_ _modifiers_ _button_ _action_
-	Move or resize views when _button_ and _modifiers_ are held down
-	while in the specified _mode_.
+*map-pointer* _mode_ _modifiers_ _button_ _action_|_command_
+	Move or resize views or run _command_ when _button_ and _modifiers_ are held
+	down while in the specified _mode_. The view under the cursor will be
+	focused.
 
 	- _mode_: name of the mode for which to create the mapping
 	- _modifiers_: one or more of the modifiers listed above, separated
@@ -223,6 +224,7 @@ A complete list may be found in _/usr/include/linux/input-event-codes.h_
 	- _action_: one of the following values:
 		- move-view
 		- resize-view
+	- _command_: any command that may be run with riverctl
 
 *map-switch* _mode_ *lid*|*tablet* _state_ _command_
 	Run _command_ when river receives a certain switch event.
diff --git a/example/init b/example/init
index b95a180..08b7cb1 100755
--- a/example/init
+++ b/example/init
@@ -71,6 +71,9 @@ riverctl map-pointer normal Super BTN_LEFT move-view
 # Super + Right Mouse Button to resize views
 riverctl map-pointer normal Super BTN_RIGHT resize-view
 
+# Super + Middle Mouse Button to toggle float
+riverctl map-pointer normal Super BTN_MIDDLE toggle-float
+
 for i in $(seq 1 9)
 do
     tags=$((1 << ($i - 1)))
diff --git a/river/Cursor.zig b/river/Cursor.zig
index b09dbff..e99f8e3 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -528,6 +528,10 @@ fn handlePointerMapping(self: *Self, event: *wlr.Pointer.event.Button, view: *Vi
             switch (mapping.action) {
                 .move => if (!fullscreen) self.enterMode(.move, view),
                 .resize => if (!fullscreen) self.enterMode(.resize, view),
+                .command => |args| {
+                    self.seat.focus(view);
+                    self.seat.runCommand(args);
+                },
             }
             break true;
         }
diff --git a/river/PointerMapping.zig b/river/PointerMapping.zig
index 6a796bd..bae2cbd 100644
--- a/river/PointerMapping.zig
+++ b/river/PointerMapping.zig
@@ -14,13 +14,64 @@
 // You should have received a copy of the GNU General Public License
 // along with this program. If not, see <https://www.gnu.org/licenses/>.
 
+const Self = @This();
+
+const std = @import("std");
 const wlr = @import("wlroots");
 
-pub const Action = enum {
+const util = @import("util.zig");
+
+pub const ActionType = enum {
     move,
     resize,
+    command,
+};
+
+pub const Action = union(ActionType) {
+    move: void,
+    resize: void,
+    command: []const [:0]const u8,
 };
 
 event_code: u32,
 modifiers: wlr.Keyboard.ModifierMask,
 action: Action,
+arena: std.heap.ArenaAllocator,
+
+pub fn init(
+    event_code: u32,
+    modifiers: wlr.Keyboard.ModifierMask,
+    action_type: ActionType,
+    command_args: []const [:0]const u8,
+) !Self {
+    var arena: std.heap.ArenaAllocator = std.heap.ArenaAllocator.init(util.gpa);
+    errdefer arena.deinit();
+
+    const action: Action = switch (action_type) {
+        ActionType.move => Action.move,
+        ActionType.resize => Action.resize,
+        ActionType.command => blk: {
+            const allocator: std.mem.Allocator = arena.allocator();
+
+            var owned_args = try std.ArrayListUnmanaged([:0]const u8).initCapacity(allocator, command_args.len);
+
+            for (command_args) |arg| {
+                const owned = try allocator.dupeZ(u8, arg);
+                owned_args.appendAssumeCapacity(owned);
+            }
+
+            break :blk Action{ .command = owned_args.toOwnedSlice(allocator) };
+        },
+    };
+
+    return Self{
+        .event_code = event_code,
+        .modifiers = modifiers,
+        .action = action,
+        .arena = arena,
+    };
+}
+
+pub fn deinit(self: *Self) void {
+    self.arena.deinit();
+}
diff --git a/river/Seat.zig b/river/Seat.zig
index 1a4b7e6..892ba14 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -434,7 +434,7 @@ pub fn handleSwitchMapping(
     }
 }
 
-fn runCommand(self: *Self, args: []const [:0]const u8) void {
+pub fn runCommand(self: *Self, args: []const [:0]const u8) void {
     var out: ?[]const u8 = null;
     defer if (out) |s| util.gpa.free(s);
     command.run(self, args, &out) catch |err| {
@@ -442,7 +442,12 @@ fn runCommand(self: *Self, args: []const [:0]const u8) void {
             command.Error.Other => out.?,
             else => command.errToMsg(err),
         };
-        std.log.scoped(.command).err("{s}: {s}", .{ args[0], failure_message });
+
+        if (args.len == 0) {
+            std.log.scoped(.command).err("{s}", .{failure_message});
+        } else {
+            std.log.scoped(.command).err("{s}: {s}", .{ args[0], failure_message });
+        }
         return;
     };
     if (out) |s| {
diff --git a/river/command/map.zig b/river/command/map.zig
index 4a33503..f321531 100644
--- a/river/command/map.zig
+++ b/river/command/map.zig
@@ -130,33 +130,31 @@ pub fn mapPointer(
     out: *?[]const u8,
 ) Error!void {
     if (args.len < 5) return Error.NotEnoughArguments;
-    if (args.len > 5) return Error.TooManyArguments;
 
     const mode_id = try modeNameToId(args[1], out);
     const modifiers = try parseModifiers(args[2], out);
     const event_code = try parseEventCode(args[3], out);
 
     const action = if (mem.eql(u8, args[4], "move-view"))
-        PointerMapping.Action.move
+        PointerMapping.ActionType.move
     else if (mem.eql(u8, args[4], "resize-view"))
-        PointerMapping.Action.resize
-    else {
-        out.* = try fmt.allocPrint(
-            util.gpa,
-            "invalid pointer action {s}, must be move-view or resize-view",
-            .{args[4]},
-        );
-        return Error.Other;
-    };
+        PointerMapping.ActionType.resize
+    else
+        PointerMapping.ActionType.command;
 
-    const new = PointerMapping{
-        .event_code = event_code,
-        .modifiers = modifiers,
-        .action = action,
-    };
+    if (action != PointerMapping.ActionType.command and args.len > 5) return Error.TooManyArguments;
+
+    var new = try PointerMapping.init(
+        event_code,
+        modifiers,
+        action,
+        args[4..],
+    );
+    errdefer new.deinit();
 
     const mode_pointer_mappings = &server.config.modes.items[mode_id].pointer_mappings;
     if (pointerMappingExists(mode_pointer_mappings, modifiers, event_code)) |current| {
+        mode_pointer_mappings.items[current].deinit();
         mode_pointer_mappings.items[current] = new;
     } else {
         try mode_pointer_mappings.append(util.gpa, new);
@@ -428,5 +426,6 @@ pub fn unmapPointer(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Err
     const mode_pointer_mappings = &server.config.modes.items[mode_id].pointer_mappings;
     const mapping_idx = pointerMappingExists(mode_pointer_mappings, modifiers, event_code) orelse return;
 
-    _ = mode_pointer_mappings.swapRemove(mapping_idx);
+    var mapping = mode_pointer_mappings.swapRemove(mapping_idx);
+    mapping.deinit();
 }