diff options
Diffstat (limited to 'awesome/lib/awful/spawn.lua')
-rw-r--r-- | awesome/lib/awful/spawn.lua | 421 |
1 files changed, 0 insertions, 421 deletions
diff --git a/awesome/lib/awful/spawn.lua b/awesome/lib/awful/spawn.lua deleted file mode 100644 index bcfb68d..0000000 --- a/awesome/lib/awful/spawn.lua +++ /dev/null @@ -1,421 +0,0 @@ ---------------------------------------------------------------------------- ---- Spawning of programs. --- --- This module provides methods to start programs and supports startup --- notifications, which allows for callbacks and applying properties to the --- program after it has been launched. This requires currently that the --- applicaton supports them. --- --- **Rules of thumb when a shell is needed**: --- --- * A shell is required when the commands contain `&&`, `;`, `||`, `&` or --- any other unix shell language syntax --- * When shell variables are defined as part of the command --- * When the command is a shell alias --- --- Note that a shell is **not** a terminal emulator. A terminal emulator is --- something like XTerm, Gnome-terminal or Konsole. A shell is something like --- `bash`, `zsh`, `busybox sh` or `Debian ash`. --- --- If you wish to open a process in a terminal window, check that your terminal --- emulator supports the common `-e` option. If it does, then something like --- this should work: --- --- awful.spawn(terminal.." -e my_command") --- --- Note that some terminals, such as rxvt-unicode (urxvt) support full commands --- using quotes, while other terminal emulators require to use quoting. --- --- **Understanding clients versus PID versus commands versus class**: --- --- A *process* has a *PID* (process identifier). It can have 0, 1 or many --- *window*s. --- --- A *command* if what is used to start *process*(es). It has no direct relation --- with *process*, *client* or *window*. When a command is executed, it will --- usually start a *process* which keeps running until it exits. This however is --- not always the case as some applications use scripts as command and others --- use various single-instance mechanisms (usually client/server) and merge --- with an existing process. --- --- A *client* corresponds to a *window*. It is owned by a process. It can have --- both a parent and one or many children. A *client* has a *class*, an --- *instance*, a *role*, and a *type*. See `client.class`, `client.instance`, --- `client.role` and `client.type` for more information about these properties. --- --- **The startup notification protocol**: --- --- The startup notification protocol is an optional specification implemented --- by X11 applications to bridge the chain of knowledge between the moment a --- program is launched to the moment its window (client) is shown. It can be --- found [on the FreeDesktop.org website](https://www.freedesktop.org/wiki/Specifications/startup-notification-spec/). --- --- Awesome has support for the various events that are part of the protocol, but --- the most useful is the identifier, usually identified by its `SNID` acronym in --- the documentation. It isn't usually necessary to even know it exists, as it --- is all done automatically. However, if more control is required, the --- identifier can be specified by an environment variable called --- `DESKTOP_STARTUP_ID`. For example, let us consider execution of the following --- command: --- --- DESKTOP_STARTUP_ID="something_TIME$(date '+%s')" my_command --- --- This should (if the program correctly implements the protocol) result in --- `c.startup_id` to at least match `something`. --- This identifier can then be used in `awful.rules` to configure the client. --- --- Awesome can automatically set the `DESKTOP_STARTUP_ID` variable. This is used --- by `awful.spawn` to specify additional rules for the startup. For example: --- --- awful.spawn("urxvt -e maxima -name CALCULATOR", { --- floating = true, --- tag = mouse.screen.selected_tag, --- placement = awful.placement.bottom_right, --- }) --- --- This can also be used from the command line: --- --- awesome-client 'awful=require("awful"); --- awful.spawn("urxvt -e maxima -name CALCULATOR", { --- floating = true, --- tag = mouse.screen.selected_tag, --- placement = awful.placement.bottom_right, --- })' --- --- **Getting a command's output**: --- --- First, do **not** use `io.popen` **ever**. It is synchronous. Synchronous --- functions **block everything** until they are done. All visual applications --- lock (as Awesome no longer responds), you will probably lose some keyboard --- and mouse events and will have higher latency when playing games. This is --- also true when reading files synchronously, but this is another topic. --- --- Awesome provides a few ways to get output from commands. One is to use the --- `Gio` libraries directly. This is usually very complicated, but gives a lot --- of control on the command execution. --- --- This modules provides `with_line_callback` and `easy_async` for convenience. --- First, lets add this bash command to `rc.lua`: --- --- local noisy = [[bash -c ' --- for I in $(seq 1 5); do --- date --- echo err >&2 --- sleep 2 --- done --- ']] --- --- It prints a bunch of junk on the standard output (*stdout*) and error --- (*stderr*) streams. This command would block Awesome for 10 seconds if it --- were executed synchronously, but will not block it at all using the --- asynchronous functions. --- --- `with_line_callback` will execute the callbacks every time a new line is --- printed by the command: --- --- awful.spawn.with_line_callback(noisy, { --- stdout = function(line) --- naughty.notify { text = "LINE:"..line } --- end, --- stderr = function(line) --- naughty.notify { text = "ERR:"..line} --- end, --- }) --- --- If only the full output is needed, then `easy_async` is the right choice: --- --- awful.spawn.easy_async(noisy, function(stdout, stderr, reason, exit_code) --- naughty.notify { text = stdout } --- end) --- --- **Default applications**: --- --- If the intent is to open a file/document, then it is recommended to use the --- following standard command. The default application will be selected --- according to the [Shared MIME-info Database](https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html) --- specification. The `xdg-utils` package provided by most distributions --- includes the `xdg-open` command: --- --- awful.spawn({"xdg-open", "/path/to/file"}) --- --- Awesome **does not** manage, modify or otherwise influence the database --- for default applications. For information about how to do this, consult the --- [ARCH Linux Wiki](https://wiki.archlinux.org/index.php/default_applications). --- --- If you wish to change how the default applications behave, then consult the --- [Desktop Entry](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html) --- specification. --- --- @author Julien Danjou <julien@danjou.info> --- @author Emmanuel Lepage Vallee <elv1313@gmail.com> --- @copyright 2008 Julien Danjou --- @copyright 2014 Emmanuel Lepage Vallee --- @module awful.spawn ---------------------------------------------------------------------------- - -local capi = -{ - awesome = awesome, - mouse = mouse, - client = client, -} -local lgi = require("lgi") -local Gio = lgi.Gio -local GLib = lgi.GLib -local util = require("awful.util") -local protected_call = require("gears.protected_call") - -local spawn = {} - - -local end_of_file -do - -- API changes, bug fixes and lots of fun. Figure out how a EOF is signalled. - local input - if not pcall(function() - -- No idea when this API changed, but some versions expect a string, - -- others a table with some special(?) entries - input = Gio.DataInputStream.new(Gio.MemoryInputStream.new_from_data("")) - end) then - input = Gio.DataInputStream.new(Gio.MemoryInputStream.new_from_data({})) - end - local line, length = input:read_line() - if not line then - -- Fixed in 2016: NULL on the C side is transformed to nil in Lua - end_of_file = function(arg) - return not arg - end - elseif tostring(line) == "" and #line ~= length then - -- "Historic" behaviour for end-of-file: - -- - NULL is turned into an empty string - -- - The length variable is not initialized - -- It's highly unlikely that the uninitialized variable has value zero. - -- Use this hack to detect EOF. - end_of_file = function(arg1, arg2) - return #arg1 ~= arg2 - end - else - assert(tostring(line) == "", "Cannot determine how to detect EOF") - -- The above uninitialized variable was fixed and thus length is - -- always 0 when line is NULL in C. We cannot tell apart an empty line and - -- EOF in this case. - require("gears.debug").print_warning("Cannot reliably detect EOF on an " - .. "GIOInputStream with this LGI version") - end_of_file = function(arg) - return tostring(arg) == "" - end - end -end - -spawn.snid_buffer = {} - -function spawn.on_snid_callback(c) - local entry = spawn.snid_buffer[c.startup_id] - if entry then - local props = entry[1] - local callback = entry[2] - c:emit_signal("spawn::completed_with_payload", props, callback) - spawn.snid_buffer[c.startup_id] = nil - end -end - -function spawn.on_snid_cancel(id) - if spawn.snid_buffer[id] then - spawn.snid_buffer[id] = nil - end -end - ---- Spawn a program, and optionally apply properties and/or run a callback. --- --- Applying properties or running a callback requires the program/client to --- support startup notifications. --- --- See `awful.rules.execute` for more details about the format of `sn_rules`. --- --- @tparam string|table cmd The command. --- @tparam[opt=true] table|boolean sn_rules A table of properties to be applied --- after startup; `false` to disable startup notifications. --- @tparam[opt] function callback A callback function to be run after startup. --- @treturn[1] integer The forked PID. --- @treturn[1] ?string The startup notification ID, if `sn` is not false, or --- a `callback` is provided. --- @treturn[2] string Error message. -function spawn.spawn(cmd, sn_rules, callback) - if cmd and cmd ~= "" then - local enable_sn = (sn_rules ~= false or callback) - enable_sn = not not enable_sn -- Force into a boolean. - local pid, snid = capi.awesome.spawn(cmd, enable_sn) - -- The snid will be nil in case of failure - if snid then - sn_rules = type(sn_rules) ~= "boolean" and sn_rules or {} - spawn.snid_buffer[snid] = { sn_rules, { callback } } - end - return pid, snid - end - -- For consistency - return "Error: No command to execute" -end - ---- Spawn a program using the shell. --- This calls `cmd` with `$SHELL -c` (via `awful.util.shell`). --- @tparam string cmd The command. -function spawn.with_shell(cmd) - if cmd and cmd ~= "" then - cmd = { util.shell, "-c", cmd } - return capi.awesome.spawn(cmd, false) - end -end - ---- Spawn a program and asynchronously capture its output line by line. --- @tparam string|table cmd The command. --- @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 --- arguments. --- 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, 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, _, stdin, stdout, stderr = capi.awesome.spawn(cmd, - false, false, have_stdout, have_stderr, exit_callback) - 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 - if done_callback then - done_callback() - end - end - if have_stdout then - spawn.read_lines(Gio.UnixInputStream.new(stdout, true), - stdout_callback, step_done, true) - end - if have_stderr then - spawn.read_lines(Gio.UnixInputStream.new(stderr, true), - stderr_callback, step_done, true) - end - assert(stdin == nil) - return pid -end - ---- Asynchronously spawn a program and capture its output. --- (wraps `spawn.with_line_callback`). --- @tparam string|table cmd The command. --- @tab callback Function with the following arguments --- @tparam string callback.stdout Output on stdout. --- @tparam string callback.stderr Output on stderr. --- @tparam string callback.exitreason Exit Reason. --- The reason can be "exit" or "signal". --- @tparam integer callback.exitcode Exit code. --- For "exit" reason it's the exit code. --- For "signal" reason — the signal causing process termination. --- @treturn[1] Integer the PID of the forked process. --- @treturn[2] string Error message. --- @see spawn.with_line_callback -function spawn.easy_async(cmd, callback) - local stdout = '' - local stderr = '' - local exitcode, exitreason - local function parse_stdout(str) - stdout = stdout .. str .. "\n" - end - local function parse_stderr(str) - stderr = stderr .. str .. "\n" - end - local function done_callback() - return callback(stdout, stderr, exitreason, exitcode) - end - local exit_callback_fired = false - local output_done_callback_fired = false - local function exit_callback(reason, code) - exitcode = code - exitreason = reason - exit_callback_fired = true - if output_done_callback_fired then - return done_callback() - end - end - local function output_done_callback() - output_done_callback_fired = true - if exit_callback_fired then - return done_callback() - end - end - return spawn.with_line_callback( - cmd, { - stdout=parse_stdout, - stderr=parse_stderr, - exit=exit_callback, - output_done=output_done_callback - }) -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 spawn.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 - protected_call(done_callback) - 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.spawn.read_lines:", tostring(length)) - done() - elseif end_of_file(line, length) then - -- End of file - done() - else - -- Read a line - -- This needs tostring() for older lgi versions which returned - -- "GLib.Bytes" instead of Lua strings (I guess) - protected_call(line_callback, tostring(line)) - - -- Read the next line - start_read() - end - end - start_read() -end - -capi.awesome.connect_signal("spawn::canceled" , spawn.on_snid_cancel ) -capi.awesome.connect_signal("spawn::timeout" , spawn.on_snid_cancel ) -capi.client.connect_signal ("manage" , spawn.on_snid_callback ) - -return setmetatable(spawn, { __call = function(_, ...) return spawn.spawn(...) end }) --- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 |