window-to-window snapping is now a run-time option.
[mikachu/openbox.git] / src / blackbox.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2 // blackbox.cc for Blackbox - an X11 Window manager
3 // Copyright (c) 2001 - 2002 Sean 'Shaleh' Perry <shaleh@debian.org>
4 // Copyright (c) 1997 - 2000 Brad Hughes (bhughes@tcac.net)
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a
7 // copy of this software and associated documentation files (the "Software"),
8 // to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 // and/or sell copies of the Software, and to permit persons to whom the
11 // Software is furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 // DEALINGS IN THE SOFTWARE.
23
24 #ifdef    HAVE_CONFIG_H
25 #  include "../config.h"
26 #endif // HAVE_CONFIG_H
27
28 extern "C" {
29 #include <X11/Xlib.h>
30 #include <X11/Xutil.h>
31 #include <X11/Xatom.h>
32 #include <X11/cursorfont.h>
33 #include <X11/keysym.h>
34
35 #ifdef    SHAPE
36 #include <X11/extensions/shape.h>
37 #endif // SHAPE
38
39 #ifdef    HAVE_STDIO_H
40 #  include <stdio.h>
41 #endif // HAVE_STDIO_H
42
43 #ifdef HAVE_STDLIB_H
44 #  include <stdlib.h>
45 #endif // HAVE_STDLIB_H
46
47 #ifdef HAVE_STRING_H
48 #  include <string.h>
49 #endif // HAVE_STRING_H
50
51 #ifdef    HAVE_UNISTD_H
52 #  include <sys/types.h>
53 #  include <unistd.h>
54 #endif // HAVE_UNISTD_H
55
56 #ifdef    HAVE_SYS_PARAM_H
57 #  include <sys/param.h>
58 #endif // HAVE_SYS_PARAM_H
59
60 #ifdef    HAVE_SYS_SELECT_H
61 #  include <sys/select.h>
62 #endif // HAVE_SYS_SELECT_H
63
64 #ifdef    HAVE_SIGNAL_H
65 #  include <signal.h>
66 #endif // HAVE_SIGNAL_H
67
68 #ifdef    HAVE_SYS_SIGNAL_H
69 #  include <sys/signal.h>
70 #endif // HAVE_SYS_SIGNAL_H
71
72 #ifdef    HAVE_SYS_STAT_H
73 #  include <sys/types.h>
74 #  include <sys/stat.h>
75 #endif // HAVE_SYS_STAT_H
76
77 #ifdef    TIME_WITH_SYS_TIME
78 #  include <sys/time.h>
79 #  include <time.h>
80 #else // !TIME_WITH_SYS_TIME
81 #  ifdef    HAVE_SYS_TIME_H
82 #    include <sys/time.h>
83 #  else // !HAVE_SYS_TIME_H
84 #    include <time.h>
85 #  endif // HAVE_SYS_TIME_H
86 #endif // TIME_WITH_SYS_TIME
87
88 #ifdef    HAVE_LIBGEN_H
89 #  include <libgen.h>
90 #endif // HAVE_LIBGEN_H
91 }
92
93 #include <assert.h>
94
95 #include <algorithm>
96 #include <string>
97 using std::string;
98
99 #include "i18n.hh"
100 #include "blackbox.hh"
101 #include "Basemenu.hh"
102 #include "Clientmenu.hh"
103 #include "GCCache.hh"
104 #include "Image.hh"
105 #include "Rootmenu.hh"
106 #include "Screen.hh"
107 #include "Slit.hh"
108 #include "Toolbar.hh"
109 #include "Util.hh"
110 #include "Window.hh"
111 #include "Workspace.hh"
112 #include "Workspacemenu.hh"
113 #include "XAtom.hh"
114
115 // X event scanner for enter/leave notifies - adapted from twm
116 struct scanargs {
117   Window w;
118   bool leave, inferior, enter;
119 };
120
121 static Bool queueScanner(Display *, XEvent *e, char *args) {
122   scanargs *scan = (scanargs *) args;
123   if ((e->type == LeaveNotify) &&
124       (e->xcrossing.window == scan->w) &&
125       (e->xcrossing.mode == NotifyNormal)) {
126     scan->leave = True;
127     scan->inferior = (e->xcrossing.detail == NotifyInferior);
128   } else if ((e->type == EnterNotify) && (e->xcrossing.mode == NotifyUngrab)) {
129     scan->enter = True;
130   }
131
132   return False;
133 }
134
135 Blackbox *blackbox;
136
137
138 Blackbox::Blackbox(char **m_argv, char *dpy_name, char *rc, char *menu)
139   : BaseDisplay(m_argv[0], dpy_name) {
140   if (! XSupportsLocale())
141     fprintf(stderr, "X server does not support locale\n");
142
143   if (XSetLocaleModifiers("") == NULL)
144     fprintf(stderr, "cannot set locale modifiers\n");
145
146   ::blackbox = this;
147   argv = m_argv;
148   if (! rc) rc = "~/.openbox/rc";
149   rc_file = expandTilde(rc);
150   config.setFile(rc_file);  
151   if (! menu) menu = "~/.openbox/menu";
152   menu_file = expandTilde(menu);
153
154   no_focus = False;
155
156   resource.auto_raise_delay.tv_sec = resource.auto_raise_delay.tv_usec = 0;
157
158   active_screen = 0;
159   focused_window = (BlackboxWindow *) 0;
160
161   XrmInitialize();
162   load_rc();
163
164   xatom = new XAtom(this);
165
166   cursor.session = XCreateFontCursor(getXDisplay(), XC_left_ptr);
167   cursor.move = XCreateFontCursor(getXDisplay(), XC_fleur);
168   cursor.ll_angle = XCreateFontCursor(getXDisplay(), XC_ll_angle);
169   cursor.lr_angle = XCreateFontCursor(getXDisplay(), XC_lr_angle);
170
171   for (unsigned int i = 0; i < getNumberOfScreens(); i++) {
172     BScreen *screen = new BScreen(this, i);
173
174     if (! screen->isScreenManaged()) {
175       delete screen;
176       continue;
177     }
178
179     screenList.push_back(screen);
180   }
181
182   if (screenList.empty()) {
183     fprintf(stderr,
184             i18n(blackboxSet, blackboxNoManagableScreens,
185               "Blackbox::Blackbox: no managable screens found, aborting.\n"));
186     ::exit(3);
187   }
188
189   // save current settings and default values
190   save_rc();
191
192   // set the screen with mouse to the first managed screen
193   active_screen = screenList.front();
194   setFocusedWindow(0);
195
196   XSynchronize(getXDisplay(), False);
197   XSync(getXDisplay(), False);
198
199   reconfigure_wait = reread_menu_wait = False;
200
201   timer = new BTimer(this, this);
202   timer->setTimeout(0l);
203 }
204
205
206 Blackbox::~Blackbox(void) {
207   std::for_each(screenList.begin(), screenList.end(), PointerAssassin());
208
209   std::for_each(menuTimestamps.begin(), menuTimestamps.end(),
210                 PointerAssassin());
211
212   delete xatom;
213
214   delete timer;
215 }
216
217
218 void Blackbox::process_event(XEvent *e) {
219   switch (e->type) {
220   case ButtonPress: {
221     // strip the lock key modifiers
222     e->xbutton.state &= ~(NumLockMask | ScrollLockMask | LockMask);
223
224     last_time = e->xbutton.time;
225
226     BlackboxWindow *win = (BlackboxWindow *) 0;
227     Basemenu *menu = (Basemenu *) 0;
228     Slit *slit = (Slit *) 0;
229     Toolbar *tbar = (Toolbar *) 0;
230     BScreen *scrn = (BScreen *) 0;
231
232     if ((win = searchWindow(e->xbutton.window))) {
233       win->buttonPressEvent(&e->xbutton);
234
235       /* XXX: is this sane on low colour desktops? */
236       if (e->xbutton.button == 1)
237         win->installColormap(True);
238     } else if ((menu = searchMenu(e->xbutton.window))) {
239       menu->buttonPressEvent(&e->xbutton);
240     } else if ((slit = searchSlit(e->xbutton.window))) {
241       slit->buttonPressEvent(&e->xbutton);
242     } else if ((tbar = searchToolbar(e->xbutton.window))) {
243       tbar->buttonPressEvent(&e->xbutton);
244     } else if ((scrn = searchScreen(e->xbutton.window))) {
245       scrn->buttonPressEvent(&e->xbutton);
246       if (active_screen != scrn) {
247         active_screen = scrn;
248         // first, set no focus window on the old screen
249         setFocusedWindow(0);
250         // and move focus to this screen
251         setFocusedWindow(0);
252       }
253     }
254     break;
255   }
256
257   case ButtonRelease: {
258     // strip the lock key modifiers
259     e->xbutton.state &= ~(NumLockMask | ScrollLockMask | LockMask);
260
261     last_time = e->xbutton.time;
262
263     BlackboxWindow *win = (BlackboxWindow *) 0;
264     Basemenu *menu = (Basemenu *) 0;
265     Toolbar *tbar = (Toolbar *) 0;
266
267     if ((win = searchWindow(e->xbutton.window)))
268       win->buttonReleaseEvent(&e->xbutton);
269     else if ((menu = searchMenu(e->xbutton.window)))
270       menu->buttonReleaseEvent(&e->xbutton);
271     else if ((tbar = searchToolbar(e->xbutton.window)))
272       tbar->buttonReleaseEvent(&e->xbutton);
273
274     break;
275   }
276
277   case ConfigureRequest: {
278     // compress configure requests...
279     XEvent realevent;
280     unsigned int i = 0;
281     while(XCheckTypedWindowEvent(getXDisplay(), e->xconfigurerequest.window,
282                                  ConfigureRequest, &realevent)) {
283       i++;
284     }
285     if ( i > 0 )
286       e = &realevent;
287
288     BlackboxWindow *win = (BlackboxWindow *) 0;
289     Slit *slit = (Slit *) 0;
290
291     if ((win = searchWindow(e->xconfigurerequest.window))) {
292       win->configureRequestEvent(&e->xconfigurerequest);
293     } else if ((slit = searchSlit(e->xconfigurerequest.window))) {
294       slit->configureRequestEvent(&e->xconfigurerequest);
295     } else {
296       if (validateWindow(e->xconfigurerequest.window)) {
297         XWindowChanges xwc;
298
299         xwc.x = e->xconfigurerequest.x;
300         xwc.y = e->xconfigurerequest.y;
301         xwc.width = e->xconfigurerequest.width;
302         xwc.height = e->xconfigurerequest.height;
303         xwc.border_width = e->xconfigurerequest.border_width;
304         xwc.sibling = e->xconfigurerequest.above;
305         xwc.stack_mode = e->xconfigurerequest.detail;
306
307         XConfigureWindow(getXDisplay(), e->xconfigurerequest.window,
308                          e->xconfigurerequest.value_mask, &xwc);
309       }
310     }
311
312     break;
313   }
314
315   case MapRequest: {
316 #ifdef    DEBUG
317     fprintf(stderr, "Blackbox::process_event(): MapRequest for 0x%lx\n",
318             e->xmaprequest.window);
319 #endif // DEBUG
320
321     BlackboxWindow *win = searchWindow(e->xmaprequest.window);
322
323     if (! win) {
324       BScreen *screen = searchScreen(e->xmaprequest.parent);
325
326       if (! screen) {
327         /*
328           we got a map request for a window who's parent isn't root. this
329           can happen in only one circumstance:
330
331             a client window unmapped a managed window, and then remapped it
332             somewhere between unmapping the client window and reparenting it
333             to root.
334
335           regardless of how it happens, we need to find the screen that
336           the window is on
337         */
338         XWindowAttributes wattrib;
339         if (! XGetWindowAttributes(getXDisplay(), e->xmaprequest.window,
340                                    &wattrib)) {
341           // failed to get the window attributes, perhaps the window has
342           // now been destroyed?
343           break;
344         }
345
346         screen = searchScreen(wattrib.root);
347         assert(screen != 0); // this should never happen
348       }
349
350       screen->manageWindow(e->xmaprequest.window);
351     }
352
353     break;
354   }
355
356   case UnmapNotify: {
357     BlackboxWindow *win = (BlackboxWindow *) 0;
358     Slit *slit = (Slit *) 0;
359
360     if ((win = searchWindow(e->xunmap.window))) {
361       win->unmapNotifyEvent(&e->xunmap);
362     } else if ((slit = searchSlit(e->xunmap.window))) {
363       slit->unmapNotifyEvent(&e->xunmap);
364     }
365
366     break;
367   }
368
369   case DestroyNotify: {
370     BlackboxWindow *win = (BlackboxWindow *) 0;
371     Slit *slit = (Slit *) 0;
372     BWindowGroup *group = (BWindowGroup *) 0;
373
374     if ((win = searchWindow(e->xdestroywindow.window))) {
375       win->destroyNotifyEvent(&e->xdestroywindow);
376     } else if ((slit = searchSlit(e->xdestroywindow.window))) {
377       slit->removeClient(e->xdestroywindow.window, False);
378     } else if ((group = searchGroup(e->xdestroywindow.window))) {
379       delete group;
380     }
381
382     break;
383   }
384
385   case ReparentNotify: {
386     /*
387       this event is quite rare and is usually handled in unmapNotify
388       however, if the window is unmapped when the reparent event occurs
389       the window manager never sees it because an unmap event is not sent
390       to an already unmapped window.
391     */
392     BlackboxWindow *win = searchWindow(e->xreparent.window);
393     if (win) {
394       win->reparentNotifyEvent(&e->xreparent);
395     } else {
396       Slit *slit = searchSlit(e->xreparent.window);
397       if (slit && slit->getWindowID() != e->xreparent.parent)
398         slit->removeClient(e->xreparent.window, True);
399     }
400     break;
401   }
402
403   case MotionNotify: {
404     // motion notify compression...
405     XEvent realevent;
406     unsigned int i = 0;
407     while (XCheckTypedWindowEvent(getXDisplay(), e->xmotion.window,
408                                   MotionNotify, &realevent)) {
409       i++;
410     }
411
412     // if we have compressed some motion events, use the last one
413     if ( i > 0 )
414       e = &realevent;
415
416     // strip the lock key modifiers
417     e->xbutton.state &= ~(NumLockMask | ScrollLockMask | LockMask);
418
419     last_time = e->xmotion.time;
420
421     BlackboxWindow *win = (BlackboxWindow *) 0;
422     Basemenu *menu = (Basemenu *) 0;
423
424     if ((win = searchWindow(e->xmotion.window)))
425       win->motionNotifyEvent(&e->xmotion);
426     else if ((menu = searchMenu(e->xmotion.window)))
427       menu->motionNotifyEvent(&e->xmotion);
428
429     break;
430   }
431
432   case PropertyNotify: {
433     last_time = e->xproperty.time;
434
435     if (e->xproperty.state != PropertyDelete) {
436       BlackboxWindow *win = searchWindow(e->xproperty.window);
437
438       if (win)
439         win->propertyNotifyEvent(e->xproperty.atom);
440     }
441
442     break;
443   }
444
445   case EnterNotify: {
446     last_time = e->xcrossing.time;
447
448     BScreen *screen = (BScreen *) 0;
449     BlackboxWindow *win = (BlackboxWindow *) 0;
450     Basemenu *menu = (Basemenu *) 0;
451     Toolbar *tbar = (Toolbar *) 0;
452     Slit *slit = (Slit *) 0;
453
454     if (e->xcrossing.mode == NotifyGrab) break;
455
456     XEvent dummy;
457     scanargs sa;
458     sa.w = e->xcrossing.window;
459     sa.enter = sa.leave = False;
460     XCheckIfEvent(getXDisplay(), &dummy, queueScanner, (char *) &sa);
461
462     if ((e->xcrossing.window == e->xcrossing.root) &&
463         (screen = searchScreen(e->xcrossing.window))) {
464       screen->getImageControl()->installRootColormap();
465     } else if ((win = searchWindow(e->xcrossing.window))) {
466       if (win->getScreen()->isSloppyFocus() &&
467           (! win->isFocused()) && (! no_focus)) {
468         if (((! sa.leave) || sa.inferior) && win->isVisible()) {
469           if (win->setInputFocus())
470             win->installColormap(True); // XXX: shouldnt we honour no install?
471         }
472       }
473     } else if ((menu = searchMenu(e->xcrossing.window))) {
474       menu->enterNotifyEvent(&e->xcrossing);
475     } else if ((tbar = searchToolbar(e->xcrossing.window))) {
476       tbar->enterNotifyEvent(&e->xcrossing);
477     } else if ((slit = searchSlit(e->xcrossing.window))) {
478       slit->enterNotifyEvent(&e->xcrossing);
479     }
480     break;
481   }
482
483   case LeaveNotify: {
484     last_time = e->xcrossing.time;
485
486     BlackboxWindow *win = (BlackboxWindow *) 0;
487     Basemenu *menu = (Basemenu *) 0;
488     Toolbar *tbar = (Toolbar *) 0;
489     Slit *slit = (Slit *) 0;
490
491     if ((menu = searchMenu(e->xcrossing.window)))
492       menu->leaveNotifyEvent(&e->xcrossing);
493     else if ((win = searchWindow(e->xcrossing.window)))
494       win->installColormap(False);
495     else if ((tbar = searchToolbar(e->xcrossing.window)))
496       tbar->leaveNotifyEvent(&e->xcrossing);
497     else if ((slit = searchSlit(e->xcrossing.window)))
498       slit->leaveNotifyEvent(&e->xcrossing);
499     break;
500   }
501
502   case Expose: {
503     // compress expose events
504     XEvent realevent;
505     unsigned int i = 0;
506     int ex1, ey1, ex2, ey2;
507     ex1 = e->xexpose.x;
508     ey1 = e->xexpose.y;
509     ex2 = ex1 + e->xexpose.width - 1;
510     ey2 = ey1 + e->xexpose.height - 1;
511     while (XCheckTypedWindowEvent(getXDisplay(), e->xexpose.window,
512                                   Expose, &realevent)) {
513       i++;
514
515       // merge expose area
516       ex1 = std::min(realevent.xexpose.x, ex1);
517       ey1 = std::min(realevent.xexpose.y, ey1);
518       ex2 = std::max(realevent.xexpose.x + realevent.xexpose.width - 1, ex2);
519       ey2 = std::max(realevent.xexpose.y + realevent.xexpose.height - 1, ey2);
520     }
521     if ( i > 0 )
522       e = &realevent;
523
524     // use the merged area
525     e->xexpose.x = ex1;
526     e->xexpose.y = ey1;
527     e->xexpose.width = ex2 - ex1 + 1;
528     e->xexpose.height = ey2 - ey1 + 1;
529
530     BlackboxWindow *win = (BlackboxWindow *) 0;
531     Basemenu *menu = (Basemenu *) 0;
532     Toolbar *tbar = (Toolbar *) 0;
533
534     if ((win = searchWindow(e->xexpose.window)))
535       win->exposeEvent(&e->xexpose);
536     else if ((menu = searchMenu(e->xexpose.window)))
537       menu->exposeEvent(&e->xexpose);
538     else if ((tbar = searchToolbar(e->xexpose.window)))
539       tbar->exposeEvent(&e->xexpose);
540
541     break;
542   }
543
544   case KeyPress: {
545     Toolbar *tbar = searchToolbar(e->xkey.window);
546
547     if (tbar && tbar->isEditing())
548       tbar->keyPressEvent(&e->xkey);
549
550     break;
551   }
552
553   case ColormapNotify: {
554     BScreen *screen = searchScreen(e->xcolormap.window);
555
556     if (screen)
557       screen->setRootColormapInstalled((e->xcolormap.state ==
558                                         ColormapInstalled) ? True : False);
559
560     break;
561   }
562
563   case FocusIn: {
564     if (e->xfocus.detail != NotifyNonlinear &&
565         e->xfocus.detail != NotifyAncestor) {
566       /*
567         don't process FocusIns when:
568         1. the new focus window isn't an ancestor or inferior of the old
569         focus window (NotifyNonlinear)
570         make sure to allow the FocusIn when the old focus window was an
571         ancestor but didn't have a parent, such as root (NotifyAncestor)
572       */
573       break;
574     }
575
576     BlackboxWindow *win = searchWindow(e->xfocus.window);
577     if (win) {
578       if (! win->isFocused())
579         win->setFocusFlag(True);
580
581       /*
582         set the event window to None.  when the FocusOut event handler calls
583         this function recursively, it uses this as an indication that focus
584         has moved to a known window.
585       */
586       e->xfocus.window = None;
587     }
588
589     break;
590   }
591
592   case FocusOut: {
593     if (e->xfocus.detail != NotifyNonlinear) {
594       /*
595         don't process FocusOuts when:
596         2. the new focus window isn't an ancestor or inferior of the old
597         focus window (NotifyNonlinear)
598       */
599       break;
600     }
601
602     BlackboxWindow *win = searchWindow(e->xfocus.window);
603     if (win && win->isFocused()) {
604       /*
605         before we mark "win" as unfocused, we need to verify that focus is
606         going to a known location, is in a known location, or set focus
607         to a known location.
608       */
609
610       XEvent event;
611       // don't check the current focus if FocusOut was generated during a grab
612       bool check_focus = (e->xfocus.mode == NotifyNormal);
613
614       /*
615         First, check if there is a pending FocusIn event waiting.  if there
616         is, process it and determine if focus has moved to another window
617         (the FocusIn event handler sets the window in the event
618         structure to None to indicate this).
619       */
620       if (XCheckTypedEvent(getXDisplay(), FocusIn, &event)) {
621
622         process_event(&event);
623         if (event.xfocus.window == None) {
624           // focus has moved
625           check_focus = False;
626         }
627       }
628
629       if (check_focus) {
630         /*
631           Second, we query the X server for the current input focus.
632           to make sure that we keep a consistent state.
633         */
634         BlackboxWindow *focus;
635         Window w;
636         int revert;
637         XGetInputFocus(getXDisplay(), &w, &revert);
638         focus = searchWindow(w);
639         if (focus) {
640           /*
641             focus got from "win" to "focus" under some very strange
642             circumstances, and we need to make sure that the focus indication
643             is correct.
644           */
645           setFocusedWindow(focus);
646         } else {
647           // we have no idea where focus went... so we set it to somewhere
648           setFocusedWindow(0);
649         }
650       }
651     }
652
653     break;
654   }
655
656   case ClientMessage: {
657     if (e->xclient.format == 32) {
658       if (e->xclient.message_type == xatom->getAtom(XAtom::wm_change_state)) {
659         BlackboxWindow *win = searchWindow(e->xclient.window);
660         if (! win || ! win->validateClient()) return;
661
662         if (e->xclient.data.l[0] == IconicState)
663           win->iconify();
664         if (e->xclient.data.l[0] == NormalState)
665           win->deiconify();
666       } else if(e->xclient.message_type == getBlackboxChangeWorkspaceAtom()) {
667         BScreen *screen = searchScreen(e->xclient.window);
668
669         if (screen && e->xclient.data.l[0] >= 0 &&
670             e->xclient.data.l[0] <
671             static_cast<signed>(screen->getWorkspaceCount()))
672           screen->changeWorkspaceID(e->xclient.data.l[0]);
673       } else if (e->xclient.message_type == getBlackboxChangeWindowFocusAtom()) {
674         BlackboxWindow *win = searchWindow(e->xclient.window);
675
676         if (win && win->isVisible() && win->setInputFocus())
677           win->installColormap(True);
678       } else if (e->xclient.message_type == getBlackboxCycleWindowFocusAtom()) {
679         BScreen *screen = searchScreen(e->xclient.window);
680
681         if (screen) {
682           if (! e->xclient.data.l[0])
683             screen->prevFocus();
684           else
685             screen->nextFocus();
686         }
687       } else if (e->xclient.message_type == getBlackboxChangeAttributesAtom()) {
688         BlackboxWindow *win = searchWindow(e->xclient.window);
689
690         if (win && win->validateClient()) {
691           BlackboxHints net;
692           net.flags = e->xclient.data.l[0];
693           net.attrib = e->xclient.data.l[1];
694           net.workspace = e->xclient.data.l[2];
695           net.stack = e->xclient.data.l[3];
696           net.decoration = e->xclient.data.l[4];
697
698           win->changeBlackboxHints(&net);
699         }
700       }
701     }
702
703     break;
704   }
705
706   case NoExpose:
707   case ConfigureNotify:
708   case MapNotify:
709     break; // not handled, just ignore
710
711   default: {
712 #ifdef    SHAPE
713     if (e->type == getShapeEventBase()) {
714       XShapeEvent *shape_event = (XShapeEvent *) e;
715       BlackboxWindow *win = searchWindow(e->xany.window);
716
717       if (win)
718         win->shapeEvent(shape_event);
719     }
720 #endif // SHAPE
721   }
722   } // switch
723 }
724
725
726 bool Blackbox::handleSignal(int sig) {
727   switch (sig) {
728   case SIGHUP:
729   case SIGUSR1:
730     reconfigure();
731     break;
732
733   case SIGUSR2:
734     rereadMenu();
735     break;
736
737   case SIGPIPE:
738   case SIGSEGV:
739   case SIGFPE:
740   case SIGINT:
741   case SIGTERM:
742     shutdown();
743
744   default:
745     return False;
746   }
747
748   return True;
749 }
750
751
752 bool Blackbox::validateWindow(Window window) {
753   XEvent event;
754   if (XCheckTypedWindowEvent(getXDisplay(), window, DestroyNotify, &event)) {
755     XPutBackEvent(getXDisplay(), &event);
756
757     return False;
758   }
759
760   return True;
761 }
762
763
764 BScreen *Blackbox::searchScreen(Window window) {
765   ScreenList::iterator it = screenList.begin();
766
767   for (; it != screenList.end(); ++it) {
768     BScreen *s = *it;
769     if (s->getRootWindow() == window)
770       return s;
771   }
772
773   return (BScreen *) 0;
774 }
775
776
777 BlackboxWindow *Blackbox::searchWindow(Window window) {
778   WindowLookup::iterator it = windowSearchList.find(window);
779   if (it != windowSearchList.end())
780     return it->second;
781
782   return (BlackboxWindow*) 0;
783 }
784
785
786 BWindowGroup *Blackbox::searchGroup(Window window) {
787   GroupLookup::iterator it = groupSearchList.find(window);
788   if (it != groupSearchList.end())
789     return it->second;
790
791   return (BWindowGroup *) 0;
792 }
793
794
795 Basemenu *Blackbox::searchMenu(Window window) {
796   MenuLookup::iterator it = menuSearchList.find(window);
797   if (it != menuSearchList.end())
798     return it->second;
799
800   return (Basemenu*) 0;
801 }
802
803
804 Toolbar *Blackbox::searchToolbar(Window window) {
805   ToolbarLookup::iterator it = toolbarSearchList.find(window);
806   if (it != toolbarSearchList.end())
807     return it->second;
808
809   return (Toolbar*) 0;
810 }
811
812
813 Slit *Blackbox::searchSlit(Window window) {
814   SlitLookup::iterator it = slitSearchList.find(window);
815   if (it != slitSearchList.end())
816     return it->second;
817
818   return (Slit*) 0;
819 }
820
821
822 void Blackbox::saveWindowSearch(Window window, BlackboxWindow *data) {
823   windowSearchList.insert(WindowLookupPair(window, data));
824 }
825
826
827 void Blackbox::saveGroupSearch(Window window, BWindowGroup *data) {
828   groupSearchList.insert(GroupLookupPair(window, data));
829 }
830
831
832 void Blackbox::saveMenuSearch(Window window, Basemenu *data) {
833   menuSearchList.insert(MenuLookupPair(window, data));
834 }
835
836
837 void Blackbox::saveToolbarSearch(Window window, Toolbar *data) {
838   toolbarSearchList.insert(ToolbarLookupPair(window, data));
839 }
840
841
842 void Blackbox::saveSlitSearch(Window window, Slit *data) {
843   slitSearchList.insert(SlitLookupPair(window, data));
844 }
845
846
847 void Blackbox::removeWindowSearch(Window window) {
848   windowSearchList.erase(window);
849 }
850
851
852 void Blackbox::removeGroupSearch(Window window) {
853   groupSearchList.erase(window);
854 }
855
856
857 void Blackbox::removeMenuSearch(Window window) {
858   menuSearchList.erase(window);
859 }
860
861
862 void Blackbox::removeToolbarSearch(Window window) {
863   toolbarSearchList.erase(window);
864 }
865
866
867 void Blackbox::removeSlitSearch(Window window) {
868   slitSearchList.erase(window);
869 }
870
871
872 void Blackbox::restart(const char *prog) {
873   shutdown();
874
875   if (prog) {
876     execlp(prog, prog, NULL);
877     perror(prog);
878   }
879
880   // fall back in case the above execlp doesn't work
881   execvp(argv[0], argv);
882   string name = basename(argv[0]);
883   execvp(name.c_str(), argv);
884 }
885
886
887 void Blackbox::shutdown(void) {
888   BaseDisplay::shutdown();
889
890   XSetInputFocus(getXDisplay(), PointerRoot, None, CurrentTime);
891
892   std::for_each(screenList.begin(), screenList.end(),
893                 std::mem_fun(&BScreen::shutdown));
894
895   XSync(getXDisplay(), False);
896 }
897
898
899 void Blackbox::saveWindowToWindowSnap(bool s) {
900   resource.window_to_window_snap = s;
901   config.setValue("session.windowToWindowSnap", resource.window_to_window_snap);
902 }
903
904
905 void Blackbox::saveWindowCornerSnap(bool s) {
906   resource.window_corner_snap = s;
907   config.setValue("session.windowCornerSnap", resource.window_corner_snap);
908 }
909
910
911 /*
912  * Save all values as they are so that the defaults will be written to the rc
913  * file
914  */
915 void Blackbox::save_rc(void) {
916   config.setAutoSave(false);
917
918   config.setValue("session.colorsPerChannel", resource.colors_per_channel);
919   config.setValue("session.doubleClickInterval",
920                   resource.double_click_interval);
921   config.setValue("session.autoRaiseDelay",
922                   ((resource.auto_raise_delay.tv_sec * 1000) +
923                    (resource.auto_raise_delay.tv_usec / 1000)));
924   config.setValue("session.cacheLife", resource.cache_life / 60000);
925   config.setValue("session.cacheMax", resource.cache_max);
926   config.setValue("session.styleFile", resource.style_file);
927   config.setValue("session.titlebarLayout", resource.titlebar_layout);
928   saveWindowToWindowSnap(resource.window_to_window_snap);
929   saveWindowCornerSnap(resource.window_corner_snap);
930   
931   std::for_each(screenList.begin(), screenList.end(),
932                 std::mem_fun(&BScreen::save_rc));
933  
934   config.setAutoSave(true);
935   config.save();
936 }
937
938
939 void Blackbox::load_rc(void) {
940   if (! config.load())
941         config.create();
942   
943   string s;
944
945   if (! config.getValue("session.colorsPerChannel",
946                         resource.colors_per_channel))
947     resource.colors_per_channel = 4;
948   if (resource.colors_per_channel < 2) resource.colors_per_channel = 2;
949   else if (resource.colors_per_channel > 6) resource.colors_per_channel = 6;
950
951   if (config.getValue("session.styleFile", s))
952     resource.style_file = expandTilde(s);
953   else
954     resource.style_file = DEFAULTSTYLE;
955
956   if (! config.getValue("session.doubleClickInterval",
957                        resource.double_click_interval));
958     resource.double_click_interval = 250;
959
960   if (! config.getValue("session.autoRaiseDelay",
961                        resource.auto_raise_delay.tv_usec))
962     resource.auto_raise_delay.tv_usec = 400;
963   resource.auto_raise_delay.tv_sec = resource.auto_raise_delay.tv_usec / 1000;
964   resource.auto_raise_delay.tv_usec -=
965     (resource.auto_raise_delay.tv_sec * 1000);
966   resource.auto_raise_delay.tv_usec *= 1000;
967
968   if (! config.getValue("session.cacheLife", resource.cache_life))
969     resource.cache_life = 5;
970   resource.cache_life *= 60000;
971
972   if (! config.getValue("session.cacheMax", resource.cache_max))
973     resource.cache_max = 200;
974   
975   if (! config.getValue("session.titlebarLayout", resource.titlebar_layout))
976     resource.titlebar_layout = "ILMC";
977
978   if (! config.getValue("session.windowToWindowSnap",
979                         resource.window_to_window_snap))
980     resource.window_to_window_snap = true;
981
982   if (! config.getValue("session.windowCornerSnap",
983                         resource.window_corner_snap))
984     resource.window_corner_snap = true;
985 }
986
987
988 void Blackbox::reconfigure(void) {
989   reconfigure_wait = True;
990
991   if (! timer->isTiming()) timer->start();
992 }
993
994
995 void Blackbox::real_reconfigure(void) {
996   load_rc();
997   
998   std::for_each(menuTimestamps.begin(), menuTimestamps.end(),
999                 PointerAssassin());
1000   menuTimestamps.clear();
1001
1002   gcCache()->purge();
1003
1004   std::for_each(screenList.begin(), screenList.end(),
1005                 std::mem_fun(&BScreen::reconfigure));
1006 }
1007
1008
1009 void Blackbox::checkMenu(void) {
1010   bool reread = False;
1011   MenuTimestampList::iterator it = menuTimestamps.begin();
1012   for(; it != menuTimestamps.end(); ++it) {
1013     MenuTimestamp *tmp = *it;
1014     struct stat buf;
1015
1016     if (! stat(tmp->filename.c_str(), &buf)) {
1017       if (tmp->timestamp != buf.st_ctime)
1018         reread = True;
1019     } else {
1020       reread = True;
1021     }
1022   }
1023
1024   if (reread) rereadMenu();
1025 }
1026
1027
1028 void Blackbox::rereadMenu(void) {
1029   reread_menu_wait = True;
1030
1031   if (! timer->isTiming()) timer->start();
1032 }
1033
1034
1035 void Blackbox::real_rereadMenu(void) {
1036   std::for_each(menuTimestamps.begin(), menuTimestamps.end(),
1037                 PointerAssassin());
1038   menuTimestamps.clear();
1039
1040   std::for_each(screenList.begin(), screenList.end(),
1041                 std::mem_fun(&BScreen::rereadMenu));
1042 }
1043
1044
1045 void Blackbox::saveStyleFilename(const string& filename) {
1046   assert(! filename.empty());
1047   resource.style_file = filename;
1048   config.setValue("session.styleFile", resource.style_file);
1049 }
1050
1051
1052 void Blackbox::addMenuTimestamp(const string& filename) {
1053   assert(! filename.empty());
1054   bool found = False;
1055
1056   MenuTimestampList::iterator it = menuTimestamps.begin();
1057   for (; it != menuTimestamps.end() && ! found; ++it) {
1058     if ((*it)->filename == filename) found = True;
1059   }
1060   if (! found) {
1061     struct stat buf;
1062
1063     if (! stat(filename.c_str(), &buf)) {
1064       MenuTimestamp *ts = new MenuTimestamp;
1065
1066       ts->filename = filename;
1067       ts->timestamp = buf.st_ctime;
1068
1069       menuTimestamps.push_back(ts);
1070     }
1071   }
1072 }
1073
1074
1075 void Blackbox::timeout(void) {
1076   if (reconfigure_wait)
1077     real_reconfigure();
1078
1079   if (reread_menu_wait)
1080     real_rereadMenu();
1081
1082   reconfigure_wait = reread_menu_wait = False;
1083 }
1084
1085
1086 void Blackbox::setFocusedWindow(BlackboxWindow *win) {
1087   if (focused_window && focused_window == win) // nothing to do
1088     return;
1089
1090   BScreen *old_screen = 0;
1091
1092   if (focused_window) {
1093     focused_window->setFocusFlag(False);
1094     old_screen = focused_window->getScreen();
1095   }
1096
1097   if (win && ! win->isIconic()) {
1098     // the active screen is the one with the last focused window...
1099     // this will keep focus on this screen no matter where the mouse goes,
1100     // so multihead keybindings will continue to work on that screen until the
1101     // user focuses a window on a different screen.
1102     active_screen = win->getScreen();
1103     focused_window = win;
1104   } else {
1105     focused_window = 0;
1106     if (! old_screen) {
1107       if (active_screen) {
1108         // set input focus to the toolbar of the screen with mouse
1109         XSetInputFocus(getXDisplay(),
1110                        active_screen->getRootWindow(),
1111                        RevertToPointerRoot, CurrentTime);
1112       } else {
1113         // set input focus to the toolbar of the first managed screen
1114         XSetInputFocus(getXDisplay(),
1115                        screenList.front()->getRootWindow(),
1116                        RevertToPointerRoot, CurrentTime);
1117       }
1118     } else {
1119       // set input focus to the toolbar of the last screen
1120       XSetInputFocus(getXDisplay(), old_screen->getRootWindow(),
1121                      RevertToPointerRoot, CurrentTime);
1122     }
1123   }
1124
1125   if (active_screen && active_screen->isScreenManaged()) {
1126     active_screen->getToolbar()->redrawWindowLabel(True);
1127     active_screen->updateNetizenWindowFocus();
1128   }
1129
1130   if (old_screen && old_screen != active_screen) {
1131     old_screen->getToolbar()->redrawWindowLabel(True);
1132     old_screen->updateNetizenWindowFocus();
1133   }
1134 }