]> git.sur5r.net Git - i3/i3/blob - src/util.c
ipc/parser: commands can now return custom JSON replies
[i3/i3] / src / util.c
1 /*
2  * vim:ts=8:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  *
6  * © 2009 Michael Stapelberg and contributors
7  *
8  * See file LICENSE for license information.
9  *
10  * util.c: Utility functions, which can be useful everywhere.
11  *
12  */
13 #include <sys/wait.h>
14 #include <stdarg.h>
15 #include <iconv.h>
16 #if defined(__OpenBSD__)
17 #include <sys/cdefs.h>
18 #endif
19
20 #include <fcntl.h>
21
22 #include "all.h"
23
24 static iconv_t conversion_descriptor = 0;
25
26 int min(int a, int b) {
27         return (a < b ? a : b);
28 }
29
30 int max(int a, int b) {
31         return (a > b ? a : b);
32 }
33
34 bool rect_contains(Rect rect, uint32_t x, uint32_t y) {
35         return (x >= rect.x &&
36                 x <= (rect.x + rect.width) &&
37                 y >= rect.y &&
38                 y <= (rect.y + rect.height));
39 }
40
41 /*
42  * Updates *destination with new_value and returns true if it was changed or false
43  * if it was the same
44  *
45  */
46 bool update_if_necessary(uint32_t *destination, const uint32_t new_value) {
47         uint32_t old_value = *destination;
48
49         return ((*destination = new_value) != old_value);
50 }
51
52 /*
53  * The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of
54  * the called functions returns NULL, meaning that there is no more memory available
55  *
56  */
57 void *smalloc(size_t size) {
58         void *result = malloc(size);
59         exit_if_null(result, "Error: out of memory (malloc(%zd))\n", size);
60         return result;
61 }
62
63 void *scalloc(size_t size) {
64         void *result = calloc(size, 1);
65         exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size);
66         return result;
67 }
68
69 void *srealloc(void *ptr, size_t size) {
70         void *result = realloc(ptr, size);
71         exit_if_null(result, "Error: out memory (realloc(%zd))\n", size);
72         return result;
73 }
74
75 char *sstrdup(const char *str) {
76         char *result = strdup(str);
77         exit_if_null(result, "Error: out of memory (strdup())\n");
78         return result;
79 }
80
81 /*
82  * Starts the given application by passing it through a shell. We use double fork
83  * to avoid zombie processes. As the started application’s parent exits (immediately),
84  * the application is reparented to init (process-id 1), which correctly handles
85  * childs, so we don’t have to do it :-).
86  *
87  * The shell is determined by looking for the SHELL environment variable. If it
88  * does not exist, /bin/sh is used.
89  *
90  */
91 void start_application(const char *command) {
92         LOG("executing: %s\n", command);
93         if (fork() == 0) {
94                 /* Child process */
95                 if (fork() == 0) {
96                         /* Stores the path of the shell */
97                         static const char *shell = NULL;
98
99                         if (shell == NULL)
100                                 if ((shell = getenv("SHELL")) == NULL)
101                                         shell = "/bin/sh";
102
103                         /* This is the child */
104                         execl(shell, shell, "-c", command, (void*)NULL);
105                         /* not reached */
106                 }
107                 exit(0);
108         }
109         wait(0);
110 }
111
112 /*
113  * Checks a generic cookie for errors and quits with the given message if there
114  * was an error.
115  *
116  */
117 void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_message) {
118         xcb_generic_error_t *error = xcb_request_check(conn, cookie);
119         if (error != NULL) {
120                 fprintf(stderr, "ERROR: %s (X error %d)\n", err_message , error->error_code);
121                 xcb_disconnect(conn);
122                 exit(-1);
123         }
124 }
125
126 /*
127  * Converts the given string to UCS-2 big endian for use with
128  * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
129  * a buffer containing the UCS-2 encoded string (16 bit per glyph) is
130  * returned. It has to be freed when done.
131  *
132  */
133 char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
134         size_t input_size = strlen(input) + 1;
135         /* UCS-2 consumes exactly two bytes for each glyph */
136         int buffer_size = input_size * 2;
137
138         char *buffer = smalloc(buffer_size);
139         size_t output_size = buffer_size;
140         /* We need to use an additional pointer, because iconv() modifies it */
141         char *output = buffer;
142
143         /* We convert the input into UCS-2 big endian */
144         if (conversion_descriptor == 0) {
145                 conversion_descriptor = iconv_open("UCS-2BE", "UTF-8");
146                 if (conversion_descriptor == 0) {
147                         fprintf(stderr, "error opening the conversion context\n");
148                         exit(1);
149                 }
150         }
151
152         /* Get the conversion descriptor back to original state */
153         iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
154
155         /* Convert our text */
156         int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
157         if (rc == (size_t)-1) {
158                 perror("Converting to UCS-2 failed");
159                 if (real_strlen != NULL)
160                         *real_strlen = 0;
161                 return NULL;
162         }
163
164         if (real_strlen != NULL)
165                 *real_strlen = ((buffer_size - output_size) / 2) - 1;
166
167         return buffer;
168 }
169 #if 0
170
171 /*
172  * Returns the client which comes next in focus stack (= was selected before) for
173  * the given container, optionally excluding the given client.
174  *
175  */
176 Client *get_last_focused_client(xcb_connection_t *conn, Container *container, Client *exclude) {
177         Client *current;
178         SLIST_FOREACH(current, &(container->workspace->focus_stack), focus_clients)
179                 if ((current->container == container) && ((exclude == NULL) || (current != exclude)))
180                         return current;
181         return NULL;
182 }
183
184
185 /*
186  * Sets the given client as focused by updating the data structures correctly,
187  * updating the X input focus and finally re-decorating both windows (to signalize
188  * the user the new focus situation)
189  *
190  */
191 void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
192         /* The dock window cannot be focused, but enter notifies are still handled correctly */
193         if (client->dock)
194                 return;
195
196         /* Store the old client */
197         Client *old_client = SLIST_FIRST(&(c_ws->focus_stack));
198
199         /* Check if the focus needs to be changed at all */
200         if (!set_anyways && (old_client == client))
201                 return;
202
203         /* Store current_row/current_col */
204         c_ws->current_row = current_row;
205         c_ws->current_col = current_col;
206         c_ws = client->workspace;
207         ewmh_update_current_desktop();
208         /* Load current_col/current_row if we switch to a client without a container */
209         current_col = c_ws->current_col;
210         current_row = c_ws->current_row;
211
212         /* Update container */
213         if (client->container != NULL) {
214                 client->container->currently_focused = client;
215
216                 current_col = client->container->col;
217                 current_row = client->container->row;
218         }
219
220         CLIENT_LOG(client);
221         /* Set focus to the entered window, and flush xcb buffer immediately */
222         xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME);
223         ewmh_update_active_window(client->child);
224         //xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10);
225
226         if (client->container != NULL) {
227                 /* Get the client which was last focused in this particular container, it may be a different
228                    one than old_client */
229                 Client *last_focused = get_last_focused_client(conn, client->container, NULL);
230
231                 /* In stacking containers, raise the client in respect to the one which was focused before */
232                 if ((client->container->mode == MODE_STACK || client->container->mode == MODE_TABBED) &&
233                     client->container->workspace->fullscreen_client == NULL) {
234                         /* We need to get the client again, this time excluding the current client, because
235                          * we might have just gone into stacking mode and need to raise */
236                         Client *last_focused = get_last_focused_client(conn, client->container, client);
237
238                         if (last_focused != NULL) {
239                                 DLOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child);
240                                 uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE };
241                                 xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
242                         }
243                 }
244
245                 /* If it is the same one as old_client, we save us the unnecessary redecorate */
246                 if ((last_focused != NULL) && (last_focused != old_client))
247                         redecorate_window(conn, last_focused);
248         }
249
250         /* If the last client was a floating client, we need to go to the next
251          * tiling client in stack and re-decorate it. */
252         if (old_client != NULL && client_is_floating(old_client)) {
253                 DLOG("Coming from floating client, searching next tiling...\n");
254                 Client *current;
255                 SLIST_FOREACH(current, &(client->workspace->focus_stack), focus_clients) {
256                         if (client_is_floating(current))
257                                 continue;
258
259                         DLOG("Found window: %p / child %p\n", current->frame, current->child);
260                         redecorate_window(conn, current);
261                         break;
262                 }
263         }
264
265         SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
266         SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
267
268         /* Clear the urgency flag if set (necessary when i3 sets the flag, for
269          * example when automatically putting windows on the workspace of their
270          * leader) */
271         client->urgent = false;
272         workspace_update_urgent_flag(client->workspace);
273
274         /* If we’re in stacking mode, this renders the container to update changes in the title
275            bars and to raise the focused client */
276         if ((old_client != NULL) && (old_client != client) && !old_client->dock)
277                 redecorate_window(conn, old_client);
278
279         /* redecorate_window flushes, so we don’t need to */
280         redecorate_window(conn, client);
281 }
282
283 /*
284  * Called when the user switches to another mode or when the container is
285  * destroyed and thus needs to be cleaned up.
286  *
287  */
288 void leave_stack_mode(xcb_connection_t *conn, Container *container) {
289         /* When going out of stacking mode, we need to close the window */
290         struct Stack_Window *stack_win = &(container->stack_win);
291
292         SLIST_REMOVE(&stack_wins, stack_win, Stack_Window, stack_windows);
293
294         xcb_free_gc(conn, stack_win->pixmap.gc);
295         xcb_free_pixmap(conn, stack_win->pixmap.id);
296         xcb_destroy_window(conn, stack_win->window);
297
298         stack_win->rect.width = -1;
299         stack_win->rect.height = -1;
300 }
301
302 /*
303  * Switches the layout of the given container taking care of the necessary house-keeping
304  *
305  */
306 void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) {
307         if (mode == MODE_STACK || mode == MODE_TABBED) {
308                 /* When we’re already in stacking mode, nothing has to be done */
309                 if ((mode == MODE_STACK && container->mode == MODE_STACK) ||
310                     (mode == MODE_TABBED && container->mode == MODE_TABBED))
311                         return;
312
313                 if (container->mode == MODE_STACK || container->mode == MODE_TABBED)
314                         goto after_stackwin;
315
316                 /* When entering stacking mode, we need to open a window on
317                  * which we can draw the title bars of the clients, it has
318                  * height 1 because we don’t bother here with calculating the
319                  * correct height - it will be adjusted when rendering anyways.
320                  * Also, we need to use max(width, 1) because windows cannot
321                  * be created with either width == 0 or height == 0. */
322                 Rect rect = {container->x, container->y, max(container->width, 1), 1};
323
324                 uint32_t mask = 0;
325                 uint32_t values[2];
326
327                 /* Don’t generate events for our new window, it should *not* be managed */
328                 mask |= XCB_CW_OVERRIDE_REDIRECT;
329                 values[0] = 1;
330
331                 /* We want to know when… */
332                 mask |= XCB_CW_EVENT_MASK;
333                 values[1] =     XCB_EVENT_MASK_ENTER_WINDOW |   /* …mouse is moved into our window */
334                                 XCB_EVENT_MASK_BUTTON_PRESS |   /* …mouse is pressed */
335                                 XCB_EVENT_MASK_EXPOSURE;        /* …our window needs to be redrawn */
336
337                 struct Stack_Window *stack_win = &(container->stack_win);
338                 stack_win->window = create_window(conn, rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
339
340                 stack_win->rect.height = 0;
341
342                 /* Initialize the entry for our cached pixmap. It will be
343                  * created as soon as it’s needed (see cached_pixmap_prepare). */
344                 memset(&(stack_win->pixmap), 0, sizeof(struct Cached_Pixmap));
345                 stack_win->pixmap.referred_rect = &stack_win->rect;
346                 stack_win->pixmap.referred_drawable = stack_win->window;
347
348                 stack_win->container = container;
349
350                 SLIST_INSERT_HEAD(&stack_wins, stack_win, stack_windows);
351         } else {
352                 if (container->mode == MODE_STACK || container->mode == MODE_TABBED)
353                         leave_stack_mode(conn, container);
354         }
355 after_stackwin:
356         container->mode = mode;
357
358         /* Force reconfiguration of each client */
359         Client *client;
360
361         CIRCLEQ_FOREACH(client, &(container->clients), clients)
362                 client->force_reconfigure = true;
363
364         render_layout(conn);
365
366         if (container->currently_focused != NULL) {
367                 /* We need to make sure that this client is above *each* of the
368                  * other clients in this container */
369                 Client *last_focused = get_last_focused_client(conn, container, container->currently_focused);
370
371                 CIRCLEQ_FOREACH(client, &(container->clients), clients) {
372                         if (client == container->currently_focused || client == last_focused)
373                                 continue;
374
375                         DLOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame);
376                         uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
377                         xcb_configure_window(conn, client->frame,
378                                              XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
379                 }
380
381                 if (last_focused != NULL) {
382                         DLOG("Putting last_focused directly underneath the currently focused\n");
383                         uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
384                         xcb_configure_window(conn, last_focused->frame,
385                                              XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
386                 }
387
388
389                 set_focus(conn, container->currently_focused, true);
390         }
391 }
392
393 /*
394  * Gets the first matching client for the given window class/window title.
395  * If the paramater specific is set to a specific client, only this one
396  * will be checked.
397  *
398  */
399 Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle,
400                             Client *specific) {
401         char *to_class, *to_title, *to_title_ucs = NULL;
402         int to_title_ucs_len = 0;
403         Client *matching = NULL;
404
405         to_class = sstrdup(window_classtitle);
406
407         /* If a title was specified, split both strings at the slash */
408         if ((to_title = strstr(to_class, "/")) != NULL) {
409                 *(to_title++) = '\0';
410                 /* Convert to UCS-2 */
411                 to_title_ucs = convert_utf8_to_ucs2(to_title, &to_title_ucs_len);
412         }
413
414         /* If we were given a specific client we only check if that one matches */
415         if (specific != NULL) {
416                 if (client_matches_class_name(specific, to_class, to_title, to_title_ucs, to_title_ucs_len))
417                         matching = specific;
418                 goto done;
419         }
420
421         DLOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title);
422         Workspace *ws;
423         TAILQ_FOREACH(ws, workspaces, workspaces) {
424                 if (ws->output == NULL)
425                         continue;
426
427                 Client *client;
428                 SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) {
429                         DLOG("Checking client with class=%s / %s, name=%s\n", client->window_class_instance,
430                              client->window_class_class, client->name);
431                         if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len))
432                                 continue;
433
434                         matching = client;
435                         goto done;
436                 }
437         }
438
439 done:
440         free(to_class);
441         FREE(to_title_ucs);
442         return matching;
443 }
444 #endif
445
446 /*
447  * Goes through the list of arguments (for exec()) and checks if the given argument
448  * is present. If not, it copies the arguments (because we cannot realloc it) and
449  * appends the given argument.
450  *
451  */
452 static char **append_argument(char **original, char *argument) {
453         int num_args;
454         for (num_args = 0; original[num_args] != NULL; num_args++) {
455                 DLOG("original argument: \"%s\"\n", original[num_args]);
456                 /* If the argument is already present we return the original pointer */
457                 if (strcmp(original[num_args], argument) == 0)
458                         return original;
459         }
460         /* Copy the original array */
461         char **result = smalloc((num_args+2) * sizeof(char*));
462         memcpy(result, original, num_args * sizeof(char*));
463         result[num_args] = argument;
464         result[num_args+1] = NULL;
465
466         return result;
467 }
468
469 #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__)
470 #define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str))
471
472 void store_restart_layout() {
473         yajl_gen gen = yajl_gen_alloc(NULL, NULL);
474
475         dump_node(gen, croot, true);
476
477         const unsigned char *payload;
478         unsigned int length;
479         y(get_buf, &payload, &length);
480
481         char *globbed = glob_path("~/.i3/_restart.json");
482         int fd = open(globbed, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
483         free(globbed);
484         if (fd == -1) {
485                 perror("open()");
486                 return;
487         }
488
489         int written = 0;
490         while (written < length) {
491                 int n = write(fd, payload + written, length - written);
492                 /* TODO: correct error-handling */
493                 if (n == -1) {
494                         perror("write()");
495                         return;
496                 }
497                 if (n == 0) {
498                         printf("write == 0?\n");
499                         return;
500                 }
501                 written += n;
502                 printf("written: %d of %d\n", written, length);
503         }
504         close(fd);
505
506         printf("layout: %.*s\n", length, payload);
507
508         y(free);
509 }
510
511 /*
512  * Restart i3 in-place
513  * appends -a to argument list to disable autostart
514  *
515  */
516 void i3_restart() {
517         store_restart_layout();
518         restore_geometry();
519
520         //ipc_shutdown();
521
522         LOG("restarting \"%s\"...\n", start_argv[0]);
523         /* make sure -a is in the argument list or append it */
524         start_argv = append_argument(start_argv, "-a");
525
526         execvp(start_argv[0], start_argv);
527         /* not reached */
528 }
529
530 #if 0
531
532 #if defined(__OpenBSD__)
533
534 /*
535  * Taken from FreeBSD
536  * Find the first occurrence of the byte string s in byte string l.
537  *
538  */
539 void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) {
540         register char *cur, *last;
541         const char *cl = (const char *)l;
542         const char *cs = (const char *)s;
543
544         /* we need something to compare */
545         if (l_len == 0 || s_len == 0)
546                 return NULL;
547
548         /* "s" must be smaller or equal to "l" */
549         if (l_len < s_len)
550                 return NULL;
551
552         /* special case where s_len == 1 */
553         if (s_len == 1)
554                 return memchr(l, (int)*cs, l_len);
555
556         /* the last position where its possible to find "s" in "l" */
557         last = (char *)cl + l_len - s_len;
558
559         for (cur = (char *)cl; cur <= last; cur++)
560                 if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
561                         return cur;
562
563         return NULL;
564 }
565
566 #endif
567 #endif