From 0e20fef2bde2ee5d0a40d3466d83bf4aef866807 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Wed, 2 Sep 2015 22:18:33 +0200 Subject: [PATCH] Add awful.util.spawn_with_line_callback This new function spawns a program, similarly to awful.spawn, but captures its output. On each line of output on stdout / stderr, a Lua function is called with this line. There are different callbacks for stdout and stderr. When both stdout and stderr are closed, another callback function is called. The intention for this last callback is "the program is done", because most programs should only close their output when they exit. Signed-off-by: Uli Schlachter --- lib/awful/util.lua | 90 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/lib/awful/util.lua b/lib/awful/util.lua index ab157171d..280878998 100644 --- a/lib/awful/util.lua +++ b/lib/awful/util.lua @@ -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, @@ -110,6 +112,94 @@ function util.spawn_with_shell(cmd, sn) end end +--- Spawn a program and asynchronously 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.