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