From a8ac146bd0516635326a85060e19943f1bf84992 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 29 Nov 2015 11:53:12 +0100 Subject: [PATCH 1/4] awesome.spawn: Add exit status support This adds a new argument to awesome.spawn. This argument is a function that will be called with the exit status once the spawned process terminates. For normal exit, the function is called with "exit" and the exit code. If the process is terminated by a signal, the function will be called with "signal" and the signal number of the signal that caused termination. Signed-off-by: Uli Schlachter --- spawn.c | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/spawn.c b/spawn.c index 51830046f..bdf715aec 100644 --- a/spawn.c +++ b/spawn.c @@ -342,6 +342,28 @@ parse_command(lua_State *L, int idx, GError **error) return argv; } +/** Callback for when a spawned process exits. */ +static void +child_exit_callback(GPid pid, gint status, gpointer user_data) +{ + lua_State *L = globalconf_get_lua_State(); + int exit_callback = GPOINTER_TO_INT(user_data); + + /* 'Decode' the exit status */ + if (WIFEXITED(status)) { + lua_pushliteral(L, "exit"); + lua_pushnumber(L, WEXITSTATUS(status)); + } else { + assert(WIFSIGNALED(status)); + lua_pushliteral(L, "signal"); + lua_pushnumber(L, WTERMSIG(status)); + } + + lua_rawgeti(L, LUA_REGISTRYINDEX, exit_callback); + luaA_dofunction(L, 2, 0); + luaA_unregister(L, &exit_callback); +} + /** Spawn a program. * The program will be started on the default screen. * @@ -350,6 +372,9 @@ parse_command(lua_State *L, int idx, GError **error) * @tparam[opt=false] boolean stdin Return a fd for stdin? * @tparam[opt=false] boolean stdout Return a fd for stdout? * @tparam[opt=false] boolean stderr Return a fd for stderr? + * @tparam[opt=nil] function exit_callback Function to call on process exit. The + * function arguments will be type of exit ("exit" or "signal") and the exit + * code / the signal number causing process termination. * @treturn[1] integer Process ID if everything is OK. * @treturn[1] string Startup-notification ID, if `use_sn` is true. * @treturn[1] integer stdin, if `stdin` is true. @@ -365,6 +390,7 @@ luaA_spawn(lua_State *L) bool use_sn = true, return_stdin = false, return_stdout = false, return_stderr = false; int stdin_fd = -1, stdout_fd = -1, stderr_fd = -1; int *stdin_ptr = NULL, *stdout_ptr = NULL, *stderr_ptr = NULL; + GSpawnFlags flags = 0; gboolean retval; GPid pid; @@ -376,6 +402,11 @@ luaA_spawn(lua_State *L) return_stdout = luaA_checkboolean(L, 4); if(lua_gettop(L) >= 5) return_stderr = luaA_checkboolean(L, 5); + if(lua_gettop(L) >= 6) + { + luaA_checkfunction(L, 6); + flags |= G_SPAWN_DO_NOT_REAP_CHILD; + } if(return_stdin) stdin_ptr = &stdin_fd; if(return_stdout) @@ -409,7 +440,8 @@ luaA_spawn(lua_State *L) g_timeout_add_seconds(AWESOME_SPAWN_TIMEOUT, spawn_launchee_timeout, context); } - retval = g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, + flags |= G_SPAWN_SEARCH_PATH; + retval = g_spawn_async_with_pipes(NULL, argv, NULL, flags, spawn_callback, context, &pid, stdin_ptr, stdout_ptr, stderr_ptr, &error); g_strfreev(argv); @@ -424,6 +456,14 @@ luaA_spawn(lua_State *L) return 1; } + if(flags & G_SPAWN_DO_NOT_REAP_CHILD) + { + int exit_callback = LUA_REFNIL; + /* Only do this down here to avoid leaks in case of errors */ + luaA_registerfct(L, 6, &exit_callback); + g_child_watch_add(pid, child_exit_callback, GINT_TO_POINTER(exit_callback)); + } + /* push pid on stack */ lua_pushnumber(L, pid); From 44a3e27e813fbda449c92e4a2361dfc622c1913e Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 29 Nov 2015 11:56:05 +0100 Subject: [PATCH 2/4] awful.spawn.with_line_callback: Add exit callback This adds support for the callback that was added in the previous commit to this function. We can just pass on the function that the caller gives us. Fixes: https://github.com/awesomeWM/awesome/issues/185 Signed-off-by: Uli Schlachter --- lib/awful/spawn.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/awful/spawn.lua b/lib/awful/spawn.lua index 113ee6b02..f83e22f96 100644 --- a/lib/awful/spawn.lua +++ b/lib/awful/spawn.lua @@ -97,11 +97,17 @@ end -- output on stderr, e.g. `stderr_callback(line)`. -- @tparam[opt] function done_callback Function to call when no more output is -- produced. +-- @tparam[opt] function exit_callback Function to call when the spawned process +-- exits. This function gets the exit reason and code as its argument. The +-- reason can be "exit" or "signal". For "exit", the second argument is the exit +-- code. For "signal", the second argument is the signal causing process +-- termination. -- @treturn[1] Integer the PID of the forked process. -- @treturn[2] string Error message. -function spawn.with_line_callback(cmd, stdout_callback, stderr_callback, done_callback) +function spawn.with_line_callback(cmd, stdout_callback, stderr_callback, done_callback, exit_callback) local have_stdout, have_stderr = stdout_callback ~= nil, stderr_callback ~= nil - local pid, sn_id, stdin, stdout, stderr = capi.awesome.spawn(cmd, false, false, have_stdout, have_stderr) + local pid, sn_id, stdin, stdout, stderr = capi.awesome.spawn(cmd, + false, false, have_stdout, have_stderr, exit_callback) if type(pid) == "string" then -- Error return pid From 2f5ade49c27c9b610bbfe5cb8f1807531447d800 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 29 Nov 2015 11:57:35 +0100 Subject: [PATCH 3/4] test-spawn.lua: Also test exit statuses Signed-off-by: Uli Schlachter --- tests/test-spawn.lua | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/test-spawn.lua b/tests/test-spawn.lua index 8341f6533..b38715388 100644 --- a/tests/test-spawn.lua +++ b/tests/test-spawn.lua @@ -3,6 +3,7 @@ local spawn = require("awful.spawn") local spawns_done = 0 +local exit_yay, exit_snd = nil, nil local steps = { function(count) @@ -16,11 +17,16 @@ local steps = { assert(steps_yay == 1) steps_yay = steps_yay + 1 spawns_done = spawns_done + 1 + end, function(reason, code) + assert(reason == "exit") + assert(exit_yay == nil) + assert(code == 0) + exit_yay = code end) local steps_count = 0 local err_count = 0 - spawn.with_line_callback({ "sh", "-c", "printf line1\\\\nline2\\\\nline3 ; echo err >&2" }, + spawn.with_line_callback({ "sh", "-c", "printf line1\\\\nline2\\\\nline3 ; echo err >&2 ; exit 42" }, function(line) assert(steps_count < 3) steps_count = steps_count + 1 @@ -34,9 +40,16 @@ local steps = { assert(err_count == 1) steps_count = steps_count + 1 spawns_done = spawns_done + 1 + end, function(reason, code) + assert(reason == "exit") + assert(exit_snd == nil) + assert(code == 42) + exit_snd = code end) end if spawns_done == 2 then + assert(exit_yay == 0) + assert(exit_snd == 42) return true end end, From 469433e10a140fa1f05696b9448ee6a7f1ba8144 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Fri, 11 Dec 2015 18:50:41 +0100 Subject: [PATCH 4/4] awful.spawn.with_line_callback: Use an arguments table Having many arguments can easily get confusing and hard to understand. This commit uses a table instead so that we have names that identify what each callback does. Signed-off-by: Uli Schlachter --- lib/awful/spawn.lua | 20 +++++++----- tests/test-spawn.lua | 76 ++++++++++++++++++++++++-------------------- 2 files changed, 54 insertions(+), 42 deletions(-) diff --git a/lib/awful/spawn.lua b/lib/awful/spawn.lua index f83e22f96..3aa432dc3 100644 --- a/lib/awful/spawn.lua +++ b/lib/awful/spawn.lua @@ -91,20 +91,24 @@ end --- Spawn a program and asynchronously and capture its output line by line. -- @tparam string|table cmd The command. --- @tparam[opt] function stdout_callback Function that is called with each line of --- output on stdout, e.g. `stdout_callback(line)`. --- @tparam[opt] function stderr_callback Function that is called with each line of --- output on stderr, e.g. `stderr_callback(line)`. --- @tparam[opt] function done_callback Function to call when no more output is --- produced. --- @tparam[opt] function exit_callback Function to call when the spawned process +-- @tab callbacks Table containing callbacks that should be +-- invoked on various conditions. +-- @tparam[opt] function callbacks.stdout Function that is called with each line of +-- output on stdout, e.g. `stdout(line)`. +-- @tparam[opt] function callbacks.stderr Function that is called with each line of +-- output on stderr, e.g. `stderr(line)`. +-- @tparam[opt] function callbacks.output_done Function to call when no more output +-- is produced. +-- @tparam[opt] function callbacks.exit Function to call when the spawned process -- exits. This function gets the exit reason and code as its argument. The -- reason can be "exit" or "signal". For "exit", the second argument is the exit -- code. For "signal", the second argument is the signal causing process -- termination. -- @treturn[1] Integer the PID of the forked process. -- @treturn[2] string Error message. -function spawn.with_line_callback(cmd, stdout_callback, stderr_callback, done_callback, exit_callback) +function spawn.with_line_callback(cmd, callbacks) + local stdout_callback, stderr_callback, done_callback, exit_callback = + callbacks.stdout, callbacks.stderr, callbacks.output_done, callbacks.exit local have_stdout, have_stderr = stdout_callback ~= nil, stderr_callback ~= nil local pid, sn_id, stdin, stdout, stderr = capi.awesome.spawn(cmd, false, false, have_stdout, have_stderr, exit_callback) diff --git a/tests/test-spawn.lua b/tests/test-spawn.lua index b38715388..b087af93f 100644 --- a/tests/test-spawn.lua +++ b/tests/test-spawn.lua @@ -9,43 +9,51 @@ local steps = { function(count) if count == 1 then local steps_yay = 0 - spawn.with_line_callback("echo yay", function(line) - assert(line == "yay", "line == '" .. tostring(line) .. "'") - assert(steps_yay == 0) - steps_yay = steps_yay + 1 - end, nil, function() - assert(steps_yay == 1) - steps_yay = steps_yay + 1 - spawns_done = spawns_done + 1 - end, function(reason, code) - assert(reason == "exit") - assert(exit_yay == nil) - assert(code == 0) - exit_yay = code - end) + spawn.with_line_callback("echo yay", { + stdout = function(line) + assert(line == "yay", "line == '" .. tostring(line) .. "'") + assert(steps_yay == 0) + steps_yay = steps_yay + 1 + end, + output_done = function() + assert(steps_yay == 1) + steps_yay = steps_yay + 1 + spawns_done = spawns_done + 1 + end, + exit = function(reason, code) + assert(reason == "exit") + assert(exit_yay == nil) + assert(code == 0) + exit_yay = code + end + }) local steps_count = 0 local err_count = 0 - spawn.with_line_callback({ "sh", "-c", "printf line1\\\\nline2\\\\nline3 ; echo err >&2 ; exit 42" }, - function(line) - assert(steps_count < 3) - steps_count = steps_count + 1 - assert(line == "line" .. steps_count, "line == '" .. tostring(line) .. "'") - end, function(line) - assert(err_count == 0) - err_count = err_count + 1 - assert(line == "err", "line == '" .. tostring(line) .. "'") - end, function() - assert(steps_count == 3) - assert(err_count == 1) - steps_count = steps_count + 1 - spawns_done = spawns_done + 1 - end, function(reason, code) - assert(reason == "exit") - assert(exit_snd == nil) - assert(code == 42) - exit_snd = code - end) + spawn.with_line_callback({ "sh", "-c", "printf line1\\\\nline2\\\\nline3 ; echo err >&2 ; exit 42" }, { + stdout = function(line) + assert(steps_count < 3) + steps_count = steps_count + 1 + assert(line == "line" .. steps_count, "line == '" .. tostring(line) .. "'") + end, + stderr = function(line) + assert(err_count == 0) + err_count = err_count + 1 + assert(line == "err", "line == '" .. tostring(line) .. "'") + end, + output_done = function() + assert(steps_count == 3) + assert(err_count == 1) + steps_count = steps_count + 1 + spawns_done = spawns_done + 1 + end, + exit = function(reason, code) + assert(reason == "exit") + assert(exit_snd == nil) + assert(code == 42) + exit_snd = code + end + }) end if spawns_done == 2 then assert(exit_yay == 0)