i3
|
00001 /* 00002 * vim:ts=4:sw=4:expandtab 00003 * 00004 * i3 - an improved dynamic tiling window manager 00005 * 00006 * © 2009-2011 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 <sys/wait.h> 00014 #include <stdarg.h> 00015 #include <iconv.h> 00016 #if defined(__OpenBSD__) 00017 #include <sys/cdefs.h> 00018 #endif 00019 #include <fcntl.h> 00020 #include <pwd.h> 00021 #include <yajl/yajl_version.h> 00022 #include <libgen.h> 00023 00024 #include "all.h" 00025 00026 static iconv_t conversion_descriptor = 0; 00027 00028 int min(int a, int b) { 00029 return (a < b ? a : b); 00030 } 00031 00032 int max(int a, int b) { 00033 return (a > b ? a : b); 00034 } 00035 00036 bool rect_contains(Rect rect, uint32_t x, uint32_t y) { 00037 return (x >= rect.x && 00038 x <= (rect.x + rect.width) && 00039 y >= rect.y && 00040 y <= (rect.y + rect.height)); 00041 } 00042 00043 Rect rect_add(Rect a, Rect b) { 00044 return (Rect){a.x + b.x, 00045 a.y + b.y, 00046 a.width + b.width, 00047 a.height + b.height}; 00048 } 00049 00050 /* 00051 * Updates *destination with new_value and returns true if it was changed or false 00052 * if it was the same 00053 * 00054 */ 00055 bool update_if_necessary(uint32_t *destination, const uint32_t new_value) { 00056 uint32_t old_value = *destination; 00057 00058 return ((*destination = new_value) != old_value); 00059 } 00060 00061 /* 00062 * The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of 00063 * the called functions returns NULL, meaning that there is no more memory available 00064 * 00065 */ 00066 void *smalloc(size_t size) { 00067 void *result = malloc(size); 00068 exit_if_null(result, "Error: out of memory (malloc(%zd))\n", size); 00069 return result; 00070 } 00071 00072 void *scalloc(size_t size) { 00073 void *result = calloc(size, 1); 00074 exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size); 00075 return result; 00076 } 00077 00078 void *srealloc(void *ptr, size_t size) { 00079 void *result = realloc(ptr, size); 00080 exit_if_null(result, "Error: out memory (realloc(%zd))\n", size); 00081 return result; 00082 } 00083 00084 char *sstrdup(const char *str) { 00085 char *result = strdup(str); 00086 exit_if_null(result, "Error: out of memory (strdup())\n"); 00087 return result; 00088 } 00089 00090 /* 00091 * Starts the given application by passing it through a shell. We use double fork 00092 * to avoid zombie processes. As the started application’s parent exits (immediately), 00093 * the application is reparented to init (process-id 1), which correctly handles 00094 * childs, so we don’t have to do it :-). 00095 * 00096 * The shell is determined by looking for the SHELL environment variable. If it 00097 * does not exist, /bin/sh is used. 00098 * 00099 */ 00100 void start_application(const char *command) { 00101 LOG("executing: %s\n", command); 00102 if (fork() == 0) { 00103 /* Child process */ 00104 setsid(); 00105 if (fork() == 0) { 00106 /* Stores the path of the shell */ 00107 static const char *shell = NULL; 00108 00109 if (shell == NULL) 00110 if ((shell = getenv("SHELL")) == NULL) 00111 shell = "/bin/sh"; 00112 00113 /* This is the child */ 00114 execl(shell, shell, "-c", command, (void*)NULL); 00115 /* not reached */ 00116 } 00117 exit(0); 00118 } 00119 wait(0); 00120 } 00121 00122 /* 00123 * exec()s an i3 utility, for example the config file migration script or 00124 * i3-nagbar. This function first searches $PATH for the given utility named, 00125 * then falls back to the dirname() of the i3 executable path and then falls 00126 * back to the dirname() of the target of /proc/self/exe (on linux). 00127 * 00128 * This function should be called after fork()ing. 00129 * 00130 * The first argument of the given argv vector will be overwritten with the 00131 * executable name, so pass NULL. 00132 * 00133 * If the utility cannot be found in any of these locations, it exits with 00134 * return code 2. 00135 * 00136 */ 00137 void exec_i3_utility(char *name, char *argv[]) { 00138 /* start the migration script, search PATH first */ 00139 char *migratepath = name; 00140 argv[0] = migratepath; 00141 execvp(migratepath, argv); 00142 00143 /* if the script is not in path, maybe the user installed to a strange 00144 * location and runs the i3 binary with an absolute path. We use 00145 * argv[0]’s dirname */ 00146 char *pathbuf = strdup(start_argv[0]); 00147 char *dir = dirname(pathbuf); 00148 asprintf(&migratepath, "%s/%s", dir, name); 00149 argv[0] = migratepath; 00150 execvp(migratepath, argv); 00151 00152 #if defined(__linux__) 00153 /* on linux, we have one more fall-back: dirname(/proc/self/exe) */ 00154 char buffer[BUFSIZ]; 00155 if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) { 00156 warn("could not read /proc/self/exe"); 00157 exit(1); 00158 } 00159 dir = dirname(buffer); 00160 asprintf(&migratepath, "%s/%s", dir, name); 00161 argv[0] = migratepath; 00162 execvp(migratepath, argv); 00163 #endif 00164 00165 warn("Could not start %s", name); 00166 exit(2); 00167 } 00168 00169 /* 00170 * Checks a generic cookie for errors and quits with the given message if there 00171 * was an error. 00172 * 00173 */ 00174 void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_message) { 00175 xcb_generic_error_t *error = xcb_request_check(conn, cookie); 00176 if (error != NULL) { 00177 fprintf(stderr, "ERROR: %s (X error %d)\n", err_message , error->error_code); 00178 xcb_disconnect(conn); 00179 exit(-1); 00180 } 00181 } 00182 00183 /* 00184 * Converts the given string to UCS-2 big endian for use with 00185 * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen, 00186 * a buffer containing the UCS-2 encoded string (16 bit per glyph) is 00187 * returned. It has to be freed when done. 00188 * 00189 */ 00190 char *convert_utf8_to_ucs2(char *input, int *real_strlen) { 00191 size_t input_size = strlen(input) + 1; 00192 /* UCS-2 consumes exactly two bytes for each glyph */ 00193 int buffer_size = input_size * 2; 00194 00195 char *buffer = smalloc(buffer_size); 00196 size_t output_size = buffer_size; 00197 /* We need to use an additional pointer, because iconv() modifies it */ 00198 char *output = buffer; 00199 00200 /* We convert the input into UCS-2 big endian */ 00201 if (conversion_descriptor == 0) { 00202 conversion_descriptor = iconv_open("UCS-2BE", "UTF-8"); 00203 if (conversion_descriptor == 0) { 00204 fprintf(stderr, "error opening the conversion context\n"); 00205 exit(1); 00206 } 00207 } 00208 00209 /* Get the conversion descriptor back to original state */ 00210 iconv(conversion_descriptor, NULL, NULL, NULL, NULL); 00211 00212 /* Convert our text */ 00213 int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size); 00214 if (rc == (size_t)-1) { 00215 perror("Converting to UCS-2 failed"); 00216 if (real_strlen != NULL) 00217 *real_strlen = 0; 00218 return NULL; 00219 } 00220 00221 if (real_strlen != NULL) 00222 *real_strlen = ((buffer_size - output_size) / 2) - 1; 00223 00224 return buffer; 00225 } 00226 00227 /* 00228 * This function resolves ~ in pathnames. 00229 * It may resolve wildcards in the first part of the path, but if no match 00230 * or multiple matches are found, it just returns a copy of path as given. 00231 * 00232 */ 00233 char *resolve_tilde(const char *path) { 00234 static glob_t globbuf; 00235 char *head, *tail, *result; 00236 00237 tail = strchr(path, '/'); 00238 head = strndup(path, tail ? tail - path : strlen(path)); 00239 00240 int res = glob(head, GLOB_TILDE, NULL, &globbuf); 00241 free(head); 00242 /* no match, or many wildcard matches are bad */ 00243 if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1) 00244 result = sstrdup(path); 00245 else if (res != 0) { 00246 die("glob() failed"); 00247 } else { 00248 head = globbuf.gl_pathv[0]; 00249 result = scalloc(strlen(head) + (tail ? strlen(tail) : 0) + 1); 00250 strncpy(result, head, strlen(head)); 00251 if (tail) 00252 strncat(result, tail, strlen(tail)); 00253 } 00254 globfree(&globbuf); 00255 00256 return result; 00257 } 00258 00259 /* 00260 * Checks if the given path exists by calling stat(). 00261 * 00262 */ 00263 bool path_exists(const char *path) { 00264 struct stat buf; 00265 return (stat(path, &buf) == 0); 00266 } 00267 00268 /* 00269 * Goes through the list of arguments (for exec()) and checks if the given argument 00270 * is present. If not, it copies the arguments (because we cannot realloc it) and 00271 * appends the given argument. 00272 * 00273 */ 00274 static char **append_argument(char **original, char *argument) { 00275 int num_args; 00276 for (num_args = 0; original[num_args] != NULL; num_args++) { 00277 DLOG("original argument: \"%s\"\n", original[num_args]); 00278 /* If the argument is already present we return the original pointer */ 00279 if (strcmp(original[num_args], argument) == 0) 00280 return original; 00281 } 00282 /* Copy the original array */ 00283 char **result = smalloc((num_args+2) * sizeof(char*)); 00284 memcpy(result, original, num_args * sizeof(char*)); 00285 result[num_args] = argument; 00286 result[num_args+1] = NULL; 00287 00288 return result; 00289 } 00290 00291 /* 00292 * Returns the name of a temporary file with the specified prefix. 00293 * 00294 */ 00295 char *get_process_filename(const char *prefix) { 00296 char *dir = getenv("XDG_RUNTIME_DIR"); 00297 if (dir == NULL) { 00298 struct passwd *pw = getpwuid(getuid()); 00299 const char *username = pw ? pw->pw_name : "unknown"; 00300 if (asprintf(&dir, "/tmp/i3-%s", username) == -1) { 00301 perror("asprintf()"); 00302 return NULL; 00303 } 00304 } else { 00305 char *tmp; 00306 if (asprintf(&tmp, "%s/i3", dir) == -1) { 00307 perror("asprintf()"); 00308 return NULL; 00309 } 00310 dir = tmp; 00311 } 00312 if (!path_exists(dir)) { 00313 if (mkdir(dir, 0700) == -1) { 00314 perror("mkdir()"); 00315 return NULL; 00316 } 00317 } 00318 char *filename; 00319 if (asprintf(&filename, "%s/%s.%d", dir, prefix, getpid()) == -1) { 00320 perror("asprintf()"); 00321 filename = NULL; 00322 } 00323 00324 free(dir); 00325 return filename; 00326 } 00327 00328 #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) 00329 #define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str)) 00330 00331 char *store_restart_layout() { 00332 setlocale(LC_NUMERIC, "C"); 00333 #if YAJL_MAJOR >= 2 00334 yajl_gen gen = yajl_gen_alloc(NULL); 00335 #else 00336 yajl_gen gen = yajl_gen_alloc(NULL, NULL); 00337 #endif 00338 00339 dump_node(gen, croot, true); 00340 00341 setlocale(LC_NUMERIC, ""); 00342 00343 const unsigned char *payload; 00344 #if YAJL_MAJOR >= 2 00345 size_t length; 00346 #else 00347 unsigned int length; 00348 #endif 00349 y(get_buf, &payload, &length); 00350 00351 /* create a temporary file if one hasn't been specified, or just 00352 * resolve the tildes in the specified path */ 00353 char *filename; 00354 if (config.restart_state_path == NULL) { 00355 filename = get_process_filename("restart-state"); 00356 if (!filename) 00357 return NULL; 00358 } else { 00359 filename = resolve_tilde(config.restart_state_path); 00360 } 00361 00362 int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); 00363 if (fd == -1) { 00364 perror("open()"); 00365 free(filename); 00366 return NULL; 00367 } 00368 00369 int written = 0; 00370 while (written < length) { 00371 int n = write(fd, payload + written, length - written); 00372 /* TODO: correct error-handling */ 00373 if (n == -1) { 00374 perror("write()"); 00375 free(filename); 00376 return NULL; 00377 } 00378 if (n == 0) { 00379 printf("write == 0?\n"); 00380 free(filename); 00381 return NULL; 00382 } 00383 written += n; 00384 #if YAJL_MAJOR >= 2 00385 printf("written: %d of %zd\n", written, length); 00386 #else 00387 printf("written: %d of %d\n", written, length); 00388 #endif 00389 } 00390 close(fd); 00391 00392 if (length > 0) { 00393 printf("layout: %.*s\n", (int)length, payload); 00394 } 00395 00396 y(free); 00397 00398 return filename; 00399 } 00400 00401 /* 00402 * Restart i3 in-place 00403 * appends -a to argument list to disable autostart 00404 * 00405 */ 00406 void i3_restart(bool forget_layout) { 00407 char *restart_filename = forget_layout ? NULL : store_restart_layout(); 00408 00409 kill_configerror_nagbar(true); 00410 00411 restore_geometry(); 00412 00413 ipc_shutdown(); 00414 00415 LOG("restarting \"%s\"...\n", start_argv[0]); 00416 /* make sure -a is in the argument list or append it */ 00417 start_argv = append_argument(start_argv, "-a"); 00418 00419 /* replace -r <file> so that the layout is restored */ 00420 if (restart_filename != NULL) { 00421 /* create the new argv */ 00422 int num_args; 00423 for (num_args = 0; start_argv[num_args] != NULL; num_args++); 00424 char **new_argv = scalloc((num_args + 3) * sizeof(char*)); 00425 00426 /* copy the arguments, but skip the ones we'll replace */ 00427 int write_index = 0; 00428 bool skip_next = false; 00429 for (int i = 0; i < num_args; ++i) { 00430 if (skip_next) 00431 skip_next = false; 00432 else if (!strcmp(start_argv[i], "-r") || 00433 !strcmp(start_argv[i], "--restart")) 00434 skip_next = true; 00435 else 00436 new_argv[write_index++] = start_argv[i]; 00437 } 00438 00439 /* add the arguments we'll replace */ 00440 new_argv[write_index++] = "--restart"; 00441 new_argv[write_index] = restart_filename; 00442 00443 /* swap the argvs */ 00444 start_argv = new_argv; 00445 } 00446 00447 execvp(start_argv[0], start_argv); 00448 /* not reached */ 00449 } 00450 00451 #if defined(__OpenBSD__) || defined(__APPLE__) 00452 00453 /* 00454 * Taken from FreeBSD 00455 * Find the first occurrence of the byte string s in byte string l. 00456 * 00457 */ 00458 void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) { 00459 register char *cur, *last; 00460 const char *cl = (const char *)l; 00461 const char *cs = (const char *)s; 00462 00463 /* we need something to compare */ 00464 if (l_len == 0 || s_len == 0) 00465 return NULL; 00466 00467 /* "s" must be smaller or equal to "l" */ 00468 if (l_len < s_len) 00469 return NULL; 00470 00471 /* special case where s_len == 1 */ 00472 if (s_len == 1) 00473 return memchr(l, (int)*cs, l_len); 00474 00475 /* the last position where its possible to find "s" in "l" */ 00476 last = (char *)cl + l_len - s_len; 00477 00478 for (cur = (char *)cl; cur <= last; cur++) 00479 if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) 00480 return cur; 00481 00482 return NULL; 00483 } 00484 00485 #endif 00486 00487 #if defined(__APPLE__) 00488 00489 /* 00490 * Taken from FreeBSD 00491 * Returns a pointer to a new string which is a duplicate of the 00492 * string, but only copies at most n characters. 00493 * 00494 */ 00495 char *strndup(const char *str, size_t n) { 00496 size_t len; 00497 char *copy; 00498 00499 for (len = 0; len < n && str[len]; len++) 00500 continue; 00501 00502 if ((copy = malloc(len + 1)) == NULL) 00503 return (NULL); 00504 memcpy(copy, str, len); 00505 copy[len] = '\0'; 00506 return (copy); 00507 } 00508 00509 #endif