Advertisement
Guest User

Untitled

a guest
Jun 23rd, 2018
179
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 17.33 KB | None | 0 0
  1. --luacheck: no max line length
  2.  
  3. ---------------------------------------------------------------------------
  4. --- Spawning of programs.
  5. --
  6. -- This module provides methods to start programs and supports startup
  7. -- notifications, which allows for callbacks and applying properties to the
  8. -- program after it has been launched. This requires currently that the
  9. -- applicaton supports them.
  10. --
  11. -- **Rules of thumb when a shell is needed**:
  12. --
  13. -- * A shell is required when the commands contain `&&`, `;`, `||`, `&` or
  14. -- any other unix shell language syntax
  15. -- * When shell variables are defined as part of the command
  16. -- * When the command is a shell alias
  17. --
  18. -- Note that a shell is **not** a terminal emulator. A terminal emulator is
  19. -- something like XTerm, Gnome-terminal or Konsole. A shell is something like
  20. -- `bash`, `zsh`, `busybox sh` or `Debian ash`.
  21. --
  22. -- If you wish to open a process in a terminal window, check that your terminal
  23. -- emulator supports the common `-e` option. If it does, then something like
  24. -- this should work:
  25. --
  26. -- awful.spawn(terminal.." -e my_command")
  27. --
  28. -- Note that some terminals, such as rxvt-unicode (urxvt) support full commands
  29. -- using quotes, while other terminal emulators require to use quoting.
  30. --
  31. -- **Understanding clients versus PID versus commands versus class**:
  32. --
  33. -- A *process* has a *PID* (process identifier). It can have 0, 1 or many
  34. -- *window*s.
  35. --
  36. -- A *command* if what is used to start *process*(es). It has no direct relation
  37. -- with *process*, *client* or *window*. When a command is executed, it will
  38. -- usually start a *process* which keeps running until it exits. This however is
  39. -- not always the case as some applications use scripts as command and others
  40. -- use various single-instance mechanisms (usually client/server) and merge
  41. -- with an existing process.
  42. --
  43. -- A *client* corresponds to a *window*. It is owned by a process. It can have
  44. -- both a parent and one or many children. A *client* has a *class*, an
  45. -- *instance*, a *role*, and a *type*. See `client.class`, `client.instance`,
  46. -- `client.role` and `client.type` for more information about these properties.
  47. --
  48. -- **The startup notification protocol**:
  49. --
  50. -- The startup notification protocol is an optional specification implemented
  51. -- by X11 applications to bridge the chain of knowledge between the moment a
  52. -- program is launched to the moment its window (client) is shown. It can be
  53. -- found [on the FreeDesktop.org website](https://www.freedesktop.org/wiki/Specifications/startup-notification-spec/).
  54. --
  55. -- Awesome has support for the various events that are part of the protocol, but
  56. -- the most useful is the identifier, usually identified by its `SNID` acronym in
  57. -- the documentation. It isn't usually necessary to even know it exists, as it
  58. -- is all done automatically. However, if more control is required, the
  59. -- identifier can be specified by an environment variable called
  60. -- `DESKTOP_STARTUP_ID`. For example, let us consider execution of the following
  61. -- command:
  62. --
  63. -- DESKTOP_STARTUP_ID="something_TIME$(date '+%s')" my_command
  64. --
  65. -- This should (if the program correctly implements the protocol) result in
  66. -- `c.startup_id` to at least match `something`.
  67. -- This identifier can then be used in `awful.rules` to configure the client.
  68. --
  69. -- Awesome can automatically set the `DESKTOP_STARTUP_ID` variable. This is used
  70. -- by `awful.spawn` to specify additional rules for the startup. For example:
  71. --
  72. -- awful.spawn("urxvt -e maxima -name CALCULATOR", {
  73. -- floating = true,
  74. -- tag = mouse.screen.selected_tag,
  75. -- placement = awful.placement.bottom_right,
  76. -- })
  77. --
  78. -- This can also be used from the command line:
  79. --
  80. -- awesome-client 'awful=require("awful");
  81. -- awful.spawn("urxvt -e maxima -name CALCULATOR", {
  82. -- floating = true,
  83. -- tag = mouse.screen.selected_tag,
  84. -- placement = awful.placement.bottom_right,
  85. -- })'
  86. --
  87. -- **Getting a command's output**:
  88. --
  89. -- First, do **not** use `io.popen` **ever**. It is synchronous. Synchronous
  90. -- functions **block everything** until they are done. All visual applications
  91. -- lock (as Awesome no longer responds), you will probably lose some keyboard
  92. -- and mouse events and will have higher latency when playing games. This is
  93. -- also true when reading files synchronously, but this is another topic.
  94. --
  95. -- Awesome provides a few ways to get output from commands. One is to use the
  96. -- `Gio` libraries directly. This is usually very complicated, but gives a lot
  97. -- of control on the command execution.
  98. --
  99. -- This modules provides `with_line_callback` and `easy_async` for convenience.
  100. -- First, lets add this bash command to `rc.lua`:
  101. --
  102. -- local noisy = [[bash -c '
  103. -- for I in $(seq 1 5); do
  104. -- date
  105. -- echo err >&2
  106. -- sleep 2
  107. -- done
  108. -- ']]
  109. --
  110. -- It prints a bunch of junk on the standard output (*stdout*) and error
  111. -- (*stderr*) streams. This command would block Awesome for 10 seconds if it
  112. -- were executed synchronously, but will not block it at all using the
  113. -- asynchronous functions.
  114. --
  115. -- `with_line_callback` will execute the callbacks every time a new line is
  116. -- printed by the command:
  117. --
  118. -- awful.spawn.with_line_callback(noisy, {
  119. -- stdout = function(line)
  120. -- naughty.notify { text = "LINE:"..line }
  121. -- end,
  122. -- stderr = function(line)
  123. -- naughty.notify { text = "ERR:"..line}
  124. -- end,
  125. -- })
  126. --
  127. -- If only the full output is needed, then `easy_async` is the right choice:
  128. --
  129. -- awful.spawn.easy_async(noisy, function(stdout, stderr, reason, exit_code)
  130. -- naughty.notify { text = stdout }
  131. -- end)
  132. --
  133. -- **Default applications**:
  134. --
  135. -- If the intent is to open a file/document, then it is recommended to use the
  136. -- following standard command. The default application will be selected
  137. -- according to the [Shared MIME-info Database](https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html)
  138. -- specification. The `xdg-utils` package provided by most distributions
  139. -- includes the `xdg-open` command:
  140. --
  141. -- awful.spawn({"xdg-open", "/path/to/file"})
  142. --
  143. -- Awesome **does not** manage, modify or otherwise influence the database
  144. -- for default applications. For information about how to do this, consult the
  145. -- [ARCH Linux Wiki](https://wiki.archlinux.org/index.php/default_applications).
  146. --
  147. -- If you wish to change how the default applications behave, then consult the
  148. -- [Desktop Entry](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html)
  149. -- specification.
  150. --
  151. -- @author Julien Danjou <julien@danjou.info>
  152. -- @author Emmanuel Lepage Vallee <elv1313@gmail.com>
  153. -- @copyright 2008 Julien Danjou
  154. -- @copyright 2014 Emmanuel Lepage Vallee
  155. -- @module awful.spawn
  156. ---------------------------------------------------------------------------
  157.  
  158. local capi =
  159. {
  160. awesome = awesome,
  161. mouse = mouse,
  162. client = client,
  163. }
  164. local lgi = require("lgi")
  165. local Gio = lgi.Gio
  166. local GLib = lgi.GLib
  167. local util = require("awful.util")
  168. local protected_call = require("gears.protected_call")
  169.  
  170. local spawn = {}
  171.  
  172.  
  173. local end_of_file
  174. do
  175. -- API changes, bug fixes and lots of fun. Figure out how a EOF is signalled.
  176. local input
  177. if not pcall(function()
  178. -- No idea when this API changed, but some versions expect a string,
  179. -- others a table with some special(?) entries
  180. input = Gio.DataInputStream.new(Gio.MemoryInputStream.new_from_data(""))
  181. end) then
  182. input = Gio.DataInputStream.new(Gio.MemoryInputStream.new_from_data({}))
  183. end
  184. local line, length = input:read_line()
  185. if not line then
  186. -- Fixed in 2016: NULL on the C side is transformed to nil in Lua
  187. end_of_file = function(arg)
  188. return not arg
  189. end
  190. elseif tostring(line) == "" and #line ~= length then
  191. -- "Historic" behaviour for end-of-file:
  192. -- - NULL is turned into an empty string
  193. -- - The length variable is not initialized
  194. -- It's highly unlikely that the uninitialized variable has value zero.
  195. -- Use this hack to detect EOF.
  196. end_of_file = function(arg1, arg2)
  197. return #arg1 ~= arg2
  198. end
  199. else
  200. assert(tostring(line) == "", "Cannot determine how to detect EOF")
  201. -- The above uninitialized variable was fixed and thus length is
  202. -- always 0 when line is NULL in C. We cannot tell apart an empty line and
  203. -- EOF in this case.
  204. require("gears.debug").print_warning("Cannot reliably detect EOF on an "
  205. .. "GIOInputStream with this LGI version")
  206. end_of_file = function(arg)
  207. return tostring(arg) == ""
  208. end
  209. end
  210. end
  211.  
  212. spawn.snid_buffer = {}
  213.  
  214. function spawn.on_snid_callback(c)
  215. local entry = spawn.snid_buffer[c.startup_id]
  216. if entry then
  217. local props = entry[1]
  218. local callback = entry[2]
  219. c:emit_signal("spawn::completed_with_payload", props, callback)
  220. spawn.snid_buffer[c.startup_id] = nil
  221. end
  222. end
  223.  
  224. function spawn.on_snid_cancel(id)
  225. if spawn.snid_buffer[id] then
  226. spawn.snid_buffer[id] = nil
  227. end
  228. end
  229.  
  230. --- Spawn a program, and optionally apply properties and/or run a callback.
  231. --
  232. -- Applying properties or running a callback requires the program/client to
  233. -- support startup notifications.
  234. --
  235. -- See `awful.rules.execute` for more details about the format of `sn_rules`.
  236. --
  237. -- @tparam string|table cmd The command.
  238. -- @tparam[opt=true] table|boolean sn_rules A table of properties to be applied
  239. -- after startup; `false` to disable startup notifications.
  240. -- @tparam[opt] function callback A callback function to be run after startup.
  241. -- @treturn[1] integer The forked PID.
  242. -- @treturn[1] ?string The startup notification ID, if `sn` is not false, or
  243. -- a `callback` is provided.
  244. -- @treturn[2] string Error message.
  245. function spawn.spawn(cmd, sn_rules, callback)
  246. if cmd and cmd ~= "" then
  247. local enable_sn = (sn_rules ~= false or callback)
  248. enable_sn = not not enable_sn -- Force into a boolean.
  249. local pid, snid = capi.awesome.spawn(cmd, enable_sn)
  250. -- The snid will be nil in case of failure
  251. if snid then
  252. sn_rules = type(sn_rules) ~= "boolean" and sn_rules or {}
  253. spawn.snid_buffer[snid] = { sn_rules, { callback } }
  254. end
  255. return pid, snid
  256. end
  257. -- For consistency
  258. return "Error: No command to execute"
  259. end
  260.  
  261. --- Spawn a program using the shell.
  262. -- This calls `cmd` with `$SHELL -c` (via `awful.util.shell`).
  263. -- @tparam string cmd The command.
  264. function spawn.with_shell(cmd)
  265. if cmd and cmd ~= "" then
  266. cmd = { util.shell, "-c", cmd }
  267. return capi.awesome.spawn(cmd, false)
  268. end
  269. end
  270.  
  271. --- Spawn a program and asynchronously capture its output line by line.
  272. -- @tparam string|table cmd The command.
  273. -- @tab callbacks Table containing callbacks that should be invoked on
  274. -- various conditions.
  275. -- @tparam[opt] function callbacks.stdout Function that is called with each
  276. -- line of output on stdout, e.g. `stdout(line)`.
  277. -- @tparam[opt] function callbacks.stderr Function that is called with each
  278. -- line of output on stderr, e.g. `stderr(line)`.
  279. -- @tparam[opt] function callbacks.output_done Function to call when no more
  280. -- output is produced.
  281. -- @tparam[opt] function callbacks.exit Function to call when the spawned
  282. -- process exits. This function gets the exit reason and code as its
  283. -- arguments.
  284. -- The reason can be "exit" or "signal".
  285. -- For "exit", the second argument is the exit code.
  286. -- For "signal", the second argument is the signal causing process
  287. -- termination.
  288. -- @treturn[1] Integer the PID of the forked process.
  289. -- @treturn[2] string Error message.
  290. function spawn.with_line_callback(cmd, callbacks)
  291. local stdout_callback, stderr_callback, done_callback, exit_callback =
  292. callbacks.stdout, callbacks.stderr, callbacks.output_done, callbacks.exit
  293. local have_stdout, have_stderr = stdout_callback ~= nil, stderr_callback ~= nil
  294. local pid, _, stdin, stdout, stderr = capi.awesome.spawn(cmd,
  295. false, false, have_stdout, have_stderr, exit_callback)
  296. if type(pid) == "string" then
  297. -- Error
  298. return pid
  299. end
  300.  
  301. local done_before = false
  302. local function step_done()
  303. if have_stdout and have_stderr and not done_before then
  304. done_before = true
  305. return
  306. end
  307. if done_callback then
  308. done_callback()
  309. end
  310. end
  311. if have_stdout then
  312. spawn.read_lines(Gio.UnixInputStream.new(stdout, true),
  313. stdout_callback, step_done, true)
  314. end
  315. if have_stderr then
  316. spawn.read_lines(Gio.UnixInputStream.new(stderr, true),
  317. stderr_callback, step_done, true)
  318. end
  319. assert(stdin == nil)
  320. return pid
  321. end
  322.  
  323. --- Asynchronously spawn a program and capture its output.
  324. -- (wraps `spawn.with_line_callback`).
  325. -- @tparam string|table cmd The command.
  326. -- @tab callback Function with the following arguments
  327. -- @tparam string callback.stdout Output on stdout.
  328. -- @tparam string callback.stderr Output on stderr.
  329. -- @tparam string callback.exitreason Exit reason ("exit" or "signal").
  330. -- @tparam integer callback.exitcode Exit code (exit code or signal number,
  331. -- depending on "exitreason").
  332. -- @treturn[1] Integer the PID of the forked process.
  333. -- @treturn[2] string Error message.
  334. -- @see spawn.with_line_callback
  335. function spawn.easy_async(cmd, callback)
  336. local stdout = ''
  337. local stderr = ''
  338. local exitcode, exitreason
  339. local function parse_stdout(str)
  340. stdout = stdout .. str .. "\n"
  341. end
  342. local function parse_stderr(str)
  343. stderr = stderr .. str .. "\n"
  344. end
  345. local function done_callback()
  346. return callback(stdout, stderr, exitreason, exitcode)
  347. end
  348. local exit_callback_fired = false
  349. local output_done_callback_fired = false
  350. local function exit_callback(reason, code)
  351. exitcode = code
  352. exitreason = reason
  353. exit_callback_fired = true
  354. if output_done_callback_fired then
  355. return done_callback()
  356. end
  357. end
  358. local function output_done_callback()
  359. output_done_callback_fired = true
  360. if exit_callback_fired then
  361. return done_callback()
  362. end
  363. end
  364. return spawn.with_line_callback(
  365. cmd, {
  366. stdout=parse_stdout,
  367. stderr=parse_stderr,
  368. exit=exit_callback,
  369. output_done=output_done_callback
  370. })
  371. end
  372.  
  373. --- Call `spawn.easy_async` with a shell.
  374. -- This calls `cmd` with `$SHELL -c` (via `awful.util.shell`).
  375. -- @tparam string|table cmd The command.
  376. -- @tab callback Function with the following arguments
  377. -- @tparam string callback.stdout Output on stdout.
  378. -- @tparam string callback.stderr Output on stderr.
  379. -- @tparam string callback.exitreason Exit reason ("exit" or "signal").
  380. -- @tparam integer callback.exitcode Exit code (exit code or signal number,
  381. -- depending on "exitreason").
  382. -- @treturn[1] Integer the PID of the forked process.
  383. -- @treturn[2] string Error message.
  384. -- @see spawn.with_line_callback
  385. function spawn.easy_async_with_shell(cmd, callback)
  386. return spawn.easy_async({ util.shell, "-c", cmd or "" }, callback)
  387. end
  388.  
  389. --- Read lines from a Gio input stream
  390. -- @tparam Gio.InputStream input_stream The input stream to read from.
  391. -- @tparam function line_callback Function that is called with each line
  392. -- read, e.g. `line_callback(line_from_stream)`.
  393. -- @tparam[opt] function done_callback Function that is called when the
  394. -- operation finishes (e.g. due to end of file).
  395. -- @tparam[opt=false] boolean close Should the stream be closed after end-of-file?
  396. function spawn.read_lines(input_stream, line_callback, done_callback, close)
  397. local stream = Gio.DataInputStream.new(input_stream)
  398. local function done()
  399. if close then
  400. stream:close()
  401. end
  402. if done_callback then
  403. protected_call(done_callback)
  404. end
  405. end
  406. local start_read, finish_read
  407. start_read = function()
  408. stream:read_line_async(GLib.PRIORITY_DEFAULT, nil, finish_read)
  409. end
  410. finish_read = function(obj, res)
  411. local line, length = obj:read_line_finish(res)
  412. if type(length) ~= "number" then
  413. -- Error
  414. print("Error in awful.spawn.read_lines:", tostring(length))
  415. done()
  416. elseif end_of_file(line, length) then
  417. -- End of file
  418. done()
  419. else
  420. -- Read a line
  421. -- This needs tostring() for older lgi versions which returned
  422. -- "GLib.Bytes" instead of Lua strings (I guess)
  423. protected_call(line_callback, tostring(line))
  424.  
  425. -- Read the next line
  426. start_read()
  427. end
  428. end
  429. start_read()
  430. end
  431.  
  432. capi.awesome.connect_signal("spawn::canceled" , spawn.on_snid_cancel )
  433. capi.awesome.connect_signal("spawn::timeout" , spawn.on_snid_cancel )
  434. capi.client.connect_signal ("manage" , spawn.on_snid_callback )
  435.  
  436. return setmetatable(spawn, { __call = function(_, ...) return spawn.spawn(...) end })
  437. -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement