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