main.c (12665B)
1 /* 2 * Copyright (C) Raymond Cole <rc@wolog.xyz> 3 * License: GPL-3.0-or-later 4 * 5 * Portions of the code are derived from suckless dwm. See file 6 * LICENSE-dwm for copyright and license details of those portions. 7 */ 8 #include <errno.h> 9 #include <locale.h> 10 #include <poll.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 #include <unistd.h> 15 #include <sys/wait.h> 16 #include <X11/cursorfont.h> 17 #include <X11/Xatom.h> 18 #include <X11/Xlib.h> 19 #include <X11/Xproto.h> 20 #include <X11/Xutil.h> 21 22 #include "swm.h" 23 24 extern char **environ; 25 26 int running = 1; 27 int restart; 28 int pending; 29 Display *dpy; 30 Window root; 31 int screen; 32 int sw, sh; 33 Monitor *mons, *selmon; 34 Atom wmatom[WMLast], netatom[NetLast]; 35 const char *tags[] = TAGS; 36 unsigned long pixelfocus, pixelunfocus, pixelurgent; 37 Cursor curnormal, curresize, curmove; 38 unsigned lockmasks[4]; 39 40 static int xerror(Display *dpy, XErrorEvent *ee); 41 static int xerrorstart(Display *dpy, XErrorEvent *ee); 42 static void checkotherwm(void); 43 static void initatoms(void); 44 static unsigned long *getpixel(const char *name, unsigned long *pixel); 45 static unsigned getnumlockmask(void); 46 static void dumpmonitors(char *var); 47 static void loadmonitors(char *s); 48 static void dumpclients(char *var, Monitor *m); 49 static void loadclients(int n, char *s); 50 static void dumpworkspaces(char *var, Monitor *m); 51 static void loadworkspaces(int n, char *s); 52 static void dumpstate(void); 53 static void loadstate(void); 54 static void setup(void); 55 static void scan(void); 56 static void run(void); 57 static void cleanup(void); 58 59 static int (*xerrorxlib)(Display *, XErrorEvent *); 60 61 int 62 xerror(Display *dpy, XErrorEvent *ee) 63 { 64 unsigned char r = ee->request_code; 65 unsigned char e = ee->error_code; 66 67 warn("Xlib error: request code=%d, error code=%d", r, e); 68 switch (e) { 69 case BadAccess: 70 if (r == X_GrabButton 71 || r == X_GrabKey) 72 return 0; 73 break; 74 case BadDrawable: 75 if (r == X_PolyText8 76 || r == X_PolyFillRectangle 77 || r == X_PolySegment 78 || r == X_CopyArea) 79 return 0; 80 break; 81 case BadMatch: 82 if (r == X_SetInputFocus 83 || r == X_ConfigureWindow) 84 return 0; 85 break; 86 case BadWindow: 87 return 0; 88 default: 89 break; 90 } 91 return xerrorxlib(dpy, ee); /* might call exit */ 92 } 93 94 int 95 xerrorstart(Display *dpy, XErrorEvent *ee) 96 { 97 die("another window manager is already running"); 98 return -1; 99 } 100 101 void 102 checkotherwm(void) 103 { 104 xerrorxlib = XSetErrorHandler(xerrorstart); 105 XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask); 106 XSync(dpy, False); 107 XSetErrorHandler(xerror); 108 XSync(dpy, False); 109 } 110 111 void 112 initatoms(void) 113 { 114 static const struct { 115 Atom *p; 116 const char *s; 117 } a[] = { 118 { wmatom + WMProtocols, "WM_PROTOCOLS" }, 119 { wmatom + WMDeleteWindow, "WM_DELETE_WINDOW" }, 120 { wmatom + WMTakeFocus, "WM_TAKE_FOCUS" }, 121 { wmatom + WMState, "WM_STATE" }, 122 { netatom + NetSupported, "_NET_SUPPORTED" }, 123 { netatom + NetActiveWindow, "_NET_ACTIVE_WINDOW" }, 124 { netatom + NetWMName, "_NET_WM_NAME" }, 125 { netatom + NetWMWindowType, "_NET_WM_WINDOW_TYPE" }, 126 { netatom + NetWMState, "_NET_WM_STATE" }, 127 { netatom + NetWMWindowTypeDialog,"_NET_WM_WINDOW_TYPE_DIALOG"}, 128 { netatom + NetWMStateFullscreen, "_NET_WM_STATE_FULLSCREEN" }, 129 }; 130 int i; 131 132 for (i = 0; i < LENGTH(a); ++i) 133 *a[i].p = XInternAtom(dpy, a[i].s, False); 134 } 135 136 unsigned long * 137 getpixel(const char *name, unsigned long *pixel) 138 { 139 Colormap cmap = DefaultColormap(dpy, screen); 140 XColor color[2]; 141 142 if (!XAllocNamedColor(dpy, cmap, name, color, color + 1)) { 143 warn("cannot allocate color `%s'", name); 144 return NULL; 145 } 146 *pixel = color->pixel; 147 return pixel; 148 } 149 150 unsigned 151 getnumlockmask(void) 152 { 153 XModifierKeymap *modmap = XGetModifierMapping(dpy); 154 KeyCode k; 155 int i, n; 156 157 if (!modmap) { 158 warn("cannot get modifier mapping"); 159 return 0; 160 } 161 k = XKeysymToKeycode(dpy, XK_Num_Lock); 162 n = modmap->max_keypermod; 163 for (i = 0; i < 8 * n && modmap->modifiermap[i] != k; ++i) 164 ; 165 XFreeModifiermap(modmap); 166 return i < 8 * n ? 1 << (i / n) : 0; 167 } 168 169 void 170 dumpmonitors(char *var) 171 { 172 char *buf, *p; 173 Monitor *m; 174 int n; 175 176 n = 0; 177 for (m = mons; m; m = m->next) 178 n += 6; 179 if (!n) 180 return; 181 if (!(buf = malloc(n))) { 182 warn("cannot dump state of clients:"); 183 return; 184 } 185 p = buf; 186 for (m = mons; m; m = m->next) { 187 sprintf(p, "%d %c%c", m->selws, m->showbar ? 'B' : 'b', 188 m->nogaps ? 'g' : 'G'); 189 p += strlen(p); 190 *p++ = ':'; 191 } 192 *--p = '\0'; 193 if (setenv(var, buf, 1) < 0) 194 warn("setenv `%s':", var); 195 free(buf); 196 } 197 198 void 199 loadmonitors(char *val) 200 { 201 Monitor *m = mons; 202 int i, l; 203 204 for (val = strtok(val, ":"); val && m; val = strtok(NULL, ":")) { 205 if (sscanf(val, "%i %n", &i, &l) != 1) 206 continue; 207 if ((unsigned)i < LENGTH(tags)) { 208 wssync(m); 209 m->selws = i; 210 wsload(m); 211 } 212 for (; val[l]; ++l) { 213 switch (val[l]) { 214 case 'b': 215 case 'B': 216 m->showbar = val[l] == 'B'; 217 XMoveWindow(dpy, m->barwin, m->x, BY(m)); 218 break; 219 case 'g': 220 case 'G': 221 m->nogaps = val[l] == 'g'; 222 break; 223 } 224 } 225 m = m->next; 226 } 227 } 228 229 void 230 dumpclients(char *var, Monitor *m) 231 { 232 char *buf, *p; 233 Client *c; 234 size_t n; 235 236 n = 0; 237 for (c = m->clients; c; c = c->next) 238 n += 18 + (LENGTH(tags) + 3) / 4; 239 if (!n) 240 return; 241 if (!(buf = malloc(n))) { 242 warn("cannot dump state of clients:"); 243 return; 244 } 245 p = buf; 246 for (c = m->clients; c; c = c->next) { 247 sprintf(p, "0x%x 0x%x ", (unsigned)c->win, c->tags); 248 p += strlen(p); 249 if (c->isfloating) 250 *p++ = 'f'; 251 if (c->isfullscreen) 252 *p++ = 'F'; 253 if (c->isscratch) 254 *p++ = 's'; 255 if (p[-1] == ' ') 256 --p; 257 *p++ = ':'; 258 } 259 *--p = '\0'; 260 if (setenv(var, buf, 1) < 0) 261 warn("setenv `%s':", var); 262 free(buf); 263 } 264 265 void 266 loadclients(int n, char *val) 267 { 268 Monitor *m; 269 Client *c, **tc; 270 int win, t, l; 271 272 for (m = mons; m && m->num != n; m = m->next) 273 ; 274 if (!m) 275 m = mons; 276 for (val = strtok(val, ":"); val; val = strtok(NULL, ":")) { 277 if (sscanf(val, "%i %i %n", &win, &t, &l) != 2 278 || !(c = wintoclient(win))) 279 continue; 280 if (c->mon != m) 281 sendtomon(c, m); 282 detach(c); 283 c->next = NULL; 284 for (tc = &m->clients; *tc; tc = &(*tc)->next) 285 ; 286 *tc = c; 287 tc = &c->next; 288 for (; val[l]; ++l) { 289 switch (val[l]) { 290 case 'f': 291 setfloating(c, 1); 292 break; 293 case 'F': 294 setfullscreen(c, 1); 295 break; 296 case 's': 297 setscratch(c, 1); 298 break; 299 } 300 } 301 if ((t &= TAGMASK) || c->isscratch) 302 c->tags = t; 303 } 304 } 305 306 void 307 dumpworkspaces(char *var, Monitor *m) 308 { 309 char *buf, *p; 310 int i; 311 312 buf = malloc(LENGTH(tags) 313 * ((LENGTH(tags) + 3) / 4 + 1 + sizeof(m->lytdesc))); 314 if (!buf) { 315 warn("cannot dump state of workspaces:", var); 316 return; 317 } 318 wssync(m); 319 p = buf; 320 i = m->selws; 321 for (m->selws = 0; m->selws < LENGTH(tags); ++m->selws) { 322 wsload(m); 323 sprintf(p, "0x%x %s", m->tagset, m->lytdesc); 324 p += strlen(p); 325 *p++ = ';'; 326 } 327 m->selws = i; 328 wsload(m); 329 *--p = '\0'; 330 if (setenv(var, buf, 1) < 0) 331 warn("setenv `%s':", var); 332 free(buf); 333 } 334 335 void 336 loadworkspaces(int n, char *s) 337 { 338 Monitor *m; 339 LytNode *lyt; 340 int i, t, l; 341 342 for (m = mons; m && m->num != n; m = m->next) 343 ; 344 if (!m) 345 return; 346 wssync(m); 347 i = m->selws; 348 for (s = strtok(s, ";"), m->selws = 0; 349 s && m->selws < LENGTH(tags); 350 s = strtok(NULL, ";"), ++m->selws) { 351 if (sscanf(s, "%i %n", &t, &l) != 1) 352 continue; 353 wsload(m); 354 m->tagset = t & TAGMASK; 355 if ((lyt = lytparse(s + l))) { 356 lytfree(m->lyt); 357 m->lyt = lyt; 358 } 359 wssync(m); 360 } 361 m->selws = i; 362 wsload(m); 363 } 364 365 void 366 dumpstate(void) 367 { 368 char s[32]; 369 Monitor *m; 370 371 strcpy(s, "SWM_MONITORS"); 372 dumpmonitors(s); 373 for (m = mons; m; m = m->next) { 374 sprintf(s, "SWM_MON%d_CLIENTS", m->num); 375 dumpclients(s, m); 376 sprintf(s, "SWM_MON%d_WORKSPACES", m->num); 377 dumpworkspaces(s, m); 378 } 379 if (selmon->sel) { 380 sprintf(s, "0x%x", (unsigned)selmon->sel->win); 381 if (setenv("SWM_FOCUS", s, 1)) 382 warn("setenv `%s':", s); 383 } 384 } 385 386 void 387 loadstate(void) 388 { 389 int i, n, l; 390 char *entry, *val; 391 Client *c; 392 393 if (!environ) 394 return; 395 for (i = 0; environ[i]; ++i) { 396 if (strncmp(environ[i], "SWM_", 4) 397 || !strncmp(environ[i], "SWM_FOCUS=", 10)) 398 continue; 399 if (!(entry = strdup(environ[i]))) { 400 warn("cannot load state from `%s':", environ[i]); 401 continue; 402 } 403 if (!(val = strchr(entry, '='))) { 404 free(entry); 405 continue; 406 } 407 *val++ = '\0'; 408 l = 0; 409 if (!strcmp(entry, "SWM_MONITORS")) 410 loadmonitors(val); 411 else if (sscanf(entry, "SWM_MON%d_CLIENTS%n", &n, &l) == 1 412 && !entry[l]) 413 loadclients(n, val); 414 else if (sscanf(entry, "SWM_MON%d_WORKSPACES%n", &n, &l) == 1 415 && !entry[l]) 416 loadworkspaces(n, val); 417 unsetenv(entry); 418 free(entry); 419 --i; 420 } 421 val = getenv("SWM_FOCUS"); 422 if (val && sscanf(val, "%i", &i) == 1 && (c = wintoclient(i))) 423 focus(c); 424 else 425 focus(selmon->stack); 426 unsetenv("SWM_FOCUS"); 427 } 428 429 void 430 setup(void) 431 { 432 XSetWindowAttributes wa; 433 struct sigaction sa; 434 Monitor *m; 435 436 sigemptyset(&sa.sa_mask); 437 sa.sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT|SA_RESTART; 438 sa.sa_handler = SIG_IGN; 439 sigaction(SIGCHLD, &sa, NULL); 440 while (waitpid(-1, NULL, WNOHANG) > 0) 441 ; 442 screen = DefaultScreen(dpy); 443 sw = DisplayWidth(dpy, screen); 444 sh = DisplayHeight(dpy, screen); 445 root = RootWindow(dpy, screen); 446 initatoms(); 447 if (updategeom() < 0) 448 die("cannot set up monitor objects"); 449 curnormal = XCreateFontCursor(dpy, XC_left_ptr); 450 curresize = XCreateFontCursor(dpy, XC_sizing); 451 curmove = XCreateFontCursor(dpy, XC_fleur); 452 if (!getpixel(colfocus, &pixelfocus) 453 || !getpixel(colunfocus, &pixelunfocus) 454 || !getpixel(colurgent, &pixelurgent)) 455 die("cannot initialize border pixels"); 456 barsetup(); 457 for (m = mons; m; m = m->next) 458 initbar(m); 459 XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32, 460 PropModeReplace, (unsigned char *)netatom, NetLast); 461 wa.cursor = curnormal; 462 wa.event_mask = SubstructureRedirectMask 463 |StructureNotifyMask|SubstructureNotifyMask 464 |PointerMotionMask|ButtonPressMask 465 |EnterWindowMask|LeaveWindowMask; 466 XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa); 467 XSelectInput(dpy, root, wa.event_mask); 468 lockmasks[1] = LockMask; 469 lockmasks[2] = getnumlockmask(); 470 lockmasks[3] = lockmasks[1]|lockmasks[2]; 471 grabkeys(); 472 focus(selmon->stack); 473 } 474 475 void 476 scan(void) 477 { 478 Window t, *wins = NULL; 479 XWindowAttributes wa; 480 unsigned n, i, j; 481 482 if (!XQueryTree(dpy, root, &t, &t, &wins, &n) || !wins) 483 return; 484 for (i = j = 0; i < n; ++i) { 485 if (!XGetWindowAttributes(dpy, wins[i], &wa) 486 || wa.override_redirect 487 || (wa.map_state != IsViewable 488 && getstate(wins[i]) != IconicState)) 489 continue; 490 if (XGetTransientForHint(dpy, wins[i], &t)) 491 wins[j++] = wins[i]; 492 else 493 manage(wins[i], &wa); 494 } 495 while (j--) { 496 if (!XGetWindowAttributes(dpy, wins[j], &wa)) 497 continue; 498 manage(wins[j], &wa); 499 } 500 XFree(wins); 501 loadstate(); 502 } 503 504 void 505 run(void) 506 { 507 struct pollfd fds[2]; 508 XEvent ev; 509 Monitor *m; 510 511 runsprog(); 512 fds[0].fd = ConnectionNumber(dpy); 513 fds[0].events = fds[1].events = POLLIN; 514 for (;;) { 515 while (XPending(dpy)) { 516 XNextEvent(dpy, &ev); 517 if (!handler[ev.type]) 518 continue; 519 handler[ev.type](&ev); 520 if (!running) 521 return; 522 } 523 do { 524 if (pending) { 525 for (m = mons; m; m = m->next) { 526 if (m->pending & PArrange) 527 arrange(m); 528 if (m->pending & PDrawbar) 529 drawbar(m); 530 m->pending = 0; 531 } 532 XSync(dpy, False); 533 pending = 0; 534 } 535 fds[1].fd = sfdr; 536 if (poll(fds, 2, -1) < 0) { 537 if (errno == EINTR) 538 continue; 539 die("poll:"); 540 } 541 if (fds[1].revents) 542 updatestatus(); 543 } while (!fds[0].revents); 544 if (fds[0].revents != POLLIN) 545 die("X connection broken"); 546 } 547 } 548 549 void 550 cleanup(void) 551 { 552 Monitor *m; 553 554 if (restart) 555 dumpstate(); 556 for (m = mons; m; m = m->next) 557 while (m->stack) 558 unmanage(m->stack, 0); 559 XUngrabKey(dpy, AnyKey, AnyModifier, root); 560 endsprog(); 561 barcleanup(); 562 XFreeCursor(dpy, curnormal); 563 XFreeCursor(dpy, curresize); 564 XFreeCursor(dpy, curmove); 565 cleanupgeom(); 566 XSync(dpy, False); 567 XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); 568 XDeleteProperty(dpy, root, netatom[NetActiveWindow]); 569 } 570 571 int 572 main(int argc, char *argv[]) 573 { 574 if (argc > 1) 575 die("excessive arguments"); 576 progname = argv[0]; 577 if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) 578 die("no locale support"); 579 if (!(dpy = XOpenDisplay(NULL))) 580 die("cannot open display"); 581 checkotherwm(); 582 setup(); 583 #ifdef __OpenBSD__ 584 if (pledge("stdio rpath proc exec", NULL) == -1) 585 die("pledge:"); 586 #endif 587 scan(); 588 run(); 589 cleanup(); 590 XCloseDisplay(dpy); 591 if (restart) { 592 execvp(argv[0], argv); 593 die("execvp '%s':", argv[0]); 594 } 595 return 0; 596 }