Merge pull request #406 from psychon/spawn_with_pipes2
Spawn with pipes Closes https://github.com/awesomeWM/awesome/pull/406.
This commit is contained in:
commit
7c8e97ca31
|
@ -32,4 +32,4 @@ lua -e '_, _, major_minor, patch = string.find(require("lgi.version"), "^(%d%.%d
|
|||
"0.7.1", require("lgi.version"))) end' || die
|
||||
|
||||
# Check for the needed gi files
|
||||
lua -e 'l = require("lgi") assert(l.cairo, l.Pango, l.PangoCairo)' || die
|
||||
lua -e 'l = require("lgi") assert(l.cairo, l.Pango, l.PangoCairo, l.GLib, l.Gio)' || die
|
||||
|
|
|
@ -22,6 +22,8 @@ local pairs = pairs
|
|||
local string = string
|
||||
local lgi = require("lgi")
|
||||
local Pango = lgi.Pango
|
||||
local Gio = lgi.Gio
|
||||
local GLib = lgi.GLib
|
||||
local capi =
|
||||
{
|
||||
awesome = awesome,
|
||||
|
@ -111,6 +113,94 @@ function util.spawn_with_shell(cmd, sn)
|
|||
end
|
||||
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.
|
||||
-- @treturn[1] Integer the PID of the forked process.
|
||||
-- @treturn[2] string Error message.
|
||||
function util.spawn_with_line_callback(cmd, stdout_callback, stderr_callback, done_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)
|
||||
if type(pid) == "string" then
|
||||
-- Error
|
||||
return pid
|
||||
end
|
||||
|
||||
local done_before = false
|
||||
local function step_done()
|
||||
if have_stdout and have_stderr and not done_before then
|
||||
done_before = true
|
||||
return
|
||||
end
|
||||
done_callback()
|
||||
end
|
||||
if have_stdout then
|
||||
util.read_lines(Gio.UnixInputStream.new(stdout, true),
|
||||
stdout_callback, step_done, true)
|
||||
end
|
||||
if have_stderr then
|
||||
util.read_lines(Gio.UnixInputStream.new(stderr, true),
|
||||
stderr_callback, step_done, true)
|
||||
end
|
||||
assert(stdin == nil)
|
||||
end
|
||||
|
||||
--- Read lines from a Gio input stream
|
||||
-- @tparam Gio.InputStream input_stream The input stream to read from.
|
||||
-- @tparam function line_callback Function that is called with each line
|
||||
-- read, e.g. `line_callback(line_from_stream)`.
|
||||
-- @tparam[opt] function done_callback Function that is called when the
|
||||
-- operation finishes (e.g. due to end of file).
|
||||
-- @tparam[opt=false] boolean close Should the stream be closed after end-of-file?
|
||||
function util.read_lines(input_stream, line_callback, done_callback, close)
|
||||
local stream = Gio.DataInputStream.new(input_stream)
|
||||
local function done()
|
||||
if close then
|
||||
stream:close()
|
||||
end
|
||||
if done_callback then
|
||||
xpcall(done_callback, function(err)
|
||||
print(debug.traceback("Error while calling done_callback:"
|
||||
.. tostring(err), 2))
|
||||
end)
|
||||
end
|
||||
end
|
||||
local start_read, finish_read
|
||||
start_read = function()
|
||||
stream:read_line_async(GLib.PRIORITY_DEFAULT, nil, finish_read)
|
||||
end
|
||||
finish_read = function(obj, res)
|
||||
local line, length = obj:read_line_finish(res)
|
||||
if type(length) ~= "number" then
|
||||
-- Error
|
||||
print("Error in awful.util.read_lines:", tostring(length))
|
||||
done()
|
||||
elseif #line ~= length then
|
||||
-- End of file
|
||||
done()
|
||||
else
|
||||
-- Read a line
|
||||
xpcall(function()
|
||||
-- This needs tostring() for older lgi versions which returned
|
||||
-- "GLib.Bytes" instead of Lua strings (I guess)
|
||||
line_callback(tostring(line))
|
||||
end, function(err)
|
||||
print(debug.traceback("Error while calling line_callback: "
|
||||
.. tostring(err), 2))
|
||||
end)
|
||||
|
||||
-- Read the next line
|
||||
start_read()
|
||||
end
|
||||
end
|
||||
start_read()
|
||||
end
|
||||
|
||||
--- Read a program output and returns its output as a string.
|
||||
-- @param cmd The command to run.
|
||||
-- @return A string with the program output, or the error if one occured.
|
||||
|
|
46
spawn.c
46
spawn.c
|
@ -340,8 +340,14 @@ parse_command(lua_State *L, int idx, GError **error)
|
|||
*
|
||||
* @tparam string|table cmd The command to launch.
|
||||
* @tparam[opt=true] boolean use_sn Use startup-notification?
|
||||
* @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?
|
||||
* @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.
|
||||
* @treturn[1] integer stdout, if `stdout` is true.
|
||||
* @treturn[1] integer stderr, if `stderr` is true.
|
||||
* @treturn[2] string An error string if an error occured.
|
||||
* @function spawn
|
||||
*/
|
||||
|
@ -349,12 +355,26 @@ int
|
|||
luaA_spawn(lua_State *L)
|
||||
{
|
||||
gchar **argv = NULL;
|
||||
bool use_sn = true;
|
||||
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;
|
||||
gboolean retval;
|
||||
GPid pid;
|
||||
|
||||
if(lua_gettop(L) >= 2)
|
||||
use_sn = luaA_checkboolean(L, 2);
|
||||
if(lua_gettop(L) >= 3)
|
||||
return_stdin = luaA_checkboolean(L, 3);
|
||||
if(lua_gettop(L) >= 4)
|
||||
return_stdout = luaA_checkboolean(L, 4);
|
||||
if(lua_gettop(L) >= 5)
|
||||
return_stderr = luaA_checkboolean(L, 5);
|
||||
if(return_stdin)
|
||||
stdin_ptr = &stdin_fd;
|
||||
if(return_stdout)
|
||||
stdout_ptr = &stdout_fd;
|
||||
if(return_stderr)
|
||||
stderr_ptr = &stderr_fd;
|
||||
|
||||
GError *error = NULL;
|
||||
argv = parse_command(L, 1, &error);
|
||||
|
@ -383,8 +403,9 @@ luaA_spawn(lua_State *L)
|
|||
sn_launcher_context_setup_child_process(context);
|
||||
}
|
||||
|
||||
retval = g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
|
||||
spawn_callback, NULL, &pid, &error);
|
||||
retval = g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
|
||||
spawn_callback, NULL, &pid,
|
||||
stdin_ptr, stdout_ptr, stderr_ptr, &error);
|
||||
g_strfreev(argv);
|
||||
if(!retval)
|
||||
{
|
||||
|
@ -402,9 +423,24 @@ luaA_spawn(lua_State *L)
|
|||
|
||||
/* push sn on stack */
|
||||
if (context)
|
||||
lua_pushstring(L,sn_launcher_context_get_startup_id(context));
|
||||
lua_pushstring(L, sn_launcher_context_get_startup_id(context));
|
||||
else
|
||||
lua_pushnil(L);
|
||||
|
||||
return (context)?2:1;
|
||||
if(return_stdin)
|
||||
lua_pushinteger(L, stdin_fd);
|
||||
else
|
||||
lua_pushnil(L);
|
||||
if(return_stdout)
|
||||
lua_pushinteger(L, stdout_fd);
|
||||
else
|
||||
lua_pushnil(L);
|
||||
if(return_stderr)
|
||||
lua_pushinteger(L, stderr_fd);
|
||||
else
|
||||
lua_pushnil(L);
|
||||
|
||||
return 5;
|
||||
}
|
||||
|
||||
// vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
timer = require("gears.timer")
|
||||
local timer = require("gears.timer")
|
||||
local awful = require("awful")
|
||||
|
||||
runner = {
|
||||
quit_awesome_on_error = os.getenv('TEST_PAUSE_ON_ERRORS') ~= '1',
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
--- Tests for spawn
|
||||
|
||||
local util = require("awful.util")
|
||||
|
||||
local spawns_done = 0
|
||||
|
||||
local steps = {
|
||||
function(count)
|
||||
if count == 1 then
|
||||
local steps_yay = 0
|
||||
util.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)
|
||||
|
||||
local steps_count = 0
|
||||
local err_count = 0
|
||||
util.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)
|
||||
end
|
||||
if spawns_done == 2 then
|
||||
return true
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
require("_runner").run_steps(steps)
|
Loading…
Reference in New Issue