i3
startup.c
Go to the documentation of this file.
1/*
2 * vim:ts=4:sw=4:expandtab
3 *
4 * i3 - an improved tiling window manager
5 * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
6 *
7 * startup.c: Startup notification code. Ensures a startup notification context
8 * is setup when launching applications. We store the current
9 * workspace to open windows in that startup notification context on
10 * the appropriate workspace.
11 *
12 */
13#include "all.h"
14#include "sd-daemon.h"
15
16#include <paths.h>
17#include <sys/types.h>
18#include <sys/wait.h>
19#include <unistd.h>
20
21#define SN_API_NOT_YET_FROZEN 1
22#include <libsn/sn-launcher.h>
23
24static TAILQ_HEAD(startup_sequence_head, Startup_Sequence) startup_sequences =
25 TAILQ_HEAD_INITIALIZER(startup_sequences);
26
27/*
28 * After 60 seconds, a timeout will be triggered for each startup sequence.
29 *
30 * The timeout will just trigger completion of the sequence, so the normal
31 * completion process takes place (startup_monitor_event will free it).
32 *
33 */
34static void startup_timeout(EV_P_ ev_timer *w, int revents) {
35 const char *id = sn_launcher_context_get_startup_id(w->data);
36 DLOG("Timeout for startup sequence %s\n", id);
37
38 struct Startup_Sequence *current, *sequence = NULL;
39 TAILQ_FOREACH (current, &startup_sequences, sequences) {
40 if (strcmp(current->id, id) != 0) {
41 continue;
42 }
43
44 sequence = current;
45 break;
46 }
47
48 /* Unref the context (for the timeout itself, see start_application) */
49 sn_launcher_context_unref(w->data);
50
51 if (!sequence) {
52 DLOG("Sequence already deleted, nevermind.\n");
53 free(w);
54 return;
55 }
56
57 /* Complete the startup sequence, will trigger its deletion. */
58 sn_launcher_context_complete(w->data);
59 free(w);
60}
61
62/*
63 * Some applications (such as Firefox) mark a startup sequence as completed
64 * *before* they even map a window. Therefore, we cannot entirely delete the
65 * startup sequence once it’s marked as complete. Instead, we’ll mark it for
66 * deletion in 30 seconds and use that chance to delete old sequences.
67 *
68 * This function returns the number of active (!) startup notifications, that
69 * is, those which are not marked for deletion yet. This is used for changing
70 * the root window cursor.
71 *
72 */
73static int _prune_startup_sequences(void) {
74 time_t current_time = time(NULL);
75 int active_sequences = 0;
76
77 /* Traverse the list and delete everything which was marked for deletion 30
78 * seconds ago or earlier. */
79 struct Startup_Sequence *current, *next;
80 for (next = TAILQ_FIRST(&startup_sequences);
81 next != TAILQ_END(&startup_sequences);) {
82 current = next;
83 next = TAILQ_NEXT(next, sequences);
84
85 if (current->delete_at == 0) {
86 active_sequences++;
87 continue;
88 }
89
90 if (current_time <= current->delete_at) {
91 continue;
92 }
93
95 }
96
97 return active_sequences;
98}
99
100/*
101 * Deletes a startup sequence, ignoring whether its timeout has elapsed.
102 * Useful when e.g. a window is moved between workspaces and its children
103 * shouldn't spawn on the original workspace.
104 *
105 */
107 assert(sequence != NULL);
108 DLOG("Deleting startup sequence %s, delete_at = %lld, current_time = %lld\n",
109 sequence->id, (long long)sequence->delete_at, (long long)time(NULL));
110
111 /* Unref the context, will be free()d */
112 sn_launcher_context_unref(sequence->context);
113
114 /* Delete our internal sequence */
115 TAILQ_REMOVE(&startup_sequences, sequence, sequences);
116
117 free(sequence->id);
118 free(sequence->workspace);
119 FREE(sequence);
120}
121
122/*
123 * Starts the given application by passing it through a shell. Zombie processes
124 * will be collected by ev in the default loop, we don't have to manually
125 * deal with it.
126 *
127 * The shell used to start applications is the system's bourne shell (i.e.,
128 * /bin/sh).
129 *
130 * The no_startup_id flag determines whether a startup notification context
131 * (and ID) should be created, which is the default and encouraged behavior.
132 *
133 */
134void start_application(const char *command, bool no_startup_id) {
135 SnLauncherContext *context = NULL;
136
137 if (!no_startup_id) {
138 /* Create a startup notification context to monitor the progress of this
139 * startup. */
140 context = sn_launcher_context_new(sndisplay, conn_screen);
141 sn_launcher_context_set_name(context, "i3");
142 sn_launcher_context_set_description(context, "exec command in i3");
143 /* Chop off everything starting from the first space (if there are any
144 * spaces in the command), since we don’t want the parameters. */
145 char *first_word = sstrdup(command);
146 char *space = strchr(first_word, ' ');
147 if (space) {
148 *space = '\0';
149 }
150 sn_launcher_context_initiate(context, "i3", first_word, last_timestamp);
151 free(first_word);
152
153 /* Trigger a timeout after 60 seconds */
154 struct ev_timer *timeout = scalloc(1, sizeof(struct ev_timer));
155 ev_timer_init(timeout, startup_timeout, 60.0, 0.);
156 timeout->data = context;
157 ev_timer_start(main_loop, timeout);
158
159 LOG("startup id = %s\n", sn_launcher_context_get_startup_id(context));
160
161 /* Save the ID and current workspace in our internal list of startup
162 * sequences */
164 struct Startup_Sequence *sequence = scalloc(1, sizeof(struct Startup_Sequence));
165 sequence->id = sstrdup(sn_launcher_context_get_startup_id(context));
166 sequence->workspace = sstrdup(ws->name);
167 sequence->context = context;
168 TAILQ_INSERT_TAIL(&startup_sequences, sequence, sequences);
169
170 /* Increase the refcount once (it starts with 1, so it will be 2 now) for
171 * the timeout. Even if the sequence gets completed, the timeout still
172 * needs the context (but will unref it then) */
173 sn_launcher_context_ref(context);
174 }
175
176 LOG("executing: %s\n", command);
177 if (fork() == 0) {
178 /* Child process.
179 * It will be reaped by ev, even though there is no corresponding ev_child */
180 setsid();
181 setrlimit(RLIMIT_CORE, &original_rlimit_core);
182 /* Close all socket activation file descriptors explicitly, we disabled
183 * FD_CLOEXEC to keep them open when restarting i3. */
184 for (int fd = SD_LISTEN_FDS_START;
186 fd++) {
187 close(fd);
188 }
189 unsetenv("LISTEN_PID");
190 unsetenv("LISTEN_FDS");
191 signal(SIGPIPE, SIG_DFL);
192 /* Setup the environment variable(s) */
193 if (!no_startup_id) {
194 sn_launcher_context_setup_child_process(context);
195 }
196 setenv("I3SOCK", current_socketpath, 1);
197
198 execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, NULL);
199 /* not reached */
200 }
201
202 if (!no_startup_id) {
203 /* Change the pointer of the root window to indicate progress */
205 }
206}
207
208/*
209 * Called by libstartup-notification when something happens
210 *
211 */
212void startup_monitor_event(SnMonitorEvent *event, void *userdata) {
213 SnStartupSequence *snsequence;
214
215 snsequence = sn_monitor_event_get_startup_sequence(event);
216
217 /* Get the corresponding internal startup sequence */
218 const char *id = sn_startup_sequence_get_id(snsequence);
219 struct Startup_Sequence *current, *sequence = NULL;
220 TAILQ_FOREACH (current, &startup_sequences, sequences) {
221 if (strcmp(current->id, id) != 0) {
222 continue;
223 }
224
225 sequence = current;
226 break;
227 }
228
229 if (!sequence) {
230 DLOG("Got event for startup sequence that we did not initiate (ID = %s). Ignoring.\n", id);
231 return;
232 }
233
234 switch (sn_monitor_event_get_type(event)) {
235 case SN_MONITOR_EVENT_COMPLETED:
236 DLOG("startup sequence %s completed\n", sn_startup_sequence_get_id(snsequence));
237
238 /* Mark the given sequence for deletion in 30 seconds. */
239 time_t current_time = time(NULL);
240 sequence->delete_at = current_time + 30;
241 DLOG("Will delete startup sequence %s at timestamp %lld\n",
242 sequence->id, (long long)sequence->delete_at);
243
244 if (_prune_startup_sequences() == 0) {
245 DLOG("No more startup sequences running, changing root window cursor to default pointer.\n");
246 /* Change the pointer of the root window to indicate progress */
248 }
249 break;
250 default:
251 /* ignore */
252 break;
253 }
254}
255
256/*
257 * Renames workspaces that are mentioned in the startup sequences.
258 *
259 */
260void startup_sequence_rename_workspace(const char *old_name, const char *new_name) {
261 struct Startup_Sequence *current;
262 TAILQ_FOREACH (current, &startup_sequences, sequences) {
263 if (strcmp(current->workspace, old_name) != 0) {
264 continue;
265 }
266 DLOG("Renaming workspace \"%s\" to \"%s\" in startup sequence %s.\n",
267 old_name, new_name, current->id);
268 free(current->workspace);
269 current->workspace = sstrdup(new_name);
270 }
271}
272
273/*
274 * Gets the stored startup sequence for the _NET_STARTUP_ID of a given window.
275 *
276 */
278 xcb_get_property_reply_t *startup_id_reply, bool ignore_mapped_leader) {
279 /* The _NET_STARTUP_ID is only needed during this function, so we get it
280 * here and don’t save it in the 'cwindow'. */
281 if (startup_id_reply == NULL || xcb_get_property_value_length(startup_id_reply) == 0) {
282 FREE(startup_id_reply);
283 DLOG("No _NET_STARTUP_ID set on window 0x%08x\n", cwindow->id);
284 if (cwindow->leader == XCB_NONE) {
285 return NULL;
286 }
287
288 /* This is a special case that causes the leader's startup sequence
289 * to only be returned if it has never been mapped, useful primarily
290 * when trying to delete a sequence.
291 *
292 * It's generally inappropriate to delete a leader's sequence when
293 * moving a child window, but if the leader has no container, it's
294 * likely permanently unmapped and the child is the "real" window. */
295 if (ignore_mapped_leader && con_by_window_id(cwindow->leader) != NULL) {
296 DLOG("Ignoring leader window 0x%08x\n", cwindow->leader);
297 return NULL;
298 }
299
300 DLOG("Checking leader window 0x%08x\n", cwindow->leader);
301
302 xcb_get_property_cookie_t cookie;
303
304 cookie = xcb_get_property(conn, false, cwindow->leader,
305 A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
306 startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
307
308 if (startup_id_reply == NULL ||
309 xcb_get_property_value_length(startup_id_reply) == 0) {
310 FREE(startup_id_reply);
311 DLOG("No _NET_STARTUP_ID set on the leader either\n");
312 return NULL;
313 }
314 }
315
316 char *startup_id;
317 sasprintf(&startup_id, "%.*s", xcb_get_property_value_length(startup_id_reply),
318 (char *)xcb_get_property_value(startup_id_reply));
319 struct Startup_Sequence *current, *sequence = NULL;
320 TAILQ_FOREACH (current, &startup_sequences, sequences) {
321 if (strcmp(current->id, startup_id) != 0) {
322 continue;
323 }
324
325 sequence = current;
326 break;
327 }
328
329 if (!sequence) {
330 DLOG("WARNING: This sequence (ID %s) was not found\n", startup_id);
331 free(startup_id);
332 free(startup_id_reply);
333 return NULL;
334 }
335
336 free(startup_id);
337 free(startup_id_reply);
338
339 return sequence;
340}
341
342/*
343 * Checks if the given window belongs to a startup notification by checking if
344 * the _NET_STARTUP_ID property is set on the window (or on its leader, if it’s
345 * unset).
346 *
347 * If so, returns the workspace on which the startup was initiated.
348 * Returns NULL otherwise.
349 *
350 */
351char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply) {
352 struct Startup_Sequence *sequence = startup_sequence_get(cwindow, startup_id_reply, false);
353 if (sequence == NULL) {
354 return NULL;
355 }
356
357 /* If the startup sequence's time span has elapsed, delete it. */
358 time_t current_time = time(NULL);
359 if (sequence->delete_at > 0 && current_time > sequence->delete_at) {
360 DLOG("Deleting expired startup sequence %s\n", sequence->id);
361 startup_sequence_delete(sequence);
362 return NULL;
363 }
364
365 return sequence->workspace;
366}
367
368/*
369 * Deletes the startup sequence for a window if it exists.
370 *
371 */
373 struct Startup_Sequence *sequence;
374 xcb_get_property_cookie_t cookie;
375 xcb_get_property_reply_t *startup_id_reply;
376
377 cookie = xcb_get_property(conn, false, win->id, A__NET_STARTUP_ID,
378 XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
379 startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
380
381 sequence = startup_sequence_get(win, startup_id_reply, true);
382 if (sequence != NULL) {
383 startup_sequence_delete(sequence);
384 }
385}
Con * con_get_workspace(Con *con)
Gets the workspace container this node is on.
Definition con.c:547
Con * con_by_window_id(xcb_window_t window)
Returns the container with the given client window ID or NULL if no such container exists.
Definition con.c:752
void startup_sequence_delete_by_window(i3Window *win)
Deletes the startup sequence for a window if it exists.
Definition startup.c:372
char * startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply)
Checks if the given window belongs to a startup notification by checking if the _NET_STARTUP_ID prope...
Definition startup.c:351
static int _prune_startup_sequences(void)
Definition startup.c:73
void start_application(const char *command, bool no_startup_id)
Starts the given application by passing it through a shell.
Definition startup.c:134
void startup_sequence_delete(struct Startup_Sequence *sequence)
Deletes a startup sequence, ignoring whether its timeout has elapsed.
Definition startup.c:106
struct Startup_Sequence * startup_sequence_get(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply, bool ignore_mapped_leader)
Gets the stored startup sequence for the _NET_STARTUP_ID of a given window.
Definition startup.c:277
void startup_sequence_rename_workspace(const char *old_name, const char *new_name)
Renames workspaces that are mentioned in the startup sequences.
Definition startup.c:260
void startup_monitor_event(SnMonitorEvent *event, void *userdata)
Called by libstartup-notification when something happens.
Definition startup.c:212
struct Con * focused
Definition tree.c:13
void xcursor_set_root_cursor(int cursor_id)
Sets the cursor of the root window to the 'pointer' cursor.
Definition xcursor.c:49
char * current_socketpath
Definition ipc.c:26
xcb_timestamp_t last_timestamp
The last timestamp we got from X11 (timestamps are included in some events and are used for some thin...
Definition main.c:64
int conn_screen
Definition main.c:56
xcb_connection_t * conn
XCB connection and root screen.
Definition main.c:54
int listen_fds
The number of file descriptors passed via socket activation.
Definition main.c:46
SnDisplay * sndisplay
Definition main.c:59
struct rlimit original_rlimit_core
The original value of RLIMIT_CORE when i3 was started.
Definition main.c:43
struct ev_loop * main_loop
Definition main.c:79
#define DLOG(fmt,...)
Definition libi3.h:105
#define LOG(fmt,...)
Definition libi3.h:95
char * sstrdup(const char *str)
Safe-wrapper around strdup which exits if malloc returns NULL (meaning that there is no more memory a...
void * scalloc(size_t num, size_t size)
Safe-wrapper around calloc which exits if malloc returns NULL (meaning that there is no more memory a...
int sasprintf(char **strp, const char *fmt,...)
Safe-wrapper around asprintf which exits if it returns -1 (meaning that there is no more memory avail...
#define TAILQ_FOREACH(var, head, field)
Definition queue.h:347
#define TAILQ_END(head)
Definition queue.h:337
#define TAILQ_HEAD(name, type)
Definition queue.h:318
#define TAILQ_INSERT_TAIL(head, elm, field)
Definition queue.h:376
#define TAILQ_FIRST(head)
Definition queue.h:336
#define TAILQ_REMOVE(head, elm, field)
Definition queue.h:402
#define TAILQ_NEXT(elm, field)
Definition queue.h:338
#define TAILQ_HEAD_INITIALIZER(head)
Definition queue.h:324
#define SD_LISTEN_FDS_START
Definition sd-daemon.h:102
#define FREE(pointer)
Definition util.h:47
@ XCURSOR_CURSOR_WATCH
Definition xcursor.h:24
@ XCURSOR_CURSOR_POINTER
Definition xcursor.h:17
Used during the config file lexing/parsing to keep the state of the lexer in order to provide useful ...
Stores internal information about a startup sequence, like the workspace it was initiated on.
Definition data.h:257
char * id
startup ID for this sequence, generated by libstartup-notification
Definition data.h:259
time_t delete_at
time at which this sequence should be deleted (after it was marked as completed)
Definition data.h:266
char * workspace
workspace on which this startup was initiated
Definition data.h:261
SnLauncherContext * context
libstartup-notification context for this launch
Definition data.h:263
A 'Window' is a type which contains an xcb_window_t and all the related information (hints like _NET_...
Definition data.h:424
xcb_window_t id
Definition data.h:425
xcb_window_t leader
Holds the xcb_window_t (just an ID) for the leader window (logical parent for toolwindows and similar...
Definition data.h:429
A 'Con' represents everything from the X11 root window down to a single X11 window.
Definition data.h:643
char * name
Definition data.h:692