/* * spawn.c - Lua configuration management * * Copyright © 2009 Julien Danjou * * 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 #include #include #include #include #include "spawn.h" #include "screen.h" #include "luaa.h" #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. * \return True if found and removed. */ static inline bool 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); sn_startup_sequence_unref(s); return true; } return false; } static void spawn_monitor_timeout(struct ev_loop *loop, ev_timer *w, int revents) { if(spawn_sequence_remove(w->data)) { 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); lua_pushstring(globalconf.L, sn_startup_sequence_get_id(w->data)); 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); } } sn_startup_sequence_unref(w->data); p_delete(&w); } 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); lua_createtable(globalconf.L, 0, 2); lua_pushstring(globalconf.L, sn_startup_sequence_get_id(sequence)); lua_setfield(globalconf.L, -2, "id"); const char *event_type_str = NULL; switch(event_type) { case SN_MONITOR_EVENT_INITIATED: /* ref the sequence for the array */ sn_startup_sequence_ref(sequence); SnStartupSequence_array_append(&sn_waits, sequence); event_type_str = "spawn::initiated"; /* Add a timeout function so we do not wait for this event to complete * for ever */ struct ev_timer *ev_timeout = p_new(struct ev_timer, 1); ev_timer_init(ev_timeout, spawn_monitor_timeout, AWESOME_SPAWN_TIMEOUT, 0.); /* ref the sequence for the callback event */ sn_startup_sequence_ref(sequence); ev_timeout->data = sequence; ev_timer_start(globalconf.loop, ev_timeout); break; case SN_MONITOR_EVENT_CHANGED: event_type_str = "spawn::change"; break; case SN_MONITOR_EVENT_COMPLETED: event_type_str = "spawn::completed"; break; case SN_MONITOR_EVENT_CANCELED: event_type_str = "spawn::canceled"; 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; } /* 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); } } /** Tell the spawn module that an app has been started. * \param c The client that just started. * \param startup_id The startup id of the started application. */ void spawn_start_notify(client_t *c, const char * startup_id) { foreach(_seq, sn_waits) { SnStartupSequence *seq = *_seq; bool found = false; const char *seqid = sn_startup_sequence_get_id(seq); if(!a_strcmp(seqid, startup_id)) found = true; else { const char *seqclass = sn_startup_sequence_get_wmclass(seq); if(!a_strcmp(seqclass, c->class) || !a_strcmp(seqclass, c->instance)) found = true; else { const char *seqbin = sn_startup_sequence_get_binary_name(seq); if(!a_strcasecmp(seqbin, c->class) || !a_strcasecmp(seqbin, c->instance)) 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); const int screen_max = xcb_setup_roots_length(xcb_get_setup(globalconf.connection)); for(int screen = 0; screen < screen_max; screen++) globalconf.screens.tab[screen].snmonitor = sn_monitor_context_new(globalconf.sndisplay, screen, spawn_monitor_event, NULL, NULL); } static void spawn_launchee_timeout(struct ev_loop *loop, ev_timer *w, int revents) { sn_launcher_context_complete(w->data); sn_launcher_context_unref(w->data); p_delete(&w); } /** This callback is executed in the child process after fork(). It clears the * signal mask. Without this, child processes would be started with e.g. * SIGINT blocked if we are compiled against libev 3.8 or newer. * BEWARE: Pending signals are inherited by child processes. This might mean * that we receive a signal that was meant for awesome after clearing the signal * mask! Sadly, we can't do anything about this. * \param user_data Not used. */ static void spawn_child_callback(gpointer user_data) { sigset_t empty; sigemptyset(&empty); sigprocmask(SIG_SETMASK, &empty, NULL); } /** Spawn a command. * \param command_line The command line to launch. * \param error A error pointer to fill with the possible error from * g_spawn_async. * \return g_spawn_async value. */ static GPid spawn_command(const gchar *command_line, GError **error) { gboolean retval; GPid pid; gchar **argv = 0; if(!g_shell_parse_argv(command_line, NULL, &argv, error)) return 0; retval = g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, spawn_child_callback, NULL, &pid, error); g_strfreev (argv); if (!retval) return 0; return pid; } /** 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. * \lparam Use startup-notification, true or false, default to true. * \lparam The optional screen number to spawn the command on. * \lreturn Process ID if everything is OK, or an error string if an error occured. */ int luaA_spawn(lua_State *L) { char *host, newdisplay[128]; const char *cmd; bool use_sn = true; int screen = 0, screenp, displayp; if(lua_gettop(L) >= 2) use_sn = luaA_checkboolean(L, 2); if(lua_gettop(L) == 3) { screen = luaL_checknumber(L, 3) - 1; luaA_checkscreen(screen); } cmd = luaL_checkstring(L, 1); if(!globalconf.xinerama_is_active) { xcb_parse_display(NULL, &host, &displayp, &screenp); snprintf(newdisplay, sizeof(newdisplay), "%s:%d.%d", host, displayp, screen); setenv("DISPLAY", newdisplay, 1); p_delete(&host); } SnLauncherContext *context = NULL; if(use_sn) { char *cmdname, *space; const char *first_no_space_char = cmd; /* 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, ' '))) cmdname = a_strndup(cmd, space - cmd); else cmdname = a_strdup(cmd); context = sn_launcher_context_new(globalconf.sndisplay, screen_virttophys(screen)); sn_launcher_context_set_name(context, "awesome"); sn_launcher_context_set_description(context, "awesome spawn"); sn_launcher_context_set_binary_name(context, cmdname); sn_launcher_context_initiate(context, "awesome", cmdname, globalconf.timestamp); p_delete(&cmdname); /* app will have AWESOME_SPAWN_TIMEOUT seconds to complete, * or the timeout function will terminate the launch sequence anyway */ struct ev_timer *ev_timeout = p_new(struct ev_timer, 1); ev_timer_init(ev_timeout, spawn_launchee_timeout, AWESOME_SPAWN_TIMEOUT, 0.); ev_timeout->data = context; ev_timer_start(globalconf.loop, ev_timeout); sn_launcher_context_setup_child_process(context); } GError *error = NULL; GPid pid = spawn_command(cmd, &error); if(!pid) { /* push error on stack */ lua_pushstring(L, error->message); g_error_free(error); if(context) sn_launcher_context_complete(context); return 1; } /* push pid on stack */ lua_pushnumber(L, pid); return 1; } /** Spawn a program. This works exactly as system() does, but clears the signal mask. * \param arg The shell command to execute. * \return The return status of the program. */ int spawn_system(const char *arg) { GError *error; gboolean retval; gint status; static const char *shell = NULL; if(!shell && !(shell = getenv("SHELL"))) shell = "/bin/sh"; char *argv[] = { (char *) shell, (char *) "-c", (char *) arg, NULL }; retval = g_spawn_sync(NULL, argv, NULL, 0, spawn_child_callback, NULL, NULL, NULL, &status, &error); if (retval) return status; g_error_free(error); return -1; } // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80