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(<name, 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