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.
-- @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

42
spawn.c
View File

@ -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);

View File

@ -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,