JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
center floaters
[spectrwm.git] / scrotwm.c
1 /* $scrotwm$ */
2 /*
3  * Copyright (c) 2009 Marco Peereboom <marco@peereboom.us>
4  * Copyright (c) 2009 Ryan McBride <mcbride@countersiege.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 /*
19  * Much code and ideas taken from dwm under the following license:
20  * MIT/X Consortium License
21  * 
22  * 2006-2008 Anselm R Garbe <garbeam at gmail dot com>
23  * 2006-2007 Sander van Dijk <a dot h dot vandijk at gmail dot com>
24  * 2006-2007 Jukka Salmi <jukka at salmi dot ch>
25  * 2007 Premysl Hruby <dfenze at gmail dot com>
26  * 2007 Szabolcs Nagy <nszabolcs at gmail dot com>
27  * 2007 Christof Musik <christof at sendfax dot de>
28  * 2007-2008 Enno Gottox Boland <gottox at s01 dot de>
29  * 2007-2008 Peter Hartlich <sgkkr at hartlich dot com>
30  * 2008 Martin Hurton <martin dot hurton at gmail dot com>
31  * 
32  * Permission is hereby granted, free of charge, to any person obtaining a
33  * copy of this software and associated documentation files (the "Software"),
34  * to deal in the Software without restriction, including without limitation
35  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
36  * and/or sell copies of the Software, and to permit persons to whom the
37  * Software is furnished to do so, subject to the following conditions:
38  * 
39  * The above copyright notice and this permission notice shall be included in
40  * all copies or substantial portions of the Software.
41  * 
42  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
43  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
44  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
45  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
46  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
47  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
48  * DEALINGS IN THE SOFTWARE.
49  */
50
51 #define SWM_VERSION     "0.5"
52
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <err.h>
56 #include <locale.h>
57 #include <unistd.h>
58 #include <time.h>
59 #include <signal.h>
60 #include <string.h>
61 #include <util.h>
62 #include <pwd.h>
63
64 #include <sys/types.h>
65 #include <sys/stat.h>
66 #include <sys/wait.h>
67 #include <sys/queue.h>
68 #include <sys/param.h>
69
70 #include <X11/cursorfont.h>
71 #include <X11/keysym.h>
72 #include <X11/Xatom.h>
73 #include <X11/Xlib.h>
74 #include <X11/Xproto.h>
75 #include <X11/Xutil.h>
76
77 /* #define SWM_DEBUG */
78 #ifdef SWM_DEBUG
79 #define DPRINTF(x...)           do { if (swm_debug) fprintf(stderr, x); } while(0)
80 #define DNPRINTF(n,x...)        do { if (swm_debug & n) fprintf(stderr, x); } while(0)
81 #define SWM_D_EVENT             0x0001
82 #define SWM_D_WS                0x0002
83 #define SWM_D_FOCUS             0x0004
84 #define SWM_D_MISC              0x0008
85
86 uint32_t                swm_debug = 0
87                             | SWM_D_EVENT
88                             | SWM_D_WS
89                             | SWM_D_FOCUS
90                             | SWM_D_MISC
91                             ;
92 #else
93 #define DPRINTF(x...)
94 #define DNPRINTF(n,x...)
95 #endif
96
97 #define LENGTH(x)               (sizeof x / sizeof x[0])
98 #define MODKEY                  Mod1Mask
99 #define CLEANMASK(mask)         (mask & ~(numlockmask | LockMask))
100
101 int                     (*xerrorxlib)(Display *, XErrorEvent *);
102 int                     other_wm;
103 int                     screen;
104 int                     width, height;
105 int                     running = 1;
106 int                     ignore_enter = 0;
107 unsigned int            numlockmask = 0;
108 unsigned long           color_focus = 0xff0000; /* XXX should this be per ws? */
109 unsigned long           color_unfocus = 0x888888;
110 Display                 *display;
111 Window                  root;
112
113 /* status bar */
114 int                     bar_enabled = 1;
115 int                     bar_height = 0;
116 unsigned long           bar_border = 0x008080;
117 unsigned long           bar_color = 0x000000;
118 unsigned long           bar_font_color = 0xa0a0a0;
119 Window                  bar_window;
120 GC                      bar_gc;
121 XGCValues               bar_gcv;
122 XFontStruct             *bar_fs;
123 char                    bar_text[128];
124 char                    *bar_fonts[] = {
125                             "-*-terminus-*-*-*-*-*-*-*-*-*-*-*-*",
126                             "-*-times-medium-r-*-*-*-*-*-*-*-*-*-*",
127                             NULL
128 };
129
130 /* terminal + args */
131 char                            *spawn_term[] = { "xterm", NULL };
132 char                            *spawn_menu[] = { "dmenu_run", NULL };
133
134 struct ws_win {
135         TAILQ_ENTRY(ws_win)     entry;
136         Window                  id;
137         int                     x;
138         int                     y;
139         int                     width;
140         int                     height;
141         int                     floating;
142         int                     transient;
143         XWindowAttributes       wa;
144 };
145
146 TAILQ_HEAD(ws_win_list, ws_win);
147
148 /* define work spaces */
149 #define SWM_WS_MAX              (10)
150 struct workspace {
151         int                     visible;        /* workspace visible */
152         int                     restack;        /* restack on switch */
153         struct ws_win           *focus;         /* which win has focus */
154         struct ws_win_list      winlist;        /* list of windows in ws */
155 } ws[SWM_WS_MAX];
156 int                     current_ws = 0;
157
158 /* args to functions */
159 union arg {
160         int                     id;
161 #define SWM_ARG_ID_FOCUSNEXT    (0)
162 #define SWM_ARG_ID_FOCUSPREV    (1)
163 #define SWM_ARG_ID_FOCUSMAIN    (2)
164         char                    **argv;
165 };
166
167
168 void    stack(void);
169
170 #define SWM_CONF_WS     "\n= \t"
171 #define SWM_CONF_FILE   "scrotwm.conf"
172 int
173 conf_load(char *filename)
174 {
175         FILE                    *config;
176         char                    *line, *cp, *var, *val;
177         size_t                   len, lineno = 0;
178
179         DNPRINTF(SWM_D_MISC, "conf_load: filename %s\n", filename);
180
181         if (filename == NULL)
182                 return (1);
183
184         if ((config = fopen(filename, "r")) == NULL)
185                 return (1);
186
187         for (;;) {
188                 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
189                         if (feof(config))
190                                 break;
191                 cp = line;
192                 cp += (long)strspn(cp, SWM_CONF_WS);
193                 if (cp[0] == '\0') {
194                         /* empty line */
195                         free(line);
196                         continue;
197                 }
198                 if ((var = strsep(&cp, SWM_CONF_WS)) == NULL || cp == NULL)
199                         break;
200                 cp += (long)strspn(cp, SWM_CONF_WS);
201                 if ((val = strsep(&cp, SWM_CONF_WS)) == NULL)
202                         break;
203
204                 DNPRINTF(SWM_D_MISC, "conf_load: %s=%s\n",var ,val);
205                 switch (var[0]) {
206                 case 'b':
207                         if (!strncmp(var, "bar_enabled", strlen("bar_enabled")))
208                                 bar_enabled = atoi(val);
209                         else if (!strncmp(var, "bar_border",
210                             strlen("bar_border")))
211                                 bar_border = strtol(val, NULL, 16);
212                         else if (!strncmp(var, "bar_color",
213                             strlen("bar_color")))
214                                 bar_color = strtol(val, NULL, 16);
215                         else if (!strncmp(var, "bar_font_color",
216                             strlen("bar_font_color")))
217                                 bar_font_color = strtol(val, NULL, 16);
218                         else if (!strncmp(var, "bar_font", strlen("bar_font")))
219                                 asprintf(&bar_fonts[0], "%s", val);
220                         else
221                                 goto bad;
222                         break;
223
224                 case 'c':
225                         if (!strncmp(var, "color_focus", strlen("color_focus")))
226                                 color_focus = strtol(val, NULL, 16);
227                         else if (!strncmp(var, "color_unfocus",
228                             strlen("color_unfocus")))
229                                 color_unfocus = strtol(val, NULL, 16);
230                         else
231                                 goto bad;
232                         break;
233
234                 case 's':
235                         if (!strncmp(var, "spawn_term", strlen("spawn_term")))
236                                 asprintf(&spawn_term[0], "%s", val); /* XXX args? */
237                         break;
238                 default:
239                         goto bad;
240                 }
241                 free(line);
242         }
243
244         fclose(config);
245         return (0);
246 bad:
247         errx(1, "invalid conf file entry: %s=%s", var, val);
248 }
249
250 void
251 bar_print(void)
252 {
253         time_t                  tmt;
254         struct tm               tm;
255
256         if (bar_enabled == 0)
257                 return;
258
259         /* clear old text */
260         XSetForeground(display, bar_gc, bar_color);
261         XDrawString(display, bar_window, bar_gc, 4, bar_fs->ascent, bar_text,
262             strlen(bar_text));
263
264         /* draw new text */
265         time(&tmt);
266         localtime_r(&tmt, &tm);
267         strftime(bar_text, sizeof bar_text, "%a %b %d %R %Z %Y", &tm);
268         XSetForeground(display, bar_gc, bar_font_color);
269         XDrawString(display, bar_window, bar_gc, 4, bar_fs->ascent, bar_text,
270             strlen(bar_text));
271         XSync(display, False);
272
273         alarm(60);
274 }
275
276 void
277 bar_signal(int sig)
278 {
279         /* XXX yeah yeah byte me */
280         bar_print();
281 }
282
283 void
284 bar_toggle(union arg *args)
285 {
286         int i;
287
288         DNPRINTF(SWM_D_MISC, "bar_toggle\n");
289
290         if (bar_enabled) {
291                 bar_enabled = 0;
292                 height += bar_height; /* correct screen height */
293                 XUnmapWindow(display, bar_window);
294         } else {
295                 bar_enabled = 1;
296                 height -= bar_height; /* correct screen height */
297                 XMapWindow(display, bar_window);
298         }
299         XSync(display, False);
300         for (i = 0; i < SWM_WS_MAX; i++)
301                 ws[i].restack = 1;
302
303         stack();
304         bar_print(); /* must be after stack */
305 }
306
307 void
308 bar_setup(void)
309 {
310         int                     i;
311
312         for (i = 0; bar_fonts[i] != NULL; i++) {
313                 bar_fs = XLoadQueryFont(display, bar_fonts[i]);
314                 if (bar_fs)
315                         break;
316         }
317         if (bar_fonts[i] == NULL)
318                         errx(1, "couldn't load font");
319         bar_height = bar_fs->ascent + bar_fs->descent + 3;
320
321         bar_window = XCreateSimpleWindow(display, root, 0, 0, width,
322             bar_height - 2, 1, bar_border, bar_color);
323         bar_gc = XCreateGC(display, bar_window, 0, &bar_gcv);
324         XSetFont(display, bar_gc, bar_fs->fid);
325         XSelectInput(display, bar_window, VisibilityChangeMask);
326         if (bar_enabled) {
327                 height -= bar_height; /* correct screen height */
328                 XMapWindow(display, bar_window);
329         }
330         DNPRINTF(SWM_D_MISC, "bar_setup: bar_window %d\n", (int)bar_window);
331
332         if (signal(SIGALRM, bar_signal) == SIG_ERR)
333                 err(1, "could not install bar_signal");
334         bar_print();
335 }
336
337 int
338 count_win(int wsid, int count_transient)
339 {
340         struct ws_win           *win;
341         int                     count = 0;
342
343         TAILQ_FOREACH (win, &ws[wsid].winlist, entry) {
344                 if (count_transient == 0 && win->transient)
345                         continue;
346                 count++;
347         }
348         DNPRINTF(SWM_D_MISC, "count_win: %d\n", count);
349
350         return (count);
351 }
352 void
353 quit(union arg *args)
354 {
355         DNPRINTF(SWM_D_MISC, "quit\n");
356         running = 0;
357 }
358
359 void
360 spawn(union arg *args)
361 {
362         DNPRINTF(SWM_D_MISC, "spawn: %s\n", args->argv[0]);
363         /*
364          * The double-fork construct avoids zombie processes and keeps the code
365          * clean from stupid signal handlers.
366          */
367         if(fork() == 0) {
368                 if(fork() == 0) {
369                         if(display)
370                                 close(ConnectionNumber(display));
371                         setsid();
372                         execvp(args->argv[0], args->argv);
373                         fprintf(stderr, "execvp failed\n");
374                         perror(" failed");
375                 }
376                 exit(0);
377         }
378         wait(0);
379 }
380
381 void
382 focus_win(struct ws_win *win)
383 {
384         DNPRINTF(SWM_D_FOCUS, "focus_win: id: %lu\n", win->id);
385         XSetWindowBorder(display, win->id, color_focus);
386         XSetInputFocus(display, win->id, RevertToPointerRoot, CurrentTime);
387         ws[current_ws].focus = win;
388 }
389
390 void
391 unfocus_win(struct ws_win *win)
392 {
393         DNPRINTF(SWM_D_FOCUS, "unfocus_win: id: %lu\n", win->id);
394         XSetWindowBorder(display, win->id, color_unfocus);
395         if (ws[current_ws].focus == win)
396                 ws[current_ws].focus = NULL;
397 }
398
399 void
400 switchws(union arg *args)
401 {
402         int                     wsid = args->id;
403         struct ws_win           *win;
404
405         DNPRINTF(SWM_D_WS, "switchws: %d\n", wsid + 1);
406
407         if (wsid == current_ws)
408                 return;
409
410         /* map new window first to prevent ugly blinking */
411         TAILQ_FOREACH (win, &ws[wsid].winlist, entry)
412                 XMapRaised(display, win->id);
413         ws[wsid].visible = 1;
414
415         TAILQ_FOREACH (win, &ws[current_ws].winlist, entry)
416                 XUnmapWindow(display, win->id);
417         ws[current_ws].visible = 0;
418
419         current_ws = wsid;
420
421         ignore_enter = 1;
422         if (ws[wsid].restack) {
423                 stack();
424                 bar_print();
425         } else {
426                 if (ws[wsid].focus != NULL)
427                         focus_win(ws[wsid].focus);
428                 XSync(display, False);
429         }
430 }
431
432 void
433 focus(union arg *args)
434 {
435         struct ws_win           *winfocus, *winlostfocus;
436
437         DNPRINTF(SWM_D_FOCUS, "focus: id %d\n", args->id);
438         if (ws[current_ws].focus == NULL || count_win(current_ws, 1) == 0)
439                 return;
440
441         winlostfocus = ws[current_ws].focus;
442
443         switch (args->id) {
444         case SWM_ARG_ID_FOCUSPREV:
445                 ws[current_ws].focus =
446                     TAILQ_PREV(ws[current_ws].focus, ws_win_list, entry);
447                 if (ws[current_ws].focus == NULL)
448                         ws[current_ws].focus =
449                             TAILQ_LAST(&ws[current_ws].winlist, ws_win_list);
450                 break;
451
452         case SWM_ARG_ID_FOCUSNEXT:
453                 ws[current_ws].focus = TAILQ_NEXT(ws[current_ws].focus, entry);
454                 if (ws[current_ws].focus == NULL)
455                         ws[current_ws].focus =
456                             TAILQ_FIRST(&ws[current_ws].winlist);
457                 break;
458
459         case SWM_ARG_ID_FOCUSMAIN:
460                 ws[current_ws].focus = TAILQ_FIRST(&ws[current_ws].winlist);
461                 break;
462
463         default:
464                 return;
465         }
466
467         winfocus = ws[current_ws].focus;
468         unfocus_win(winlostfocus);
469         focus_win(winfocus);
470         XSync(display, False);
471 }
472
473 /* I know this sucks but it works well enough */
474 void
475 stack(void)
476 {
477         XWindowChanges          wc;
478         struct ws_win           wf, *win, *winfocus = &wf;
479         int                     i, h, w, x, y, hrh, winno;
480         int floater = 0;
481         unsigned int mask;
482
483         DNPRINTF(SWM_D_EVENT, "stack: workspace: %d\n", current_ws);
484
485         winfocus->id = root;
486
487         ws[current_ws].restack = 0;
488
489         winno = count_win(current_ws, 0);
490         if (winno == 0)
491                 return;
492
493         if (winno > 1)
494                 w = width / 2;
495         else
496                 w = width;
497
498         if (winno > 2)
499                 hrh = height / (winno - 1);
500         else
501                 hrh = 0;
502
503         x = 0;
504         y = bar_enabled ? bar_height : 0;
505         h = height;
506         i = 0;
507         TAILQ_FOREACH (win, &ws[current_ws].winlist, entry) {
508                 if (i == 1) {
509                         x += w + 2;
510                         w -= 2;
511                 }
512                 if (i != 0 && hrh != 0) {
513                         /* correct the last window for lost pixels */
514                         if (win == TAILQ_LAST(&ws[current_ws].winlist,
515                             ws_win_list)) {
516                                 h = height - (i * hrh);
517                                 if (h == 0)
518                                         h = hrh;
519                                 else
520                                         h += hrh;
521                                 y += hrh;
522                         } else {
523                                 h = hrh - 2;
524                                 /* leave first right hand window at y = 0 */
525                                 if (i > 1)
526                                         y += h + 2;
527                         }
528                 }
529
530                 if (win->transient != 0 || win->floating != 0)
531                         floater = 1;
532                 else
533                         floater = 0;
534
535                 bzero(&wc, sizeof wc);
536                 wc.border_width = 1;
537                 if (floater == 0) {
538                         win->x = wc.x = x;
539                         win->y = wc.y = y;
540                         win->width = wc.width = w;
541                         win->height = wc.height = h;
542                         mask = CWX | CWY | CWWidth | CWHeight | CWBorderWidth;
543                 } else {
544                         /* make sure we don't clobber the screen */
545                         if (win->wa.width > width)
546                                 win->wa.width = width;
547                         if (win->wa.height > height)
548                                 win->wa.width = height;
549                         win->x = wc.x = (width - win->wa.width) / 2;
550                         win->y = wc.y = (height - win->wa.height) / 2;
551                         mask = CWX | CWY | CWBorderWidth;
552                 }
553                 XConfigureWindow(display, win->id, mask, &wc);
554
555                 if (win == ws[current_ws].focus)
556                         winfocus = win;
557                 else
558                         unfocus_win(win);
559                 XMapRaised(display, win->id);
560                 i++;
561         }
562
563         focus_win(winfocus); /* this has to be done outside of the loop */
564         XSync(display, False);
565 }
566
567 void
568 swap_to_main(union arg *args)
569 {
570         struct ws_win           *tmpwin = TAILQ_FIRST(&ws[current_ws].winlist);
571
572         DNPRINTF(SWM_D_MISC, "swap_to_main: win: %lu\n",
573             ws[current_ws].focus ? ws[current_ws].focus->id : 0);
574
575         if (ws[current_ws].focus == NULL || ws[current_ws].focus == tmpwin)
576                 return;
577
578         TAILQ_REMOVE(&ws[current_ws].winlist, tmpwin, entry);
579         TAILQ_INSERT_AFTER(&ws[current_ws].winlist, ws[current_ws].focus,
580             tmpwin, entry);
581         TAILQ_REMOVE(&ws[current_ws].winlist, ws[current_ws].focus, entry);
582         TAILQ_INSERT_HEAD(&ws[current_ws].winlist, ws[current_ws].focus, entry);
583         ws[current_ws].focus = TAILQ_FIRST(&ws[current_ws].winlist);
584         ignore_enter = 2;
585         stack();
586 }
587
588 void
589 send_to_ws(union arg *args)
590 {
591         int                     wsid = args->id;
592         struct ws_win           *win = ws[current_ws].focus;
593
594         DNPRINTF(SWM_D_WS, "send_to_ws: win: %lu\n", win->id);
595
596         XUnmapWindow(display, win->id);
597
598         /* find a window to focus */
599         ws[current_ws].focus = TAILQ_PREV(win, ws_win_list,entry);
600         if (ws[current_ws].focus == NULL)
601                 ws[current_ws].focus = TAILQ_FIRST(&ws[current_ws].winlist);
602
603         TAILQ_REMOVE(&ws[current_ws].winlist, win, entry);
604
605         TAILQ_INSERT_TAIL(&ws[wsid].winlist, win, entry);
606         if (count_win(wsid, 1) == 0)
607                 ws[wsid].focus = win;
608         ws[wsid].restack = 1;
609
610         stack();
611 }
612
613 /* key definitions */
614 struct key {
615         unsigned int            mod;
616         KeySym                  keysym;
617         void                    (*func)(union arg *);
618         union arg               args;
619 } keys[] = {
620         /* modifier             key     function                argument */
621         { MODKEY,               XK_Return,      swap_to_main,   {0} },
622         { MODKEY | ShiftMask,   XK_Return,      spawn,          {.argv = spawn_term} },
623         { MODKEY,               XK_p,           spawn,          {.argv = spawn_menu} },
624         { MODKEY | ShiftMask,   XK_q,           quit,           {0} },
625         { MODKEY,               XK_m,           focus,          {.id = SWM_ARG_ID_FOCUSMAIN} },
626         { MODKEY,               XK_1,           switchws,       {.id = 0} },
627         { MODKEY,               XK_2,           switchws,       {.id = 1} },
628         { MODKEY,               XK_3,           switchws,       {.id = 2} },
629         { MODKEY,               XK_4,           switchws,       {.id = 3} },
630         { MODKEY,               XK_5,           switchws,       {.id = 4} },
631         { MODKEY,               XK_6,           switchws,       {.id = 5} },
632         { MODKEY,               XK_7,           switchws,       {.id = 6} },
633         { MODKEY,               XK_8,           switchws,       {.id = 7} },
634         { MODKEY,               XK_9,           switchws,       {.id = 8} },
635         { MODKEY,               XK_0,           switchws,       {.id = 9} },
636         { MODKEY | ShiftMask,   XK_1,           send_to_ws,     {.id = 0} },
637         { MODKEY | ShiftMask,   XK_2,           send_to_ws,     {.id = 1} },
638         { MODKEY | ShiftMask,   XK_3,           send_to_ws,     {.id = 2} },
639         { MODKEY | ShiftMask,   XK_4,           send_to_ws,     {.id = 3} },
640         { MODKEY | ShiftMask,   XK_5,           send_to_ws,     {.id = 4} },
641         { MODKEY | ShiftMask,   XK_6,           send_to_ws,     {.id = 5} },
642         { MODKEY | ShiftMask,   XK_7,           send_to_ws,     {.id = 6} },
643         { MODKEY | ShiftMask,   XK_8,           send_to_ws,     {.id = 7} },
644         { MODKEY | ShiftMask,   XK_9,           send_to_ws,     {.id = 8} },
645         { MODKEY | ShiftMask,   XK_0,           send_to_ws,     {.id = 9} },
646         { MODKEY,               XK_b,           bar_toggle,     {0} },
647         { MODKEY,               XK_Tab,         focus,          {.id = SWM_ARG_ID_FOCUSNEXT} },
648         { MODKEY | ShiftMask,   XK_Tab,         focus,          {.id = SWM_ARG_ID_FOCUSPREV} },
649 };
650
651 void
652 updatenumlockmask(void)
653 {
654         unsigned int            i, j;
655         XModifierKeymap         *modmap;
656
657         DNPRINTF(SWM_D_MISC, "updatenumlockmask\n");
658         numlockmask = 0;
659         modmap = XGetModifierMapping(display);
660         for (i = 0; i < 8; i++)
661                 for (j = 0; j < modmap->max_keypermod; j++)
662                         if (modmap->modifiermap[i * modmap->max_keypermod + j]
663                           == XKeysymToKeycode(display, XK_Num_Lock))
664                                 numlockmask = (1 << i);
665
666         XFreeModifiermap(modmap);
667 }
668
669 void
670 grabkeys(void)
671 {
672         unsigned int            i, j;
673         KeyCode                 code;
674         unsigned int            modifiers[] =
675             { 0, LockMask, numlockmask, numlockmask | LockMask };
676
677         DNPRINTF(SWM_D_MISC, "grabkeys\n");
678         updatenumlockmask();
679
680         XUngrabKey(display, AnyKey, AnyModifier, root);
681         for(i = 0; i < LENGTH(keys); i++) {
682                 if((code = XKeysymToKeycode(display, keys[i].keysym)))
683                         for(j = 0; j < LENGTH(modifiers); j++)
684                                 XGrabKey(display, code,
685                                     keys[i].mod | modifiers[j], root,
686                                     True, GrabModeAsync, GrabModeAsync);
687         }
688 }
689 void
690 expose(XEvent *e)
691 {
692         DNPRINTF(SWM_D_EVENT, "expose: window: %lu\n", e->xexpose.window);
693 }
694
695 void
696 keypress(XEvent *e)
697 {
698         unsigned int            i;
699         KeySym                  keysym;
700         XKeyEvent               *ev = &e->xkey;
701
702         DNPRINTF(SWM_D_EVENT, "keypress: window: %lu\n", ev->window);
703
704         keysym = XKeycodeToKeysym(display, (KeyCode)ev->keycode, 0);
705         for(i = 0; i < LENGTH(keys); i++)
706                 if(keysym == keys[i].keysym
707                    && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state)
708                    && keys[i].func)
709                         keys[i].func(&(keys[i].args));
710 }
711
712 void
713 buttonpress(XEvent *e)
714 {
715         XButtonPressedEvent     *ev = &e->xbutton;
716 #ifdef SWM_CLICKTOFOCUS
717         struct ws_win           *win;
718 #endif
719
720
721         DNPRINTF(SWM_D_EVENT, "buttonpress: window: %lu\n", ev->window);
722
723         if (ev->window == root)
724                 return;
725         if (ev->window == ws[current_ws].focus->id)
726                 return;
727 #ifdef SWM_CLICKTOFOCUS
728         TAILQ_FOREACH(win, &ws[current_ws].winlist, entry)
729                 if (win->id == ev->window) {
730                         /* focus in the clicked window */
731                         XSetWindowBorder(display, ev->window, 0xff0000);
732                         XSetWindowBorder(display,
733                             ws[current_ws].focus->id, 0x888888);
734                         XSetInputFocus(display, ev->window, RevertToPointerRoot,
735                             CurrentTime);
736                         ws[current_ws].focus = win;
737                         XSync(display, False);
738                         break;
739         }
740 #endif
741 }
742
743 void
744 configurerequest(XEvent *e)
745 {
746         XConfigureRequestEvent  *ev = &e->xconfigurerequest;
747         Window                  trans;
748         struct ws_win           *win;
749
750         DNPRINTF(SWM_D_EVENT, "configurerequest: window: %lu\n", ev->window);
751
752         TAILQ_FOREACH (win, &ws[current_ws].winlist, entry) {
753                 if (ev->window == win->id)
754                         return;
755         }
756
757         XSelectInput(display, ev->window, ButtonPressMask | EnterWindowMask |
758             FocusChangeMask | ExposureMask);
759
760         if ((win = calloc(1, sizeof(struct ws_win))) == NULL)
761                 errx(1, "calloc: failed to allocate memory for new window");
762
763         win->id = ev->window;
764         TAILQ_INSERT_TAIL(&ws[current_ws].winlist, win, entry);
765         ws[current_ws].focus = win; /* make new win focused */
766
767         XGetTransientForHint(display, win->id, &trans);
768         if (trans) {
769                 win->transient = trans;
770                 DNPRINTF(SWM_D_MISC, "configurerequest: win %u transient %u\n",
771                     (unsigned)win->id, win->transient);
772         }
773         XGetWindowAttributes(display, win->id, &win->wa);
774 #if 0
775         XClassHint ch = { 0 };
776         if(XGetClassHint(display, win->id, &ch)) {
777                 fprintf(stderr, "class: %s name: %s\n", ch.res_class, ch.res_name);
778                 if (!strcmp(ch.res_class, "Gvim") && !strcmp(ch.res_name, "gvim")) {
779                         win->floating = 0;
780                 }
781                 if(ch.res_class)
782                         XFree(ch.res_class);
783                 if(ch.res_name)
784                         XFree(ch.res_name);
785         }
786 #endif
787         stack();
788 }
789
790 void
791 configurenotify(XEvent *e)
792 {
793         DNPRINTF(SWM_D_EVENT, "configurenotify: window: %lu\n",
794             e->xconfigure.window);
795 }
796
797 void
798 destroynotify(XEvent *e)
799 {
800         struct ws_win           *win;
801         XDestroyWindowEvent     *ev = &e->xdestroywindow;
802
803         DNPRINTF(SWM_D_EVENT, "destroynotify: window %lu\n", ev->window);
804
805         TAILQ_FOREACH (win, &ws[current_ws].winlist, entry) {
806                 if (ev->window == win->id) {
807                         /* find a window to focus */
808                         ws[current_ws].focus =
809                             TAILQ_PREV(win, ws_win_list,entry);
810                         if (ws[current_ws].focus == NULL)
811                                 ws[current_ws].focus =
812                                     TAILQ_FIRST(&ws[current_ws].winlist);
813         
814                         TAILQ_REMOVE(&ws[current_ws].winlist, win, entry);
815                         free(win);
816                         break;
817                 }
818         }
819
820         stack();
821 }
822
823 void
824 enternotify(XEvent *e)
825 {
826         XCrossingEvent          *ev = &e->xcrossing;
827         struct ws_win           *win;
828
829         DNPRINTF(SWM_D_EVENT, "enternotify: window: %lu\n", ev->window);
830
831         if((ev->mode != NotifyNormal || ev->detail == NotifyInferior) &&
832             ev->window != root)
833                 return;
834         if (ignore_enter) {
835                 /* eat event(s) to prevent autofocus */
836                 ignore_enter--;
837                 return;
838         }
839         TAILQ_FOREACH (win, &ws[current_ws].winlist, entry) {
840                 if (win->id == ev->window)
841                         focus_win(win);
842                 else
843                         unfocus_win(win);
844         }
845 }
846
847 void
848 focusin(XEvent *e)
849 {
850         XFocusChangeEvent       *ev = &e->xfocus;
851
852         DNPRINTF(SWM_D_EVENT, "focusin: window: %lu\n", ev->window);
853
854         XSync(display, False); /* not sure this helps redrawing graphic apps */
855
856         if (ev->window == root)
857                 return;
858         /*
859          * kill grab for now so that we can cut and paste , this screws up
860          * click to focus
861          */
862         /*
863         DNPRINTF(SWM_D_EVENT, "focusin: window: %lu grabbing\n", ev->window);
864         XGrabButton(display, Button1, AnyModifier, ev->window, False,
865             ButtonPress, GrabModeAsync, GrabModeSync, None, None);
866         */
867 }
868
869 void
870 mappingnotify(XEvent *e)
871 {
872         XMappingEvent           *ev = &e->xmapping;
873
874         DNPRINTF(SWM_D_EVENT, "mappingnotify: window: %lu\n", ev->window);
875
876         XRefreshKeyboardMapping(ev);
877         if(ev->request == MappingKeyboard)
878                 grabkeys();
879 }
880
881 void
882 maprequest(XEvent *e)
883 {
884         DNPRINTF(SWM_D_EVENT, "maprequest: window: %lu\n",
885             e->xmaprequest.window);
886 }
887
888 void
889 propertynotify(XEvent *e)
890 {
891         DNPRINTF(SWM_D_EVENT, "propertynotify: window: %lu\n",
892             e->xproperty.window);
893 }
894
895 void
896 unmapnotify(XEvent *e)
897 {
898         DNPRINTF(SWM_D_EVENT, "unmapnotify: window: %lu\n", e->xunmap.window);
899 }
900
901 void
902 visibilitynotify(XEvent *e)
903 {
904         DNPRINTF(SWM_D_EVENT, "visibilitynotify: window: %lu\n", e->xvisibility.window);
905
906         if (e->xvisibility.window == bar_window &&
907             e->xvisibility.state == VisibilityUnobscured)
908                 bar_print();
909 }
910
911 void                    (*handler[LASTEvent])(XEvent *) = {
912                                 [Expose] = expose,
913                                 [KeyPress] = keypress,
914                                 [ButtonPress] = buttonpress,
915                                 [ConfigureRequest] = configurerequest,
916                                 [ConfigureNotify] = configurenotify,
917                                 [DestroyNotify] = destroynotify,
918                                 [EnterNotify] = enternotify,
919                                 [FocusIn] = focusin,
920                                 [MappingNotify] = mappingnotify,
921                                 [MapRequest] = maprequest,
922                                 [PropertyNotify] = propertynotify,
923                                 [UnmapNotify] = unmapnotify,
924                                 [VisibilityNotify] = visibilitynotify,
925 };
926
927 int
928 xerror_start(Display *d, XErrorEvent *ee)
929 {
930         other_wm = 1;
931         return (-1);
932 }
933
934 int
935 xerror(Display *d, XErrorEvent *ee)
936 {
937         fprintf(stderr, "error: %p %p\n", display, ee);
938
939         return (-1);
940 }
941
942 int
943 active_wm(void)
944 {
945         other_wm = 0;
946         xerrorxlib = XSetErrorHandler(xerror_start);
947
948         /* this causes an error if some other window manager is running */
949         XSelectInput(display, DefaultRootWindow(display),
950             SubstructureRedirectMask);
951         XSync(display, False);
952         if(other_wm)
953                 return (1);
954
955         XSetErrorHandler(xerror);
956         XSync(display, False);
957         return (0);
958 }
959
960 int
961 main(int argc, char *argv[])
962 {
963         struct passwd           *pwd;
964         char                    conf[PATH_MAX], *cfile = NULL;
965         struct stat             sb;
966         XEvent                  e;
967         int                     i;
968
969         fprintf(stderr, "Welcome to scrotwm V%s\n", SWM_VERSION);
970         if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
971                 warnx("no locale support");
972
973         if(!(display = XOpenDisplay(0)))
974                 errx(1, "can not open display");
975
976         if (active_wm())
977                 errx(1, "other wm running");
978
979         screen = DefaultScreen(display);
980         root = RootWindow(display, screen);
981         width = DisplayWidth(display, screen) - 2;
982         height = DisplayHeight(display, screen) - 2;
983
984         /* look for local and global conf file */
985         pwd = getpwuid(getuid());
986         if (pwd == NULL)
987                 errx(1, "invalid user %d", getuid());
988
989         snprintf(conf, sizeof conf, "%s/.%s", pwd->pw_dir, SWM_CONF_FILE);
990         if (stat(conf, &sb) != -1) {
991                 if (S_ISREG(sb.st_mode))
992                         cfile = conf;
993         } else {
994                 /* try global conf file */
995                 snprintf(conf, sizeof conf, "/etc/%s", SWM_CONF_FILE);
996                 if (!stat(conf, &sb))
997                         if (S_ISREG(sb.st_mode))
998                                 cfile = conf;
999         }
1000         if (cfile)
1001                 conf_load(cfile);
1002
1003         /* make work space 1 active */
1004         ws[0].visible = 1;
1005         ws[0].restack = 0;
1006         ws[0].focus = NULL;
1007         TAILQ_INIT(&ws[0].winlist);
1008         for (i = 1; i < SWM_WS_MAX; i++) {
1009                 ws[i].visible = 0;
1010                 ws[i].restack = 0;
1011                 ws[i].focus = NULL;
1012                 TAILQ_INIT(&ws[i].winlist);
1013         }
1014
1015         /* setup status bar */
1016         bar_setup();
1017
1018         XSelectInput(display, root, SubstructureRedirectMask |
1019             SubstructureNotifyMask | ButtonPressMask | KeyPressMask |
1020             EnterWindowMask | LeaveWindowMask | StructureNotifyMask |
1021             FocusChangeMask | PropertyChangeMask | ExposureMask);
1022
1023         grabkeys();
1024
1025         while (running) {
1026                 XNextEvent(display, &e);
1027                 if (handler[e.type])
1028                         handler[e.type](&e);
1029         }
1030
1031         XCloseDisplay(display);
1032
1033         return (0);
1034 }