Mirror of Awesome WM window manager
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

spawn.c 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. /*
  2. * spawn.c - Lua configuration management
  3. *
  4. * Copyright © 2009 Julien Danjou <julien@danjou.info>
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along
  17. * with this program; if not, write to the Free Software Foundation, Inc.,
  18. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  19. *
  20. */
  21. /** awesome core API
  22. *
  23. * @author Julien Danjou &lt;julien@danjou.info&gt;
  24. * @copyright 2008-2009 Julien Danjou
  25. * @module awesome
  26. */
  27. /** For some reason the application aborted startup
  28. * @param arg Table which only got the "id" key set
  29. * @signal spawn::canceled
  30. */
  31. /** When one of the fields from the @{spawn::initiated} table changes
  32. * @param arg Table which describes the spawn event
  33. * @signal spawn::change
  34. */
  35. /** An application finished starting
  36. * @param arg Table which only got the "id" key set
  37. * @signal spawn::completed
  38. */
  39. /** When a new client is beginning to start
  40. * @param arg Table which describes the spawn event
  41. * @signal spawn::initiated
  42. */
  43. /** An application started a spawn event but didn't start in time.
  44. * @param arg Table which only got the "id" key set
  45. * @signal spawn::timeout
  46. */
  47. #include "spawn.h"
  48. #include <sys/types.h>
  49. #include <sys/wait.h>
  50. #include <unistd.h>
  51. #include <glib.h>
  52. /** 20 seconds timeout */
  53. #define AWESOME_SPAWN_TIMEOUT 20.0
  54. /** Wrapper for unrefing startup sequence.
  55. */
  56. static inline void
  57. a_sn_startup_sequence_unref(SnStartupSequence **sss)
  58. {
  59. return sn_startup_sequence_unref(*sss);
  60. }
  61. DO_ARRAY(SnStartupSequence *, SnStartupSequence, a_sn_startup_sequence_unref)
  62. /** The array of startup sequence running */
  63. static SnStartupSequence_array_t sn_waits;
  64. typedef struct {
  65. GPid pid;
  66. int exit_callback;
  67. } running_child_t;
  68. static int
  69. compare_pids(const void *a, const void *b)
  70. {
  71. return ((running_child_t *) a)->pid - ((running_child_t *) b)->pid;
  72. }
  73. DO_BARRAY(running_child_t, running_child, DO_NOTHING, compare_pids)
  74. static running_child_array_t running_children;
  75. /** Remove a SnStartupSequence pointer from an array and forget about it.
  76. * \param s The startup sequence to find, remove and unref.
  77. * \return True if found and removed.
  78. */
  79. static inline bool
  80. spawn_sequence_remove(SnStartupSequence *s)
  81. {
  82. for(int i = 0; i < sn_waits.len; i++)
  83. if(sn_waits.tab[i] == s)
  84. {
  85. SnStartupSequence_array_take(&sn_waits, i);
  86. sn_startup_sequence_unref(s);
  87. return true;
  88. }
  89. return false;
  90. }
  91. static gboolean
  92. spawn_monitor_timeout(gpointer sequence)
  93. {
  94. if(spawn_sequence_remove(sequence))
  95. {
  96. signal_t *sig = signal_array_getbyname(&global_signals, "spawn::timeout");
  97. if(sig)
  98. {
  99. /* send a timeout signal */
  100. lua_State *L = globalconf_get_lua_State();
  101. lua_createtable(L, 0, 2);
  102. lua_pushstring(L, sn_startup_sequence_get_id(sequence));
  103. lua_setfield(L, -2, "id");
  104. foreach(func, sig->sigfuncs)
  105. {
  106. lua_pushvalue(L, -1);
  107. luaA_object_push(L, (void *) *func);
  108. luaA_dofunction(L, 1, 0);
  109. }
  110. lua_pop(L, 1);
  111. }
  112. }
  113. sn_startup_sequence_unref(sequence);
  114. return FALSE;
  115. }
  116. static void
  117. spawn_monitor_event(SnMonitorEvent *event, void *data)
  118. {
  119. lua_State *L = globalconf_get_lua_State();
  120. SnStartupSequence *sequence = sn_monitor_event_get_startup_sequence(event);
  121. SnMonitorEventType event_type = sn_monitor_event_get_type(event);
  122. lua_createtable(L, 0, 2);
  123. lua_pushstring(L, sn_startup_sequence_get_id(sequence));
  124. lua_setfield(L, -2, "id");
  125. const char *event_type_str = NULL;
  126. switch(event_type)
  127. {
  128. case SN_MONITOR_EVENT_INITIATED:
  129. /* ref the sequence for the array */
  130. sn_startup_sequence_ref(sequence);
  131. SnStartupSequence_array_append(&sn_waits, sequence);
  132. event_type_str = "spawn::initiated";
  133. /* Add a timeout function so we do not wait for this event to complete
  134. * for ever */
  135. g_timeout_add_seconds(AWESOME_SPAWN_TIMEOUT, spawn_monitor_timeout, sequence);
  136. /* ref the sequence for the callback event */
  137. sn_startup_sequence_ref(sequence);
  138. break;
  139. case SN_MONITOR_EVENT_CHANGED:
  140. event_type_str = "spawn::change";
  141. break;
  142. case SN_MONITOR_EVENT_COMPLETED:
  143. event_type_str = "spawn::completed";
  144. break;
  145. case SN_MONITOR_EVENT_CANCELED:
  146. event_type_str = "spawn::canceled";
  147. break;
  148. }
  149. /* common actions */
  150. switch(event_type)
  151. {
  152. case SN_MONITOR_EVENT_INITIATED:
  153. case SN_MONITOR_EVENT_CHANGED:
  154. {
  155. const char *s = sn_startup_sequence_get_name(sequence);
  156. if(s)
  157. {
  158. lua_pushstring(L, s);
  159. lua_setfield(L, -2, "name");
  160. }
  161. if((s = sn_startup_sequence_get_description(sequence)))
  162. {
  163. lua_pushstring(L, s);
  164. lua_setfield(L, -2, "description");
  165. }
  166. lua_pushinteger(L, sn_startup_sequence_get_workspace(sequence));
  167. lua_setfield(L, -2, "workspace");
  168. if((s = sn_startup_sequence_get_binary_name(sequence)))
  169. {
  170. lua_pushstring(L, s);
  171. lua_setfield(L, -2, "binary_name");
  172. }
  173. if((s = sn_startup_sequence_get_icon_name(sequence)))
  174. {
  175. lua_pushstring(L, s);
  176. lua_setfield(L, -2, "icon_name");
  177. }
  178. if((s = sn_startup_sequence_get_wmclass(sequence)))
  179. {
  180. lua_pushstring(L, s);
  181. lua_setfield(L, -2, "wmclass");
  182. }
  183. }
  184. break;
  185. case SN_MONITOR_EVENT_COMPLETED:
  186. case SN_MONITOR_EVENT_CANCELED:
  187. spawn_sequence_remove(sequence);
  188. break;
  189. }
  190. /* send the signal */
  191. signal_t *sig = signal_array_getbyname(&global_signals, event_type_str);
  192. if(sig)
  193. {
  194. foreach(func, sig->sigfuncs)
  195. {
  196. lua_pushvalue(L, -1);
  197. luaA_object_push(L, (void *) *func);
  198. luaA_dofunction(L, 1, 0);
  199. }
  200. lua_pop(L, 1);
  201. }
  202. }
  203. /** Tell the spawn module that an app has been started.
  204. * \param c The client that just started.
  205. * \param startup_id The startup id of the started application.
  206. */
  207. void
  208. spawn_start_notify(client_t *c, const char * startup_id)
  209. {
  210. foreach(_seq, sn_waits)
  211. {
  212. SnStartupSequence *seq = *_seq;
  213. bool found = false;
  214. const char *seqid = sn_startup_sequence_get_id(seq);
  215. if (A_STRNEQ(seqid, startup_id))
  216. found = true;
  217. else
  218. {
  219. const char *seqclass = sn_startup_sequence_get_wmclass(seq);
  220. if (A_STREQ(seqclass, c->class) || A_STREQ(seqclass, c->instance))
  221. found = true;
  222. else
  223. {
  224. const char *seqbin = sn_startup_sequence_get_binary_name(seq);
  225. if (A_STREQ_CASE(seqbin, c->class) || A_STREQ_CASE(seqbin, c->instance))
  226. found = true;
  227. }
  228. }
  229. if(found)
  230. {
  231. sn_startup_sequence_complete(seq);
  232. break;
  233. }
  234. }
  235. }
  236. /** Initialize program spawner.
  237. */
  238. void
  239. spawn_init(void)
  240. {
  241. globalconf.sndisplay = sn_xcb_display_new(globalconf.connection, NULL, NULL);
  242. globalconf.snmonitor = sn_monitor_context_new(globalconf.sndisplay,
  243. globalconf.default_screen,
  244. spawn_monitor_event,
  245. NULL, NULL);
  246. }
  247. static gboolean
  248. spawn_launchee_timeout(gpointer context)
  249. {
  250. sn_launcher_context_complete(context);
  251. sn_launcher_context_unref(context);
  252. return FALSE;
  253. }
  254. static void
  255. spawn_callback(gpointer user_data)
  256. {
  257. SnLauncherContext *context = (SnLauncherContext *) user_data;
  258. setsid();
  259. if (context)
  260. sn_launcher_context_setup_child_process(context);
  261. else
  262. /* Unset in case awesome was already started with this variable set */
  263. unsetenv("DESKTOP_STARTUP_ID");
  264. }
  265. /** Convert a Lua table of strings to a char** array.
  266. * \param L The Lua VM state.
  267. * \param idx The index of the table that we should parse.
  268. * \return The argv array.
  269. */
  270. static gchar **
  271. parse_table_array(lua_State *L, int idx, GError **error)
  272. {
  273. gchar **argv = NULL;
  274. size_t i, len;
  275. luaL_checktype(L, idx, LUA_TTABLE);
  276. idx = luaA_absindex(L, idx);
  277. len = luaA_rawlen(L, idx);
  278. /* First verify that the table is sane: All integer keys must contain
  279. * strings. Do this by pushing them all onto the stack.
  280. */
  281. for (i = 0; i < len; i++)
  282. {
  283. lua_rawgeti(L, idx, i+1);
  284. if (lua_type(L, -1) != LUA_TSTRING)
  285. {
  286. g_set_error(error, G_SPAWN_ERROR, 0,
  287. "Non-string argument at table index %zd", i+1);
  288. return NULL;
  289. }
  290. }
  291. /* From this point on nothing can go wrong and so we can safely allocate
  292. * memory.
  293. */
  294. argv = g_new0(gchar *, len + 1);
  295. for (i = 0; i < len; i++)
  296. {
  297. argv[len - i - 1] = g_strdup(lua_tostring(L, -1));
  298. lua_pop(L, 1);
  299. }
  300. return argv;
  301. }
  302. /** Parse a command line.
  303. * \param L The Lua VM state.
  304. * \param idx The index of the argument that we should parse.
  305. * \return The argv array for the new process.
  306. */
  307. static gchar **
  308. parse_command(lua_State *L, int idx, GError **error)
  309. {
  310. gchar **argv = NULL;
  311. if (lua_isstring(L, idx))
  312. {
  313. const char *cmd = luaL_checkstring(L, idx);
  314. if(!g_shell_parse_argv(cmd, NULL, &argv, error))
  315. return NULL;
  316. }
  317. else if (lua_istable(L, idx))
  318. {
  319. argv = parse_table_array(L, idx, error);
  320. }
  321. else
  322. {
  323. g_set_error_literal(error, G_SPAWN_ERROR, 0,
  324. "Invalid argument to spawn(), expected string or table");
  325. return NULL;
  326. }
  327. return argv;
  328. }
  329. /** Callback for when a spawned process exits. */
  330. void
  331. spawn_child_exited(pid_t pid, int status)
  332. {
  333. int exit_callback;
  334. running_child_t needle = { .pid = pid };
  335. lua_State *L = globalconf_get_lua_State();
  336. running_child_t *child = running_child_array_lookup(&running_children, &needle);
  337. if (child == NULL) {
  338. warn("Unknown child %d exited with %s %d",
  339. (int)pid, WIFEXITED(status) ? "status" : "signal", status);
  340. return;
  341. }
  342. exit_callback = child->exit_callback;
  343. running_child_array_remove(&running_children, child);
  344. /* 'Decode' the exit status */
  345. if (WIFEXITED(status)) {
  346. lua_pushliteral(L, "exit");
  347. lua_pushinteger(L, WEXITSTATUS(status));
  348. } else {
  349. check(WIFSIGNALED(status));
  350. lua_pushliteral(L, "signal");
  351. lua_pushinteger(L, WTERMSIG(status));
  352. }
  353. lua_rawgeti(L, LUA_REGISTRYINDEX, exit_callback);
  354. luaA_dofunction(L, 2, 0);
  355. luaA_unregister(L, &exit_callback);
  356. }
  357. /** Spawn a program.
  358. * The program will be started on the default screen.
  359. *
  360. * @tparam string|table cmd The command to launch.
  361. * @tparam[opt=true] boolean use_sn Use startup-notification?
  362. * @tparam[opt=false] boolean stdin Return a fd for stdin?
  363. * @tparam[opt=false] boolean stdout Return a fd for stdout?
  364. * @tparam[opt=false] boolean stderr Return a fd for stderr?
  365. * @tparam[opt=nil] function exit_callback Function to call on process exit. The
  366. * function arguments will be type of exit ("exit" or "signal") and the exit
  367. * code / the signal number causing process termination.
  368. * @tparam[opt=nil] table cmd The environment to use for the spawned program.
  369. * Without this the spawned process inherits awesome's environment.
  370. * @treturn[1] integer Process ID if everything is OK.
  371. * @treturn[1] string Startup-notification ID, if `use_sn` is true.
  372. * @treturn[1] integer stdin, if `stdin` is true.
  373. * @treturn[1] integer stdout, if `stdout` is true.
  374. * @treturn[1] integer stderr, if `stderr` is true.
  375. * @treturn[2] string An error string if an error occurred.
  376. * @staticfct spawn
  377. */
  378. int
  379. luaA_spawn(lua_State *L)
  380. {
  381. gchar **argv = NULL, **envp = NULL;
  382. bool use_sn = true, return_stdin = false, return_stdout = false, return_stderr = false;
  383. int stdin_fd = -1, stdout_fd = -1, stderr_fd = -1;
  384. int *stdin_ptr = NULL, *stdout_ptr = NULL, *stderr_ptr = NULL;
  385. GSpawnFlags flags = 0;
  386. gboolean retval;
  387. GPid pid;
  388. if(lua_gettop(L) >= 2)
  389. use_sn = luaA_checkboolean(L, 2);
  390. if(lua_gettop(L) >= 3)
  391. return_stdin = luaA_checkboolean(L, 3);
  392. if(lua_gettop(L) >= 4)
  393. return_stdout = luaA_checkboolean(L, 4);
  394. if(lua_gettop(L) >= 5)
  395. return_stderr = luaA_checkboolean(L, 5);
  396. if (!lua_isnoneornil(L, 6))
  397. {
  398. luaA_checkfunction(L, 6);
  399. flags |= G_SPAWN_DO_NOT_REAP_CHILD;
  400. }
  401. if(return_stdin)
  402. stdin_ptr = &stdin_fd;
  403. if(return_stdout)
  404. stdout_ptr = &stdout_fd;
  405. if(return_stderr)
  406. stderr_ptr = &stderr_fd;
  407. GError *error = NULL;
  408. argv = parse_command(L, 1, &error);
  409. if(!argv || !argv[0])
  410. {
  411. g_strfreev(argv);
  412. if (error) {
  413. lua_pushfstring(L, "spawn: parse error: %s", error->message);
  414. g_error_free(error);
  415. }
  416. else
  417. lua_pushliteral(L, "spawn: There is nothing to execute");
  418. return 1;
  419. }
  420. if (!lua_isnoneornil(L, 7)) {
  421. envp = parse_table_array(L, 7, &error);
  422. if (error) {
  423. g_strfreev(argv);
  424. g_strfreev(envp);
  425. lua_pushfstring(L, "spawn: environment parse error: %s", error->message);
  426. g_error_free(error);
  427. return 1;
  428. }
  429. }
  430. SnLauncherContext *context = NULL;
  431. if(use_sn)
  432. {
  433. context = sn_launcher_context_new(globalconf.sndisplay, globalconf.default_screen);
  434. sn_launcher_context_set_name(context, "awesome");
  435. sn_launcher_context_set_description(context, "awesome spawn");
  436. sn_launcher_context_set_binary_name(context, argv[0]);
  437. sn_launcher_context_initiate(context, "awesome", argv[0], globalconf.timestamp);
  438. /* app will have AWESOME_SPAWN_TIMEOUT seconds to complete,
  439. * or the timeout function will terminate the launch sequence anyway */
  440. g_timeout_add_seconds(AWESOME_SPAWN_TIMEOUT, spawn_launchee_timeout, context);
  441. }
  442. flags |= G_SPAWN_SEARCH_PATH | G_SPAWN_CLOEXEC_PIPES;
  443. retval = g_spawn_async_with_pipes(NULL, argv, envp, flags,
  444. spawn_callback, context, &pid,
  445. stdin_ptr, stdout_ptr, stderr_ptr, &error);
  446. g_strfreev(argv);
  447. g_strfreev(envp);
  448. if(!retval)
  449. {
  450. lua_pushstring(L, error->message);
  451. g_error_free(error);
  452. if(context)
  453. sn_launcher_context_complete(context);
  454. return 1;
  455. }
  456. if(flags & G_SPAWN_DO_NOT_REAP_CHILD)
  457. {
  458. /* Only do this down here to avoid leaks in case of errors */
  459. running_child_t child = { .pid = pid, .exit_callback = LUA_REFNIL };
  460. luaA_registerfct(L, 6, &child.exit_callback);
  461. running_child_array_insert(&running_children, child);
  462. }
  463. /* push pid on stack */
  464. lua_pushinteger(L, pid);
  465. /* push sn on stack */
  466. if (context)
  467. lua_pushstring(L, sn_launcher_context_get_startup_id(context));
  468. else
  469. lua_pushnil(L);
  470. if(return_stdin)
  471. lua_pushinteger(L, stdin_fd);
  472. else
  473. lua_pushnil(L);
  474. if(return_stdout)
  475. lua_pushinteger(L, stdout_fd);
  476. else
  477. lua_pushnil(L);
  478. if(return_stderr)
  479. lua_pushinteger(L, stderr_fd);
  480. else
  481. lua_pushnil(L);
  482. return 5;
  483. }
  484. // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80