swm

sigma window manager
git clone git://wolog.xyz/swm
Log | Files | Refs | README | LICENSE

main.c (12855B)


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