diff --git a/lib/awful/util.lua b/lib/awful/util.lua index ab157171..28087899 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.