shod

mouse-based window manager that can tile windows inside floating containers
Log | Files | Refs | README | LICENSE

shod.c (11270B)


      1 #include <sys/wait.h>
      2 
      3 #include <err.h>
      4 #include <limits.h>
      5 #include <locale.h>
      6 #include <signal.h>
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <string.h>
     10 #include <unistd.h>
     11 
     12 #include <X11/extensions/Xrandr.h>
     13 
     14 #include "shod.h"
     15 
     16 #define WMNAME          "shod"
     17 #define ROOT_EVENTS     (SubstructureRedirectMask | SubstructureNotifyMask \
     18                         | StructureNotifyMask | ButtonPressMask \
     19                         | PropertyChangeMask)
     20 
     21 static int (*xerrorxlib)(Display *, XErrorEvent *);
     22 
     23 /* shared variables */
     24 unsigned long clientmask = CWEventMask | CWColormap | CWBackPixel | CWBorderPixel;
     25 XSetWindowAttributes clientswa = {
     26 	.event_mask = SubstructureNotifyMask | ButtonReleaseMask
     27 	            | SubstructureRedirectMask | ButtonPressMask | FocusChangeMask
     28 	            | Button1MotionMask
     29 };
     30 struct WM wm = { .running = 1 };
     31 struct Dock dock;
     32 
     33 /* show usage and exit */
     34 static void
     35 usage(void)
     36 {
     37 	(void)fprintf(stderr, "usage: shod [-AcdhSstW] [file]\n");
     38 	exit(1);
     39 }
     40 
     41 /* call fork checking for error; exit on error */
     42 static pid_t
     43 efork(void)
     44 {
     45 	pid_t pid;
     46 
     47 	if ((pid = fork()) < 0)
     48 		err(1, "fork");
     49 	return pid;
     50 }
     51 
     52 /* call execlp checking for error; exit on error */
     53 static void
     54 execshell(char *filename)
     55 {
     56 	char *argv[3];
     57 
     58 	if ((argv[0] = getenv(SHELL)) == NULL)
     59 		argv[0] = DEF_SHELL;
     60 	if (filename[0] == '-' && filename[1] == '\0')
     61 		argv[1] = NULL;         /* read commands from stdin */
     62 	else
     63 		argv[1] = filename;     /* read commands from file */
     64 	argv[2] = NULL;
     65 	if (execvp(argv[0], argv) == -1) {
     66 		err(1, "%s", argv[0]);
     67 	}
     68 }
     69 
     70 /* error handler */
     71 static int
     72 xerror(Display *dpy, XErrorEvent *e)
     73 {
     74 	/* stolen from berry, which stole from katriawm, which stole from dwm lol */
     75 
     76 	/* There's no way to check accesses to destroyed windows, thus those
     77 	 * cases are ignored (especially on UnmapNotify's). Other types of
     78 	 * errors call Xlibs default error handler, which may call exit. */
     79 	if (e->error_code == BadWindow ||
     80 	    (e->request_code == X_SetInputFocus && e->error_code == BadMatch) ||
     81 	    (e->request_code == X_PolyText8 && e->error_code == BadDrawable) ||
     82 	    (e->request_code == X_PolyFillRectangle && e->error_code == BadDrawable) ||
     83 	    (e->request_code == X_PolySegment && e->error_code == BadDrawable) ||
     84 	    (e->request_code == X_ConfigureWindow && e->error_code == BadMatch) ||
     85 	    (e->request_code == X_ConfigureWindow && e->error_code == BadValue) ||
     86 	    (e->request_code == X_GrabButton && e->error_code == BadAccess) ||
     87 	    (e->request_code == X_GrabKey && e->error_code == BadAccess) ||
     88 	    (e->request_code == X_CopyArea && e->error_code == BadDrawable) ||
     89 	    (e->request_code == 139 && e->error_code == BadDrawable) ||
     90 	    (e->request_code == 139 && e->error_code == 143))
     91 		return 0;
     92 
     93 	fprintf(stderr, "shod: ");
     94 	return xerrorxlib(dpy, e);
     95 	exit(1);        /* unreached */
     96 }
     97 
     98 /* startup error handler to check if another window manager is already running */
     99 static int
    100 xerrorstart(Display *dpy, XErrorEvent *e)
    101 {
    102 	(void)dpy;
    103 	(void)e;
    104 	errx(1, "another window manager is already running");
    105 }
    106 
    107 /* read command-line options */
    108 static char *
    109 getoptions(int argc, char *argv[])
    110 {
    111 	int c;
    112 
    113 	while ((c = getopt(argc, argv, "AcdhSstW")) != -1) {
    114 		switch (c) {
    115 		case 'A' :
    116 			config.altkeysym = XK_Alt_L;
    117 			config.modifier = Mod1Mask;
    118 			break;
    119 		case 'c':
    120 			config.honorconfig = 1;
    121 			break;
    122 		case 'd':
    123 			config.floatdialog = 1;
    124 			break;
    125 		case 'h':
    126 			config.disablehidden = 1;
    127 			break;
    128 		case 'S':
    129 			config.sloppytiles = 1;
    130 			break;
    131 		case 's':
    132 			config.sloppyfocus = 1;
    133 			break;
    134 		case 't':
    135 			config.disablealttab = 1;
    136 			break;
    137 		case 'W' :
    138 			config.altkeysym = XK_Super_L;
    139 			config.modifier = Mod4Mask;
    140 			break;
    141 		default:
    142 			usage();
    143 			break;
    144 		}
    145 	}
    146 	argc -= optind;
    147 	argv += optind;
    148 	if (argc > 1)
    149 		usage();
    150 	return *argv;
    151 }
    152 
    153 /* initialize signals */
    154 static void
    155 initsignal(void)
    156 {
    157 	struct sigaction sa;
    158 
    159 	/* remove zombies, we may inherit children when exec'ing shod in .xinitrc */
    160 	sa.sa_handler = SIG_IGN;
    161 	sa.sa_flags = SA_NOCLDSTOP | SA_NOCLDWAIT | SA_RESTART;
    162 	sigemptyset(&sa.sa_mask);
    163 	if (sigaction(SIGCHLD, &sa, NULL) == -1)
    164 		err(1, "sigaction");
    165 	while (waitpid(-1, NULL, WNOHANG) > 0)
    166 		;
    167 }
    168 
    169 /* initialize cursors */
    170 static void
    171 initcursors(void)
    172 {
    173 	wm.cursors[CURSOR_NORMAL] = XCreateFontCursor(dpy, XC_left_ptr);
    174 	wm.cursors[CURSOR_MOVE] = XCreateFontCursor(dpy, XC_fleur);
    175 	wm.cursors[CURSOR_NW] = XCreateFontCursor(dpy, XC_top_left_corner);
    176 	wm.cursors[CURSOR_NE] = XCreateFontCursor(dpy, XC_top_right_corner);
    177 	wm.cursors[CURSOR_SW] = XCreateFontCursor(dpy, XC_bottom_left_corner);
    178 	wm.cursors[CURSOR_SE] = XCreateFontCursor(dpy, XC_bottom_right_corner);
    179 	wm.cursors[CURSOR_N] = XCreateFontCursor(dpy, XC_top_side);
    180 	wm.cursors[CURSOR_S] = XCreateFontCursor(dpy, XC_bottom_side);
    181 	wm.cursors[CURSOR_W] = XCreateFontCursor(dpy, XC_left_side);
    182 	wm.cursors[CURSOR_E] = XCreateFontCursor(dpy, XC_right_side);
    183 	wm.cursors[CURSOR_V] = XCreateFontCursor(dpy, XC_sb_v_double_arrow);
    184 	wm.cursors[CURSOR_H] = XCreateFontCursor(dpy, XC_sb_h_double_arrow);
    185 	wm.cursors[CURSOR_HAND] = XCreateFontCursor(dpy, XC_hand2);
    186 	wm.cursors[CURSOR_PIRATE] = XCreateFontCursor(dpy, XC_pirate);
    187 }
    188 
    189 static void
    190 initxrm(void)
    191 {
    192 	static struct {
    193 		const char *class, *name;
    194 	} resourceids[NRESOURCES] = {
    195 #define X(res, s1, s2) [res] = { .class = s1, .name = s2, },
    196 		RESOURCES
    197 #undef  X
    198 	};
    199 	long n;
    200 	int i;
    201 	char *value;
    202 
    203 	XrmInitialize();
    204 	wm.application.class = XrmPermStringToQuark("Shod");
    205 	wm.application.name = XrmPermStringToQuark("shod");
    206 	wm.anyresource = XrmPermStringToQuark("?");
    207 	for (i = 0; i < NRESOURCES; i++) {
    208 		wm.resources[i].class = XrmPermStringToQuark(resourceids[i].class);
    209 		wm.resources[i].name = XrmPermStringToQuark(resourceids[i].name);
    210 	}
    211 	if (!settheme())
    212 		exit(EXIT_FAILURE);
    213 	setresources(XResourceManagerString(dpy));
    214 	if (xdb != NULL) {
    215 		value = getresource(
    216 			xdb,
    217 			(XrmClass[]){
    218 				wm.application.class,
    219 				wm.resources[RES_NDESKTOPS].class,
    220 				NULLQUARK,
    221 			},
    222 			(XrmName[]){
    223 				wm.application.name,
    224 				wm.resources[RES_NDESKTOPS].name,
    225 				NULLQUARK,
    226 			}
    227 		);
    228 		if (value != NULL && (n = strtol(value, NULL, 10)) > 0 && n < 100) {
    229 			config.ndesktops = n;
    230 		}
    231 	}
    232 }
    233 
    234 /* set up root window */
    235 static void
    236 initroot(void)
    237 {
    238 	/* change default cursor */
    239 	XDefineCursor(dpy, root, wm.cursors[CURSOR_NORMAL]);
    240 
    241 	/* Set focus to root window */
    242 	XSetInputFocus(dpy, root, RevertToParent, CurrentTime);
    243 }
    244 
    245 /* create dock window */
    246 static void
    247 initdock(void)
    248 {
    249 	XSetWindowAttributes swa;
    250 
    251 	TAILQ_INIT(&dock.dappq);
    252 	dock.pix = None;
    253 	swa.event_mask = SubstructureNotifyMask | SubstructureRedirectMask;
    254 	swa.background_pixel = BlackPixel(dpy, screen);
    255 	swa.border_pixel = BlackPixel(dpy, screen);
    256 	swa.colormap = colormap;
    257 	dock.win = XCreateWindow(dpy, root, 0, 0, 1, 1, 0,
    258 		                     depth, InputOutput, visual, clientmask, &swa);
    259 }
    260 
    261 /* create dummy windows used for controlling focus and the layer of clients */
    262 static void
    263 initdummywindows(void)
    264 {
    265 	int i;
    266 	XSetWindowAttributes swa;
    267 
    268 	for (i = 0; i < LAYER_LAST; i++) {
    269 		wm.layers[i].ncols = 0;
    270 		wm.layers[i].frame = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0);
    271 		XRaiseWindow(dpy, wm.layers[i].frame);
    272 		TAILQ_INSERT_HEAD(&wm.stackq, &wm.layers[i], raiseentry);
    273 	}
    274 	swa = clientswa;
    275 	swa.event_mask |= KeyPressMask;
    276 	wm.checkwin = wm.focuswin = wm.dragwin = wm.restackwin = XCreateWindow(
    277 		dpy, root,
    278 		- (2 * config.borderwidth + config.titlewidth),
    279 		- (2 * config.borderwidth + config.titlewidth),
    280 		2 * config.borderwidth + config.titlewidth,
    281 		2 * config.borderwidth + config.titlewidth,
    282 		0, depth, CopyFromParent, visual,
    283 		clientmask,
    284 		&swa
    285 	);
    286 }
    287 
    288 /* map and hide dummy windows */
    289 static void
    290 mapdummywins(void)
    291 {
    292 	XMapWindow(dpy, wm.focuswin);
    293 	XSetInputFocus(dpy, wm.focuswin, RevertToParent, CurrentTime);
    294 }
    295 
    296 /* run stdin on sh */
    297 static void
    298 autostart(char *filename)
    299 {
    300 	pid_t pid;
    301 
    302 	if (filename == NULL)
    303 		return;
    304 	if ((pid = efork()) == 0) {
    305 		if (efork() == 0)
    306 			execshell(filename);
    307 		exit(0);
    308 	}
    309 	waitpid(pid, NULL, 0);
    310 }
    311 
    312 static void
    313 checkotherwm(void)
    314 {
    315 	xerrorxlib = XSetErrorHandler(xerrorstart);
    316 	XSelectInput(dpy, root, ROOT_EVENTS);
    317 	XSync(dpy, False);
    318 	(void)XSetErrorHandler(xerror);
    319 	XSync(dpy, False);
    320 }
    321 
    322 /* destroy dummy windows */
    323 static void
    324 cleandummywindows(void)
    325 {
    326 	int i;
    327 
    328 	XDestroyWindow(dpy, wm.checkwin);
    329 	for (i = 0; i < LAYER_LAST; i++) {
    330 		XDestroyWindow(dpy, wm.layers[i].frame);
    331 	}
    332 }
    333 
    334 /* free cursors */
    335 static void
    336 cleancursors(void)
    337 {
    338 	size_t i;
    339 
    340 	for (i = 0; i < CURSOR_LAST; i++) {
    341 		XFreeCursor(dpy, wm.cursors[i]);
    342 	}
    343 }
    344 
    345 /* clean window manager structures */
    346 static void
    347 cleanwm(void)
    348 {
    349 	struct Monitor *mon;
    350 	struct Object *obj;
    351 	struct Container *c;
    352 
    353 	while ((c = TAILQ_FIRST(&wm.focusq)) != NULL)
    354 		containerdel(c);
    355 	while ((obj = TAILQ_FIRST(&wm.notifq)) != NULL)
    356 		(void)unmanagenotif(obj, 0);
    357 	while ((obj = TAILQ_FIRST(&wm.barq)) != NULL)
    358 		(void)unmanagebar(obj, 0);
    359 	while ((obj = TAILQ_FIRST(&wm.splashq)) != NULL)
    360 		(void)unmanagesplash(obj, 0);
    361 	while ((obj = TAILQ_FIRST(&dock.dappq)) != NULL)
    362 		(void)unmanagedockapp(obj, 0);
    363 	while ((mon = TAILQ_FIRST(&wm.monq)) != NULL)
    364 		mondel(mon);
    365 	if (dock.pix != None)
    366 		XFreePixmap(dpy, dock.pix);
    367 	XDestroyWindow(dpy, dock.win);
    368 }
    369 
    370 /* shod window manager */
    371 int
    372 main(int argc, char *argv[])
    373 {
    374 	XEvent ev;
    375 	char *filename, *wmname;
    376 
    377 	if (!setlocale(LC_ALL, "") || !XSupportsLocale())
    378 		warnx("warning: no locale support");
    379 	xinit();
    380 	checkotherwm();
    381 	moninit();
    382 	xinitvisual();
    383 	clientswa.colormap = colormap;
    384 	clientswa.border_pixel = BlackPixel(dpy, screen);
    385 	clientswa.background_pixel = BlackPixel(dpy, screen);
    386 
    387 	/* get configuration */
    388 	if ((wmname = strrchr(argv[0], '/')) != NULL)
    389 		wmname++;
    390 	else if (argv[0] != NULL && argv[0][0] != '\0')
    391 		wmname = argv[0];
    392 	else
    393 		wmname = WMNAME;
    394 	filename = getoptions(argc, argv);
    395 
    396 	/* check sloppy focus */
    397 	if (config.sloppyfocus || config.sloppytiles)
    398 		clientswa.event_mask |= EnterWindowMask;
    399 
    400 	/* initialize queues */
    401 	TAILQ_INIT(&wm.monq);
    402 	TAILQ_INIT(&wm.barq);
    403 	TAILQ_INIT(&wm.splashq);
    404 	TAILQ_INIT(&wm.notifq);
    405 	TAILQ_INIT(&wm.focusq);
    406 	TAILQ_INIT(&wm.stackq);
    407 
    408 	/* initialize */
    409 	initsignal();
    410 	initcursors();
    411 	initatoms();
    412 	initroot();
    413 	initdummywindows();
    414 	initdock();
    415 	initxrm();
    416 
    417 	/* set up list of monitors */
    418 	monupdate();
    419 	wm.selmon = TAILQ_FIRST(&wm.monq);
    420 
    421 	/* initialize ewmh hints */
    422 	ewmhinit(wmname);
    423 	ewmhsetcurrentdesktop(0);
    424 	ewmhsetshowingdesktop(0);
    425 	ewmhsetclients();
    426 	ewmhsetactivewindow(None);
    427 
    428 	/* run sh script */
    429 	autostart(filename);
    430 
    431 	/* scan windows */
    432 	scan();
    433 	mapdummywins();
    434 
    435 	/* set modifier key and grab alt key */
    436 	setmod();
    437 
    438 	/* run main event loop */
    439 	while (!XNextEvent(dpy, &ev)) {
    440 		wm.setclientlist = 0;
    441 		if (wm.xrandr && ev.type - wm.xrandrev == RRScreenChangeNotify)
    442 			monevent(&ev);
    443 		else if (ev.type < LASTEvent && xevents[ev.type] != NULL)
    444 			(*xevents[ev.type])(&ev);
    445 		if (!wm.running)
    446 			break;
    447 		if (wm.setclientlist) {
    448 			ewmhsetclients();
    449 		}
    450 	}
    451 
    452 	/* clean up */
    453 	cleandummywindows();
    454 	cleancursors();
    455 	cleanwm();
    456 	cleantheme();
    457 
    458 	/* clear ewmh hints */
    459 	ewmhsetclients();
    460 	ewmhsetactivewindow(None);
    461 
    462 	/* close connection to server */
    463 	XUngrabPointer(dpy, CurrentTime);
    464 	XUngrabKeyboard(dpy, CurrentTime);
    465 	XCloseDisplay(dpy);
    466 
    467 	return 0;
    468 }