swm

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

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 }