A tiling window manager
at master 695 lines 15 kB view raw
1/* 2 * Copyright (C) 2000, 2001, 2002, 2003, 2004 Shawn Betts <sabetts@vcn.bc.ca> 3 * 4 * This program is free software; you can redistribute it and/or modify it 5 * under the terms of the GNU General Public License as published by the Free 6 * Software Foundation; either version 2 of the License, or (at your option) 7 * any later version. 8 * 9 * This program is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 12 * more details. 13 * 14 * You should have received a copy of the GNU General Public License along with 15 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 16 * Place, Suite 330, Boston, MA 02111-1307 USA. 17 */ 18 19#include "sdorfehs.h" 20 21#include <sys/types.h> 22#include <sys/wait.h> 23 24#include <ctype.h> 25#include <errno.h> 26#include <err.h> 27#include <pwd.h> 28#include <signal.h> 29#include <unistd.h> 30#include <dirent.h> 31 32/* 33 * Several systems seem not to have WAIT_ANY defined, so define it if it isn't. 34 */ 35#ifndef WAIT_ANY 36#define WAIT_ANY -1 37#endif 38 39/* Some systems don't define the close-on-exec flag in fcntl.h */ 40#ifndef FD_CLOEXEC 41#define FD_CLOEXEC 1 42#endif 43 44int alarm_signalled = 0; 45int kill_signalled = 0; 46int hup_signalled = 0; 47int chld_signalled = 0; 48 49int rp_font_ascent, rp_font_descent, rp_font_width; 50 51Atom wm_name; 52Atom wm_state; 53Atom wm_change_state; 54Atom wm_protocols; 55Atom wm_delete; 56Atom wm_take_focus; 57Atom wm_colormaps; 58 59Atom rp_selection; 60 61/* TEXT atoms */ 62Atom xa_string; 63Atom xa_compound_text; 64Atom xa_utf8_string; 65 66/* netwm atoms */ 67Atom _net_active_window; 68Atom _net_client_list; 69Atom _net_client_list_stacking; 70Atom _net_current_desktop; 71Atom _net_number_of_desktops; 72Atom _net_supported; 73Atom _net_workarea; 74Atom _net_wm_name; 75Atom _net_wm_pid; 76Atom _net_wm_state; 77Atom _net_wm_state_fullscreen; 78Atom _net_wm_window_type; 79Atom _net_wm_window_type_dialog; 80Atom _net_wm_window_type_dock; 81Atom _net_wm_window_type_splash; 82Atom _net_wm_window_type_tooltip; 83Atom _net_wm_window_type_utility; 84Atom _net_supporting_wm_check; 85 86LIST_HEAD(rp_screens); 87rp_screen *rp_current_screen; 88rp_global_screen rp_glob_screen; 89 90Display *dpy; 91 92int rp_have_xrandr; 93 94LIST_HEAD(rp_children); 95struct rp_defaults defaults; 96 97int ignore_badwindow = 0; 98 99char **myargv; 100 101struct rp_key prefix_key; 102 103struct modifier_info rp_modifier_info; 104 105/* rudeness levels */ 106int rp_honour_transient_raise = 1; 107int rp_honour_normal_raise = 1; 108int rp_honour_transient_map = 1; 109int rp_honour_normal_map = 1; 110int rp_honour_vscreen_switch = 0; 111 112char *rp_error_msg = NULL; 113 114/* Global frame numset */ 115struct numset *rp_frame_numset; 116 117/* The X11 selection globals */ 118rp_xselection selection; 119 120static void 121x_export_selection(void) 122{ 123 rp_screen *screen; 124 125 list_first(screen, &rp_screens, node); 126 if (!screen) 127 return; 128 129 /* Hang the selections off screen 0's key window. */ 130 XSetSelectionOwner(dpy, XA_PRIMARY, screen->key_window, CurrentTime); 131 if (XGetSelectionOwner(dpy, XA_PRIMARY) != screen->key_window) 132 warnx("can't get primary selection"); 133 XChangeProperty(dpy, screen->root, XA_CUT_BUFFER0, xa_string, 8, 134 PropModeReplace, (unsigned char *) selection.text, selection.len); 135} 136 137void 138set_nselection(char *txt, int len) 139{ 140 int i; 141 142 /* Update the selection structure */ 143 free(selection.text); 144 145 /* Copy the string by hand. */ 146 selection.text = xmalloc(len + 1); 147 selection.len = len + 1; 148 for (i = 0; i < len; i++) 149 selection.text[i] = txt[i]; 150 selection.text[len] = 0; 151 152 x_export_selection(); 153} 154 155void 156set_selection(char *txt) 157{ 158 /* Update the selection structure */ 159 free(selection.text); 160 selection.text = xstrdup(txt); 161 selection.len = strlen(txt); 162 163 x_export_selection(); 164} 165 166static char * 167get_cut_buffer(void) 168{ 169 int nbytes; 170 char *data; 171 172 PRINT_DEBUG(("trying the cut buffer\n")); 173 174 data = XFetchBytes(dpy, &nbytes); 175 176 if (data) { 177 struct sbuf *s = sbuf_new(0); 178 sbuf_nconcat(s, data, nbytes); 179 XFree(data); 180 return sbuf_free_struct(s); 181 } else 182 return NULL; 183} 184 185/* Lifted the code from rxvt. */ 186static char * 187get_primary_selection(void) 188{ 189 long nread; 190 unsigned long bytes_after; 191 XTextProperty ct; 192 struct sbuf *s = sbuf_new(0); 193 194 for (nread = 0, bytes_after = 1; bytes_after > 0; nread += ct.nitems) { 195 if ((XGetWindowProperty(dpy, rp_current_screen->input_window, 196 rp_selection, (nread / 4), 4096, True, AnyPropertyType, 197 &ct.encoding, &ct.format, &ct.nitems, &bytes_after, 198 &ct.value) != Success)) { 199 XFree(ct.value); 200 sbuf_free(s); 201 return NULL; 202 } 203 if (ct.value == NULL) 204 continue; 205 /* 206 * Accumulate the data. FIXME: ct.value may not be NULL 207 * terminated. 208 */ 209 sbuf_nconcat(s, (const char *) ct.value, ct.nitems); 210 XFree(ct.value); 211 } 212 return sbuf_free_struct(s); 213} 214 215char * 216get_selection(void) 217{ 218 Atom property; 219 XEvent ev; 220 rp_screen *s = rp_current_screen; 221 int loops = 1000; 222 223 /* Just insert our text, if we own the selection. */ 224 if (selection.text) { 225 return xstrdup(selection.text); 226 } else { 227 /* be a good icccm citizen */ 228 XDeleteProperty(dpy, s->input_window, rp_selection); 229 /* 230 * TODO: we shouldn't use CurrentTime here, use the time of the 231 * XKeyEvent, should we fake it? 232 */ 233 XConvertSelection(dpy, XA_PRIMARY, xa_string, rp_selection, 234 s->input_window, CurrentTime); 235 236 /* This seems like a hack. */ 237 while (!XCheckTypedWindowEvent(dpy, s->input_window, 238 SelectionNotify, &ev)) { 239 if (loops == 0) { 240 warnx("selection request timed out"); 241 return NULL; 242 } 243 usleep(10000); 244 loops--; 245 } 246 247 PRINT_DEBUG(("SelectionNotify event\n")); 248 249 property = ev.xselection.property; 250 251 if (property != None) 252 return get_primary_selection(); 253 else 254 return get_cut_buffer(); 255 } 256} 257 258/* The hook dictionary globals. */ 259 260LIST_HEAD(rp_key_hook); 261LIST_HEAD(rp_switch_win_hook); 262LIST_HEAD(rp_switch_frame_hook); 263LIST_HEAD(rp_switch_screen_hook); 264LIST_HEAD(rp_switch_vscreen_hook); 265LIST_HEAD(rp_quit_hook); 266LIST_HEAD(rp_restart_hook); 267LIST_HEAD(rp_delete_window_hook); 268LIST_HEAD(rp_new_window_hook); 269LIST_HEAD(rp_title_changed_hook); 270 271struct rp_hook_db_entry rp_hook_db[] = 272 {{"key", &rp_key_hook}, 273 {"switchwin", &rp_switch_win_hook}, 274 {"switchframe", &rp_switch_frame_hook}, 275 {"switchscreen", &rp_switch_screen_hook}, 276 {"switchvscreen", &rp_switch_vscreen_hook}, 277 {"deletewindow", &rp_delete_window_hook}, 278 {"quit", &rp_quit_hook}, 279 {"restart", &rp_restart_hook}, 280 {"newwindow", &rp_new_window_hook}, 281 {"titlechanged", &rp_title_changed_hook}, 282 {NULL, NULL} 283}; 284 285void 286set_rp_window_focus(rp_window *win) 287{ 288 PRINT_DEBUG(("Giving focus to '%s'\n", window_name(win))); 289 XSetInputFocus(dpy, win->w, 290 RevertToPointerRoot, CurrentTime); 291 set_atom(win->vscreen->screen->root, _net_active_window, XA_WINDOW, 292 &win->w, 1); 293} 294 295void 296set_window_focus(Window window) 297{ 298 PRINT_DEBUG(("Giving focus to %ld\n", window)); 299 XSetInputFocus(dpy, window, 300 RevertToPointerRoot, CurrentTime); 301} 302 303XftFont * 304rp_get_font(rp_screen *s, char *font) 305{ 306 XftFont *f; 307 int fslots = sizeof(s->xft_font_cache) / sizeof(struct rp_font); 308 int x; 309 310 if (!font || font[0] == '\0') 311 return s->xft_font; 312 313 for (x = 0; x < fslots; x++) { 314 if (!s->xft_font_cache[x].name) 315 break; 316 317 if (strcmp(s->xft_font_cache[x].name, font) == 0) 318 return s->xft_font_cache[x].font; 319 } 320 321 /* not in the cache, make sure we can open it first */ 322 f = XftFontOpenName(dpy, DefaultScreen(dpy), font); 323 if (!f) { 324 warnx("failed opening xft font \"%s\"", font); 325 return s->xft_font; 326 } 327 328 PRINT_DEBUG(("font \"%s\" not in font cache\n", font)); 329 330 /* free up the last slot if needed */ 331 if (x == fslots) { 332 free(s->xft_font_cache[x - 1].name); 333 XftFontClose(dpy, s->xft_font_cache[x - 1].font); 334 } 335 336 /* shift all the cache entries to free up the first slot */ 337 for (x = fslots - 1; x >= 1; x--) 338 memcpy(&s->xft_font_cache[x], &s->xft_font_cache[x - 1], 339 sizeof(struct rp_font)); 340 341 s->xft_font_cache[0].name = xstrdup(font); 342 s->xft_font_cache[0].font = f; 343 344 return f; 345} 346 347void 348rp_clear_cached_fonts(rp_screen *s) 349{ 350 int x; 351 352 for (x = 0; x < (sizeof(s->xft_font_cache) / sizeof(struct rp_font)); 353 x++) { 354 if (s->xft_font_cache[x].name) { 355 free(s->xft_font_cache[x - 1].name); 356 XftFontClose(dpy, s->xft_font_cache[x].font); 357 } 358 } 359} 360 361void 362rp_draw_string(rp_screen *s, Drawable d, int style, int x, int y, char *string, 363 int length, char *font, char *color) 364{ 365 XftDraw *draw; 366 XftColor xftcolor; 367 XftFont *f = rp_get_font(s, font); 368 369 if (length < 0) 370 length = strlen(string); 371 372 draw = XftDrawCreate(dpy, d, DefaultVisual(dpy, s->screen_num), 373 DefaultColormap(dpy, s->screen_num)); 374 if (!draw) { 375 warnx("no Xft font available"); 376 return; 377 } 378 379 if (color == NULL) { 380 if (style == STYLE_NORMAL) 381 memcpy(&xftcolor, &s->xft_fgcolor, sizeof(XftColor)); 382 else 383 memcpy(&xftcolor, &s->xft_bgcolor, sizeof(XftColor)); 384 } else { 385 /* 386 * This won't actually allocate anything if the color is 387 * already allocated. 388 */ 389 if (!XftColorAllocName(dpy, DefaultVisual(dpy, s->screen_num), 390 DefaultColormap(dpy, s->screen_num), color, &xftcolor)) { 391 warnx("couldn't XftColorAllocName \"%s\"", color); 392 memcpy(&xftcolor, &s->xft_fgcolor, sizeof(XftColor)); 393 } 394 } 395 396 XftDrawStringUtf8(draw, &xftcolor, f, x, y, (FcChar8 *)string, length); 397 XftDrawDestroy(draw); 398} 399 400int 401rp_text_width(rp_screen *s, char *string, int count, char *font) 402{ 403 XGlyphInfo extents; 404 XftFont *f = rp_get_font(s, font); 405 406 if (count < 0) 407 count = strlen(string); 408 409 XftTextExtentsUtf8(dpy, f, (FcChar8 *)string, count, &extents); 410 411 return extents.xOff; 412} 413 414/* A case insensitive strncmp. */ 415int 416str_comp(char *s1, char *s2, size_t len) 417{ 418 size_t i; 419 420 for (i = 0; i < len; i++) 421 if (toupper((unsigned char)s1[i]) != toupper((unsigned char)s2[i])) 422 return 0; 423 424 return 1; 425} 426 427/* 428 * Check for child processes that have quit but haven't been acknowledged yet. 429 * Update their structure. 430 */ 431void 432check_child_procs(void) 433{ 434 rp_child_info *cur; 435 int pid, status; 436 while (1) { 437 pid = waitpid(WAIT_ANY, &status, WNOHANG); 438 if (pid <= 0) 439 break; 440 441 PRINT_DEBUG(("Child status: %d\n", WEXITSTATUS(status))); 442 443 /* Find the child and update its structure. */ 444 list_for_each_entry(cur, &rp_children, node) { 445 if (cur->pid == pid) { 446 cur->terminated = 1; 447 cur->status = WEXITSTATUS(status); 448 break; 449 } 450 } 451 452 chld_signalled = 1; 453 } 454} 455 456void 457chld_handler(int signum) 458{ 459 int serrno; 460 461 serrno = errno; 462 check_child_procs(); 463 errno = serrno; 464} 465 466void 467set_sig_handler(int sig, void (*action)(int)) 468{ 469 struct sigaction act; 470 471 memset(&act, 0, sizeof(act)); 472 act.sa_handler = action; 473 sigemptyset(&act.sa_mask); 474 if (sigaction(sig, &act, NULL)) 475 warnx("error setting signal handler"); 476} 477 478void 479set_close_on_exec(int fd) 480{ 481 int flags = fcntl(fd, F_GETFD); 482 if (flags >= 0) 483 fcntl(fd, F_SETFD, flags | FD_CLOEXEC); 484} 485 486void 487read_rc_file(FILE *file) 488{ 489 char *line; 490 size_t linesize = 256; 491 492 line = xmalloc(linesize); 493 494 while (getline(&line, &linesize, file) != -1) { 495 line[strcspn(line, "\n")] = '\0'; 496 497 PRINT_DEBUG(("rcfile line: %s\n", line)); 498 499 if (*line != '\0' && *line != '#') { 500 cmdret *result; 501 result = command(0, line); 502 503 /* Gobble the result. */ 504 if (result) 505 cmdret_free(result); 506 } 507 } 508 509 free(line); 510} 511 512const char * 513get_homedir(void) 514{ 515 char *homedir; 516 517 homedir = getenv("HOME"); 518 if (homedir != NULL && homedir[0] == '\0') 519 homedir = NULL; 520 521 if (homedir == NULL) { 522 struct passwd *pw; 523 524 pw = getpwuid(getuid()); 525 if (pw != NULL) 526 homedir = pw->pw_dir; 527 528 if (homedir != NULL && homedir[0] == '\0') 529 homedir = NULL; 530 } 531 532 return homedir; 533} 534 535char * 536get_config_dir(void) 537{ 538 DIR *d; 539 const char *homedir; 540 char *xdg_config, *home_config; 541 int xdg_alloc = 0; 542 543 homedir = get_homedir(); 544 if (!homedir) 545 errx(1, "no home directory"); 546 547 xdg_config = getenv("XDG_CONFIG_HOME"); 548 if (xdg_config == NULL || !strlen(xdg_config)) { 549 xdg_config = xsprintf("%s/.config", homedir); 550 xdg_alloc = 1; 551 } 552 553 if (!(d = opendir(xdg_config))) { 554 if (mkdir(xdg_config, 0755) == -1) 555 err(1, "failed creating %s", xdg_config); 556 557 if (!(d = opendir(xdg_config))) 558 err(1, "failed opening %s", xdg_config); 559 } 560 closedir(d); 561 562 home_config = xsprintf("%s/sdorfehs", xdg_config); 563 if (!(d = opendir(home_config))) { 564 if (mkdir(home_config, 0755) == -1) 565 err(1, "failed creating %s", home_config); 566 567 if (!(d = opendir(home_config))) 568 err(1, "failed opening %s", home_config); 569 } 570 closedir(d); 571 572 if (xdg_alloc) 573 free(xdg_config); 574 575 return home_config; 576} 577 578void 579clean_up(void) 580{ 581 rp_screen *cur; 582 rp_vscreen *vcur; 583 struct list_head *iter, *tmp, *iter2, *tmp2; 584 585 history_save(); 586 587 free_keymaps(); 588 free_aliases(); 589 free_user_commands(); 590 free_bar(); 591 free_window_stuff(); 592 593 list_for_each_safe_entry(cur, iter, tmp, &rp_screens, node) { 594 list_for_each_safe_entry(vcur, iter2, tmp2, &cur->vscreens, node) 595 vscreen_del(vcur); 596 597 list_del(&cur->node); 598 screen_free(cur); 599 free(cur); 600 } 601 602 screen_free_final(); 603 604 /* Delete the undo histories */ 605 clear_frame_undos(); 606 607 /* Free the global frame numset shared by all screens. */ 608 numset_free(rp_frame_numset); 609 610 free(defaults.window_fmt); 611 612 XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); 613 XCloseDisplay(dpy); 614} 615 616void 617register_atom(Atom *a, char *name) 618{ 619 *a = XInternAtom(dpy, name, False); 620 PRINT_DEBUG(("Registered Atom %ld = %s\n", (unsigned long)*a, name)); 621 append_atom(DefaultRootWindow(dpy), _net_supported, XA_ATOM, a, 1); 622} 623 624int 625set_atom(Window w, Atom a, Atom type, unsigned long *val, unsigned long nitems) 626{ 627 return (XChangeProperty(dpy, w, a, type, 32, PropModeReplace, 628 (unsigned char *)val, nitems) == Success); 629} 630 631int 632append_atom(Window w, Atom a, Atom type, unsigned long *val, 633 unsigned long nitems) 634{ 635 return (XChangeProperty(dpy, w, a, type, 32, PropModeAppend, 636 (unsigned char *)val, nitems) == Success); 637} 638 639unsigned long 640get_atom(Window w, Atom a, Atom type, unsigned long off, unsigned long *ret, 641 unsigned long nitems, unsigned long *left) 642{ 643 Atom real_type; 644 int i, real_format = 0; 645 unsigned long items_read = 0; 646 unsigned long bytes_left = 0; 647 unsigned long *p; 648 unsigned char *data; 649 650 XGetWindowProperty(dpy, w, a, off, nitems, False, type, &real_type, 651 &real_format, &items_read, &bytes_left, &data); 652 653 if (real_format == 32 && items_read) { 654 p = (unsigned long *)data; 655 for (i = 0; i < items_read; i++) 656 *ret++ = *p++; 657 XFree(data); 658 if (left) 659 *left = bytes_left; 660 return items_read; 661 } 662 663 return 0; 664} 665 666void 667remove_atom(Window w, Atom a, Atom type, unsigned long remove) 668{ 669 unsigned long tmp, read, left, *new; 670 int i, j = 0; 671 672 read = get_atom(w, a, type, 0, &tmp, 1, &left); 673 if (!read) 674 return; 675 676 new = malloc((read + left) * sizeof(*new)); 677 if (read && tmp != remove) 678 new[j++] = tmp; 679 680 for (i = 1, read = left = 1; read && left; i += read) { 681 read = get_atom(w, a, type, i, &tmp, 1, &left); 682 if (!read) 683 break; 684 if (tmp != remove) 685 new[j++] = tmp; 686 } 687 688 if (j) 689 XChangeProperty(dpy, w, a, type, 32, PropModeReplace, 690 (unsigned char *)new, j); 691 else 692 XDeleteProperty(dpy, w, a); 693 694 free(new); 695}