2009-04-03 13:09:17 +02:00
|
|
|
/*
|
|
|
|
* spawn.c - Lua configuration management
|
|
|
|
*
|
|
|
|
* Copyright © 2009 Julien Danjou <julien@danjou.info>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
2010-10-06 20:08:24 +02:00
|
|
|
#include <glib.h>
|
2009-04-27 20:18:39 +02:00
|
|
|
|
2009-04-03 13:09:17 +02:00
|
|
|
#include "spawn.h"
|
2009-04-03 16:30:18 +02:00
|
|
|
#include "screen.h"
|
2009-04-03 13:09:17 +02:00
|
|
|
#include "luaa.h"
|
2009-04-03 16:30:18 +02:00
|
|
|
#include "event.h"
|
|
|
|
|
|
|
|
/** 20 seconds timeout */
|
|
|
|
#define AWESOME_SPAWN_TIMEOUT 20.0
|
|
|
|
|
|
|
|
/** Wrapper for unrefing startup sequence.
|
|
|
|
*/
|
|
|
|
static inline void
|
|
|
|
a_sn_startup_sequence_unref(SnStartupSequence **sss)
|
|
|
|
{
|
|
|
|
return sn_startup_sequence_unref(*sss);
|
|
|
|
}
|
|
|
|
|
|
|
|
DO_ARRAY(SnStartupSequence *, SnStartupSequence, a_sn_startup_sequence_unref)
|
|
|
|
|
|
|
|
/** The array of startup sequence running */
|
|
|
|
SnStartupSequence_array_t sn_waits;
|
|
|
|
|
|
|
|
/** Remove a SnStartupSequence pointer from an array and forget about it.
|
|
|
|
* \param s The startup sequence to found, remove and unref.
|
2009-05-10 01:10:39 +02:00
|
|
|
* \return True if found and removed.
|
2009-04-03 16:30:18 +02:00
|
|
|
*/
|
2009-05-10 01:10:39 +02:00
|
|
|
static inline bool
|
2009-04-03 16:30:18 +02:00
|
|
|
spawn_sequence_remove(SnStartupSequence *s)
|
|
|
|
{
|
|
|
|
for(int i = 0; i < sn_waits.len; i++)
|
|
|
|
if(sn_waits.tab[i] == s)
|
|
|
|
{
|
|
|
|
SnStartupSequence_array_take(&sn_waits, i);
|
2009-05-12 08:11:35 +02:00
|
|
|
sn_startup_sequence_unref(s);
|
2009-05-10 01:10:39 +02:00
|
|
|
return true;
|
2009-04-03 16:30:18 +02:00
|
|
|
}
|
2009-05-10 01:10:39 +02:00
|
|
|
return false;
|
2009-04-03 16:30:18 +02:00
|
|
|
}
|
|
|
|
|
2012-11-21 21:01:12 +01:00
|
|
|
static gboolean
|
|
|
|
spawn_monitor_timeout(gpointer sequence)
|
2009-04-03 16:30:18 +02:00
|
|
|
{
|
2012-11-21 21:01:12 +01:00
|
|
|
if(spawn_sequence_remove(sequence))
|
2009-05-09 16:03:27 +02:00
|
|
|
{
|
2009-07-31 16:28:17 +02:00
|
|
|
signal_t *sig = signal_array_getbyid(&global_signals,
|
|
|
|
a_strhash((const unsigned char *) "spawn::timeout"));
|
|
|
|
if(sig)
|
|
|
|
{
|
|
|
|
/* send a timeout signal */
|
|
|
|
lua_createtable(globalconf.L, 0, 2);
|
2012-11-21 21:01:12 +01:00
|
|
|
lua_pushstring(globalconf.L, sn_startup_sequence_get_id(sequence));
|
2009-07-31 16:28:17 +02:00
|
|
|
lua_setfield(globalconf.L, -2, "id");
|
|
|
|
foreach(func, sig->sigfuncs)
|
|
|
|
{
|
|
|
|
lua_pushvalue(globalconf.L, -1);
|
|
|
|
luaA_object_push(globalconf.L, (void *) *func);
|
|
|
|
luaA_dofunction(globalconf.L, 1, 0);
|
|
|
|
}
|
|
|
|
lua_pop(globalconf.L, 1);
|
|
|
|
}
|
2010-08-25 23:00:36 +02:00
|
|
|
else
|
|
|
|
warn("spawn::timeout signal is missing");
|
2009-05-09 16:03:27 +02:00
|
|
|
}
|
2012-11-21 21:01:12 +01:00
|
|
|
sn_startup_sequence_unref(sequence);
|
|
|
|
return FALSE;
|
2009-04-03 16:30:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
spawn_monitor_event(SnMonitorEvent *event, void *data)
|
|
|
|
{
|
|
|
|
SnStartupSequence *sequence = sn_monitor_event_get_startup_sequence(event);
|
|
|
|
SnMonitorEventType event_type = sn_monitor_event_get_type(event);
|
|
|
|
|
2009-04-25 15:04:27 +02:00
|
|
|
lua_createtable(globalconf.L, 0, 2);
|
2009-04-03 16:30:18 +02:00
|
|
|
lua_pushstring(globalconf.L, sn_startup_sequence_get_id(sequence));
|
|
|
|
lua_setfield(globalconf.L, -2, "id");
|
|
|
|
|
2009-07-31 16:28:17 +02:00
|
|
|
const char *event_type_str = NULL;
|
|
|
|
|
2009-04-03 16:30:18 +02:00
|
|
|
switch(event_type)
|
|
|
|
{
|
|
|
|
case SN_MONITOR_EVENT_INITIATED:
|
2009-05-09 15:48:45 +02:00
|
|
|
/* ref the sequence for the array */
|
2009-04-03 16:30:18 +02:00
|
|
|
sn_startup_sequence_ref(sequence);
|
|
|
|
SnStartupSequence_array_append(&sn_waits, sequence);
|
2009-07-31 16:28:17 +02:00
|
|
|
event_type_str = "spawn::initiated";
|
2009-04-03 16:30:18 +02:00
|
|
|
|
|
|
|
/* Add a timeout function so we do not wait for this event to complete
|
|
|
|
* for ever */
|
2012-11-21 21:01:12 +01:00
|
|
|
g_timeout_add_seconds(AWESOME_SPAWN_TIMEOUT, spawn_monitor_timeout, sequence);
|
2009-05-09 15:48:45 +02:00
|
|
|
/* ref the sequence for the callback event */
|
|
|
|
sn_startup_sequence_ref(sequence);
|
2009-04-03 16:30:18 +02:00
|
|
|
break;
|
|
|
|
case SN_MONITOR_EVENT_CHANGED:
|
2009-07-31 16:28:17 +02:00
|
|
|
event_type_str = "spawn::change";
|
2009-04-03 16:30:18 +02:00
|
|
|
break;
|
|
|
|
case SN_MONITOR_EVENT_COMPLETED:
|
2009-07-31 16:28:17 +02:00
|
|
|
event_type_str = "spawn::completed";
|
2009-04-03 16:30:18 +02:00
|
|
|
break;
|
|
|
|
case SN_MONITOR_EVENT_CANCELED:
|
2009-07-31 16:28:17 +02:00
|
|
|
event_type_str = "spawn::canceled";
|
2009-04-03 16:30:18 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* common actions */
|
|
|
|
switch(event_type)
|
|
|
|
{
|
|
|
|
case SN_MONITOR_EVENT_INITIATED:
|
|
|
|
case SN_MONITOR_EVENT_CHANGED:
|
|
|
|
{
|
|
|
|
const char *s = sn_startup_sequence_get_name(sequence);
|
|
|
|
if(s)
|
|
|
|
{
|
|
|
|
lua_pushstring(globalconf.L, s);
|
|
|
|
lua_setfield(globalconf.L, -2, "name");
|
|
|
|
}
|
|
|
|
|
|
|
|
if((s = sn_startup_sequence_get_description(sequence)))
|
|
|
|
{
|
|
|
|
lua_pushstring(globalconf.L, s);
|
|
|
|
lua_setfield(globalconf.L, -2, "description");
|
|
|
|
}
|
|
|
|
|
|
|
|
lua_pushnumber(globalconf.L, sn_startup_sequence_get_workspace(sequence));
|
|
|
|
lua_setfield(globalconf.L, -2, "workspace");
|
|
|
|
|
|
|
|
if((s = sn_startup_sequence_get_binary_name(sequence)))
|
|
|
|
{
|
|
|
|
lua_pushstring(globalconf.L, s);
|
|
|
|
lua_setfield(globalconf.L, -2, "binary_name");
|
|
|
|
}
|
|
|
|
|
|
|
|
if((s = sn_startup_sequence_get_icon_name(sequence)))
|
|
|
|
{
|
|
|
|
lua_pushstring(globalconf.L, s);
|
|
|
|
lua_setfield(globalconf.L, -2, "icon_name");
|
|
|
|
}
|
|
|
|
|
|
|
|
if((s = sn_startup_sequence_get_wmclass(sequence)))
|
|
|
|
{
|
|
|
|
lua_pushstring(globalconf.L, s);
|
|
|
|
lua_setfield(globalconf.L, -2, "wmclass");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SN_MONITOR_EVENT_COMPLETED:
|
|
|
|
case SN_MONITOR_EVENT_CANCELED:
|
|
|
|
spawn_sequence_remove(sequence);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2009-07-31 16:28:17 +02:00
|
|
|
/* send the signal */
|
|
|
|
signal_t *sig = signal_array_getbyid(&global_signals,
|
|
|
|
a_strhash((const unsigned char *) event_type_str));
|
|
|
|
|
|
|
|
if(sig)
|
|
|
|
{
|
|
|
|
foreach(func, sig->sigfuncs)
|
|
|
|
{
|
|
|
|
lua_pushvalue(globalconf.L, -1);
|
|
|
|
luaA_object_push(globalconf.L, (void *) *func);
|
|
|
|
luaA_dofunction(globalconf.L, 1, 0);
|
|
|
|
}
|
|
|
|
lua_pop(globalconf.L, 1);
|
|
|
|
}
|
2010-08-25 23:00:36 +02:00
|
|
|
else
|
|
|
|
warn("%s signal is missing", event_type_str);
|
2009-04-03 16:30:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Tell the spawn module that an app has been started.
|
|
|
|
* \param c The client that just started.
|
2009-08-24 10:16:52 +02:00
|
|
|
* \param startup_id The startup id of the started application.
|
2009-04-03 16:30:18 +02:00
|
|
|
*/
|
|
|
|
void
|
2009-08-24 10:16:52 +02:00
|
|
|
spawn_start_notify(client_t *c, const char * startup_id)
|
2009-04-03 16:30:18 +02:00
|
|
|
{
|
|
|
|
foreach(_seq, sn_waits)
|
|
|
|
{
|
|
|
|
SnStartupSequence *seq = *_seq;
|
|
|
|
bool found = false;
|
|
|
|
const char *seqid = sn_startup_sequence_get_id(seq);
|
|
|
|
|
2011-11-17 16:38:39 +01:00
|
|
|
if (A_STRNEQ(seqid, startup_id))
|
2009-04-03 16:30:18 +02:00
|
|
|
found = true;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const char *seqclass = sn_startup_sequence_get_wmclass(seq);
|
2011-11-17 16:38:39 +01:00
|
|
|
if (A_STREQ(seqclass, c->class) || A_STREQ(seqclass, c->instance))
|
2009-04-03 16:30:18 +02:00
|
|
|
found = true;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const char *seqbin = sn_startup_sequence_get_binary_name(seq);
|
2011-11-17 16:38:39 +01:00
|
|
|
if (A_STREQ_CASE(seqbin, c->class) || A_STREQ_CASE(seqbin, c->instance))
|
2009-04-03 16:30:18 +02:00
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(found)
|
|
|
|
{
|
|
|
|
sn_startup_sequence_complete(seq);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Initialize program spawner.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
spawn_init(void)
|
|
|
|
{
|
|
|
|
globalconf.sndisplay = sn_xcb_display_new(globalconf.connection, NULL, NULL);
|
|
|
|
|
2010-08-16 13:57:31 +02:00
|
|
|
globalconf.snmonitor = sn_monitor_context_new(globalconf.sndisplay,
|
|
|
|
globalconf.default_screen,
|
|
|
|
spawn_monitor_event,
|
|
|
|
NULL, NULL);
|
2010-08-25 23:00:36 +02:00
|
|
|
|
2010-08-26 17:46:17 +02:00
|
|
|
signal_add(&global_signals, "spawn::canceled");
|
2010-08-25 23:00:36 +02:00
|
|
|
signal_add(&global_signals, "spawn::change");
|
2010-08-26 17:46:17 +02:00
|
|
|
signal_add(&global_signals, "spawn::completed");
|
2010-08-25 23:00:36 +02:00
|
|
|
signal_add(&global_signals, "spawn::initiated");
|
2010-08-26 17:46:17 +02:00
|
|
|
signal_add(&global_signals, "spawn::timeout");
|
2009-04-03 16:30:18 +02:00
|
|
|
}
|
|
|
|
|
2012-11-21 21:01:12 +01:00
|
|
|
static gboolean
|
|
|
|
spawn_launchee_timeout(gpointer context)
|
2009-04-03 16:30:18 +02:00
|
|
|
{
|
2012-11-21 21:01:12 +01:00
|
|
|
sn_launcher_context_complete(context);
|
|
|
|
sn_launcher_context_unref(context);
|
|
|
|
return FALSE;
|
2009-04-03 16:30:18 +02:00
|
|
|
}
|
2009-04-03 13:09:17 +02:00
|
|
|
|
2010-12-04 13:19:01 +01:00
|
|
|
static void
|
|
|
|
spawn_callback(gpointer user_data)
|
|
|
|
{
|
|
|
|
setsid();
|
|
|
|
}
|
|
|
|
|
2013-03-10 14:54:58 +01:00
|
|
|
/** Parse a command line.
|
|
|
|
* \param L The Lua VM state.
|
|
|
|
* \param idx The index of the argument that we should parse
|
|
|
|
* \return The argv array for the new process.
|
2009-08-25 11:05:58 +02:00
|
|
|
*/
|
2013-03-10 14:54:58 +01:00
|
|
|
static gchar **
|
|
|
|
parse_command(lua_State *L, int idx)
|
2009-08-25 11:05:58 +02:00
|
|
|
{
|
2013-03-10 14:54:58 +01:00
|
|
|
gchar **argv = NULL;
|
2009-08-25 11:05:58 +02:00
|
|
|
|
2013-03-10 14:54:58 +01:00
|
|
|
if (lua_isstring(L, idx))
|
|
|
|
{
|
|
|
|
const char *cmd = luaL_checkstring(L, idx);
|
|
|
|
if(!g_shell_parse_argv(cmd, NULL, &argv, NULL))
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
else if (lua_istable(L, idx))
|
|
|
|
{
|
|
|
|
size_t i, len = luaA_rawlen(L, idx);
|
|
|
|
argv = g_new0(gchar *, len + 1);
|
2009-08-25 11:05:58 +02:00
|
|
|
|
2013-03-10 14:54:58 +01:00
|
|
|
for (i = 0; i < len; i++)
|
|
|
|
{
|
|
|
|
lua_rawgeti(L, idx, i+1);
|
|
|
|
argv[i] = g_strdup(lua_tostring(L, -1));
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
luaL_error(L, "Invalid argument to spawn(), expect string or table");
|
|
|
|
}
|
2009-08-25 11:05:58 +02:00
|
|
|
|
2013-03-10 14:54:58 +01:00
|
|
|
return argv;
|
2009-08-25 11:05:58 +02:00
|
|
|
}
|
|
|
|
|
2009-04-03 13:09:17 +02:00
|
|
|
/** Spawn a program.
|
|
|
|
* This function is multi-head (Zaphod) aware and will set display to
|
|
|
|
* the right screen according to mouse position.
|
|
|
|
* \param L The Lua VM state.
|
|
|
|
* \return The number of elements pushed on stack
|
|
|
|
* \luastack
|
|
|
|
* \lparam The command to launch.
|
2009-04-03 16:30:18 +02:00
|
|
|
* \lparam Use startup-notification, true or false, default to true.
|
2010-04-24 17:52:52 +02:00
|
|
|
* \lreturn Process ID if everything is OK, or an error string if an error occured.
|
2009-04-03 13:09:17 +02:00
|
|
|
*/
|
|
|
|
int
|
|
|
|
luaA_spawn(lua_State *L)
|
|
|
|
{
|
2013-03-10 14:54:58 +01:00
|
|
|
gchar **argv = NULL;
|
2009-04-03 16:30:18 +02:00
|
|
|
bool use_sn = true;
|
2013-03-10 14:54:58 +01:00
|
|
|
gboolean retval;
|
|
|
|
GPid pid;
|
2009-04-03 13:09:17 +02:00
|
|
|
|
2009-04-03 16:30:18 +02:00
|
|
|
if(lua_gettop(L) >= 2)
|
|
|
|
use_sn = luaA_checkboolean(L, 2);
|
|
|
|
|
2013-03-10 14:54:58 +01:00
|
|
|
argv = parse_command(L, 1);
|
2014-03-12 11:10:48 +01:00
|
|
|
if(!argv || !argv[0])
|
|
|
|
{
|
|
|
|
g_strfreev(argv);
|
2013-03-10 14:54:58 +01:00
|
|
|
return 0;
|
2014-03-12 11:10:48 +01:00
|
|
|
}
|
2009-04-03 13:09:17 +02:00
|
|
|
|
2009-04-27 20:18:39 +02:00
|
|
|
SnLauncherContext *context = NULL;
|
2009-04-03 16:30:18 +02:00
|
|
|
if(use_sn)
|
|
|
|
{
|
|
|
|
char *cmdname, *space;
|
2013-03-10 14:54:58 +01:00
|
|
|
const char *first_no_space_char = argv[0];
|
2009-06-09 15:59:38 +02:00
|
|
|
/* Look for the first char which is not space */
|
|
|
|
while(*first_no_space_char && *first_no_space_char == ' ')
|
|
|
|
first_no_space_char++;
|
|
|
|
/* Look for space in the string to get the command name. */
|
|
|
|
if((space = strchr(first_no_space_char, ' ')))
|
2013-03-10 14:54:58 +01:00
|
|
|
cmdname = a_strndup(argv[0], space - argv[0]);
|
2009-04-03 16:30:18 +02:00
|
|
|
else
|
2013-03-10 14:54:58 +01:00
|
|
|
cmdname = a_strdup(argv[0]);
|
2009-04-03 16:30:18 +02:00
|
|
|
|
2010-08-16 13:47:40 +02:00
|
|
|
context = sn_launcher_context_new(globalconf.sndisplay, globalconf.default_screen);
|
2009-04-03 16:30:18 +02:00
|
|
|
sn_launcher_context_set_name(context, "awesome");
|
|
|
|
sn_launcher_context_set_description(context, "awesome spawn");
|
|
|
|
sn_launcher_context_set_binary_name(context, cmdname);
|
2010-08-12 14:52:23 +02:00
|
|
|
sn_launcher_context_initiate(context, "awesome", cmdname, globalconf.timestamp);
|
2009-04-03 16:30:18 +02:00
|
|
|
p_delete(&cmdname);
|
|
|
|
|
|
|
|
/* app will have AWESOME_SPAWN_TIMEOUT seconds to complete,
|
|
|
|
* or the timeout function will terminate the launch sequence anyway */
|
2012-11-21 21:01:12 +01:00
|
|
|
g_timeout_add_seconds(AWESOME_SPAWN_TIMEOUT, spawn_launchee_timeout, context);
|
2009-04-03 16:30:18 +02:00
|
|
|
sn_launcher_context_setup_child_process(context);
|
|
|
|
}
|
|
|
|
|
2009-04-27 20:18:39 +02:00
|
|
|
GError *error = NULL;
|
2013-03-10 14:54:58 +01:00
|
|
|
retval = g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
|
|
|
|
spawn_callback, NULL, &pid, &error);
|
|
|
|
g_strfreev(argv);
|
|
|
|
if(!retval)
|
2009-04-03 13:09:17 +02:00
|
|
|
{
|
2009-04-27 20:18:39 +02:00
|
|
|
/* push error on stack */
|
|
|
|
lua_pushstring(L, error->message);
|
|
|
|
g_error_free(error);
|
|
|
|
if(context)
|
|
|
|
sn_launcher_context_complete(context);
|
|
|
|
return 1;
|
2009-04-03 13:09:17 +02:00
|
|
|
}
|
|
|
|
|
2010-04-24 11:49:57 +02:00
|
|
|
/* push pid on stack */
|
|
|
|
lua_pushnumber(L, pid);
|
|
|
|
|
|
|
|
return 1;
|
2009-04-03 13:09:17 +02:00
|
|
|
}
|
|
|
|
|
2011-09-11 16:50:01 +02:00
|
|
|
// vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|