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

commitec12628fc80d2be73eabb3d11ba32a7b87ec4ffb
parent86a1fa0070
authorIsaac Freund <[email protected]>
date2021-07-16 00:00
river: make spawn command take only one argument

Currently the spawn command takes any number of arguments and naively
joins them together with spaces before passing them as the single
argument of `/bin/sh -c`. This however produces unexpected results as
soon as shell quoting gets involved in the arguments passed to spawn.
For example, running

riverctl spawn foo "bar baz"

will execute `/bin/sh -c "foo bar baz"`, unexpectedly splitting bar and
baz into separate arguments. To avoid this confusion, make the spawn
command take only a single argument, forcing the user to quote properly
to spawn multi-argument commands.

 doc/riverctl.1.scd      | 10 +++++-----
 example/init            | 20 ++++++++++----------
 river/command/spawn.zig |  6 ++----
 3 files changed, 17 insertions(+), 19 deletions(-)

diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd
index 994c78d..060888e 100644
--- a/doc/riverctl.1.scd
+++ b/doc/riverctl.1.scd
@@ -65,9 +65,9 @@ over the Wayland protocol.
 	output in any direction.
 
 *spawn* _shell_command_
-	Run _shell_command_ using _/bin/sh -c_. Put single quotes around
-	_shell_command_ if you do not want special characters to get
-	interpreted by your shell before the command gets passed to _/bin/sh_.
+	Run _shell_command_ using `/bin/sh -c _shell_command_`. Note that
+	*spawn* only takes a single argument. To spawn a command taking
+	multiple arguments, wrapping the command in quotes is recommended.
 
 *swap* *next*|*previous*
 	Swap the focused view with the next/previous visible non-floating
@@ -336,9 +336,9 @@ However note that not every input device supports every property.
 
 # EXAMPLES
 
-Bind bemenu-run to Super+P in normal mode:
+Bind Super+Enter in normal mode to spawn a *foot*(1) terminal:
 
-	riverctl map normal Mod4 P spawn bemenu-run
+	riverctl map normal Mod4 Enter spawn 'foot --app-id=foobar'
 
 See also the example init script at /etc/river/init.
 
diff --git a/example/init b/example/init
index ae85914..a00a5b1 100755
--- a/example/init
+++ b/example/init
@@ -122,22 +122,22 @@ riverctl map passthrough $mod F11 enter-mode normal
 for mode in normal locked
 do
     # Eject the optical drive
-    riverctl map $mode None XF86Eject spawn eject -T
+    riverctl map $mode None XF86Eject spawn 'eject -T'
 
     # Control pulse audio volume with pamixer (https://github.com/cdemoulins/pamixer)
-    riverctl map $mode None XF86AudioRaiseVolume  spawn pamixer -i 5
-    riverctl map $mode None XF86AudioLowerVolume  spawn pamixer -d 5
-    riverctl map $mode None XF86AudioMute         spawn pamixer --toggle-mute
+    riverctl map $mode None XF86AudioRaiseVolume  spawn 'pamixer -i 5'
+    riverctl map $mode None XF86AudioLowerVolume  spawn 'pamixer -d 5'
+    riverctl map $mode None XF86AudioMute         spawn 'pamixer --toggle-mute'
 
     # Control MPRIS aware media players with playerctl (https://github.com/altdesktop/playerctl)
-    riverctl map $mode None XF86AudioMedia spawn playerctl play-pause
-    riverctl map $mode None XF86AudioPlay  spawn playerctl play-pause
-    riverctl map $mode None XF86AudioPrev  spawn playerctl previous
-    riverctl map $mode None XF86AudioNext  spawn playerctl next
+    riverctl map $mode None XF86AudioMedia spawn 'playerctl play-pause'
+    riverctl map $mode None XF86AudioPlay  spawn 'playerctl play-pause'
+    riverctl map $mode None XF86AudioPrev  spawn 'playerctl previous'
+    riverctl map $mode None XF86AudioNext  spawn 'playerctl next'
 
     # Control screen backlight brighness with light (https://github.com/haikarainen/light)
-    riverctl map $mode None XF86MonBrightnessUp   spawn light -A 5
-    riverctl map $mode None XF86MonBrightnessDown spawn light -U 5
+    riverctl map $mode None XF86MonBrightnessUp   spawn 'light -A 5'
+    riverctl map $mode None XF86MonBrightnessDown spawn 'light -U 5'
 done
 
 # Set repeat rate
diff --git a/river/command/spawn.zig b/river/command/spawn.zig
index 26047c4..6ad3936 100644
--- a/river/command/spawn.zig
+++ b/river/command/spawn.zig
@@ -30,11 +30,9 @@ pub fn spawn(
     out: *?[]const u8,
 ) Error!void {
     if (args.len < 2) return Error.NotEnoughArguments;
+    if (args.len > 2) return Error.TooManyArguments;
 
-    const cmd = try std.mem.joinZ(allocator, " ", args[1..]);
-    defer allocator.free(cmd);
-
-    const child_args = [_:null]?[*:0]const u8{ "/bin/sh", "-c", cmd, null };
+    const child_args = [_:null]?[*:0]const u8{ "/bin/sh", "-c", args[1], null };
 
     const pid = std.os.fork() catch {
         out.* = try std.fmt.allocPrint(allocator, "fork/execve failed", .{});