i3
|
00001 /* 00002 * vim:ts=8:expandtab 00003 * 00004 * i3 - an improved dynamic tiling window manager 00005 * 00006 * © 2009 Michael Stapelberg and contributors 00007 * 00008 * See file LICENSE for license information. 00009 * 00010 * util.c: Utility functions, which can be useful everywhere. 00011 * 00012 */ 00013 #include <stdio.h> 00014 #include <stdlib.h> 00015 #include <unistd.h> 00016 #include <string.h> 00017 #include <sys/wait.h> 00018 #include <stdarg.h> 00019 #include <assert.h> 00020 #include <iconv.h> 00021 #if defined(__OpenBSD__) 00022 #include <sys/cdefs.h> 00023 #endif 00024 00025 #include <xcb/xcb_icccm.h> 00026 00027 #include "i3.h" 00028 #include "data.h" 00029 #include "table.h" 00030 #include "layout.h" 00031 #include "util.h" 00032 #include "xcb.h" 00033 #include "client.h" 00034 #include "log.h" 00035 #include "ewmh.h" 00036 #include "manage.h" 00037 #include "workspace.h" 00038 #include "ipc.h" 00039 00040 static iconv_t conversion_descriptor = 0; 00041 struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent); 00042 struct keyvalue_table_head by_child = TAILQ_HEAD_INITIALIZER(by_child); 00043 00044 int min(int a, int b) { 00045 return (a < b ? a : b); 00046 } 00047 00048 int max(int a, int b) { 00049 return (a > b ? a : b); 00050 } 00051 00052 /* 00053 * Updates *destination with new_value and returns true if it was changed or false 00054 * if it was the same 00055 * 00056 */ 00057 bool update_if_necessary(uint32_t *destination, const uint32_t new_value) { 00058 uint32_t old_value = *destination; 00059 00060 return ((*destination = new_value) != old_value); 00061 } 00062 00063 /* 00064 * The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of 00065 * the called functions returns NULL, meaning that there is no more memory available 00066 * 00067 */ 00068 void *smalloc(size_t size) { 00069 void *result = malloc(size); 00070 exit_if_null(result, "Error: out of memory (malloc(%zd))\n", size); 00071 return result; 00072 } 00073 00074 void *scalloc(size_t size) { 00075 void *result = calloc(size, 1); 00076 exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size); 00077 return result; 00078 } 00079 00080 char *sstrdup(const char *str) { 00081 char *result = strdup(str); 00082 exit_if_null(result, "Error: out of memory (strdup())\n"); 00083 return result; 00084 } 00085 00086 /* 00087 * The table_* functions emulate the behaviour of libxcb-wm, which in libxcb 0.3.4 suddenly 00088 * vanished. Great. 00089 * 00090 */ 00091 bool table_put(struct keyvalue_table_head *head, uint32_t key, void *value) { 00092 struct keyvalue_element *element = scalloc(sizeof(struct keyvalue_element)); 00093 element->key = key; 00094 element->value = value; 00095 00096 TAILQ_INSERT_TAIL(head, element, elements); 00097 return true; 00098 } 00099 00100 void *table_remove(struct keyvalue_table_head *head, uint32_t key) { 00101 struct keyvalue_element *element; 00102 00103 TAILQ_FOREACH(element, head, elements) 00104 if (element->key == key) { 00105 void *value = element->value; 00106 TAILQ_REMOVE(head, element, elements); 00107 free(element); 00108 return value; 00109 } 00110 00111 return NULL; 00112 } 00113 00114 void *table_get(struct keyvalue_table_head *head, uint32_t key) { 00115 struct keyvalue_element *element; 00116 00117 TAILQ_FOREACH(element, head, elements) 00118 if (element->key == key) 00119 return element->value; 00120 00121 return NULL; 00122 } 00123 00124 /* 00125 * Starts the given application by passing it through a shell. We use double fork 00126 * to avoid zombie processes. As the started application’s parent exits (immediately), 00127 * the application is reparented to init (process-id 1), which correctly handles 00128 * childs, so we don’t have to do it :-). 00129 * 00130 * The shell is determined by looking for the SHELL environment variable. If it 00131 * does not exist, /bin/sh is used. 00132 * 00133 */ 00134 void start_application(const char *command) { 00135 if (fork() == 0) { 00136 /* Child process */ 00137 if (fork() == 0) { 00138 /* Stores the path of the shell */ 00139 static const char *shell = NULL; 00140 00141 if (shell == NULL) 00142 if ((shell = getenv("SHELL")) == NULL) 00143 shell = "/bin/sh"; 00144 00145 /* This is the child */ 00146 execl(shell, shell, "-c", command, (void*)NULL); 00147 /* not reached */ 00148 } 00149 exit(0); 00150 } 00151 wait(0); 00152 } 00153 00154 /* 00155 * Checks a generic cookie for errors and quits with the given message if there 00156 * was an error. 00157 * 00158 */ 00159 void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_message) { 00160 xcb_generic_error_t *error = xcb_request_check(conn, cookie); 00161 if (error != NULL) { 00162 fprintf(stderr, "ERROR: %s (X error %d)\n", err_message , error->error_code); 00163 xcb_disconnect(conn); 00164 exit(-1); 00165 } 00166 } 00167 00168 /* 00169 * Converts the given string to UCS-2 big endian for use with 00170 * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen, 00171 * a buffer containing the UCS-2 encoded string (16 bit per glyph) is 00172 * returned. It has to be freed when done. 00173 * 00174 */ 00175 char *convert_utf8_to_ucs2(char *input, int *real_strlen) { 00176 size_t input_size = strlen(input) + 1; 00177 /* UCS-2 consumes exactly two bytes for each glyph */ 00178 int buffer_size = input_size * 2; 00179 00180 char *buffer = smalloc(buffer_size); 00181 size_t output_size = buffer_size; 00182 /* We need to use an additional pointer, because iconv() modifies it */ 00183 char *output = buffer; 00184 00185 /* We convert the input into UCS-2 big endian */ 00186 if (conversion_descriptor == 0) { 00187 conversion_descriptor = iconv_open("UCS-2BE", "UTF-8"); 00188 if (conversion_descriptor == 0) { 00189 fprintf(stderr, "error opening the conversion context\n"); 00190 exit(1); 00191 } 00192 } 00193 00194 /* Get the conversion descriptor back to original state */ 00195 iconv(conversion_descriptor, NULL, NULL, NULL, NULL); 00196 00197 /* Convert our text */ 00198 int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size); 00199 if (rc == (size_t)-1) { 00200 perror("Converting to UCS-2 failed"); 00201 if (real_strlen != NULL) 00202 *real_strlen = 0; 00203 return NULL; 00204 } 00205 00206 if (real_strlen != NULL) 00207 *real_strlen = ((buffer_size - output_size) / 2) - 1; 00208 00209 return buffer; 00210 } 00211 00212 /* 00213 * Returns the client which comes next in focus stack (= was selected before) for 00214 * the given container, optionally excluding the given client. 00215 * 00216 */ 00217 Client *get_last_focused_client(xcb_connection_t *conn, Container *container, Client *exclude) { 00218 Client *current; 00219 SLIST_FOREACH(current, &(container->workspace->focus_stack), focus_clients) 00220 if ((current->container == container) && ((exclude == NULL) || (current != exclude))) 00221 return current; 00222 return NULL; 00223 } 00224 00225 00226 /* 00227 * Sets the given client as focused by updating the data structures correctly, 00228 * updating the X input focus and finally re-decorating both windows (to signalize 00229 * the user the new focus situation) 00230 * 00231 */ 00232 void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { 00233 /* The dock window cannot be focused, but enter notifies are still handled correctly */ 00234 if (client->dock) 00235 return; 00236 00237 /* Store the old client */ 00238 Client *old_client = SLIST_FIRST(&(c_ws->focus_stack)); 00239 00240 /* Check if the focus needs to be changed at all */ 00241 if (!set_anyways && (old_client == client)) 00242 return; 00243 00244 /* Store current_row/current_col */ 00245 c_ws->current_row = current_row; 00246 c_ws->current_col = current_col; 00247 c_ws = client->workspace; 00248 ewmh_update_current_desktop(); 00249 /* Load current_col/current_row if we switch to a client without a container */ 00250 current_col = c_ws->current_col; 00251 current_row = c_ws->current_row; 00252 00253 /* Update container */ 00254 if (client->container != NULL) { 00255 client->container->currently_focused = client; 00256 00257 current_col = client->container->col; 00258 current_row = client->container->row; 00259 } 00260 00261 CLIENT_LOG(client); 00262 /* Set focus to the entered window, and flush xcb buffer immediately */ 00263 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME); 00264 ewmh_update_active_window(client->child); 00265 //xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10); 00266 00267 if (client->container != NULL) { 00268 /* Get the client which was last focused in this particular container, it may be a different 00269 one than old_client */ 00270 Client *last_focused = get_last_focused_client(conn, client->container, NULL); 00271 00272 /* In stacking containers, raise the client in respect to the one which was focused before */ 00273 if ((client->container->mode == MODE_STACK || client->container->mode == MODE_TABBED) && 00274 client->container->workspace->fullscreen_client == NULL) { 00275 /* We need to get the client again, this time excluding the current client, because 00276 * we might have just gone into stacking mode and need to raise */ 00277 Client *last_focused = get_last_focused_client(conn, client->container, client); 00278 00279 if (last_focused != NULL) { 00280 DLOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child); 00281 uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE }; 00282 xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); 00283 } 00284 } 00285 00286 /* If it is the same one as old_client, we save us the unnecessary redecorate */ 00287 if ((last_focused != NULL) && (last_focused != old_client)) 00288 redecorate_window(conn, last_focused); 00289 } 00290 00291 /* If the last client was a floating client, we need to go to the next 00292 * tiling client in stack and re-decorate it. */ 00293 if (old_client != NULL && client_is_floating(old_client)) { 00294 DLOG("Coming from floating client, searching next tiling...\n"); 00295 Client *current; 00296 SLIST_FOREACH(current, &(client->workspace->focus_stack), focus_clients) { 00297 if (client_is_floating(current)) 00298 continue; 00299 00300 DLOG("Found window: %p / child %p\n", current->frame, current->child); 00301 redecorate_window(conn, current); 00302 break; 00303 } 00304 } 00305 00306 SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); 00307 SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients); 00308 00309 /* Clear the urgency flag if set (necessary when i3 sets the flag, for 00310 * example when automatically putting windows on the workspace of their 00311 * leader) */ 00312 client->urgent = false; 00313 workspace_update_urgent_flag(client->workspace); 00314 00315 /* If we’re in stacking mode, this renders the container to update changes in the title 00316 bars and to raise the focused client */ 00317 if ((old_client != NULL) && (old_client != client) && !old_client->dock) 00318 redecorate_window(conn, old_client); 00319 00320 /* redecorate_window flushes, so we don’t need to */ 00321 redecorate_window(conn, client); 00322 } 00323 00324 /* 00325 * Called when the user switches to another mode or when the container is 00326 * destroyed and thus needs to be cleaned up. 00327 * 00328 */ 00329 void leave_stack_mode(xcb_connection_t *conn, Container *container) { 00330 /* When going out of stacking mode, we need to close the window */ 00331 struct Stack_Window *stack_win = &(container->stack_win); 00332 00333 SLIST_REMOVE(&stack_wins, stack_win, Stack_Window, stack_windows); 00334 00335 xcb_free_gc(conn, stack_win->pixmap.gc); 00336 xcb_free_pixmap(conn, stack_win->pixmap.id); 00337 xcb_destroy_window(conn, stack_win->window); 00338 00339 stack_win->rect.width = -1; 00340 stack_win->rect.height = -1; 00341 } 00342 00343 /* 00344 * Switches the layout of the given container taking care of the necessary house-keeping 00345 * 00346 */ 00347 void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) { 00348 if (mode == MODE_STACK || mode == MODE_TABBED) { 00349 /* When we’re already in stacking mode, nothing has to be done */ 00350 if ((mode == MODE_STACK && container->mode == MODE_STACK) || 00351 (mode == MODE_TABBED && container->mode == MODE_TABBED)) 00352 return; 00353 00354 if (container->mode == MODE_STACK || container->mode == MODE_TABBED) 00355 goto after_stackwin; 00356 00357 /* When entering stacking mode, we need to open a window on 00358 * which we can draw the title bars of the clients, it has 00359 * height 1 because we don’t bother here with calculating the 00360 * correct height - it will be adjusted when rendering anyways. 00361 * Also, we need to use max(width, 1) because windows cannot 00362 * be created with either width == 0 or height == 0. */ 00363 Rect rect = {container->x, container->y, max(container->width, 1), 1}; 00364 00365 uint32_t mask = 0; 00366 uint32_t values[2]; 00367 00368 /* Don’t generate events for our new window, it should *not* be managed */ 00369 mask |= XCB_CW_OVERRIDE_REDIRECT; 00370 values[0] = 1; 00371 00372 /* We want to know when… */ 00373 mask |= XCB_CW_EVENT_MASK; 00374 values[1] = XCB_EVENT_MASK_ENTER_WINDOW | /* …mouse is moved into our window */ 00375 XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed */ 00376 XCB_EVENT_MASK_EXPOSURE; /* …our window needs to be redrawn */ 00377 00378 struct Stack_Window *stack_win = &(container->stack_win); 00379 stack_win->window = create_window(conn, rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values); 00380 00381 stack_win->rect.height = 0; 00382 00383 /* Initialize the entry for our cached pixmap. It will be 00384 * created as soon as it’s needed (see cached_pixmap_prepare). */ 00385 memset(&(stack_win->pixmap), 0, sizeof(struct Cached_Pixmap)); 00386 stack_win->pixmap.referred_rect = &stack_win->rect; 00387 stack_win->pixmap.referred_drawable = stack_win->window; 00388 00389 stack_win->container = container; 00390 00391 SLIST_INSERT_HEAD(&stack_wins, stack_win, stack_windows); 00392 } else { 00393 if (container->mode == MODE_STACK || container->mode == MODE_TABBED) 00394 leave_stack_mode(conn, container); 00395 } 00396 after_stackwin: 00397 container->mode = mode; 00398 00399 /* Force reconfiguration of each client */ 00400 Client *client; 00401 00402 CIRCLEQ_FOREACH(client, &(container->clients), clients) 00403 client->force_reconfigure = true; 00404 00405 render_layout(conn); 00406 00407 if (container->currently_focused != NULL) { 00408 /* We need to make sure that this client is above *each* of the 00409 * other clients in this container */ 00410 Client *last_focused = get_last_focused_client(conn, container, container->currently_focused); 00411 00412 CIRCLEQ_FOREACH(client, &(container->clients), clients) { 00413 if (client == container->currently_focused || client == last_focused) 00414 continue; 00415 00416 DLOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame); 00417 uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW }; 00418 xcb_configure_window(conn, client->frame, 00419 XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); 00420 } 00421 00422 if (last_focused != NULL) { 00423 DLOG("Putting last_focused directly underneath the currently focused\n"); 00424 uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW }; 00425 xcb_configure_window(conn, last_focused->frame, 00426 XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); 00427 } 00428 00429 00430 set_focus(conn, container->currently_focused, true); 00431 } 00432 } 00433 00434 /* 00435 * Gets the first matching client for the given window class/window title. 00436 * If the paramater specific is set to a specific client, only this one 00437 * will be checked. 00438 * 00439 */ 00440 Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle, 00441 Client *specific) { 00442 char *to_class, *to_title, *to_title_ucs = NULL; 00443 int to_title_ucs_len = 0; 00444 Client *matching = NULL; 00445 00446 to_class = sstrdup(window_classtitle); 00447 00448 /* If a title was specified, split both strings at the slash */ 00449 if ((to_title = strstr(to_class, "/")) != NULL) { 00450 *(to_title++) = '\0'; 00451 /* Convert to UCS-2 */ 00452 to_title_ucs = convert_utf8_to_ucs2(to_title, &to_title_ucs_len); 00453 } 00454 00455 /* If we were given a specific client we only check if that one matches */ 00456 if (specific != NULL) { 00457 if (client_matches_class_name(specific, to_class, to_title, to_title_ucs, to_title_ucs_len)) 00458 matching = specific; 00459 goto done; 00460 } 00461 00462 DLOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title); 00463 Workspace *ws; 00464 TAILQ_FOREACH(ws, workspaces, workspaces) { 00465 if (ws->output == NULL) 00466 continue; 00467 00468 Client *client; 00469 SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) { 00470 DLOG("Checking client with class=%s / %s, name=%s\n", client->window_class_instance, 00471 client->window_class_class, client->name); 00472 if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) 00473 continue; 00474 00475 matching = client; 00476 goto done; 00477 } 00478 } 00479 00480 done: 00481 free(to_class); 00482 FREE(to_title_ucs); 00483 return matching; 00484 } 00485 00486 /* 00487 * Goes through the list of arguments (for exec()) and checks if the given argument 00488 * is present. If not, it copies the arguments (because we cannot realloc it) and 00489 * appends the given argument. 00490 * 00491 */ 00492 static char **append_argument(char **original, char *argument) { 00493 int num_args; 00494 for (num_args = 0; original[num_args] != NULL; num_args++) { 00495 DLOG("original argument: \"%s\"\n", original[num_args]); 00496 /* If the argument is already present we return the original pointer */ 00497 if (strcmp(original[num_args], argument) == 0) 00498 return original; 00499 } 00500 /* Copy the original array */ 00501 char **result = smalloc((num_args+2) * sizeof(char*)); 00502 memcpy(result, original, num_args * sizeof(char*)); 00503 result[num_args] = argument; 00504 result[num_args+1] = NULL; 00505 00506 return result; 00507 } 00508 00509 /* 00510 * Restart i3 in-place 00511 * appends -a to argument list to disable autostart 00512 * 00513 */ 00514 void i3_restart() { 00515 restore_geometry(global_conn); 00516 00517 ipc_shutdown(); 00518 00519 LOG("restarting \"%s\"...\n", start_argv[0]); 00520 /* make sure -a is in the argument list or append it */ 00521 start_argv = append_argument(start_argv, "-a"); 00522 00523 execvp(start_argv[0], start_argv); 00524 /* not reached */ 00525 } 00526 00527 #if defined(__OpenBSD__) 00528 00529 /* 00530 * Taken from FreeBSD 00531 * Find the first occurrence of the byte string s in byte string l. 00532 * 00533 */ 00534 void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) { 00535 register char *cur, *last; 00536 const char *cl = (const char *)l; 00537 const char *cs = (const char *)s; 00538 00539 /* we need something to compare */ 00540 if (l_len == 0 || s_len == 0) 00541 return NULL; 00542 00543 /* "s" must be smaller or equal to "l" */ 00544 if (l_len < s_len) 00545 return NULL; 00546 00547 /* special case where s_len == 1 */ 00548 if (s_len == 1) 00549 return memchr(l, (int)*cs, l_len); 00550 00551 /* the last position where its possible to find "s" in "l" */ 00552 last = (char *)cl + l_len - s_len; 00553 00554 for (cur = (char *)cl; cur <= last; cur++) 00555 if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) 00556 return cur; 00557 00558 return NULL; 00559 } 00560 00561 #endif 00562