1 /* See LICENSE file for copyright and license details.
2  *
3  * echinus window manager is designed like any other X client as well. It is
4  * driven through handling X events. In contrast to other X clients, a window
5  * manager selects for SubstructureRedirectMask on the root window, to receive
6  * events about window (dis-)appearance.  Only one X connection at a time is
7  * allowed to select for this event mask.
8  *
9  * The event handlers of echinus are organized in an
10  * array which is accessed whenever a new event has been fetched. This allows
11  * event dispatching in O(1) time.
12  *
13  * Each child of the root window is called a client, except windows which have
14  * set the override_redirect flag.  Clients are organized in a global
15  * doubly-linked client list, the focus history is remembered through a global
16  * stack list. Each client contains an array of Bools of the same size as the
17  * global tags array to indicate the tags of a client.
18  *
19  * Keys and tagging rules are organized as arrays and defined in config.h.
20  *
21  * To understand everything else, start reading main().
22  */
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <sys/select.h>
26 #include <sys/types.h>
27 #include <sys/wait.h>
28 #include <assert.h>
29 #include <ctype.h>
30 #include <stdlib.h>
31 #include <fcntl.h>
32 #include <errno.h>
33 #include <locale.h>
34 #include <stdarg.h>
35 #include <stdio.h>
36 #include <strings.h>
37 #include <unistd.h>
38 #include <regex.h>
39 #include <signal.h>
40 #include <X11/cursorfont.h>
41 #include <X11/keysym.h>
42 #include <X11/XF86keysym.h>
43 #include <X11/Xatom.h>
44 #include <X11/Xlib.h>
45 #include <X11/Xproto.h>
46 #include <X11/Xutil.h>
47 #include <X11/Xresource.h>
48 #include <X11/Xft/Xft.h>
49 #ifdef XRANDR
50 #include <X11/extensions/Xrandr.h>
51 #include <X11/extensions/randr.h>
52 #endif
53 #include "echinus.h"
54 
55 /* macros */
56 #define BUTTONMASK		(ButtonPressMask | ButtonReleaseMask)
57 #define CLEANMASK(mask)		(mask & ~(numlockmask | LockMask))
58 #define MOUSEMASK		(BUTTONMASK | PointerMotionMask)
59 #define CLIENTMASK	        (PropertyChangeMask | StructureNotifyMask | FocusChangeMask)
60 #define CLIENTNOPROPAGATEMASK 	(BUTTONMASK | ButtonMotionMask)
61 #define FRAMEMASK               (MOUSEMASK | SubstructureRedirectMask | SubstructureNotifyMask | EnterWindowMask | LeaveWindowMask)
62 
63 /* function-like macros */
64 #define save(_c) { (_c)->rx = (_c)->x; \
65 	       	(_c)->ry = (_c)->y; \
66 		(_c)->rw = (_c)->w; \
67 		(_c)->rh = (_c)->h; }
68 
69 /* enums */
70 enum { StrutsOn, StrutsOff, StrutsHide };		    /* struts position */
71 enum { CurNormal, CurResize, CurMove, CurLast };	    /* cursor */
72 enum { Clk2Focus, SloppyFloat, AllSloppy, SloppyRaise };    /* focus model */
73 
74 /* function declarations */
75 void applyrules(Client * c);
76 void arrange(Monitor * m);
77 void attach(Client * c);
78 void attachstack(Client * c);
79 void ban(Client * c);
80 void buttonpress(XEvent * e);
81 void bstack(Monitor * m);
82 void checkotherwm(void);
83 void cleanup(void);
84 void compileregs(void);
85 void configure(Client * c);
86 void configurenotify(XEvent * e);
87 void configurerequest(XEvent * e);
88 void destroynotify(XEvent * e);
89 void detach(Client * c);
90 void detachstack(Client * c);
91 void *emallocz(unsigned int size);
92 void enternotify(XEvent * e);
93 void eprint(const char *errstr, ...);
94 void expose(XEvent * e);
95 void iconify(const char *arg);
96 void incnmaster(const char *arg);
97 void focus(Client * c);
98 void focusnext(const char *arg);
99 void focusprev(const char *arg);
100 Client *getclient(Window w, Client * list, int part);
101 const char *getresource(const char *resource, const char *defval);
102 long getstate(Window w);
103 Bool gettextprop(Window w, Atom atom, char *text, unsigned int size);
104 void getpointer(int *x, int *y);
105 Monitor *getmonitor(int x, int y);
106 Monitor *curmonitor();
107 Monitor *clientmonitor(Client * c);
108 int idxoftag(const char *tag);
109 Bool isvisible(Client * c, Monitor * m);
110 void initmonitors(XEvent * e);
111 void keypress(XEvent * e);
112 void killclient(const char *arg);
113 void leavenotify(XEvent * e);
114 void focusin(XEvent * e);
115 void manage(Window w, XWindowAttributes * wa);
116 void mappingnotify(XEvent * e);
117 void monocle(Monitor * m);
118 void maprequest(XEvent * e);
119 void mousemove(Client * c);
120 void mouseresize(Client * c);
121 void moveresizekb(const char *arg);
122 Client *nexttiled(Client * c, Monitor * m);
123 Client *prevtiled(Client * c, Monitor * m);
124 void place(Client *c);
125 void propertynotify(XEvent * e);
126 void reparentnotify(XEvent * e);
127 void quit(const char *arg);
128 void restart(const char *arg);
129 void resize(Client * c, int x, int y, int w, int h, Bool sizehints);
130 void restack(Monitor * m);
131 void run(void);
132 void scan(void);
133 void setclientstate(Client * c, long state);
134 void setlayout(const char *arg);
135 void setmwfact(const char *arg);
136 void setup(char *);
137 void spawn(const char *arg);
138 void tag(const char *arg);
139 void tile(Monitor * m);
140 void togglestruts(const char *arg);
141 void togglefloating(const char *arg);
142 void togglemax(const char *arg);
143 void togglefill(const char *arg);
144 void toggletag(const char *arg);
145 void toggleview(const char *arg);
146 void togglemonitor(const char *arg);
147 void focusview(const char *arg);
148 void unban(Client * c);
149 void unmanage(Client * c);
150 void updategeom(Monitor * m);
151 void updatestruts(Monitor * m);
152 void unmapnotify(XEvent * e);
153 void updatesizehints(Client * c);
154 void updateframe(Client * c);
155 void updatetitle(Client * c);
156 void view(const char *arg);
157 void viewprevtag(const char *arg);	/* views previous selected tags */
158 void viewlefttag(const char *arg);
159 void viewrighttag(const char *arg);
160 int xerror(Display * dpy, XErrorEvent * ee);
161 int xerrordummy(Display * dsply, XErrorEvent * ee);
162 int xerrorstart(Display * dsply, XErrorEvent * ee);
163 int (*xerrorxlib) (Display *, XErrorEvent *);
164 void zoom(const char *arg);
165 
166 /* variables */
167 char **cargv;
168 Display *dpy;
169 int screen;
170 Window root;
171 XrmDatabase xrdb;
172 Bool otherwm;
173 Bool running = True;
174 Bool selscreen = True;
175 Monitor *monitors;
176 Client *clients;
177 Client *sel;
178 Client *stack;
179 Cursor cursor[CurLast];
180 Style style;
181 Button button[LastBtn];
182 View *views;
183 Key **keys;
184 Rule **rules;
185 char **tags;
186 unsigned int ntags;
187 unsigned int nkeys;
188 unsigned int nrules;
189 unsigned int modkey;
190 unsigned int numlockmask;
191 /* configuration, allows nested code to access above variables */
192 #include "config.h"
193 
194 struct {
195 	Bool dectiled;
196 	Bool hidebastards;
197 	int focus;
198 	int snap;
199 	char command[255];
200 } options;
201 
202 Layout layouts[] = {
203 	/* function	symbol	features */
204 	{  NULL,	'i',	OVERLAP },
205 	{  tile,	't',	MWFACT | NMASTER | ZOOM },
206 	{  bstack,	'b',	MWFACT | ZOOM },
207 	{  monocle,	'm',	0 },
208 	{  NULL,	'f',	OVERLAP },
209 	{  NULL,	'\0',	0 },
210 };
211 
212 void (*handler[LASTEvent]) (XEvent *) = {
213 	[ButtonPress] = buttonpress,
214 	[ButtonRelease] = buttonpress,
215 	[ConfigureRequest] = configurerequest,
216 	[ConfigureNotify] = configurenotify,
217 	[DestroyNotify] = destroynotify,
218 	[EnterNotify] = enternotify,
219 	[LeaveNotify] = leavenotify,
220 	[FocusIn] = focusin,
221 	[Expose] = expose,
222 	[KeyPress] = keypress,
223 	[MappingNotify] = mappingnotify,
224 	[MapRequest] = maprequest,
225 	[PropertyNotify] = propertynotify,
226 	[ReparentNotify] = reparentnotify,
227 	[UnmapNotify] = unmapnotify,
228 	[ClientMessage] = clientmessage,
229 #ifdef XRANDR
230 	[RRScreenChangeNotify] = initmonitors,
231 #endif
232 };
233 
234 /* function implementations */
235 void
applyrules(Client * c)236 applyrules(Client * c) {
237 	static char buf[512];
238 	unsigned int i, j;
239 	regmatch_t tmp;
240 	Bool matched = False;
241 	XClassHint ch = { 0 };
242 
243 	/* rule matching */
244 	XGetClassHint(dpy, c->win, &ch);
245 	snprintf(buf, sizeof(buf), "%s:%s:%s",
246 	    ch.res_class ? ch.res_class : "", ch.res_name ? ch.res_name : "", c->name);
247 	buf[LENGTH(buf)-1] = 0;
248 	for (i = 0; i < nrules; i++)
249 		if (rules[i]->propregex && !regexec(rules[i]->propregex, buf, 1, &tmp, 0)) {
250 			c->isfloating = rules[i]->isfloating;
251 			c->title = rules[i]->hastitle;
252 			for (j = 0; rules[i]->tagregex && j < ntags; j++) {
253 				if (!regexec(rules[i]->tagregex, tags[j], 1, &tmp, 0)) {
254 					matched = True;
255 					c->tags[j] = True;
256 				}
257 			}
258 		}
259 	if (ch.res_class)
260 		XFree(ch.res_class);
261 	if (ch.res_name)
262 		XFree(ch.res_name);
263 	if (!matched)
264 		memcpy(c->tags, curseltags, ntags * sizeof(curseltags[0]));
265 }
266 
267 void
arrangefloats(Monitor * m)268 arrangefloats(Monitor * m) {
269 	Client *c;
270 	Monitor *om;
271 	int dx, dy;
272 
273 	for(c = stack; c; c = c->snext) {
274 		if(isvisible(c, m) && !c->isbastard &&
275 			       	(c->isfloating || MFEATURES(m, OVERLAP))
276 			       	&& !c->ismax && !c->isicon) {
277 			DPRINTF("%d %d\n", c->rx, c->ry);
278 			if (!(om = getmonitor(c->rx + c->rw/2,
279 				       	c->ry + c->rh/2)))
280 				continue;
281 			dx = om->sx + om->sw - c->rx;
282 			dy = om->sy + om->sh - c->ry;
283 			if (dx > m->sw)
284 				dx = m->sw;
285 			if (dy > m->sh)
286 				dy = m->sh;
287 			resize(c, m->sx + m->sw - dx, m->sy + m->sh - dy, c->rw, c->rh, True);
288 			save(c);
289 		}
290 	}
291 }
292 
293 void
arrangemon(Monitor * m)294 arrangemon(Monitor * m) {
295 	Client *c;
296 
297 	if (views[m->curtag].layout->arrange)
298 		views[m->curtag].layout->arrange(m);
299 	arrangefloats(m);
300 	restack(m);
301 	for (c = stack; c; c = c->snext) {
302 		if ((clientmonitor(c) == m) && ((!c->isbastard && !c->isicon) ||
303 			(c->isbastard && views[m->curtag].barpos == StrutsOn))) {
304 			unban(c);
305 		}
306 	}
307 
308 	for (c = stack; c; c = c->snext) {
309 		if ((clientmonitor(c) == NULL) || (!c->isbastard && c->isicon) ||
310 			(c->isbastard && views[m->curtag].barpos == StrutsHide)) {
311 			ban(c);
312 		}
313 	}
314 }
315 
316 void
arrange(Monitor * m)317 arrange(Monitor * m) {
318 	Monitor *i;
319 
320 	if (!m) {
321 		for (i = monitors; i; i = i->next)
322 			arrangemon(i);
323 	} else
324 		arrangemon(m);
325 }
326 
327 void
attach(Client * c)328 attach(Client * c) {
329 	if (clients)
330 		clients->prev = c;
331 	c->next = clients;
332 	clients = c;
333 }
334 
335 void
attachstack(Client * c)336 attachstack(Client * c) {
337 	c->snext = stack;
338 	stack = c;
339 }
340 
341 void
ban(Client * c)342 ban(Client * c) {
343 	if (c->isbanned)
344 		return;
345 	c->ignoreunmap++;
346 	setclientstate(c, IconicState);
347 	XSelectInput(dpy, c->win, CLIENTMASK & ~(StructureNotifyMask | EnterWindowMask));
348 	XSelectInput(dpy, c->frame, NoEventMask);
349 	XUnmapWindow(dpy, c->frame);
350 	XUnmapWindow(dpy, c->win);
351 	XSelectInput(dpy, c->win, CLIENTMASK);
352 	XSelectInput(dpy, c->frame, FRAMEMASK);
353 	c->isbanned = True;
354 }
355 
356 void
buttonpress(XEvent * e)357 buttonpress(XEvent * e) {
358 	Client *c;
359 	int i;
360 	XButtonPressedEvent *ev = &e->xbutton;
361 
362 	if (!curmonitor())
363 		return;
364 	if (ev->window == root) {
365 		if (ev->type != ButtonRelease)
366 			return;
367 		switch (ev->button) {
368 		case Button3:
369 			spawn(options.command);
370 			break;
371 		case Button4:
372 			viewlefttag(NULL);
373 			break;
374 		case Button5:
375 			viewrighttag(NULL);
376 			break;
377 		}
378 		return;
379 	}
380 	if ((c = getclient(ev->window, clients, ClientTitle))) {
381 		DPRINTF("TITLE %s: 0x%x\n", c->name, (int) ev->window);
382 		focus(c);
383 		for (i = 0; i < LastBtn; i++) {
384 			if (button[i].action == NULL)
385 				continue;
386 			if ((ev->x > button[i].x)
387 			    && ((int)ev->x < (int)(button[i].x + style.titleheight))
388 			    && (button[i].x != -1) && (int)ev->y < style.titleheight) {
389 				if (ev->type == ButtonPress) {
390 					DPRINTF("BUTTON %d PRESSED\n", i);
391 					button[i].pressed = 1;
392 				} else {
393 					DPRINTF("BUTTON %d RELEASED\n", i);
394 					button[i].pressed = 0;
395 					button[i].action(NULL);
396 				}
397 				drawclient(c);
398 				return;
399 			}
400 		}
401 		for (i = 0; i < LastBtn; i++) {
402 			button[i].pressed = 0;
403 			drawclient(c);
404 		}
405 		if (ev->type == ButtonRelease)
406 			return;
407 		if (FEATURES(curlayout, OVERLAP) || c->isfloating)
408 			XRaiseWindow(dpy, c->frame);
409 		if (ev->button == Button1)
410 			mousemove(c);
411 		else if (ev->button == Button3)
412 			mouseresize(c);
413 	} else if ((c = getclient(ev->window, clients, ClientWindow))) {
414 		DPRINTF("WINDOW %s: 0x%x\n", c->name, (int) ev->window);
415 		focus(c);
416 		if (FEATURES(curlayout, OVERLAP) || c->isfloating)
417 			XRaiseWindow(dpy, c->frame);
418 		if (CLEANMASK(ev->state) != modkey) {
419 			XAllowEvents(dpy, ReplayPointer, CurrentTime);
420 			return;
421 		}
422 		if (ev->button == Button1) {
423 			if (!FEATURES(curlayout, OVERLAP) && !c->isfloating)
424 				togglefloating(NULL);
425 			if (c->ismax)
426 				togglemax(NULL);
427 			mousemove(c);
428 		} else if (ev->button == Button2) {
429 			if (!FEATURES(curlayout, OVERLAP) && c->isfloating)
430 				togglefloating(NULL);
431 			else
432 				zoom(NULL);
433 		} else if (ev->button == Button3) {
434 			if (!FEATURES(curlayout, OVERLAP) && !c->isfloating)
435 				togglefloating(NULL);
436 			if (c->ismax)
437 				togglemax(NULL);
438 			mouseresize(c);
439 		}
440 	} else if ((c = getclient(ev->window, clients, ClientFrame))) {
441 		DPRINTF("FRAME %s: 0x%x\n", c->name, (int) ev->window);
442 		/* Not supposed to happen */
443 	}
444 }
445 
446 void
checkotherwm(void)447 checkotherwm(void) {
448 	otherwm = False;
449 	XSetErrorHandler(xerrorstart);
450 
451 	/* this causes an error if some other window manager is running */
452 	XSelectInput(dpy, root, SubstructureRedirectMask);
453 	XSync(dpy, False);
454 	if (otherwm)
455 		eprint("echinus: another window manager is already running\n");
456 	XSync(dpy, False);
457 	XSetErrorHandler(NULL);
458 	xerrorxlib = XSetErrorHandler(xerror);
459 	XSync(dpy, False);
460 }
461 
462 void
cleanup(void)463 cleanup(void) {
464 	while (stack) {
465 		unban(stack);
466 		unmanage(stack);
467 	}
468 	free(tags);
469 	free(keys);
470 	initmonitors(NULL);
471 	/* free resource database */
472 	XrmDestroyDatabase(xrdb);
473 	deinitstyle();
474 	XUngrabKey(dpy, AnyKey, AnyModifier, root);
475 	XFreeCursor(dpy, cursor[CurNormal]);
476 	XFreeCursor(dpy, cursor[CurResize]);
477 	XFreeCursor(dpy, cursor[CurMove]);
478 	XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
479 	XSync(dpy, False);
480 }
481 
482 void
configure(Client * c)483 configure(Client * c) {
484 	XConfigureEvent ce;
485 
486 	ce.type = ConfigureNotify;
487 	ce.display = dpy;
488 	ce.event = c->win;
489 	ce.window = c->win;
490 	ce.x = c->x;
491 	ce.y = c->y;
492 	ce.width = c->w;
493 	ce.height = c->h - c->th;
494 	ce.border_width = 0;
495 	ce.above = None;
496 	ce.override_redirect = False;
497 	XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *) & ce);
498 }
499 
500 void
configurenotify(XEvent * e)501 configurenotify(XEvent * e) {
502 	XConfigureEvent *ev = &e->xconfigure;
503 	Monitor *m;
504 	Client *c;
505 
506 	if (ev->window == root) {
507 #ifdef XRANDR
508 		if (XRRUpdateConfiguration((XEvent *) ev)) {
509 #endif
510 			initmonitors(e);
511 			for (c = clients; c; c = c->next) {
512 				if (c->isbastard) {
513 					m = getmonitor(c->x + c->w/2, c->y);
514 					c->tags = m->seltags;
515 					updatestruts(m);
516 				}
517 			}
518 			for (m = monitors; m; m = m->next)
519 				updategeom(m);
520 			arrange(NULL);
521 #ifdef XRANDR
522 		}
523 #endif
524 	}
525 }
526 
527 void
configurerequest(XEvent * e)528 configurerequest(XEvent * e) {
529 	Client *c;
530 	XConfigureRequestEvent *ev = &e->xconfigurerequest;
531 	XWindowChanges wc;
532 	Monitor *cm;
533 	int x, y, w, h;
534 
535 	if ((c = getclient(ev->window, clients, ClientWindow))) {
536 		c->ismax = False;
537 		cm = clientmonitor(c);
538 		if (ev->value_mask & CWBorderWidth)
539 			c->border = ev->border_width;
540 		if (c->isfixed || c->isfloating || MFEATURES(clientmonitor(c), OVERLAP)) {
541 			if (ev->value_mask & CWX)
542 				x = ev->x;
543 			if (ev->value_mask & CWY)
544 				y = ev->y;
545 			if (ev->value_mask & CWWidth)
546 				w = ev->width;
547 			if (ev->value_mask & CWHeight)
548 				h = ev->height + c->th;
549 			cm = getmonitor(x, y);
550 			if (!(ev->value_mask & (CWX | CWY)) /* resize request */
551 			    && (ev->value_mask & (CWWidth | CWHeight))) {
552 				DPRINTF("RESIZE %s (%d,%d)->(%d,%d)\n", c->name, c->w, c->h, w, h);
553 				resize(c, c->x, c->y, w, h, True);
554 			} else if ((ev->value_mask & (CWX | CWY)) /* move request */
555 			    && !(ev->value_mask & (CWWidth | CWHeight))) {
556 				DPRINTF("MOVE %s (%d,%d)->(%d,%d)\n", c->name, c->x, c->y, x, y);
557 				resize(c, x, y, c->w, c->h, True);
558 				save(c);
559 			} else if ((ev->value_mask & (CWX | CWY)) /* move and resize request */
560 			    && (ev->value_mask & (CWWidth | CWHeight))) {
561 				DPRINTF("MOVE&RESIZE(MOVE) %s (%d,%d)->(%d,%d)\n", c->name, c->x, c->y, ev->x, ev->y);
562 				DPRINTF("MOVE&RESIZE(RESIZE) %s (%d,%d)->(%d,%d)\n", c->name, c->w, c->h, ev->width, ev->height);
563 				resize(c, x, y, w, h, True);
564 				save(c);
565 			} else if ((ev->value_mask & CWStackMode)) {
566 				DPRINTF("RESTACK %s ignoring\n", c->name);
567 				configure(c);
568 			}
569 		} else {
570 			configure(c);
571 		}
572 	} else {
573 		wc.x = ev->x;
574 		wc.y = ev->y;
575 		wc.width = ev->width;
576 		wc.height = ev->height;
577 		wc.border_width = ev->border_width;
578 		wc.sibling = ev->above;
579 		wc.stack_mode = ev->detail;
580 		XConfigureWindow(dpy, ev->window, ev->value_mask, &wc);
581 	}
582 	XSync(dpy, False);
583 }
584 
585 void
destroynotify(XEvent * e)586 destroynotify(XEvent * e) {
587 	Client *c;
588 	XDestroyWindowEvent *ev = &e->xdestroywindow;
589 
590 	if (!(c = getclient(ev->window, clients, ClientWindow)))
591 		return;
592 	unmanage(c);
593 	updateatom[ClientList] (NULL);
594 }
595 
596 void
detach(Client * c)597 detach(Client * c) {
598 	if (c->prev)
599 		c->prev->next = c->next;
600 	if (c->next)
601 		c->next->prev = c->prev;
602 	if (c == clients)
603 		clients = c->next;
604 	c->next = c->prev = NULL;
605 }
606 
607 void
detachstack(Client * c)608 detachstack(Client * c) {
609 	Client **tc;
610 
611 	for (tc = &stack; *tc && *tc != c; tc = &(*tc)->snext);
612 	*tc = c->snext;
613 }
614 
615 void *
emallocz(unsigned int size)616 emallocz(unsigned int size) {
617 	void *res = calloc(1, size);
618 
619 	if (!res)
620 		eprint("fatal: could not malloc() %u bytes\n", size);
621 	return res;
622 }
623 
624 void
enternotify(XEvent * e)625 enternotify(XEvent * e) {
626 	XCrossingEvent *ev = &e->xcrossing;
627 	Client *c;
628 
629 	if (ev->mode != NotifyNormal || ev->detail == NotifyInferior)
630 		return;
631 	if (!curmonitor())
632 		return;
633 	if ((c = getclient(ev->window, clients, ClientFrame))) {
634 #if 0 /* WTF ? */
635 		if (!isvisible(sel, curmonitor()))
636 			focus(c);
637 #endif
638 		if (c->isbastard)
639 			return;
640 		switch (options.focus) {
641 		case Clk2Focus:
642 			break;
643 		case SloppyFloat:
644 			if (FEATURES(curlayout, OVERLAP) || c->isfloating)
645 				focus(c);
646 			break;
647 		case AllSloppy:
648 			focus(c);
649 			break;
650 		case SloppyRaise:
651 			focus(c);
652 			restack(curmonitor());
653 			break;
654 		}
655 	} else if (ev->window == root) {
656 		selscreen = True;
657 		focus(NULL);
658 	}
659 }
660 
661 void
eprint(const char * errstr,...)662 eprint(const char *errstr, ...) {
663 	va_list ap;
664 
665 	va_start(ap, errstr);
666 	vfprintf(stderr, errstr, ap);
667 	va_end(ap);
668 	exit(EXIT_FAILURE);
669 }
670 
671 void
focusin(XEvent * e)672 focusin(XEvent * e) {
673 	XFocusChangeEvent *ev = &e->xfocus;
674 	Client *c;
675 
676 	if (sel && ((c = getclient(ev->window, clients, ClientWindow)) != sel))
677 		XSetInputFocus(dpy, sel->win, RevertToPointerRoot, CurrentTime);
678 	else if (!c)
679 		fprintf(stderr, "Caught FOCUSIN for unknown window 0x%x\n", ev->window);
680 }
681 
682 void
expose(XEvent * e)683 expose(XEvent * e) {
684 	XExposeEvent *ev = &e->xexpose;
685 	XEvent tmp;
686 	Client *c;
687 
688 	while (XCheckWindowEvent(dpy, ev->window, ExposureMask, &tmp));
689 	if((c = getclient(ev->window, clients, ClientTitle)))
690 		drawclient(c);
691 }
692 
693 void
givefocus(Client * c)694 givefocus(Client * c) {
695 	XEvent ce;
696 	if (checkatom(c->win, atom[WMProto], atom[WMTakeFocus])) {
697 		ce.xclient.type = ClientMessage;
698 		ce.xclient.message_type = atom[WMProto];
699 		ce.xclient.display = dpy;
700 		ce.xclient.window = c->win;
701 		ce.xclient.format = 32;
702 		ce.xclient.data.l[0] = atom[WMTakeFocus];
703 		ce.xclient.data.l[1] = CurrentTime;	/* incorrect */
704 		ce.xclient.data.l[2] = 0l;
705 		ce.xclient.data.l[3] = 0l;
706 		ce.xclient.data.l[4] = 0l;
707 		XSendEvent(dpy, c->win, False, NoEventMask, &ce);
708 	}
709 }
710 
711 void
focus(Client * c)712 focus(Client * c) {
713 	Client *o;
714 
715 	o = sel;
716 	if ((!c && selscreen) || (c && (c->isbastard || !isvisible(c, curmonitor()))))
717 		for (c = stack;
718 		    c && (c->isbastard || c->isicon || !isvisible(c, curmonitor())); c = c->snext);
719 	if (sel && sel != c) {
720 		XSetWindowBorder(dpy, sel->frame, style.color.norm[ColBorder]);
721 	}
722 	if (c) {
723 		detachstack(c);
724 		attachstack(c);
725 		/* unban(c); */
726 	}
727 	sel = c;
728 	if (!selscreen)
729 		return;
730 	if (c) {
731 		setclientstate(c, NormalState);
732 		if (c->isfocusable) {
733 			XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime);
734 			givefocus(c);
735 		}
736 		XSetWindowBorder(dpy, sel->frame, style.color.sel[ColBorder]);
737 		drawclient(c);
738 	} else {
739 		XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
740 	}
741 	if (o)
742 		drawclient(o);
743 	updateatom[ActiveWindow] (sel);
744 	updateatom[ClientList] (NULL);
745 	updateatom[CurDesk] (NULL);
746 }
747 
748 void
focusicon(const char * arg)749 focusicon(const char *arg) {
750 	Client *c;
751 
752 	for(c = clients; c && (!c->isicon || !isvisible(c, curmonitor())); c = c->next);
753 	if (!c)
754 		return;
755 	c->isicon = False;
756 	focus(c);
757 	arrange(curmonitor());
758 }
759 
760 void
focusnext(const char * arg)761 focusnext(const char *arg) {
762 	Client *c;
763 
764 	if (!sel)
765 		return;
766 	for (c = sel->next;
767 	    c && (c->isbastard || c->isicon || !isvisible(c, curmonitor())); c = c->next);
768 	if (!c)
769 		for (c = clients;
770 		    c && (c->isbastard || c->isicon
771 			|| !isvisible(c, curmonitor())); c = c->next);
772 	if (c) {
773 		focus(c);
774 		restack(curmonitor());
775 	}
776 }
777 
778 void
focusprev(const char * arg)779 focusprev(const char *arg) {
780 	Client *c;
781 
782 	if (!sel)
783 		return;
784 	for (c = sel->prev;
785 	    c && (c->isbastard || c->isicon || !isvisible(c, curmonitor())); c = c->prev);
786 	if (!c) {
787 		for (c = clients; c && c->next; c = c->next);
788 		for (;
789 		    c && (c->isbastard || c->isicon
790 			|| !isvisible(c, curmonitor())); c = c->prev);
791 	}
792 	if (c) {
793 		focus(c);
794 		restack(curmonitor());
795 	}
796 }
797 
798 void
iconify(const char * arg)799 iconify(const char *arg) {
800 	Client *c;
801 	if (!sel)
802 		return;
803 	c = sel;
804 	focusnext(NULL);
805 	ban(c);
806 	c->isicon = True;
807 	arrange(curmonitor());
808 }
809 
810 void
incnmaster(const char * arg)811 incnmaster(const char *arg) {
812 	unsigned int i;
813 
814 	if (!FEATURES(curlayout, NMASTER))
815 		return;
816 	if (!arg) {
817 		views[curmontag].nmaster = DEFNMASTER;
818 	} else {
819 		i = atoi(arg);
820 		if ((views[curmontag].nmaster + i) < 1
821 		    || curwah / (views[curmontag].nmaster + i) <= 2 * style.border)
822 			return;
823 		views[curmontag].nmaster += i;
824 	}
825 	if (sel)
826 		arrange(curmonitor());
827 }
828 
829 Client *
getclient(Window w,Client * list,int part)830 getclient(Window w, Client * list, int part) {
831 	Client *c;
832 
833 #define ClientPart(_c, _part) (((_part) == ClientWindow) ? (_c)->win : \
834 			       ((_part) == ClientTitle) ? (_c)->title : \
835 			       ((_part) == ClientFrame) ? (_c)->frame : 0)
836 
837 	for (c = list; c && (ClientPart(c, part)) != w; c = c->next);
838 
839 	return c;
840 }
841 
842 long
getstate(Window w)843 getstate(Window w) {
844 	long ret = -1;
845 	long *p = NULL;
846 	unsigned long n;
847 
848 	p = (long*)getatom(w, atom[WMState], &n);
849 	if (n != 0)
850 		ret = *p;
851 	XFree(p);
852 	return ret;
853 }
854 
855 const char *
getresource(const char * resource,const char * defval)856 getresource(const char *resource, const char *defval) {
857 	static char name[256], class[256], *type;
858 	XrmValue value;
859 
860 	snprintf(name, sizeof(name), "%s.%s", RESNAME, resource);
861 	snprintf(class, sizeof(class), "%s.%s", RESCLASS, resource);
862 	XrmGetResource(xrdb, name, class, &type, &value);
863 	if (value.addr)
864 		return value.addr;
865 	return defval;
866 }
867 
868 Bool
gettextprop(Window w,Atom atom,char * text,unsigned int size)869 gettextprop(Window w, Atom atom, char *text, unsigned int size) {
870 	char **list = NULL;
871 	int n;
872 	XTextProperty name;
873 
874 	if (!text || size == 0)
875 		return False;
876 	text[0] = '\0';
877 	XGetTextProperty(dpy, w, &name, atom);
878 	if (!name.nitems)
879 		return False;
880 	if (name.encoding == XA_STRING) {
881 		strncpy(text, (char *) name.value, size - 1);
882 	} else {
883 		if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success
884 		    && n > 0 && *list) {
885 			strncpy(text, *list, size - 1);
886 			XFreeStringList(list);
887 		}
888 	}
889 	text[size - 1] = '\0';
890 	XFree(name.value);
891 	return True;
892 }
893 
894 int
idxoftag(const char * tag)895 idxoftag(const char *tag) {
896 	unsigned int i;
897 
898 	for (i = 0; (i < ntags) && strcmp(tag, tags[i]); i++);
899 	return (i < ntags) ? i : 0;
900 }
901 
902 Bool
isvisible(Client * c,Monitor * m)903 isvisible(Client * c, Monitor * m) {
904 	unsigned int i;
905 
906 	if (!c)
907 		return False;
908 	if (!m) {
909 		for (m = monitors; m; m = m->next) {
910 			for (i = 0; i < ntags; i++)
911 				if (c->tags[i] && m->seltags[i])
912 					return True;
913 		}
914 	} else {
915 		for (i = 0; i < ntags; i++)
916 			if (c->tags[i] && m->seltags[i])
917 				return True;
918 	}
919 	return False;
920 }
921 
922 void
grabkeys(void)923 grabkeys(void) {
924 	unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
925 	unsigned int i, j;
926 	KeyCode code;
927 	XUngrabKey(dpy, AnyKey, AnyModifier, root);
928 	for (i = 0; i < nkeys; i++) {
929 		if ((code = XKeysymToKeycode(dpy, keys[i]->keysym))) {
930 			for (j = 0; j < LENGTH(modifiers); j++)
931 				XGrabKey(dpy, code, keys[i]->mod | modifiers[j], root,
932 					 True, GrabModeAsync, GrabModeAsync);
933 		}
934     }
935 }
936 
937 void
keypress(XEvent * e)938 keypress(XEvent * e) {
939 	unsigned int i;
940 	KeySym keysym;
941 	XKeyEvent *ev;
942 
943 	if (!curmonitor())
944 		return;
945 	ev = &e->xkey;
946 	keysym = XKeycodeToKeysym(dpy, (KeyCode) ev->keycode, 0);
947 	for (i = 0; i < nkeys; i++)
948 		if (keysym == keys[i]->keysym
949 		    && CLEANMASK(keys[i]->mod) == CLEANMASK(ev->state)) {
950 			if (keys[i]->func)
951 				keys[i]->func(keys[i]->arg);
952 			XUngrabKeyboard(dpy, CurrentTime);
953 		}
954 }
955 
956 void
killclient(const char * arg)957 killclient(const char *arg) {
958 	XEvent ev;
959 
960 	if (!sel)
961 		return;
962 	if (checkatom(sel->win, atom[WMProto], atom[WMDelete])) {
963 		ev.type = ClientMessage;
964 		ev.xclient.window = sel->win;
965 		ev.xclient.message_type = atom[WMProto];
966 		ev.xclient.format = 32;
967 		ev.xclient.data.l[0] = atom[WMDelete];
968 		ev.xclient.data.l[1] = CurrentTime;
969 		XSendEvent(dpy, sel->win, False, NoEventMask, &ev);
970 	} else {
971 		XKillClient(dpy, sel->win);
972 	}
973 }
974 
975 void
leavenotify(XEvent * e)976 leavenotify(XEvent * e) {
977 	XCrossingEvent *ev = &e->xcrossing;
978 	Client *c;
979 
980 	if ((ev->window == root) && !ev->same_screen) {
981 		selscreen = False;
982 		focus(NULL);
983 	}
984 }
985 
986 void
manage(Window w,XWindowAttributes * wa)987 manage(Window w, XWindowAttributes * wa) {
988 	Client *c, *t = NULL;
989 	Monitor *m, *cm = NULL;
990 	Window trans;
991 	XWindowChanges wc;
992 	XSetWindowAttributes twa;
993 	XWMHints *wmh;
994 	unsigned long mask = 0;
995 
996 	c = emallocz(sizeof(Client));
997 	c->win = w;
998 	if (checkatom(c->win, atom[WindowType], atom[WindowTypeDesk]) ||
999 	    checkatom(c->win, atom[WindowType], atom[WindowTypeDock])) {
1000 		c->isbastard = True;
1001 		c->isfloating = True;
1002 		c->isfixed = True;
1003 	}
1004 	if (checkatom(c->win, atom[WindowType], atom[WindowTypeDialog])) {
1005 		c->isfloating = True;
1006 		c->isfixed = True;
1007 	}
1008 
1009 	cm = curmonitor();
1010 	c->isicon = False;
1011 	c->title = c->isbastard ? (Window) NULL : 1;
1012 	c->tags = emallocz(ntags * sizeof(cm->seltags[0]));
1013 	c->isfocusable = c->isbastard ? False : True;
1014 	c->border = c->isbastard ? 0 : style.border;
1015 	c->oldborder = c->isbastard ? 0 : wa->border_width; /* XXX: why? */
1016 	/*  XReparentWindow() unmaps *mapped* windows */
1017 	c->ignoreunmap = wa->map_state == IsViewable ? 1 : 0;
1018 	mwm_process_atom(c);
1019 	updatesizehints(c);
1020 
1021 	updatetitle(c);
1022 	applyrules(c);
1023 
1024 	if (XGetTransientForHint(dpy, w, &trans)) {
1025 		if (t = getclient(trans, clients, ClientWindow)) {
1026 			memcpy(c->tags, t->tags, ntags * sizeof(cm->seltags[0]));
1027 			c->isfloating = True;
1028 		}
1029 	}
1030 
1031 	c->th = c->title ? style.titleheight : 0;
1032 
1033 	if (!c->isfloating)
1034 		c->isfloating = c->isfixed;
1035 
1036 	if ((wmh = XGetWMHints(dpy, c->win))) {
1037 		c->isfocusable = !(wmh->flags & InputHint) || wmh->input;
1038 		XFree(wmh);
1039 	}
1040 
1041 	c->x = c->rx = wa->x;
1042 	c->y = c->ry = wa->y;
1043 	c->w = c->rw = wa->width;
1044 	c->h = c->rh = wa->height + c->th;
1045 
1046 	if (!wa->x && !wa->y && !c->isbastard)
1047 		place(c);
1048 
1049 	cm = c->isbastard ? getmonitor(wa->x, wa->y) : clientmonitor(c);
1050 	c->hasstruts = getstruts(c);
1051 	if (c->isbastard) {
1052 		free(c->tags);
1053 		c->tags = cm->seltags;
1054 	}
1055 #if 0
1056 	if (c->w == cm->sw && c->h == cm->sh) {
1057 		c->x = 0;
1058 		c->y = 0;
1059 	} else if (!c->isbastard) {
1060 		if (c->x + c->w > cm->wax + cm->waw)
1061 			c->x = cm->waw - c->w;
1062 		if (c->y + c->h > cm->way + cm->wah)
1063 			c->y = cm->wah - c->h;
1064 		if (c->x < cm->wax)
1065 			c->x = cm->wax;
1066 		if (c->y < cm->way)
1067 			c->y = cm->way;
1068 	}
1069 #endif
1070 
1071 	XGrabButton(dpy, AnyButton, AnyModifier, c->win, True,
1072 			ButtonPressMask, GrabModeSync, GrabModeAsync, None, None);
1073 	twa.override_redirect = True;
1074 	twa.event_mask = FRAMEMASK;
1075 	mask = CWOverrideRedirect | CWEventMask;
1076 	if (wa->depth == 32) {
1077 		mask |= CWColormap | CWBorderPixel | CWBackPixel;
1078 		twa.colormap = XCreateColormap(dpy, root, wa->visual, AllocNone);
1079 		twa.background_pixel = BlackPixel(dpy, screen);
1080 		twa.border_pixel = BlackPixel(dpy, screen);
1081 	}
1082 	c->frame =
1083 	    XCreateWindow(dpy, root, c->x, c->y, c->w,
1084 	    c->h, c->border, wa->depth == 32 ? 32 : DefaultDepth(dpy, screen),
1085 	    InputOutput, wa->depth == 32 ? wa->visual : DefaultVisual(dpy,
1086 		screen), mask, &twa);
1087 
1088 	wc.border_width = c->border;
1089 	XConfigureWindow(dpy, c->frame, CWBorderWidth, &wc);
1090 	XSetWindowBorder(dpy, c->frame, style.color.norm[ColBorder]);
1091 
1092 	twa.event_mask = ExposureMask | MOUSEMASK;
1093 	/* we create title as root's child as a workaround for 32bit visuals */
1094 	if (c->title) {
1095 		c->title = XCreateWindow(dpy, root, 0, 0, c->w, c->th,
1096 		    0, DefaultDepth(dpy, screen), CopyFromParent,
1097 		    DefaultVisual(dpy, screen), CWEventMask, &twa);
1098 		c->drawable =
1099 		    XCreatePixmap(dpy, root, c->w, c->th, DefaultDepth(dpy, screen));
1100 		c->xftdraw =
1101 		    XftDrawCreate(dpy, c->drawable, DefaultVisual(dpy, screen),
1102 		    DefaultColormap(dpy, screen));
1103 	} else {
1104 		c->title = (Window) NULL;
1105 	}
1106 
1107 	attach(c);
1108 	attachstack(c);
1109 
1110 	twa.event_mask = CLIENTMASK;
1111 	twa.do_not_propagate_mask = CLIENTNOPROPAGATEMASK;
1112 	XChangeWindowAttributes(dpy, c->win, CWEventMask|CWDontPropagate, &twa);
1113 	XSelectInput(dpy, c->win, CLIENTMASK);
1114 
1115 	XReparentWindow(dpy, c->win, c->frame, 0, c->th);
1116 	XReparentWindow(dpy, c->title, c->frame, 0, 0);
1117 	XAddToSaveSet(dpy, c->win);
1118 	XMapWindow(dpy, c->win);
1119 	wc.border_width = 0;
1120 	XConfigureWindow(dpy, c->win, CWBorderWidth, &wc);
1121 	configure(c);	/* propagates border_width, if size doesn't change */
1122 	if (checkatom(c->win, atom[WindowState], atom[WindowStateFs]))
1123 		ewmh_process_state_atom(c, atom[WindowStateFs], 1);
1124 	ban(c);
1125 	updateatom[ClientList] (NULL);
1126 	updateatom[WindowDesk] (c);
1127 	updateframe(c);
1128 	if (!cm)
1129 		return;
1130 	if (c->hasstruts)
1131 		updategeom(cm);
1132 	arrange(cm);
1133 	if (!checkatom(c->win, atom[WindowType], atom[WindowTypeDesk]))
1134 		focus(NULL);
1135 }
1136 
1137 void
mappingnotify(XEvent * e)1138 mappingnotify(XEvent * e) {
1139 	XMappingEvent *ev = &e->xmapping;
1140 
1141 	XRefreshKeyboardMapping(ev);
1142 	if (ev->request == MappingKeyboard)
1143 		grabkeys();
1144 }
1145 
1146 void
maprequest(XEvent * e)1147 maprequest(XEvent * e) {
1148 	static XWindowAttributes wa;
1149 	Client *c;
1150 	XMapRequestEvent *ev = &e->xmaprequest;
1151 
1152 	if (!XGetWindowAttributes(dpy, ev->window, &wa))
1153 		return;
1154 	if (wa.override_redirect)
1155 		return;
1156 	if (!(c = getclient(ev->window, clients, ClientWindow)))
1157 		manage(ev->window, &wa);
1158 }
1159 
1160 void
monocle(Monitor * m)1161 monocle(Monitor * m) {
1162 	Client *c;
1163 
1164 	for (c = nexttiled(clients, m); c; c = nexttiled(c->next, m)) {
1165 			if (views[m->curtag].barpos != StrutsOn)
1166 				resize(c, m->wax - c->border,
1167 						m->way - c->border, m->waw, m->wah, False);
1168 			else
1169 				resize(c, m->wax, m->way,
1170 						m->waw - 2 * c->border,
1171 						m->wah - 2 * c->border, False);
1172 	}
1173 }
1174 
1175 void
moveresizekb(const char * arg)1176 moveresizekb(const char *arg) {
1177 	int dw, dh, dx, dy;
1178 
1179 	dw = dh = dx = dy = 0;
1180 	if (!sel)
1181 		return;
1182 	if (!sel->isfloating)
1183 		return;
1184 	sscanf(arg, "%d %d %d %d", &dx, &dy, &dw, &dh);
1185 	if (dw && (dw < sel->incw))
1186 		dw = (dw / abs(dw)) * sel->incw;
1187 	if (dh && (dh < sel->inch))
1188 		dh = (dh / abs(dh)) * sel->inch;
1189 	resize(sel, sel->x + dx, sel->y + dy, sel->w + dw,
1190 	    sel->h + dh, True);
1191 }
1192 
1193 void
getpointer(int * x,int * y)1194 getpointer(int *x, int *y) {
1195 	int di;
1196 	unsigned int dui;
1197 	Window dummy;
1198 
1199 	XQueryPointer(dpy, root, &dummy, &dummy, x, y, &di, &di, &dui);
1200 }
1201 
1202 Monitor *
getmonitor(int x,int y)1203 getmonitor(int x, int y) {
1204 	Monitor *m;
1205 
1206 	for (m = monitors; m; m = m->next) {
1207 		if ((x >= m->sx && x <= m->sx + m->sw) &&
1208 		    (y >= m->sy && y <= m->sy + m->sh))
1209 			return m;
1210 	}
1211 	return NULL;
1212 }
1213 
1214 Monitor *
clientmonitor(Client * c)1215 clientmonitor(Client * c) {
1216 	Monitor *m;
1217 
1218 	assert(c != NULL);
1219 	for (m = monitors; m; m = m->next)
1220 		if (isvisible(c, m))
1221 			return m;
1222 	return NULL;
1223 }
1224 
1225 Monitor *
curmonitor()1226 curmonitor() {
1227 	int x, y;
1228 	getpointer(&x, &y);
1229 	return getmonitor(x, y);
1230 }
1231 
1232 void
mousemove(Client * c)1233 mousemove(Client * c) {
1234 	int x1, y1, ocx, ocy, nx, ny;
1235 	unsigned int i;
1236 	XEvent ev;
1237 	Monitor *m, *nm;
1238 
1239 	if (c->isbastard)
1240 		return;
1241 	m = curmonitor();
1242 	ocx = c->x;
1243 	ocy = c->y;
1244 	if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync,
1245 		GrabModeAsync, None, cursor[CurMove], CurrentTime) != GrabSuccess)
1246 		return;
1247 	getpointer(&x1, &y1);
1248 	for (;;) {
1249 		XMaskEvent(dpy, MOUSEMASK | ExposureMask | SubstructureRedirectMask, &ev);
1250 		switch (ev.type) {
1251 		case ButtonRelease:
1252 			XUngrabPointer(dpy, CurrentTime);
1253 			return;
1254 		case ConfigureRequest:
1255 		case Expose:
1256 		case MapRequest:
1257 			handler[ev.type] (&ev);
1258 			break;
1259 		case MotionNotify:
1260 			XSync(dpy, False);
1261 			/* we are probably moving to a different monitor */
1262 			if(!(nm = curmonitor()))
1263 				break;
1264 			nx = ocx + (ev.xmotion.x_root - x1);
1265 			ny = ocy + (ev.xmotion.y_root - y1);
1266 			if (abs(nx - nm->wax) < options.snap)
1267 				nx = nm->wax;
1268 			else if (abs((nm->wax + nm->waw) - (nx + c->w +
1269 				    2 * c->border)) < options.snap)
1270 				nx = nm->wax + nm->waw - c->w - 2 * c->border;
1271 			if (abs(ny - nm->way) < options.snap)
1272 				ny = nm->way;
1273 			else if (abs((nm->way + nm->wah) - (ny + c->h +
1274 				    2 * c->border)) < options.snap)
1275 				ny = nm->way + nm->wah - c->h - 2 * c->border;
1276 			resize(c, nx, ny, c->w, c->h, True);
1277 			save(c);
1278 			if (m != nm) {
1279 				for (i = 0; i < ntags; i++)
1280 					c->tags[i] = nm->seltags[i];
1281 				updateatom[WindowDesk] (c);
1282 				drawclient(c);
1283 				arrange(NULL);
1284 				m = nm;
1285 			}
1286 			break;
1287 		}
1288 	}
1289 }
1290 
1291 void
mouseresize(Client * c)1292 mouseresize(Client * c) {
1293 	int ocx, ocy, nw, nh;
1294 	Monitor *cm;
1295 	XEvent ev;
1296 
1297 	if (c->isbastard || c->isfixed)
1298 		return;
1299 	cm = curmonitor();
1300 
1301 	ocx = c->x;
1302 	ocy = c->y;
1303 	if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync,
1304 		GrabModeAsync, None, cursor[CurResize], CurrentTime) != GrabSuccess)
1305 		return;
1306 	c->ismax = False;
1307 	XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->border - 1,
1308 	    c->h + c->border - 1);
1309 	for (;;) {
1310 		XMaskEvent(dpy, MOUSEMASK | ExposureMask | SubstructureRedirectMask, &ev);
1311 		switch (ev.type) {
1312 		case ButtonRelease:
1313 			XWarpPointer(dpy, None, c->win, 0, 0, 0, 0,
1314 			    c->w + c->border - 1, c->h + c->border - 1);
1315 			XUngrabPointer(dpy, CurrentTime);
1316 			while (XCheckMaskEvent(dpy, EnterWindowMask, &ev));
1317 			return;
1318 		case ConfigureRequest:
1319 		case Expose:
1320 		case MapRequest:
1321 			handler[ev.type] (&ev);
1322 			break;
1323 		case MotionNotify:
1324 			XSync(dpy, False);
1325 			if ((nw = ev.xmotion.x - ocx - 2 * c->border + 1) <= 0)
1326 				nw = MINWIDTH;
1327 			if ((nh = ev.xmotion.y - ocy - 2 * c->border + 1) <= 0)
1328 				nh = MINHEIGHT;
1329 			resize(c, c->x, c->y, nw, nh, True);
1330 			save(c);
1331 			break;
1332 		}
1333 	}
1334 }
1335 
1336 Client *
nexttiled(Client * c,Monitor * m)1337 nexttiled(Client * c, Monitor * m) {
1338 	for (; c && (c->isfloating || !isvisible(c, m) || c->isbastard
1339 		|| c->isicon); c = c->next);
1340 	return c;
1341 }
1342 
1343 Client *
prevtiled(Client * c,Monitor * m)1344 prevtiled(Client * c, Monitor * m) {
1345 	for (; c && (c->isfloating || !isvisible(c, m) || c->isbastard
1346 		|| c->isicon); c = c->prev);
1347 	return c;
1348 }
1349 
1350 void
reparentnotify(XEvent * e)1351 reparentnotify(XEvent * e) {
1352 	Client *c;
1353 	XReparentEvent *ev = &e->xreparent;
1354 
1355 	if ((c = getclient(ev->window, clients, ClientWindow)))
1356 		if (ev->parent != c->frame)
1357 			unmanage(c);
1358 }
1359 
1360 void
place(Client * c)1361 place(Client *c) {
1362 	int x, y;
1363 	Monitor *m;
1364 	int d = style.titleheight;
1365 
1366 	/* XXX: do something better */
1367 	getpointer(&x, &y);
1368 	DPRINTF("%d %d\n", x, y);
1369 	m = getmonitor(x, y);
1370 	x = x + rand()%d - c->w/2;
1371 	y = y + rand()%d - c->h/2;
1372 	if (x < m->wax)
1373 		x = m->wax;
1374 	DPRINTF("%d %d\n", x, y);
1375 	if (y < m->way)
1376 		y = m->way;
1377 	DPRINTF("%d+%d > %d+%d\n", x, c->w, m->wax, m->waw);
1378 	if (x + c->w > m->wax + m->waw)
1379 		x = m->wax + m->waw - c->w - rand()%d;
1380 	DPRINTF("%d %d\n", x, y);
1381 	if (y + c->h > m->way + m->wah)
1382 		y = m->way + m->wah - c->h - rand()%d;
1383 	DPRINTF("%d %d\n", x, y);
1384 
1385 	c->rx = c->x = x;
1386 	c->ry = c->y = y;
1387 }
1388 
1389 void
propertynotify(XEvent * e)1390 propertynotify(XEvent * e) {
1391 	Client *c;
1392 	Window trans;
1393 	XPropertyEvent *ev = &e->xproperty;
1394 
1395 	if ((c = getclient(ev->window, clients, ClientWindow))) {
1396 		if (ev->atom == atom[StrutPartial]) {
1397 			c->hasstruts = getstruts(c);
1398 			updategeom(clientmonitor(c));
1399 			arrange(clientmonitor(c));
1400 		}
1401 		if (ev->state == PropertyDelete)
1402 			return;
1403 		if (ev->atom == atom[WindowName]) {
1404 			updatetitle(c);
1405 			drawclient(c);
1406 		}
1407 		switch (ev->atom) {
1408 		case XA_WM_TRANSIENT_FOR:
1409 			XGetTransientForHint(dpy, c->win, &trans);
1410 			if (!c->isfloating
1411 			    && (c->isfloating =
1412 				(getclient(trans, clients, ClientWindow) != NULL)))
1413 				arrange(clientmonitor(c));
1414 			break;
1415 		case XA_WM_NORMAL_HINTS:
1416 			updatesizehints(c);
1417 			break;
1418 		case XA_WM_NAME:
1419 			updatetitle(c);
1420 			drawclient(c);
1421 			break;
1422 		}
1423 	}
1424 }
1425 
1426 void
quit(const char * arg)1427 quit(const char *arg) {
1428 	running = False;
1429 	if (arg) {
1430 		cleanup();
1431 		execvp(cargv[0], cargv);
1432 		eprint("Can't exec: %s\n", strerror(errno));
1433 	}
1434 }
1435 
1436 void
resize(Client * c,int x,int y,int w,int h,Bool sizehints)1437 resize(Client * c, int x, int y, int w, int h, Bool sizehints) {
1438 	XWindowChanges wc;
1439 
1440 	if (sizehints) {
1441 		h -= c->th;
1442 		/* set minimum possible */
1443 		if (w < 1)
1444 			w = 1;
1445 		if (h < 1)
1446 			h = 1;
1447 
1448 		/* temporarily remove base dimensions */
1449 		w -= c->basew;
1450 		h -= c->baseh;
1451 
1452 		/* adjust for aspect limits */
1453 		if (c->minay > 0 && c->maxay > 0 && c->minax > 0 && c->maxax > 0) {
1454 			if (w * c->maxay > h * c->maxax)
1455 				w = h * c->maxax / c->maxay;
1456 			else if (w * c->minay < h * c->minax)
1457 				h = w * c->minay / c->minax;
1458 		}
1459 
1460 		/* adjust for increment value */
1461 		if (c->incw)
1462 			w -= w % c->incw;
1463 		if (c->inch)
1464 			h -= h % c->inch;
1465 
1466 		/* restore base dimensions */
1467 		w += c->basew;
1468 		h += c->baseh;
1469 
1470 		if (c->minw > 0 && w < c->minw)
1471 			w = c->minw;
1472 		if (c->minh > 0 && h - c->th < c->minh)
1473 			h = c->minh + c->th;
1474 		if (c->maxw > 0 && w > c->maxw)
1475 			w = c->maxw;
1476 		if (c->maxh > 0 && h - c->th > c->maxh)
1477 			h = c->maxh + c->th;
1478 		h += c->th;
1479 	}
1480 	if (w <= 0 || h <= 0)
1481 		return;
1482 	/* offscreen appearance fixes */
1483 	if (x > DisplayWidth(dpy, screen))
1484 		x = DisplayWidth(dpy, screen) - w - 2 * c->border;
1485 	if (y > DisplayHeight(dpy, screen))
1486 		y = DisplayHeight(dpy, screen) - h - 2 * c->border;
1487 	if (w != c->w && c->th) {
1488 		XMoveResizeWindow(dpy, c->title, 0, 0, w, c->th);
1489 		XFreePixmap(dpy, c->drawable);
1490 		c->drawable =
1491 			XCreatePixmap(dpy, root, w, c->th, DefaultDepth(dpy, screen));
1492 		drawclient(c);
1493 	}
1494 	if (c->x != x || c->y != y || c->w != w || c->h != h /* || sizehints */) {
1495 		c->x = x;
1496 		c->y = y;
1497 		c->w = w;
1498 		c->h = h;
1499 		DPRINTF("x = %d y = %d w = %d h = %d\n", c->x, c->y, c->w, c->h);
1500 		XMoveResizeWindow(dpy, c->frame, c->x, c->y, c->w, c->h);
1501 		XMoveResizeWindow(dpy, c->win, 0, c->th, c->w, c->h - c->th);
1502 		configure(c);
1503 		XSync(dpy, False);
1504 	}
1505 }
1506 
1507 void
restack(Monitor * m)1508 restack(Monitor * m) {
1509 	Client *c;
1510 	XEvent ev;
1511 	Window *wl;
1512 	int i, n;
1513 
1514 	if (!sel)
1515 		return;
1516 #if 0
1517 	if (MFEATURES(m, OVERLAP)) {
1518 		XRaiseWindow(dpy, stack->frame);
1519 		goto end;
1520 	}
1521 #endif
1522 	for (n = 0, c = stack; c; c = c->snext) {
1523 		if (isvisible(c, m) && !c->isicon) {
1524 			n++;
1525 		}
1526 	}
1527 	if (!n)
1528 		return;
1529 	wl = malloc(sizeof(Window) * n);
1530 	i = 0;
1531 	for (c = stack; c && i < n; c = c->snext)
1532 		if (isvisible(c, m) && !c->isicon && c->isbastard &&
1533 		    !checkatom(c->win, atom[WindowType], atom[WindowTypeDesk]))
1534 			wl[i++] = c->frame;
1535 	for (c = stack; c && i < n; c = c->snext)
1536 		if (isvisible(c, m) && !c->isicon) {
1537 			if (!c->isbastard && c->isfloating)
1538 				wl[i++] = c->frame;
1539 		}
1540 	for (c = stack; c && i < n; c = c->snext)
1541 		if (isvisible(c, m) && !c->isicon)
1542 			if (!c->isfloating && !c->isbastard)
1543 				wl[i++] = c->frame;
1544 	for (c = stack; c && i < n; c = c->snext)
1545 		if (isvisible(c, m) && !c->isicon && c->isbastard &&
1546 			checkatom(c->win, atom[WindowType], atom[WindowTypeDesk]))
1547 				wl[i++] = c->frame;
1548 	assert(i == n);
1549 	XRestackWindows(dpy, wl, n);
1550 	free(wl);
1551       end:
1552 	XSync(dpy, False);
1553 	while (XCheckMaskEvent(dpy, EnterWindowMask, &ev));
1554 }
1555 
1556 void
run(void)1557 run(void) {
1558 	fd_set rd;
1559 	int xfd;
1560 	XEvent ev;
1561 
1562 	/* main event loop */
1563 	XSync(dpy, False);
1564 	xfd = ConnectionNumber(dpy);
1565 	while (running) {
1566 		FD_ZERO(&rd);
1567 		FD_SET(xfd, &rd);
1568 		if (select(xfd + 1, &rd, NULL, NULL, NULL) == -1) {
1569 			if (errno == EINTR)
1570 				continue;
1571 			eprint("select failed\n");
1572 		}
1573 		while (XPending(dpy)) {
1574 			XNextEvent(dpy, &ev);
1575 			if (handler[ev.type])
1576 				(handler[ev.type]) (&ev);	/* call handler */
1577 		}
1578 	}
1579 }
1580 
1581 void
scan(void)1582 scan(void) {
1583 	unsigned int i, num;
1584 	Window *wins, d1, d2;
1585 	XWindowAttributes wa;
1586 
1587 	wins = NULL;
1588 	if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) {
1589 		for (i = 0; i < num; i++) {
1590 			if (!XGetWindowAttributes(dpy, wins[i], &wa) ||
1591 			    wa.override_redirect
1592 			    || XGetTransientForHint(dpy, wins[i], &d1))
1593 				continue;
1594 			if (wa.map_state == IsViewable
1595 			    || getstate(wins[i]) == IconicState
1596 			    || getstate(wins[i]) == NormalState)
1597 				manage(wins[i], &wa);
1598 		}
1599 		for (i = 0; i < num; i++) {	/* now the transients */
1600 			if (!XGetWindowAttributes(dpy, wins[i], &wa))
1601 				continue;
1602 			if (XGetTransientForHint(dpy, wins[i], &d1)
1603 			    && (wa.map_state == IsViewable
1604 				|| getstate(wins[i]) == IconicState
1605 				|| getstate(wins[i]) == NormalState))
1606 				manage(wins[i], &wa);
1607 		}
1608 	}
1609 	if (wins)
1610 		XFree(wins);
1611 }
1612 
1613 void
setclientstate(Client * c,long state)1614 setclientstate(Client * c, long state) {
1615 	long data[] = { state, None };
1616 	long winstate[2];
1617 
1618 	XChangeProperty(dpy, c->win, atom[WMState], atom[WMState], 32,
1619 	    PropModeReplace, (unsigned char *) data, 2);
1620 	if (state == NormalState) {
1621 		c->isicon = False;
1622 		XDeleteProperty(dpy, c->win, atom[WindowState]);
1623 	} else {
1624 		winstate[0] = atom[WindowStateHidden];
1625 		XChangeProperty(dpy, c->win, atom[WindowState], XA_ATOM, 32,
1626 		    PropModeReplace, (unsigned char *) winstate, 1);
1627 	}
1628 }
1629 
1630 void
setlayout(const char * arg)1631 setlayout(const char *arg) {
1632 	unsigned int i;
1633 	Client *c;
1634 	Bool wasfloat;
1635 
1636 	wasfloat = FEATURES(curlayout, OVERLAP);
1637 
1638 	if (arg) {
1639 		for (i = 0; i < LENGTH(layouts); i++)
1640 			if (*arg == layouts[i].symbol)
1641 				break;
1642 		if (i == LENGTH(layouts))
1643 			return;
1644 		views[curmontag].layout = &layouts[i];
1645 	}
1646 	if (sel) {
1647 		for (c = clients; c; c = c->next) {
1648 			if(isvisible(c, curmonitor())) {
1649 				if (wasfloat)
1650 					save(c);
1651 				if (wasfloat != FEATURES(curlayout, OVERLAP))
1652 					updateframe(c);
1653 			}
1654 		}
1655 		arrange(curmonitor());
1656 	}
1657 	updateatom[ELayout] (NULL);
1658 }
1659 
1660 void
setmwfact(const char * arg)1661 setmwfact(const char *arg) {
1662 	double delta;
1663 
1664 	if (!FEATURES(curlayout, MWFACT))
1665 		return;
1666 	/* arg handling, manipulate mwfact */
1667 	if (arg == NULL)
1668 		views[curmontag].mwfact = DEFMWFACT;
1669 	else if (sscanf(arg, "%lf", &delta) == 1) {
1670 		if (arg[0] == '+' || arg[0] == '-')
1671 			views[curmontag].mwfact += delta;
1672 		else
1673 			views[curmontag].mwfact = delta;
1674 		if (views[curmontag].mwfact < 0.1)
1675 			views[curmontag].mwfact = 0.1;
1676 		else if (views[curmontag].mwfact > 0.9)
1677 			views[curmontag].mwfact = 0.9;
1678 	}
1679 	arrange(curmonitor());
1680 }
1681 
1682 void
initlayouts()1683 initlayouts() {
1684 	unsigned int i, j;
1685 	char conf[32], ltname;
1686 	float mwfact;
1687 	int nmaster;
1688 	const char *deflayout;
1689 
1690 	/* init layouts */
1691 	mwfact = atof(getresource("mwfact", STR(DEFMWFACT)));
1692 	nmaster = atoi(getresource("nmaster", STR(DEFNMASTER)));
1693 	deflayout = getresource("deflayout", "i");
1694 	if (!nmaster)
1695 		nmaster = 1;
1696 	for (i = 0; i < ntags; i++) {
1697 		views[i].layout = &layouts[0];
1698 		snprintf(conf, sizeof(conf), "tags.layout%d", i);
1699 		strncpy(&ltname, getresource(conf, deflayout), 1);
1700 		for (j = 0; j < LENGTH(layouts); j++) {
1701 			if (layouts[j].symbol == ltname) {
1702 				views[i].layout = &layouts[j];
1703 				break;
1704 			}
1705 		}
1706 		views[i].mwfact = mwfact;
1707 		views[i].nmaster = nmaster;
1708 		views[i].barpos = StrutsOn;
1709 	}
1710 	updateatom[ELayout] (NULL);
1711 }
1712 
1713 void
initmonitors(XEvent * e)1714 initmonitors(XEvent * e) {
1715 	Monitor *m;
1716 #ifdef XRANDR
1717 	Monitor *t;
1718 	XRRCrtcInfo *ci;
1719 	XRRScreenResources *sr;
1720 	int c, n;
1721 	int ncrtc = 0;
1722 	int dummy1, dummy2, major, minor;
1723 
1724 	/* free */
1725 	if (monitors) {
1726 		m = monitors;
1727 		do {
1728 			t = m->next;
1729 			free(m->seltags);
1730 			free(m->prevtags);
1731 			free(m);
1732 			m = t;
1733 		} while (m);
1734 		monitors = NULL;
1735 	}
1736 	if(!running)
1737 	    return;
1738 	/* initial Xrandr setup */
1739 	if (XRRQueryExtension(dpy, &dummy1, &dummy2))
1740 		if (XRRQueryVersion(dpy, &major, &minor) && major < 1)
1741 			goto no_xrandr;
1742 
1743 	/* map virtual screens onto physical screens */
1744 	sr = XRRGetScreenResources(dpy, root);
1745 	if (sr == NULL)
1746 		goto no_xrandr;
1747 	else
1748 		ncrtc = sr->ncrtc;
1749 
1750 	for (c = 0, n = 0, ci = NULL; c < ncrtc; c++) {
1751 		ci = XRRGetCrtcInfo(dpy, sr, sr->crtcs[c]);
1752 		if (ci->noutput == 0)
1753 			continue;
1754 
1755 		if (ci != NULL && ci->mode == None)
1756 			fprintf(stderr, "???\n");
1757 		else {
1758 			/* If monitor is a mirror, we don't care about it */
1759 			if (n && ci->x == monitors->sx && ci->y == monitors->sy)
1760 				continue;
1761 			m = emallocz(sizeof(Monitor));
1762 			m->sx = m->wax = ci->x;
1763 			m->sy = m->way = ci->y;
1764 			m->sw = m->waw = ci->width;
1765 			m->sh = m->wah = ci->height;
1766 			m->curtag = n;
1767 			m->prevtags = emallocz(ntags * sizeof(Bool));
1768 			m->seltags = emallocz(ntags * sizeof(Bool));
1769 			m->seltags[n] = True;
1770 			m->next = monitors;
1771 			monitors = m;
1772 			n++;
1773 		}
1774 		XRRFreeCrtcInfo(ci);
1775 	}
1776 	XRRFreeScreenResources(sr);
1777 	updateatom[WorkArea](NULL);
1778 	return;
1779       no_xrandr:
1780 #endif
1781 	m = emallocz(sizeof(Monitor));
1782 	m->sx = m->wax = 0;
1783 	m->sy = m->way = 0;
1784 	m->sw = m->waw = DisplayWidth(dpy, screen);
1785 	m->sh = m->wah = DisplayHeight(dpy, screen);
1786 	m->curtag = 0;
1787 	m->prevtags = emallocz(ntags * sizeof(Bool));
1788 	m->seltags = emallocz(ntags * sizeof(Bool));
1789 	m->seltags[0] = True;
1790 	m->next = NULL;
1791 	monitors = m;
1792 	updateatom[WorkArea](NULL);;
1793 }
1794 
1795 void
inittags()1796 inittags() {
1797 	unsigned int i;
1798 	char tmp[25] = "\0";
1799 
1800 	ntags = atoi(getresource("tags.number", "5"));
1801 	views = emallocz(ntags * sizeof(View));
1802 	tags = emallocz(ntags * sizeof(char *));
1803 	for (i = 0; i < ntags; i++) {
1804 		tags[i] = emallocz(25);
1805 		snprintf(tmp, sizeof(tmp), "tags.name%d", i);
1806 		snprintf(tags[i], sizeof(tags[i]), "%s", getresource(tmp,
1807 		    "null"));
1808 	}
1809 }
1810 
1811 void
sighandler(int signum)1812 sighandler(int signum) {
1813 	if (signum == SIGHUP)
1814 		quit("HUP!");
1815 	else
1816 		quit(NULL);
1817 }
1818 
1819 void
setup(char * conf)1820 setup(char *conf) {
1821 	int d;
1822 	int i, j;
1823 	unsigned int mask;
1824 	Window w;
1825 	Monitor *m;
1826 	XModifierKeymap *modmap;
1827 	XSetWindowAttributes wa;
1828 	char oldcwd[256], path[256] = "/";
1829 	char *home, *slash;
1830 	/* configuration files to open (%s gets converted to $HOME) */
1831 	const char *confs[] = {
1832 		conf,
1833 		"%s/.echinus/echinusrc",
1834 		SYSCONFPATH "/echinusrc",
1835 		NULL
1836 	};
1837 
1838 	/* init cursors */
1839 	cursor[CurNormal] = XCreateFontCursor(dpy, XC_left_ptr);
1840 	cursor[CurResize] = XCreateFontCursor(dpy, XC_bottom_right_corner);
1841 	cursor[CurMove] = XCreateFontCursor(dpy, XC_fleur);
1842 
1843 	/* init modifier map */
1844 	modmap = XGetModifierMapping(dpy);
1845 	for (i = 0; i < 8; i++)
1846 		for (j = 0; j < modmap->max_keypermod; j++) {
1847 			if (modmap->modifiermap[i * modmap->max_keypermod + j]
1848 			    == XKeysymToKeycode(dpy, XK_Num_Lock))
1849 				numlockmask = (1 << i);
1850 		}
1851 	XFreeModifiermap(modmap);
1852 
1853 	/* select for events */
1854 	wa.event_mask = SubstructureRedirectMask | SubstructureNotifyMask
1855 	    | EnterWindowMask | LeaveWindowMask | StructureNotifyMask |
1856 	    ButtonPressMask | ButtonReleaseMask;
1857 	wa.cursor = cursor[CurNormal];
1858 	XChangeWindowAttributes(dpy, root, CWEventMask | CWCursor, &wa);
1859 	XSelectInput(dpy, root, wa.event_mask);
1860 
1861 	/* init resource database */
1862 	XrmInitialize();
1863 
1864 	home = getenv("HOME");
1865 	if (!home)
1866 		*home = '/';
1867 	if (!getcwd(oldcwd, sizeof(oldcwd)))
1868 		eprint("echinus: getcwd error: %s\n", strerror(errno));
1869 
1870 	for (i = 0; confs[i] != NULL; i++) {
1871 		if (*confs[i] == '\0')
1872 			continue;
1873 		snprintf(conf, 255, confs[i], home);
1874 		/* retrieve path to chdir(2) to it */
1875 		slash = strrchr(conf, '/');
1876 		if (slash)
1877 			snprintf(path, slash - conf + 1, "%s", conf);
1878 		chdir(path);
1879 		xrdb = XrmGetFileDatabase(conf);
1880 		/* configuration file loaded successfully; break out */
1881 		if (xrdb)
1882 			break;
1883 	}
1884 	fprintf(stderr, "echinus: no configuration file found, using defaults\n");
1885 
1886 	/* init EWMH atom */
1887 	initewmh();
1888 
1889 	/* init tags */
1890 	inittags();
1891 	/* init geometry */
1892 	initmonitors(NULL);
1893 
1894 	/* init modkey */
1895 	initrules();
1896 	initkeys();
1897 	initlayouts();
1898 	updateatom[NumberOfDesk] (NULL);
1899 	updateatom[DeskNames] (NULL);
1900 	updateatom[CurDesk] (NULL);
1901 
1902 	grabkeys();
1903 
1904 	/* init appearance */
1905 	initstyle();
1906 	strncpy(options.command, getresource("command", COMMAND), LENGTH(options.command));
1907 	options.command[LENGTH(options.command) - 1] = '\0';
1908 	options.dectiled = atoi(getresource("decoratetiled", STR(DECORATETILED)));
1909 	options.hidebastards = atoi(getresource("hidebastards", "0"));
1910 	options.focus = atoi(getresource("sloppy", "0"));
1911 	options.snap = atoi(getresource("snap", STR(SNAP)));
1912 
1913 	for (m = monitors; m; m = m->next) {
1914 		m->struts[RightStrut] = m->struts[LeftStrut] =
1915 		    m->struts[TopStrut] = m->struts[BotStrut] = 0;
1916 		updategeom(m);
1917 	}
1918 
1919 	chdir(oldcwd);
1920 
1921 	/* multihead support */
1922 	selscreen = XQueryPointer(dpy, root, &w, &w, &d, &d, &d, &d, &mask);
1923 }
1924 
1925 void
spawn(const char * arg)1926 spawn(const char *arg) {
1927 	static char shell[] = "/bin/sh";
1928 
1929 	if (!arg)
1930 		return;
1931 	/* The double-fork construct avoids zombie processes and keeps the code
1932 	 * clean from stupid signal handlers. */
1933 	if (fork() == 0) {
1934 		if (fork() == 0) {
1935 			if (dpy)
1936 				close(ConnectionNumber(dpy));
1937 			setsid();
1938 			execl(shell, shell, "-c", arg, (char *) NULL);
1939 			fprintf(stderr, "echinus: execl '%s -c %s'", shell, arg);
1940 			perror(" failed");
1941 		}
1942 		exit(0);
1943 	}
1944 	wait(0);
1945 }
1946 
1947 void
tag(const char * arg)1948 tag(const char *arg) {
1949 	unsigned int i;
1950 
1951 	if (!sel)
1952 		return;
1953 	for (i = 0; i < ntags; i++)
1954 		sel->tags[i] = (NULL == arg);
1955 	sel->tags[idxoftag(arg)] = True;
1956 	updateatom[WindowDesk] (sel);
1957 	updateframe(sel);
1958 	arrange(NULL);
1959 	focus(NULL);
1960 }
1961 
1962 void
bstack(Monitor * m)1963 bstack(Monitor * m) {
1964 	int i, n, nx, ny, nw, nh, mh, tw;
1965 	Client *c, *mc;
1966 
1967 	for (n = 0, c = nexttiled(clients, m); c; c = nexttiled(c->next, m))
1968 		n++;
1969 
1970 	mh = (n == 1) ? m->wah : views[m->curtag].mwfact * m->wah;
1971 	tw = (n > 1) ? m->waw / (n - 1) : 0;
1972 
1973 	nx = m->wax;
1974 	ny = m->way;
1975 	nh = 0;
1976 	for (i = 0, c = mc = nexttiled(clients, m); c; c = nexttiled(c->next, m), i++) {
1977 		c->ismax = False;
1978 		if (i == 0) {
1979 			nh = mh - 2 * c->border;
1980 			nw = m->waw - 2 * c->border;
1981 			nx = m->wax;
1982 		} else {
1983 			if (i == 1) {
1984 				nx = m->wax;
1985 				ny += mc->h + c->border;
1986 				nh = (m->way + m->wah) - ny - 2 * c->border;
1987 			}
1988 			if (i + 1 == n)
1989 				nw = (m->wax + m->waw) - nx - 2 * c->border;
1990 			else
1991 				nw = tw - c->border;
1992 		}
1993 		resize(c, nx, ny, nw, nh, False);
1994 		if (n > 1 && tw != curwaw)
1995 			nx = c->x + c->w + c->border;
1996 	}
1997 }
1998 
1999 void
tile(Monitor * m)2000 tile(Monitor * m) {
2001 	int nx, ny, nw, nh, mw, mh;
2002 	unsigned int i, n, th;
2003 	Client *c, *mc;
2004 
2005 	for (n = 0, c = nexttiled(clients, m); c; c = nexttiled(c->next, m))
2006 		n++;
2007 
2008 	/* window geoms */
2009 	mh = (n <= views[m->curtag].nmaster) ? m->wah / (n >
2010 	    0 ? n : 1) : m->wah / (views[m->curtag].nmaster ? views[m->curtag].nmaster : 1);
2011 	mw = (n <= views[m->curtag].nmaster) ? m->waw : views[m->curtag].mwfact * m->waw;
2012 	th = (n > views[m->curtag].nmaster) ? m->wah / (n - views[m->curtag].nmaster) : 0;
2013 	if (n > views[m->curtag].nmaster && th < style.titleheight)
2014 		th = m->wah;
2015 
2016 	nx = m->wax;
2017 	ny = m->way;
2018 	nw = 0;
2019 	for (i = 0, c = mc = nexttiled(clients, m); c; c = nexttiled(c->next, m), i++) {
2020 		c->ismax = False;
2021 		if (i < views[m->curtag].nmaster) {	/* master */
2022 			ny = m->way + i * (mh - c->border);
2023 			nw = mw - 2 * c->border;
2024 			nh = mh;
2025 			if (i + 1 == (n < views[m->curtag].nmaster ? n : views[m->curtag].nmaster))	/* remainder */
2026 				nh = m->way + m->wah - ny;
2027 			nh -= 2 * c->border;
2028 		} else {	/* tile window */
2029 			if (i == views[m->curtag].nmaster) {
2030 				ny = m->way;
2031 				nx += mc->w + mc->border;
2032 				nw = m->waw - nx - 2 * c->border + m->wax;
2033 			} else
2034 				ny -= c->border;
2035 			if (i + 1 == n)	/* remainder */
2036 				nh = (m->way + m->wah) - ny - 2 * c->border;
2037 			else
2038 				nh = th - 2 * c->border;
2039 		}
2040 		resize(c, nx, ny, nw, nh, False);
2041 		if (n > views[m->curtag].nmaster && th != (unsigned int)m->wah) {
2042 			ny = c->y + c->h + 2 * c->border;
2043 		}
2044 	}
2045 }
2046 
2047 void
togglestruts(const char * arg)2048 togglestruts(const char *arg) {
2049 	views[curmontag].barpos =
2050 	    (views[curmontag].barpos ==
2051 	    StrutsOn) ? (options.hidebastards ? StrutsHide : StrutsOff) : StrutsOn;
2052 	updategeom(curmonitor());
2053 	arrange(curmonitor());
2054 }
2055 
2056 void
togglefloating(const char * arg)2057 togglefloating(const char *arg) {
2058 	if (!sel)
2059 		return;
2060 
2061 	if (FEATURES(curlayout, OVERLAP))
2062 		return;
2063 
2064 	sel->isfloating = !sel->isfloating;
2065 	updateframe(sel);
2066 	if (sel->isfloating) {
2067 		/* restore last known float dimensions */
2068 		resize(sel, sel->rx, sel->ry, sel->rw, sel->rh, False);
2069 	} else {
2070 		/* save last known float dimensions */
2071 		save(sel);
2072 	}
2073 	arrange(curmonitor());
2074 }
2075 
2076 void
togglefill(const char * arg)2077 togglefill(const char *arg) {
2078 	XEvent ev;
2079 	Monitor *m = curmonitor();
2080 	Client *c;
2081 	int x1, x2, y1, y2, w, h;
2082 
2083 	x1 = m->wax;
2084 	x2 = m->sw;
2085 	y1 = m->way;
2086 	y2 = m->sh;
2087 
2088 	if (!sel || sel->isfixed)
2089 		return;
2090 	for(c = clients; c; c = c->next) {
2091 		if(isvisible(c, m) && (c != sel) && !c->isbastard
2092 			       	&& (c->isfloating || MFEATURES(m, OVERLAP))) {
2093 			if(c->y + c->h > sel->y && c->y < sel->y + sel->h) {
2094 				if(c->x < sel->x)
2095 					x1 = max(x1, c->x + c->w + style.border);
2096 				else
2097 					x2 = min(x2, c->x - style.border);
2098 			}
2099 			if(c->x + c->w > sel->x && c->x < sel->x + sel->w) {
2100 				if(c->y < sel->y)
2101 					y1 = max(y1, c->y + c->h + style.border);
2102 				else
2103 					y2 = max(y2, c->y - style.border);
2104 			}
2105 		}
2106 		DPRINTF("x1 = %d x2 = %d y1 = %d y2 = %d\n", x1, x2, y1, y2);
2107 	}
2108 	w = x2 - x1;
2109 	h = y2 - y1;
2110 	DPRINTF("x1 = %d w = %d y1 = %d h = %d\n", x1, w, y1, h);
2111 	if((w < sel->w) || (h < sel->h))
2112 		return;
2113 
2114 	if ((sel->isfill = !sel->isfill)) {
2115 		save(sel);
2116 		resize(sel, x1, y1, w, h, True);
2117 	} else {
2118 		resize(sel, sel->rx, sel->ry, sel->rw, sel->rh, True);
2119 	}
2120 	while (XCheckMaskEvent(dpy, EnterWindowMask, &ev));
2121 }
2122 
2123 void
togglemax(const char * arg)2124 togglemax(const char *arg) {
2125 	XEvent ev;
2126 	Monitor *m = curmonitor();
2127 
2128 	if (!sel || sel->isfixed)
2129 		return;
2130 	sel->ismax = !sel->ismax;
2131 	updateframe(sel);
2132 	if (sel->ismax) {
2133 		save(sel);
2134 		resize(sel, m->wax - sel->border,
2135 		    m->way - sel->border - sel->th, m->waw, m->wah + sel->th, False);
2136 	} else {
2137 		resize(sel, sel->rx, sel->ry, sel->rw, sel->rh, True);
2138 	}
2139 	while (XCheckMaskEvent(dpy, EnterWindowMask, &ev));
2140 }
2141 
2142 void
toggletag(const char * arg)2143 toggletag(const char *arg) {
2144 	unsigned int i, j;
2145 
2146 	if (!sel)
2147 		return;
2148 	i = idxoftag(arg);
2149 	sel->tags[i] = !sel->tags[i];
2150 	for (j = 0; j < ntags && !sel->tags[j]; j++);
2151 	if (j == ntags)
2152 		sel->tags[i] = True;	/* at least one tag must be enabled */
2153 	drawclient(sel);
2154 	arrange(NULL);
2155 }
2156 
2157 void
togglemonitor(const char * arg)2158 togglemonitor(const char *arg) {
2159 	Monitor *m, *cm;
2160 	int x, y;
2161 
2162 	getpointer(&x, &y);
2163 	for (cm = curmonitor(), m = monitors; m == cm && m && m->next; m = m->next);
2164 	if (!m)
2165 		return;
2166 	XWarpPointer(dpy, None, root, 0, 0, 0, 0, m->sx + x % m->sw, m->sy + y % m->sh);
2167 	focus(NULL);
2168 }
2169 
2170 void
toggleview(const char * arg)2171 toggleview(const char *arg) {
2172 	unsigned int i, j;
2173 	Monitor *m, *cm;
2174 
2175 	i = idxoftag(arg);
2176 	cm = curmonitor();
2177 
2178 	memcpy(cm->prevtags, cm->seltags, ntags * sizeof(cm->seltags[0]));
2179 	cm->seltags[i] = !cm->seltags[i];
2180 	for (m = monitors; m; m = m->next) {
2181 		if (m->seltags[i] && m != cm) {
2182 			memcpy(m->prevtags, m->seltags, ntags * sizeof(m->seltags[0]));
2183 			m->seltags[i] = False;
2184 			for (j = 0; j < ntags && !m->seltags[j]; j++);
2185 			if (j == ntags) {
2186 				m->seltags[i] = True;	/* at least one tag must be viewed */
2187 				cm->seltags[i] = False; /* can't toggle */
2188 				j = i;
2189 			}
2190 			if (m->curtag == i)
2191 				m->curtag = j;
2192 			arrange(m);
2193 		}
2194 	}
2195 	arrange(cm);
2196 	focus(NULL);
2197 	updateatom[CurDesk] (NULL);
2198 }
2199 
2200 void
focusview(const char * arg)2201 focusview(const char *arg) {
2202 	Client *c;
2203 	int i;
2204 
2205 	toggleview(arg);
2206 	i = idxoftag(arg);
2207 	if (!curseltags[i])
2208 		return;
2209 	for (c = stack; c; c = c->snext) {
2210 		if (c->tags[i] && !c->isbastard) {
2211 			focus(c);
2212 			break;
2213 		}
2214 	}
2215 	restack(curmonitor());
2216 }
2217 
2218 void
unban(Client * c)2219 unban(Client * c) {
2220 	if (!c->isbanned)
2221 		return;
2222 	XSelectInput(dpy, c->win, CLIENTMASK & ~(StructureNotifyMask | EnterWindowMask));
2223 	XSelectInput(dpy, c->frame, NoEventMask);
2224 	XMapWindow(dpy, c->win);
2225 	XMapWindow(dpy, c->frame);
2226 	XSelectInput(dpy, c->win, CLIENTMASK);
2227 	XSelectInput(dpy, c->frame, FRAMEMASK);
2228 	setclientstate(c, NormalState);
2229 	c->isbanned = False;
2230 }
2231 
2232 void
unmanage(Client * c)2233 unmanage(Client * c) {
2234 	Monitor *m;
2235 	XWindowChanges wc;
2236 	Bool doarrange, dostruts;
2237 	Window trans;
2238 
2239 	m = clientmonitor(c);
2240 	doarrange = !(c->isfloating || c->isfixed
2241 	    || XGetTransientForHint(dpy, c->win, &trans)) || c->isbastard;
2242 	dostruts = c->hasstruts;
2243 	/* The server grab construct avoids race conditions. */
2244 	XGrabServer(dpy);
2245 	XSelectInput(dpy, c->frame, NoEventMask);
2246 	XUnmapWindow(dpy, c->frame);
2247 	XSetErrorHandler(xerrordummy);
2248 	if (c->title) {
2249 		XftDrawDestroy(c->xftdraw);
2250 		XFreePixmap(dpy, c->drawable);
2251 		XDestroyWindow(dpy, c->title);
2252 		c->title = (Window) NULL;
2253 	}
2254 	XSelectInput(dpy, c->win, CLIENTMASK & ~(StructureNotifyMask | EnterWindowMask));
2255 	XUngrabButton(dpy, AnyButton, AnyModifier, c->win);
2256 	XReparentWindow(dpy, c->win, root, c->x, c->y);
2257 	XMoveWindow(dpy, c->win, c->x, c->y);
2258 	if (!running)
2259 		XMapWindow(dpy, c->win);
2260 	wc.border_width = c->oldborder;
2261 	XConfigureWindow(dpy, c->win, CWBorderWidth, &wc);	/* restore border */
2262 	detach(c);
2263 	detachstack(c);
2264 	if (sel == c)
2265 		focus(NULL);
2266 	setclientstate(c, WithdrawnState);
2267 	XDestroyWindow(dpy, c->frame);
2268 	/* c->tags points to monitor */
2269 	if (!c->isbastard)
2270 		free(c->tags);
2271 	free(c);
2272 	XSync(dpy, False);
2273 	XSetErrorHandler(xerror);
2274 	XUngrabServer(dpy);
2275 	if (dostruts) {
2276 		updatestruts(m);
2277 		updategeom(m);
2278 	}
2279 	if (doarrange)
2280 		arrange(m);
2281 	updateatom[ClientList] (NULL);
2282 }
2283 
2284 void
updategeom(Monitor * m)2285 updategeom(Monitor * m) {
2286 	m->wax = m->sx;
2287 	m->way = m->sy;
2288 	m->wah = m->sh;
2289 	m->waw = m->sw;
2290 	switch (views[m->curtag].barpos) {
2291 	default:
2292 		m->wax += m->struts[LeftStrut];
2293 		m->waw -= (m->struts[RightStrut] + m->struts[LeftStrut]);
2294 		m->way += m->struts[TopStrut];
2295 		m->wah = min(m->wah - m->struts[TopStrut],
2296 			(DisplayHeight(dpy, screen) - (m->struts[BotStrut] + m->struts[TopStrut])));
2297 		break;
2298 	case StrutsHide:
2299 	case StrutsOff:
2300 		break;
2301 	}
2302 }
2303 
2304 void
updatestruts(Monitor * m)2305 updatestruts(Monitor *m) {
2306 	Client *c;
2307 
2308 	m->struts[RightStrut] = m->struts[LeftStrut] = m->struts[TopStrut] =
2309 		m->struts[BotStrut] = 0;
2310 	for(c = clients; c; c = c->next)
2311 		if (c->hasstruts)
2312 			getstruts(c);
2313 }
2314 
2315 void
unmapnotify(XEvent * e)2316 unmapnotify(XEvent * e) {
2317 	Client *c;
2318 	XUnmapEvent *ev = &e->xunmap;
2319 
2320 	if ((c = getclient(ev->window, clients, ClientWindow)) /* && ev->send_event */) {
2321 		if (c->ignoreunmap--)
2322 			return;
2323 		DPRINTF("killing self-unmapped window (%s)\n", c->name);
2324 		unmanage(c);
2325 	}
2326 }
2327 
2328 void
updateframe(Client * c)2329 updateframe(Client * c) {
2330 	int i, f = 0;
2331 
2332 	if (!c->title)
2333 		return;
2334 
2335 	for (i = 0; i < ntags; i++) {
2336 		if (c->tags[i])
2337 			f += FEATURES(views[i].layout, OVERLAP);
2338 	}
2339 	c->th = !c->ismax && (c->isfloating || options.dectiled || f) ?
2340 				style.titleheight : 0;
2341 	if (!c->th)
2342 		XUnmapWindow(dpy, c->title);
2343 	else
2344 		XMapRaised(dpy, c->title);
2345 }
2346 
2347 void
updatesizehints(Client * c)2348 updatesizehints(Client * c) {
2349 	long msize;
2350 	XSizeHints size;
2351 
2352 	if (!XGetWMNormalHints(dpy, c->win, &size, &msize) || !size.flags)
2353 		size.flags = PSize;
2354 	c->flags = size.flags;
2355 	if (c->flags & PBaseSize) {
2356 		c->basew = size.base_width;
2357 		c->baseh = size.base_height;
2358 	} else if (c->flags & PMinSize) {
2359 		c->basew = size.min_width;
2360 		c->baseh = size.min_height;
2361 	} else
2362 		c->basew = c->baseh = 0;
2363 	if (c->flags & PResizeInc) {
2364 		c->incw = size.width_inc;
2365 		c->inch = size.height_inc;
2366 	} else
2367 		c->incw = c->inch = 0;
2368 	if (c->flags & PMaxSize) {
2369 		c->maxw = size.max_width;
2370 		c->maxh = size.max_height;
2371 	} else
2372 		c->maxw = c->maxh = 0;
2373 	if (c->flags & PMinSize) {
2374 		c->minw = size.min_width;
2375 		c->minh = size.min_height;
2376 	} else if (c->flags & PBaseSize) {
2377 		c->minw = size.base_width;
2378 		c->minh = size.base_height;
2379 	} else
2380 		c->minw = c->minh = 0;
2381 	if (c->flags & PAspect) {
2382 		c->minax = size.min_aspect.x;
2383 		c->maxax = size.max_aspect.x;
2384 		c->minay = size.min_aspect.y;
2385 		c->maxay = size.max_aspect.y;
2386 	} else
2387 		c->minax = c->maxax = c->minay = c->maxay = 0;
2388 	c->isfixed = (c->maxw && c->minw && c->maxh && c->minh
2389 	    && c->maxw == c->minw && c->maxh == c->minh);
2390 }
2391 
2392 void
updatetitle(Client * c)2393 updatetitle(Client * c) {
2394 	if (!gettextprop(c->win, atom[WindowName], c->name, sizeof(c->name)))
2395 		gettextprop(c->win, atom[WMName], c->name, sizeof(c->name));
2396 }
2397 
2398 /* There's no way to check accesses to destroyed windows, thus those cases are
2399  * ignored (ebastardly on UnmapNotify's).  Other types of errors call Xlibs
2400  * default error handler, which may call exit.	*/
2401 int
xerror(Display * dsply,XErrorEvent * ee)2402 xerror(Display * dsply, XErrorEvent * ee) {
2403 	if (ee->error_code == BadWindow
2404 	    || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch)
2405 	    || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable)
2406 	    || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable)
2407 	    || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable)
2408 	    || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch)
2409 	    || (ee->request_code == X_GrabKey && ee->error_code == BadAccess)
2410 	    || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable))
2411 		return 0;
2412 	fprintf(stderr,
2413 	    "echinus: fatal error: request code=%d, error code=%d\n",
2414 	    ee->request_code, ee->error_code);
2415 	return xerrorxlib(dsply, ee);	/* may call exit */
2416 }
2417 
2418 int
xerrordummy(Display * dsply,XErrorEvent * ee)2419 xerrordummy(Display * dsply, XErrorEvent * ee) {
2420 	return 0;
2421 }
2422 
2423 /* Startup Error handler to check if another window manager
2424  * is already running. */
2425 int
xerrorstart(Display * dsply,XErrorEvent * ee)2426 xerrorstart(Display * dsply, XErrorEvent * ee) {
2427 	otherwm = True;
2428 	return -1;
2429 }
2430 
2431 void
view(const char * arg)2432 view(const char *arg) {
2433 	int i, j;
2434 	Monitor *m, *cm;
2435 	int prevtag;
2436 
2437 	i = idxoftag(arg);
2438 	cm = curmonitor();
2439 
2440 	if (cm->seltags[i])
2441 		return;
2442 
2443 	memcpy(cm->prevtags, cm->seltags, ntags * sizeof(cm->seltags[0]));
2444 
2445 	for (j = 0; j < ntags; j++)
2446 		cm->seltags[j] = (arg == NULL);
2447 	cm->seltags[i] = True;
2448 	prevtag = cm->curtag;
2449 	cm->curtag = i;
2450 	for (m = monitors; m; m = m->next) {
2451 		if (m->seltags[i] && m != cm) {
2452 			m->curtag = prevtag;
2453 			memcpy(m->prevtags, m->seltags, ntags * sizeof(m->seltags[0]));
2454 			memcpy(m->seltags, cm->prevtags, ntags * sizeof(cm->seltags[0]));
2455 			updategeom(m);
2456 			arrange(m);
2457 		}
2458 	}
2459 	updategeom(cm);
2460 	arrange(cm);
2461 	focus(NULL);
2462 	updateatom[CurDesk] (NULL);
2463 }
2464 
2465 void
viewprevtag(const char * arg)2466 viewprevtag(const char *arg) {
2467 	Bool tmptags[ntags];
2468 	unsigned int i = 0;
2469 	int prevcurtag;
2470 
2471 	while (i < ntags - 1 && !curprevtags[i])
2472 		i++;
2473 	prevcurtag = curmontag;
2474 	curmontag = i;
2475 
2476 	memcpy(tmptags, curseltags, ntags * sizeof(curseltags[0]));
2477 	memcpy(curseltags, curprevtags, ntags * sizeof(curseltags[0]));
2478 	memcpy(curprevtags, tmptags, ntags * sizeof(curseltags[0]));
2479 	if (views[prevcurtag].barpos != views[curmontag].barpos)
2480 		updategeom(curmonitor());
2481 	arrange(NULL);
2482 	focus(NULL);
2483 	updateatom[CurDesk] (NULL);
2484 }
2485 
2486 void
viewlefttag(const char * arg)2487 viewlefttag(const char *arg) {
2488 	unsigned int i;
2489 
2490 	for (i = 0; i < ntags; i++) {
2491 		if (i && curseltags[i]) {
2492 			view(tags[i - 1]);
2493 			break;
2494 		}
2495 	}
2496 }
2497 
2498 void
viewrighttag(const char * arg)2499 viewrighttag(const char *arg) {
2500 	unsigned int i;
2501 
2502 	for (i = 0; i < ntags - 1; i++) {
2503 		if (curseltags[i]) {
2504 			view(tags[i + 1]);
2505 			break;
2506 		}
2507 	}
2508 }
2509 
2510 void
zoom(const char * arg)2511 zoom(const char *arg) {
2512 	Client *c;
2513 
2514 	if (!sel || !FEATURES(curlayout, ZOOM) || sel->isfloating)
2515 		return;
2516 	if ((c = sel) == nexttiled(clients, curmonitor()))
2517 		if (!(c = nexttiled(c->next, curmonitor())))
2518 			return;
2519 	detach(c);
2520 	attach(c);
2521 	arrange(curmonitor());
2522 	focus(c);
2523 }
2524 
2525 int
main(int argc,char * argv[])2526 main(int argc, char *argv[]) {
2527 	char conf[256] = "\0";
2528 
2529 	if (argc == 3 && !strcmp("-f", argv[1]))
2530 		snprintf(conf, sizeof(conf), "%s", argv[2]);
2531 	else if (argc == 2 && !strcmp("-v", argv[1]))
2532 		eprint("echinus-" VERSION " (c) 2011 Alexander Polakov\n");
2533 	else if (argc != 1)
2534 		eprint("usage: echinus [-v] [-f conf]\n");
2535 
2536 	setlocale(LC_CTYPE, "");
2537 	if (!(dpy = XOpenDisplay(0)))
2538 		eprint("echinus: cannot open display\n");
2539 	signal(SIGHUP, sighandler);
2540 	signal(SIGINT, sighandler);
2541 	signal(SIGQUIT, sighandler);
2542 	cargv = argv;
2543 	screen = DefaultScreen(dpy);
2544 	root = RootWindow(dpy, screen);
2545 
2546 	checkotherwm();
2547 	setup(conf);
2548 	scan();
2549 	run();
2550 	cleanup();
2551 
2552 	XCloseDisplay(dpy);
2553 	return 0;
2554 }
2555