diff --git a/awesome.c b/awesome.c index f6089c5aa..01cce4c15 100644 --- a/awesome.c +++ b/awesome.c @@ -32,6 +32,7 @@ #include #include "awesome.h" +#include "spawn.h" #include "client.h" #include "window.h" #include "ewmh.h" @@ -523,6 +524,9 @@ main(int argc, char **argv) systray_init(screen_nbr); } + /* init spawn (sn) */ + spawn_init(); + /* Parse and run configuration file */ luaA_parserc(xdg, confpath, true); diff --git a/awesomeConfig.cmake b/awesomeConfig.cmake index 89a35dccb..61e4aae9b 100644 --- a/awesomeConfig.cmake +++ b/awesomeConfig.cmake @@ -145,6 +145,7 @@ pkg_check_modules(AWESOME_REQUIRED REQUIRED xcb-image>=0.3.0 xcb-property>=0.3.0 cairo-xcb + libstartup-notification-1.0>=0.10 xproto>=7.0.11 imlib2 libxdg-basedir) diff --git a/client.c b/client.c index 2e91aab33..0c111d4a5 100644 --- a/client.c +++ b/client.c @@ -31,6 +31,7 @@ #include "systray.h" #include "property.h" #include "wibox.h" +#include "spawn.h" #include "common/atoms.h" DO_LUA_TOSTRING(client_t, client, "client") @@ -545,6 +546,9 @@ client_manage(xcb_window_t w, xcb_get_geometry_reply_t *wgeom, int phys_screen, client_ban(c); xcb_map_window(globalconf.connection, c->win); + if(!startup) + spawn_start_notify(c); + /* Call hook to notify list change */ if(globalconf.hooks.clients != LUA_REFNIL) luaA_dofunction(globalconf.L, globalconf.hooks.clients, 0, 0); diff --git a/event.c b/event.c index 98d4274ef..9ee396559 100644 --- a/event.c +++ b/event.c @@ -823,10 +823,13 @@ event_handle_clientmessage(void *data __attribute__ ((unused)), xcb_connection_t *connection, xcb_client_message_event_t *ev) { - client_t *c; + /* check for startup notification messages */ + if(sn_xcb_display_process_event(globalconf.sndisplay, (xcb_generic_event_t *) ev)) + return 0; if(ev->type == WM_CHANGE_STATE) { + client_t *c; if((c = client_getbywin(ev->window)) && ev->format == 32 && ev->data.data32[0] == XCB_WM_STATE_ICONIC) diff --git a/hooks.c b/hooks.c index a4409454e..1e8b74b7d 100644 --- a/hooks.c +++ b/hooks.c @@ -175,6 +175,20 @@ luaA_hooks_property(lua_State *L) HANDLE_HOOK(L, globalconf.hooks.property); } +/** Set the function called on each startup-notification events + * This function is called with a table and various fields set to describe the + * vents. + * \param L The Lua VM state. + * \return The number of elements pushed on stack. + * \luastack + * \lparam A function to call on each startup-notification event. + */ +static int +luaA_hooks_startup_notification(lua_State *L) +{ + HANDLE_HOOK(L, globalconf.hooks.startup_notification); +} + /** Set the function to be called every N seconds. * \param L The Lua VM state. * \return The number of elements pushed on stack. @@ -233,6 +247,7 @@ const struct luaL_reg awesome_hooks_lib[] = { "clients", luaA_hooks_clients }, { "tags", luaA_hooks_tags }, { "tagged", luaA_hooks_tagged }, + { "startup_notification", luaA_hooks_startup_notification }, { "timer", luaA_hooks_timer }, #ifdef WITH_DBUS { "dbus", luaA_hooks_dbus }, diff --git a/lib/awful/util.lua.in b/lib/awful/util.lua.in index 0872aefda..b0eb26a81 100644 --- a/lib/awful/util.lua.in +++ b/lib/awful/util.lua.in @@ -60,11 +60,13 @@ end --- Spawn a program. -- @param cmd The command. +-- @paran sn Enable startup-notification. -- @param screen The screen where to spawn window. -- @return The awesome.spawn return value. -function spawn(cmd, screen) +function spawn(cmd, sn, screen) if cmd and cmd ~= "" then - return capi.awesome.spawn(cmd, screen or capi.mouse.screen) + if sn == nil then sn = true end + return capi.awesome.spawn(cmd, sn, screen or capi.mouse.screen) end end diff --git a/lib/beautiful.lua.in b/lib/beautiful.lua.in index 46c478fcc..105d75a96 100644 --- a/lib/beautiful.lua.in +++ b/lib/beautiful.lua.in @@ -44,7 +44,7 @@ function init(path) for key, value in f:read("*all"):gsub("^","\n"):gmatch("\n[\t ]*([a-z_]+)[\t ]*=[\t ]*([^\n\t]+)") do if key == "wallpaper_cmd" then for s = 1, capi.screen.count() do - util.spawn(value, s) + util.spawn(value, false, s) end elseif key == "font" then capi.awesome.font = value diff --git a/luaa.c b/luaa.c index 9aa77b710..e61525967 100644 --- a/luaa.c +++ b/luaa.c @@ -882,6 +882,7 @@ luaA_init(xdgHandle xdg) globalconf.hooks.tags = LUA_REFNIL; globalconf.hooks.tagged = LUA_REFNIL; globalconf.hooks.property = LUA_REFNIL; + globalconf.hooks.startup_notification = LUA_REFNIL; globalconf.hooks.timer = LUA_REFNIL; #ifdef WITH_DBUS globalconf.hooks.dbus = LUA_REFNIL; diff --git a/spawn.c b/spawn.c index a15b6952e..b5d85a035 100644 --- a/spawn.c +++ b/spawn.c @@ -24,9 +24,200 @@ #include #include -#include "structs.h" #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 array The startup sequence array. + * \param s The startup sequence to found, remove and unref. + */ +static inline void +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); + break; + } +} + +static void +spawn_monitor_timeout(struct ev_loop *loop, ev_timer *w, int revents) +{ + spawn_sequence_remove(w->data); + p_delete(&w); +} + +static void +spawn_monitor_event(SnMonitorEvent *event, void *data) +{ + if(globalconf.hooks.startup_notification == LUA_REFNIL) + return; + + SnStartupSequence *sequence = sn_monitor_event_get_startup_sequence(event); + SnMonitorEventType event_type = sn_monitor_event_get_type(event); + + lua_newtable(globalconf.L); + lua_pushstring(globalconf.L, sn_startup_sequence_get_id(sequence)); + lua_setfield(globalconf.L, -2, "id"); + + switch(event_type) + { + case SN_MONITOR_EVENT_INITIATED: + sn_startup_sequence_ref(sequence); + SnStartupSequence_array_append(&sn_waits, sequence); + lua_pushliteral(globalconf.L, "initiated"); + lua_setfield(globalconf.L, -2, "type"); + + /* 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.); + ev_timeout->data = sequence; + ev_timer_start(globalconf.loop, ev_timeout); + break; + case SN_MONITOR_EVENT_CHANGED: + lua_pushliteral(globalconf.L, "change"); + lua_setfield(globalconf.L, -2, "type"); + break; + case SN_MONITOR_EVENT_COMPLETED: + lua_pushliteral(globalconf.L, "completed"); + lua_setfield(globalconf.L, -2, "type"); + break; + case SN_MONITOR_EVENT_CANCELED: + lua_pushliteral(globalconf.L, "canceled"); + lua_setfield(globalconf.L, -2, "type"); + 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; + } + + luaA_dofunction(globalconf.L, globalconf.hooks.startup_notification, 1, 0); +} + +/** Tell the spawn module that an app has been started. + * \param c The client that just started. + */ +void +spawn_start_notify(client_t *c) +{ + foreach(_seq, sn_waits) + { + SnStartupSequence *seq = *_seq; + bool found = false; + const char *seqid = sn_startup_sequence_get_id(seq); + + if(!a_strcmp(seqid, c->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[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); +} /** Spawn a program. * This function is multi-head (Zaphod) aware and will set display to @@ -35,6 +226,7 @@ * \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. */ int @@ -42,11 +234,15 @@ 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) + if(lua_gettop(L) >= 2) + use_sn = luaA_checkboolean(L, 2); + + if(lua_gettop(L) == 3) { - screen = luaL_checknumber(L, 2) - 1; + screen = luaL_checknumber(L, 3) - 1; luaA_checkscreen(screen); } @@ -60,6 +256,30 @@ luaA_spawn(lua_State *L) p_delete(&host); } + if(use_sn) + { + char *cmdname, *space; + if((space = strchr(cmd, ' '))) + cmdname = a_strndup(cmd, space - cmd); + else + cmdname = a_strdup(cmd); + + SnLauncherContext *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, XCB_CURRENT_TIME); + 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); + } + /* The double-fork construct avoids zombie processes and keeps the code * clean from stupid signal handlers. */ if(fork() == 0) diff --git a/spawn.h b/spawn.h index 20e35f18c..e0ba36dbc 100644 --- a/spawn.h +++ b/spawn.h @@ -23,7 +23,10 @@ #define AWESOME_SPAWN_H #include +#include "structs.h" +void spawn_init(void); +void spawn_start_notify(client_t *); int luaA_spawn(lua_State *); #endif diff --git a/structs.h b/structs.h index f24cb1cc9..a6ddb5b5b 100644 --- a/structs.h +++ b/structs.h @@ -22,6 +22,9 @@ #ifndef AWESOME_STRUCTS_H #define AWESOME_STRUCTS_H +#define SN_API_NOT_YET_FROZEN +#include + #include #include @@ -285,6 +288,8 @@ typedef struct } systray; /** Focused client */ client_t *client_focus; + /** The monitor of startup notifications */ + SnMonitorContext *snmonitor; } screen_t; /** Main configuration structure */ @@ -365,6 +370,8 @@ struct awesome_t luaA_ref property; /** Command to run on time */ luaA_ref timer; + /** Startup notification hooks */ + luaA_ref startup_notification; #ifdef WITH_DBUS /** Command to run on dbus events */ luaA_ref dbus; @@ -382,6 +389,8 @@ struct awesome_t screen_t *screen_focus; /** Need to call client_stack_refresh() */ bool client_need_stack_refresh; + /** The startup notification display struct */ + SnDisplay *sndisplay; }; DO_ARRAY(const void *, void, DO_NOTHING)