diff --git a/lib/awful/spawn.lua b/lib/awful/spawn.lua index 113ee6b02..3aa432dc3 100644 --- a/lib/awful/spawn.lua +++ b/lib/awful/spawn.lua @@ -91,17 +91,27 @@ 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. +-- @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) +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) + 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 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); diff --git a/tests/test-spawn.lua b/tests/test-spawn.lua index 8341f6533..b087af93f 100644 --- a/tests/test-spawn.lua +++ b/tests/test-spawn.lua @@ -3,40 +3,61 @@ local spawn = require("awful.spawn") local spawns_done = 0 +local exit_yay, exit_snd = nil, nil 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) + 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" }, - 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) + 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) + assert(exit_snd == 42) return true end end,