Merge pull request #576 from psychon/spawn_exit_code

At exit code support to spawn
This commit is contained in:
Daniel Hahler 2015-12-12 19:10:48 +01:00
commit 69da6ee0db
3 changed files with 104 additions and 33 deletions

View File

@ -91,17 +91,27 @@ end
--- Spawn a program and asynchronously and capture its output line by line. --- Spawn a program and asynchronously and capture its output line by line.
-- @tparam string|table cmd The command. -- @tparam string|table cmd The command.
-- @tparam[opt] function stdout_callback Function that is called with each line of -- @tab callbacks Table containing callbacks that should be
-- output on stdout, e.g. `stdout_callback(line)`. -- invoked on various conditions.
-- @tparam[opt] function stderr_callback Function that is called with each line of -- @tparam[opt] function callbacks.stdout Function that is called with each line of
-- output on stderr, e.g. `stderr_callback(line)`. -- output on stdout, e.g. `stdout(line)`.
-- @tparam[opt] function done_callback Function to call when no more output is -- @tparam[opt] function callbacks.stderr Function that is called with each line of
-- produced. -- 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[1] Integer the PID of the forked process.
-- @treturn[2] string Error message. -- @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 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 if type(pid) == "string" then
-- Error -- Error
return pid return pid

42
spawn.c
View File

@ -342,6 +342,28 @@ parse_command(lua_State *L, int idx, GError **error)
return argv; 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. /** Spawn a program.
* The program will be started on the default screen. * 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 stdin Return a fd for stdin?
* @tparam[opt=false] boolean stdout Return a fd for stdout? * @tparam[opt=false] boolean stdout Return a fd for stdout?
* @tparam[opt=false] boolean stderr Return a fd for stderr? * @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] integer Process ID if everything is OK.
* @treturn[1] string Startup-notification ID, if `use_sn` is true. * @treturn[1] string Startup-notification ID, if `use_sn` is true.
* @treturn[1] integer stdin, if `stdin` 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; 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_fd = -1, stdout_fd = -1, stderr_fd = -1;
int *stdin_ptr = NULL, *stdout_ptr = NULL, *stderr_ptr = NULL; int *stdin_ptr = NULL, *stdout_ptr = NULL, *stderr_ptr = NULL;
GSpawnFlags flags = 0;
gboolean retval; gboolean retval;
GPid pid; GPid pid;
@ -376,6 +402,11 @@ luaA_spawn(lua_State *L)
return_stdout = luaA_checkboolean(L, 4); return_stdout = luaA_checkboolean(L, 4);
if(lua_gettop(L) >= 5) if(lua_gettop(L) >= 5)
return_stderr = luaA_checkboolean(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) if(return_stdin)
stdin_ptr = &stdin_fd; stdin_ptr = &stdin_fd;
if(return_stdout) if(return_stdout)
@ -409,7 +440,8 @@ luaA_spawn(lua_State *L)
g_timeout_add_seconds(AWESOME_SPAWN_TIMEOUT, spawn_launchee_timeout, context); 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, spawn_callback, context, &pid,
stdin_ptr, stdout_ptr, stderr_ptr, &error); stdin_ptr, stdout_ptr, stderr_ptr, &error);
g_strfreev(argv); g_strfreev(argv);
@ -424,6 +456,14 @@ luaA_spawn(lua_State *L)
return 1; 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 */ /* push pid on stack */
lua_pushnumber(L, pid); lua_pushnumber(L, pid);

View File

@ -3,40 +3,61 @@
local spawn = require("awful.spawn") local spawn = require("awful.spawn")
local spawns_done = 0 local spawns_done = 0
local exit_yay, exit_snd = nil, nil
local steps = { local steps = {
function(count) function(count)
if count == 1 then if count == 1 then
local steps_yay = 0 local steps_yay = 0
spawn.with_line_callback("echo yay", function(line) spawn.with_line_callback("echo yay", {
stdout = function(line)
assert(line == "yay", "line == '" .. tostring(line) .. "'") assert(line == "yay", "line == '" .. tostring(line) .. "'")
assert(steps_yay == 0) assert(steps_yay == 0)
steps_yay = steps_yay + 1 steps_yay = steps_yay + 1
end, nil, function() end,
output_done = function()
assert(steps_yay == 1) assert(steps_yay == 1)
steps_yay = steps_yay + 1 steps_yay = steps_yay + 1
spawns_done = spawns_done + 1 spawns_done = spawns_done + 1
end) end,
exit = function(reason, code)
assert(reason == "exit")
assert(exit_yay == nil)
assert(code == 0)
exit_yay = code
end
})
local steps_count = 0 local steps_count = 0
local err_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) stdout = function(line)
assert(steps_count < 3) assert(steps_count < 3)
steps_count = steps_count + 1 steps_count = steps_count + 1
assert(line == "line" .. steps_count, "line == '" .. tostring(line) .. "'") assert(line == "line" .. steps_count, "line == '" .. tostring(line) .. "'")
end, function(line) end,
stderr = function(line)
assert(err_count == 0) assert(err_count == 0)
err_count = err_count + 1 err_count = err_count + 1
assert(line == "err", "line == '" .. tostring(line) .. "'") assert(line == "err", "line == '" .. tostring(line) .. "'")
end, function() end,
output_done = function()
assert(steps_count == 3) assert(steps_count == 3)
assert(err_count == 1) assert(err_count == 1)
steps_count = steps_count + 1 steps_count = steps_count + 1
spawns_done = spawns_done + 1 spawns_done = spawns_done + 1
end) end,
exit = function(reason, code)
assert(reason == "exit")
assert(exit_snd == nil)
assert(code == 42)
exit_snd = code
end
})
end end
if spawns_done == 2 then if spawns_done == 2 then
assert(exit_yay == 0)
assert(exit_snd == 42)
return true return true
end end
end, end,