change how the widgets' _dirty flag works so that all inheritence levels of the widge...
[mikachu/openbox.git] / src / blackbox.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #ifdef    HAVE_CONFIG_H
4 #  include "../config.h"
5 #endif // HAVE_CONFIG_H
6
7 extern "C" {
8 #include <X11/Xlib.h>
9 #include <X11/Xutil.h>
10 #include <X11/Xatom.h>
11 #include <X11/cursorfont.h>
12 #include <X11/keysym.h>
13
14 #ifdef    SHAPE
15 #include <X11/extensions/shape.h>
16 #endif // SHAPE
17
18 #ifdef    HAVE_STDIO_H
19 #  include <stdio.h>
20 #endif // HAVE_STDIO_H
21
22 #ifdef HAVE_STDLIB_H
23 #  include <stdlib.h>
24 #endif // HAVE_STDLIB_H
25
26 #ifdef HAVE_STRING_H
27 #  include <string.h>
28 #endif // HAVE_STRING_H
29
30 #ifdef    HAVE_UNISTD_H
31 #  include <sys/types.h>
32 #  include <unistd.h>
33 #endif // HAVE_UNISTD_H
34
35 #ifdef    HAVE_SYS_PARAM_H
36 #  include <sys/param.h>
37 #endif // HAVE_SYS_PARAM_H
38
39 #ifdef    HAVE_SYS_SELECT_H
40 #  include <sys/select.h>
41 #endif // HAVE_SYS_SELECT_H
42
43 #ifdef    HAVE_SIGNAL_H
44 #  include <signal.h>
45 #endif // HAVE_SIGNAL_H
46
47 #ifdef    HAVE_SYS_SIGNAL_H
48 #  include <sys/signal.h>
49 #endif // HAVE_SYS_SIGNAL_H
50
51 #ifdef    HAVE_SYS_STAT_H
52 #  include <sys/types.h>
53 #  include <sys/stat.h>
54 #endif // HAVE_SYS_STAT_H
55
56 #ifdef    TIME_WITH_SYS_TIME
57 #  include <sys/time.h>
58 #  include <time.h>
59 #else // !TIME_WITH_SYS_TIME
60 #  ifdef    HAVE_SYS_TIME_H
61 #    include <sys/time.h>
62 #  else // !HAVE_SYS_TIME_H
63 #    include <time.h>
64 #  endif // HAVE_SYS_TIME_H
65 #endif // TIME_WITH_SYS_TIME
66
67 #ifdef    HAVE_LIBGEN_H
68 #  include <libgen.h>
69 #endif // HAVE_LIBGEN_H
70 }
71
72 #include <assert.h>
73
74 #include <algorithm>
75 #include <string>
76 using std::string;
77
78 #include "blackbox.hh"
79 #include "otk/gccache.hh"
80 #include "otk/image.hh"
81 #include "otk/assassin.hh"
82 #include "bbscreen.hh"
83 #include "otk/util.hh"
84 #include "bbwindow.hh"
85 #include "workspace.hh"
86
87 namespace ob {
88
89 Blackbox *blackbox;
90
91
92 Blackbox::Blackbox(int argc, char **m_argv, char *rc)
93   : Openbox(argc, m_argv) {
94
95   if (! XSupportsLocale())
96     fprintf(stderr, "X server does not support locale\n");
97
98   if (XSetLocaleModifiers("") == NULL)
99     fprintf(stderr, "cannot set locale modifiers\n");
100
101   ob::blackbox = this;
102   argv = m_argv;
103
104   // try to make sure the ~/.openbox directory exists
105   mkdir(otk::expandTilde("~/.openbox").c_str(), S_IREAD | S_IWRITE | S_IEXEC |
106                                            S_IRGRP | S_IWGRP | S_IXGRP |
107                                            S_IROTH | S_IWOTH | S_IXOTH);
108   
109   if (! rc) rc = "~/.openbox/rc3";
110   rc_file = otk::expandTilde(rc);
111   config.setFile(rc_file);  
112
113   no_focus = False;
114
115   resource.auto_raise_delay.tv_sec = resource.auto_raise_delay.tv_usec = 0;
116
117   active_screen = 0;
118   focused_window = changing_window = (BlackboxWindow *) 0;
119
120   load_rc();
121
122   xatom = new otk::OBProperty();
123
124   cursor.session = XCreateFontCursor(otk::OBDisplay::display, XC_left_ptr);
125   cursor.move = XCreateFontCursor(otk::OBDisplay::display, XC_fleur);
126   cursor.ll_angle = XCreateFontCursor(otk::OBDisplay::display, XC_ll_angle);
127   cursor.lr_angle = XCreateFontCursor(otk::OBDisplay::display, XC_lr_angle);
128   cursor.ul_angle = XCreateFontCursor(otk::OBDisplay::display, XC_ul_angle);
129   cursor.ur_angle = XCreateFontCursor(otk::OBDisplay::display, XC_ur_angle);
130
131   for (int i = 0; i < ScreenCount(otk::OBDisplay::display); i++) {
132     BScreen *screen = new BScreen(this, i);
133
134     if (! screen->isScreenManaged()) {
135       delete screen;
136       continue;
137     }
138
139     screenList.push_back(screen);
140   }
141
142   if (screenList.empty()) {
143     fprintf(stderr,
144             "Blackbox::Blackbox: no managable screens found, aborting.\n");
145     ::exit(3);
146   }
147
148   // save current settings and default values
149   save_rc();
150
151   // set the screen with mouse to the first managed screen
152   active_screen = screenList.front();
153   setFocusedWindow(0);
154
155   XSynchronize(otk::OBDisplay::display, False);
156   XSync(otk::OBDisplay::display, False);
157
158   reconfigure_wait = False;
159
160   timer = new otk::OBTimer(Openbox::instance->timerManager(),
161                            (otk::OBTimeoutHandler)timeout,
162                            this);
163   timer->setTimeout(0l);
164 }
165
166
167 Blackbox::~Blackbox(void) {
168   std::for_each(screenList.begin(), screenList.end(), otk::PointerAssassin());
169
170   delete xatom;
171
172   delete timer;
173 }
174
175
176 void Blackbox::process_event(XEvent *e) {
177   switch (e->type) {
178   case ButtonPress: {
179     // strip the lock key modifiers
180     //e->xbutton.state &= ~(NumLockMask | ScrollLockMask | LockMask);
181
182     last_time = e->xbutton.time;
183
184     BlackboxWindow *win = (BlackboxWindow *) 0;
185     BScreen *scrn = (BScreen *) 0;
186
187     if ((win = searchWindow(e->xbutton.window))) {
188       win->buttonPressEvent(&e->xbutton);
189
190       /* XXX: is this sane on low colour desktops? */
191       if (e->xbutton.button == 1)
192         win->installColormap(True);
193     } else if ((scrn = searchScreen(e->xbutton.window))) {
194       scrn->buttonPressEvent(&e->xbutton);
195       if (active_screen != scrn) {
196         active_screen = scrn;
197         // first, set no focus window on the old screen
198         setFocusedWindow(0);
199         // and move focus to this screen
200         setFocusedWindow(0);
201       }
202     }
203     break;
204   }
205
206   case ButtonRelease: {
207     // strip the lock key modifiers
208     //e->xbutton.state &= ~(NumLockMask | ScrollLockMask | LockMask);
209
210     last_time = e->xbutton.time;
211
212     BlackboxWindow *win = (BlackboxWindow *) 0;
213
214     if ((win = searchWindow(e->xbutton.window)))
215       win->buttonReleaseEvent(&e->xbutton);
216
217     break;
218   }
219
220   case ConfigureRequest: {
221     BlackboxWindow *win = (BlackboxWindow *) 0;
222
223     if ((win = searchWindow(e->xconfigurerequest.window))) {
224       win->configureRequestEvent(&e->xconfigurerequest);
225     } else {
226       if (validateWindow(e->xconfigurerequest.window)) {
227         XWindowChanges xwc;
228
229         xwc.x = e->xconfigurerequest.x;
230         xwc.y = e->xconfigurerequest.y;
231         xwc.width = e->xconfigurerequest.width;
232         xwc.height = e->xconfigurerequest.height;
233         xwc.border_width = e->xconfigurerequest.border_width;
234         xwc.sibling = e->xconfigurerequest.above;
235         xwc.stack_mode = e->xconfigurerequest.detail;
236
237         XConfigureWindow(otk::OBDisplay::display, e->xconfigurerequest.window,
238                          e->xconfigurerequest.value_mask, &xwc);
239       }
240     }
241
242     break;
243   }
244
245   case MapRequest: {
246 #ifdef    DEBUG
247     fprintf(stderr, "Blackbox::process_event(): MapRequest for 0x%lx\n",
248             e->xmaprequest.window);
249 #endif // DEBUG
250
251     BlackboxWindow *win = searchWindow(e->xmaprequest.window);
252
253     if (win) {
254       bool focus = False;
255       if (win->isIconic()) {
256         win->deiconify();
257         focus = True;
258       }
259       if (win->isShaded()) {
260         win->shade();
261         focus = True;
262       }
263
264       if (focus && (win->isTransient() || win->getScreen()->doFocusNew()) &&
265           win->isVisible())
266         win->setInputFocus();
267     } else {
268       BScreen *screen = searchScreen(e->xmaprequest.parent);
269
270       if (! screen) {
271         /*
272           we got a map request for a window who's parent isn't root. this
273           can happen in only one circumstance:
274
275             a client window unmapped a managed window, and then remapped it
276             somewhere between unmapping the client window and reparenting it
277             to root.
278
279           regardless of how it happens, we need to find the screen that
280           the window is on
281         */
282         XWindowAttributes wattrib;
283         if (! XGetWindowAttributes(otk::OBDisplay::display, e->xmaprequest.window,
284                                    &wattrib)) {
285           // failed to get the window attributes, perhaps the window has
286           // now been destroyed?
287           break;
288         }
289
290         screen = searchScreen(wattrib.root);
291         assert(screen != 0); // this should never happen
292       }
293
294       screen->manageWindow(e->xmaprequest.window);
295     }
296
297     break;
298   }
299
300   case UnmapNotify: {
301     BlackboxWindow *win = (BlackboxWindow *) 0;
302     BScreen *screen = (BScreen *) 0;
303
304     if ((win = searchWindow(e->xunmap.window))) {
305       win->unmapNotifyEvent(&e->xunmap);
306     } else if ((screen = searchSystrayWindow(e->xunmap.window))) {
307       screen->removeSystrayWindow(e->xunmap.window);
308     }
309
310     break;
311   }
312
313   case DestroyNotify: {
314     BlackboxWindow *win = (BlackboxWindow *) 0;
315     BScreen *screen = (BScreen *) 0;
316     BWindowGroup *group = (BWindowGroup *) 0;
317
318     if ((win = searchWindow(e->xdestroywindow.window))) {
319       win->destroyNotifyEvent(&e->xdestroywindow);
320     } else if ((group = searchGroup(e->xdestroywindow.window))) {
321       delete group;
322     } else if ((screen = searchSystrayWindow(e->xunmap.window))) {
323       screen->removeSystrayWindow(e->xunmap.window);
324     }
325
326     break;
327   }
328
329   case ReparentNotify: {
330     /*
331       this event is quite rare and is usually handled in unmapNotify
332       however, if the window is unmapped when the reparent event occurs
333       the window manager never sees it because an unmap event is not sent
334       to an already unmapped window.
335     */
336     BlackboxWindow *win = searchWindow(e->xreparent.window);
337     if (win)
338       win->reparentNotifyEvent(&e->xreparent);
339     break;
340   }
341
342   case MotionNotify: {
343     // motion notify compression...
344     XEvent realevent;
345     unsigned int i = 0;
346     while (XCheckTypedWindowEvent(otk::OBDisplay::display, e->xmotion.window,
347                                   MotionNotify, &realevent)) {
348       i++;
349     }
350
351     // if we have compressed some motion events, use the last one
352     if ( i > 0 )
353       e = &realevent;
354
355     // the pointer is on the wrong screen
356     if (! e->xmotion.same_screen)
357       break;
358
359     // strip the lock key modifiers
360     //e->xmotion.state &= ~(NumLockMask | ScrollLockMask | LockMask);
361
362     last_time = e->xmotion.time;
363
364     BlackboxWindow *win = (BlackboxWindow *) 0;
365
366     if ((win = searchWindow(e->xmotion.window)))
367       win->motionNotifyEvent(&e->xmotion);
368
369     break;
370   }
371
372   case PropertyNotify: {
373     last_time = e->xproperty.time;
374
375     BlackboxWindow *win = (BlackboxWindow *) 0;
376     BScreen *screen = (BScreen *) 0;
377
378     if ((win = searchWindow(e->xproperty.window)))
379       win->propertyNotifyEvent(&e->xproperty);
380     else if ((screen = searchScreen(e->xproperty.window)))
381       screen->propertyNotifyEvent(&e->xproperty);
382     break;
383   }
384
385   case EnterNotify: {
386     last_time = e->xcrossing.time;
387
388     BScreen *screen = (BScreen *) 0;
389     BlackboxWindow *win = (BlackboxWindow *) 0;
390
391     if (e->xcrossing.mode == NotifyGrab) break;
392
393     if ((e->xcrossing.window == e->xcrossing.root) &&
394         (screen = searchScreen(e->xcrossing.window))) {
395       screen->getImageControl()->installRootColormap();
396     } else if ((win = searchWindow(e->xcrossing.window))) {
397       if (! no_focus)
398         win->enterNotifyEvent(&e->xcrossing);
399     }
400     break;
401   }
402
403   case LeaveNotify: {
404     last_time = e->xcrossing.time;
405
406     BlackboxWindow *win = (BlackboxWindow *) 0;
407
408     if ((win = searchWindow(e->xcrossing.window)))
409       win->leaveNotifyEvent(&e->xcrossing);
410     break;
411   }
412
413   case Expose: {
414     // compress expose events
415     XEvent realevent;
416     unsigned int i = 0;
417     int ex1, ey1, ex2, ey2;
418     ex1 = e->xexpose.x;
419     ey1 = e->xexpose.y;
420     ex2 = ex1 + e->xexpose.width - 1;
421     ey2 = ey1 + e->xexpose.height - 1;
422     while (XCheckTypedWindowEvent(otk::OBDisplay::display, e->xexpose.window,
423                                   Expose, &realevent)) {
424       i++;
425
426       // merge expose area
427       ex1 = std::min(realevent.xexpose.x, ex1);
428       ey1 = std::min(realevent.xexpose.y, ey1);
429       ex2 = std::max(realevent.xexpose.x + realevent.xexpose.width - 1, ex2);
430       ey2 = std::max(realevent.xexpose.y + realevent.xexpose.height - 1, ey2);
431     }
432     if ( i > 0 )
433       e = &realevent;
434
435     // use the merged area
436     e->xexpose.x = ex1;
437     e->xexpose.y = ey1;
438     e->xexpose.width = ex2 - ex1 + 1;
439     e->xexpose.height = ey2 - ey1 + 1;
440
441     BlackboxWindow *win = (BlackboxWindow *) 0;
442
443     if ((win = searchWindow(e->xexpose.window)))
444       win->exposeEvent(&e->xexpose);
445
446     break;
447   }
448
449   case KeyPress: {
450     break;
451   }
452
453   case ColormapNotify: {
454     BScreen *screen = searchScreen(e->xcolormap.window);
455
456     if (screen)
457       screen->setRootColormapInstalled((e->xcolormap.state ==
458                                         ColormapInstalled) ? True : False);
459
460     break;
461   }
462
463   case FocusIn: {
464     if (e->xfocus.detail != NotifyNonlinear &&
465         e->xfocus.detail != NotifyAncestor) {
466       /*
467         don't process FocusIns when:
468         1. the new focus window isn't an ancestor or inferior of the old
469         focus window (NotifyNonlinear)
470         make sure to allow the FocusIn when the old focus window was an
471         ancestor but didn't have a parent, such as root (NotifyAncestor)
472       */
473       break;
474     }
475
476     BlackboxWindow *win = searchWindow(e->xfocus.window);
477     if (win) {
478       if (! win->isFocused())
479         win->setFocusFlag(True);
480
481       /*
482         set the event window to None.  when the FocusOut event handler calls
483         this function recursively, it uses this as an indication that focus
484         has moved to a known window.
485       */
486       e->xfocus.window = None;
487
488       no_focus = False;   // focusing is back on
489     }
490
491     break;
492   }
493
494   case FocusOut: {
495     if (e->xfocus.detail != NotifyNonlinear) {
496       /*
497         don't process FocusOuts when:
498         2. the new focus window isn't an ancestor or inferior of the old
499         focus window (NotifyNonlinear)
500       */
501       break;
502     }
503
504     BlackboxWindow *win = searchWindow(e->xfocus.window);
505     if (win && win->isFocused()) {
506       /*
507         before we mark "win" as unfocused, we need to verify that focus is
508         going to a known location, is in a known location, or set focus
509         to a known location.
510       */
511
512       XEvent event;
513       // don't check the current focus if FocusOut was generated during a grab
514       bool check_focus = (e->xfocus.mode == NotifyNormal);
515
516       /*
517         First, check if there is a pending FocusIn event waiting.  if there
518         is, process it and determine if focus has moved to another window
519         (the FocusIn event handler sets the window in the event
520         structure to None to indicate this).
521       */
522       if (XCheckTypedEvent(otk::OBDisplay::display, FocusIn, &event)) {
523
524         process_event(&event);
525         if (event.xfocus.window == None) {
526           // focus has moved
527           check_focus = False;
528         }
529       }
530
531       if (check_focus) {
532         /*
533           Second, we query the X server for the current input focus.
534           to make sure that we keep a consistent state.
535         */
536         BlackboxWindow *focus;
537         Window w;
538         int revert;
539         XGetInputFocus(otk::OBDisplay::display, &w, &revert);
540         focus = searchWindow(w);
541         if (focus) {
542           /*
543             focus got from "win" to "focus" under some very strange
544             circumstances, and we need to make sure that the focus indication
545             is correct.
546           */
547           setFocusedWindow(focus);
548         } else {
549           // we have no idea where focus went... so we set it to somewhere
550           setFocusedWindow(0);
551         }
552       }
553     }
554
555     break;
556   }
557
558   case ClientMessage: {
559     if (e->xclient.format == 32) {
560       if (e->xclient.message_type == xatom->atom(otk::OBProperty::wm_change_state)) {
561         // WM_CHANGE_STATE message
562         BlackboxWindow *win = searchWindow(e->xclient.window);
563         if (! win || ! win->validateClient()) return;
564
565         if (e->xclient.data.l[0] == IconicState)
566           win->iconify();
567         if (e->xclient.data.l[0] == NormalState)
568           win->deiconify();
569       } else if (e->xclient.message_type == 
570                  xatom->atom(otk::OBProperty::blackbox_change_workspace) || 
571                  e->xclient.message_type == 
572                  xatom->atom(otk::OBProperty::net_current_desktop)) {
573         // NET_CURRENT_DESKTOP message
574         BScreen *screen = searchScreen(e->xclient.window);
575
576         unsigned int workspace = e->xclient.data.l[0];
577         if (screen && workspace < screen->getWorkspaceCount())
578           screen->changeWorkspaceID(workspace);
579       } else if (e->xclient.message_type == 
580                  xatom->atom(otk::OBProperty::blackbox_change_window_focus)) {
581         // TEMP HACK TO KEEP BBKEYS WORKING
582         BlackboxWindow *win = searchWindow(e->xclient.window);
583
584         if (win && win->isVisible() && win->setInputFocus())
585           win->installColormap(True);
586       } else if (e->xclient.message_type == 
587                  xatom->atom(otk::OBProperty::net_active_window)) {
588         // NET_ACTIVE_WINDOW
589         BlackboxWindow *win = searchWindow(e->xclient.window);
590
591         if (win) {
592           BScreen *screen = win->getScreen();
593
594           if (win->isIconic())
595             win->deiconify(False, False);
596           if (! win->isStuck() &&
597               (win->getWorkspaceNumber() != screen->getCurrentWorkspaceID())) {
598             no_focus = True;
599             screen->changeWorkspaceID(win->getWorkspaceNumber());
600           }
601           if (win->isVisible() && win->setInputFocus()) {
602             win->getScreen()->getWorkspace(win->getWorkspaceNumber())->
603               raiseWindow(win);
604             win->installColormap(True);
605           }
606         }
607       } else if (e->xclient.message_type == 
608                  xatom->atom(otk::OBProperty::blackbox_cycle_window_focus)) {
609         // BLACKBOX_CYCLE_WINDOW_FOCUS
610         BScreen *screen = searchScreen(e->xclient.window);
611
612         if (screen) {
613           if (! e->xclient.data.l[0])
614             screen->prevFocus();
615           else
616             screen->nextFocus();
617         }
618       } else if (e->xclient.message_type == 
619                  xatom->atom(otk::OBProperty::net_wm_desktop)) {
620         // NET_WM_DESKTOP
621         BlackboxWindow *win = searchWindow(e->xclient.window);
622
623         if (win) {
624           BScreen *screen = win->getScreen();
625           unsigned long wksp = (unsigned) e->xclient.data.l[0];
626           if (wksp < screen->getWorkspaceCount()) {
627             if (win->isIconic()) win->deiconify(False, True);
628             if (win->isStuck()) win->stick();
629             if (wksp != screen->getCurrentWorkspaceID())
630               win->withdraw();
631             else
632               win->show();
633             screen->reassociateWindow(win, wksp, True);
634           } else if (wksp == 0xfffffffe || // XXX: BUG, BUT DOING THIS SO KDE WORKS FOR NOW!!
635                      wksp == 0xffffffff) {
636             if (win->isIconic()) win->deiconify(False, True);
637             if (! win->isStuck()) win->stick();
638             if (! win->isVisible()) win->show();
639           }
640         }
641       } else if (e->xclient.message_type == 
642                  xatom->atom(otk::OBProperty::blackbox_change_attributes)) {
643         // BLACKBOX_CHANGE_ATTRIBUTES
644         BlackboxWindow *win = searchWindow(e->xclient.window);
645
646         if (win && win->validateClient()) {
647           BlackboxHints net;
648           net.flags = e->xclient.data.l[0];
649           net.attrib = e->xclient.data.l[1];
650           net.workspace = e->xclient.data.l[2];
651           net.stack = e->xclient.data.l[3];
652           net.decoration = e->xclient.data.l[4];
653
654           win->changeBlackboxHints(&net);
655         }
656       } else if (e->xclient.message_type == 
657                 xatom->atom(otk::OBProperty::net_number_of_desktops)) {
658         // NET_NUMBER_OF_DESKTOPS
659         BScreen *screen = searchScreen(e->xclient.window);
660         
661         if (e->xclient.data.l[0] > 0)
662           screen->changeWorkspaceCount((unsigned) e->xclient.data.l[0]);
663       } else if (e->xclient.message_type ==
664                  xatom->atom(otk::OBProperty::net_close_window)) {
665         // NET_CLOSE_WINDOW
666         BlackboxWindow *win = searchWindow(e->xclient.window);
667         if (win && win->validateClient())
668           win->close(); // could this be smarter?
669       } else if (e->xclient.message_type ==
670                  xatom->atom(otk::OBProperty::net_wm_moveresize)) {
671         // NET_WM_MOVERESIZE
672         BlackboxWindow *win = searchWindow(e->xclient.window);
673         if (win && win->validateClient()) {
674           int x_root = e->xclient.data.l[0],
675               y_root = e->xclient.data.l[1];
676           if ((Atom) e->xclient.data.l[2] ==
677               xatom->atom(otk::OBProperty::net_wm_moveresize_move)) {
678             win->beginMove(x_root, y_root);
679           } else {
680             if ((Atom) e->xclient.data.l[2] ==
681                 xatom->atom(otk::OBProperty::net_wm_moveresize_size_topleft))
682               win->beginResize(x_root, y_root, BlackboxWindow::TopLeft);
683             else if ((Atom) e->xclient.data.l[2] ==
684                      xatom->atom(otk::OBProperty::net_wm_moveresize_size_topright))
685               win->beginResize(x_root, y_root, BlackboxWindow::TopRight);
686             else if ((Atom) e->xclient.data.l[2] ==
687                      xatom->atom(otk::OBProperty::net_wm_moveresize_size_bottomleft))
688               win->beginResize(x_root, y_root, BlackboxWindow::BottomLeft);
689             else if ((Atom) e->xclient.data.l[2] ==
690                 xatom->atom(otk::OBProperty::net_wm_moveresize_size_bottomright))
691               win->beginResize(x_root, y_root, BlackboxWindow::BottomRight);
692           }
693         }
694       } else if (e->xclient.message_type ==
695                  xatom->atom(otk::OBProperty::net_wm_state)) {
696         // NET_WM_STATE
697         BlackboxWindow *win = searchWindow(e->xclient.window);
698         if (win && win->validateClient()) {
699           const Atom action = (Atom) e->xclient.data.l[0];
700           const Atom state[] = { (Atom) e->xclient.data.l[1],
701                                  (Atom) e->xclient.data.l[2] };
702           
703           for (int i = 0; i < 2; ++i) {
704             if (! state[i])
705               continue;
706
707             if ((Atom) e->xclient.data.l[0] == 1) {
708               // ADD
709               if (state[i] == xatom->atom(otk::OBProperty::net_wm_state_modal)) {
710                 win->setModal(True);
711               } else if (state[i] ==
712                          xatom->atom(otk::OBProperty::net_wm_state_maximized_vert)) {
713                 if (win->isMaximizedHoriz()) {
714                   win->maximize(0); // unmaximize
715                   win->maximize(1); // full
716                 } else if (! win->isMaximized()) {
717                   win->maximize(2); // vert
718                 }
719               } else if (state[i] ==
720                          xatom->atom(otk::OBProperty::net_wm_state_maximized_horz)) {
721                 if (win->isMaximizedVert()) {
722                   win->maximize(0); // unmaximize
723                   win->maximize(1); // full
724                 } else if (! win->isMaximized()) {
725                   win->maximize(3); // horiz
726                 }
727               } else if (state[i] ==
728                          xatom->atom(otk::OBProperty::net_wm_state_shaded)) {
729                 if (! win->isShaded())
730                   win->shade();
731               } else if (state[i] ==
732                          xatom->atom(otk::OBProperty::net_wm_state_skip_taskbar)) {
733                 win->setSkipTaskbar(True);
734               } else if (state[i] ==
735                          xatom->atom(otk::OBProperty::net_wm_state_skip_pager)) {
736                 win->setSkipPager(True);
737               } else if (state[i] ==
738                          xatom->atom(otk::OBProperty::net_wm_state_fullscreen)) {
739                 win->setFullscreen(True);
740               }
741             } else if (action == 0) {
742               // REMOVE
743               if (state[i] == xatom->atom(otk::OBProperty::net_wm_state_modal)) {
744                 win->setModal(False);
745               } else if (state[i] ==
746                          xatom->atom(otk::OBProperty::net_wm_state_maximized_vert)) {
747                 if (win->isMaximizedFull()) {
748                   win->maximize(0); // unmaximize
749                   win->maximize(3); // horiz
750                 } else if (win->isMaximizedVert()) {
751                   win->maximize(0); // unmaximize
752                 }
753               } else if (state[i] ==
754                          xatom->atom(otk::OBProperty::net_wm_state_maximized_horz)) {
755                 if (win->isMaximizedFull()) {
756                   win->maximize(0); // unmaximize
757                   win->maximize(2); // vert
758                 } else if (win->isMaximizedHoriz()) {
759                   win->maximize(0); // unmaximize
760                 }
761               } else if (state[i] ==
762                          xatom->atom(otk::OBProperty::net_wm_state_shaded)) {
763                 if (win->isShaded())
764                   win->shade();
765               } else if (state[i] ==
766                          xatom->atom(otk::OBProperty::net_wm_state_skip_taskbar)) {
767                 win->setSkipTaskbar(False);
768               } else if (state[i] ==
769                          xatom->atom(otk::OBProperty::net_wm_state_skip_pager)) {
770                 win->setSkipPager(False);
771               } else if (state[i] ==
772                          xatom->atom(otk::OBProperty::net_wm_state_fullscreen)) {
773                 win->setFullscreen(False);
774               }
775             } else if (action == 2) {
776               // TOGGLE
777               if (state[i] == xatom->atom(otk::OBProperty::net_wm_state_modal)) {
778                 win->setModal(! win->isModal());
779               } else if (state[i] ==
780                          xatom->atom(otk::OBProperty::net_wm_state_maximized_vert)) {
781                 if (win->isMaximizedFull()) {
782                   win->maximize(0); // unmaximize
783                   win->maximize(3); // horiz
784                 } else if (win->isMaximizedVert()) {
785                   win->maximize(0); // unmaximize
786                 } else if (win->isMaximizedHoriz()) {
787                   win->maximize(0); // unmaximize
788                   win->maximize(1); // full
789                 } else {
790                   win->maximize(2); // vert
791                 }
792               } else if (state[i] ==
793                          xatom->atom(otk::OBProperty::net_wm_state_maximized_horz)) {
794                 if (win->isMaximizedFull()) {
795                   win->maximize(0); // unmaximize
796                   win->maximize(2); // vert
797                 } else if (win->isMaximizedHoriz()) {
798                   win->maximize(0); // unmaximize
799                 } else if (win->isMaximizedVert()) {
800                   win->maximize(0); // unmaximize
801                   win->maximize(1); // full
802                 } else {
803                   win->maximize(3); // horiz
804                 }
805               } else if (state[i] ==
806                          xatom->atom(otk::OBProperty::net_wm_state_shaded)) {
807                 win->shade();
808               } else if (state[i] ==
809                          xatom->atom(otk::OBProperty::net_wm_state_skip_taskbar)) {
810                 win->setSkipTaskbar(! win->skipTaskbar());
811               } else if (state[i] ==
812                          xatom->atom(otk::OBProperty::net_wm_state_skip_pager)) {
813                 win->setSkipPager(! win->skipPager());
814               } else if (state[i] ==
815                          xatom->atom(otk::OBProperty::net_wm_state_fullscreen)) {
816                 win->setFullscreen(! win->isFullscreen());
817               }
818             }
819           }
820         }
821       }
822     }
823
824     break;
825   }
826
827   case NoExpose:
828   case ConfigureNotify:
829   case MapNotify:
830     break; // not handled, just ignore
831
832   default: {
833 #ifdef    SHAPE
834     if (e->type == otk::OBDisplay::shapeEventBase()) {
835       XShapeEvent *shape_event = (XShapeEvent *) e;
836       BlackboxWindow *win = searchWindow(e->xany.window);
837
838       if (win && shape_event->kind == ShapeBounding)
839         win->shapeEvent(shape_event);
840     }
841 #endif // SHAPE
842   }
843   } // switch
844 }
845
846
847 bool Blackbox::handleSignal(int sig) {
848   switch (sig) {
849   case SIGHUP:
850     reconfigure();
851     break;
852
853   case SIGUSR1:
854     restart();
855     break;
856
857   case SIGUSR2:
858     break;
859
860   case SIGPIPE:
861   case SIGSEGV:
862   case SIGFPE:
863   case SIGINT:
864   case SIGTERM:
865     shutdown();
866
867   default:
868     return False;
869   }
870
871   return True;
872 }
873
874
875 bool Blackbox::validateWindow(Window window) {
876   XEvent event;
877   if (XCheckTypedWindowEvent(otk::OBDisplay::display, window, DestroyNotify, &event)) {
878     XPutBackEvent(otk::OBDisplay::display, &event);
879
880     return False;
881   }
882
883   return True;
884 }
885
886
887 BScreen *Blackbox::searchScreen(Window window) {
888   ScreenList::iterator it = screenList.begin();
889
890   for (; it != screenList.end(); ++it) {
891     BScreen *s = *it;
892     if (s->getRootWindow() == window)
893       return s;
894   }
895
896   return (BScreen *) 0;
897 }
898
899
900 BScreen *Blackbox::searchSystrayWindow(Window window) {
901   WindowScreenLookup::iterator it = systraySearchList.find(window);
902   if (it != systraySearchList.end())
903     return it->second;
904
905   return (BScreen*) 0;
906 }
907
908
909 BlackboxWindow *Blackbox::searchWindow(Window window) {
910   WindowLookup::iterator it = windowSearchList.find(window);
911   if (it != windowSearchList.end())
912     return it->second;
913
914   return (BlackboxWindow*) 0;
915 }
916
917
918 BWindowGroup *Blackbox::searchGroup(Window window) {
919   GroupLookup::iterator it = groupSearchList.find(window);
920   if (it != groupSearchList.end())
921     return it->second;
922
923   return (BWindowGroup *) 0;
924 }
925
926
927 void Blackbox::saveSystrayWindowSearch(Window window, BScreen *screen) {
928   systraySearchList.insert(WindowScreenLookupPair(window, screen));
929 }
930
931
932 void Blackbox::saveWindowSearch(Window window, BlackboxWindow *data) {
933   windowSearchList.insert(WindowLookupPair(window, data));
934 }
935
936
937 void Blackbox::saveGroupSearch(Window window, BWindowGroup *data) {
938   groupSearchList.insert(GroupLookupPair(window, data));
939 }
940
941
942 void Blackbox::removeSystrayWindowSearch(Window window) {
943   systraySearchList.erase(window);
944 }
945
946
947 void Blackbox::removeWindowSearch(Window window) {
948   windowSearchList.erase(window);
949 }
950
951
952 void Blackbox::removeGroupSearch(Window window) {
953   groupSearchList.erase(window);
954 }
955
956
957 void Blackbox::restart(const char *prog) {
958   shutdown();
959
960   if (prog) {
961     putenv(const_cast<char *>(screenList.front()->displayString().c_str()));
962     execlp(prog, prog, NULL);
963     perror(prog);
964   }
965
966   // fall back in case the above execlp doesn't work
967   execvp(argv[0], argv);
968   string name = ::basename(argv[0]);
969   execvp(name.c_str(), argv);
970 }
971
972
973 void Blackbox::shutdown(void) {
974   Openbox::shutdown();
975
976   XSetInputFocus(otk::OBDisplay::display, PointerRoot, None, CurrentTime);
977
978   std::for_each(screenList.begin(), screenList.end(),
979                 std::mem_fun(&BScreen::shutdown));
980
981   XSync(otk::OBDisplay::display, False);
982 }
983
984
985 #ifdef    XINERAMA
986 void Blackbox::saveXineramaPlacement(bool x) {
987   resource.xinerama_placement = x;
988   config.setValue("session.xineramaSupport.windowPlacement",
989                   resource.xinerama_placement);
990   reconfigure();  // make sure all screens get this 
991
992 void Blackbox::saveXineramaMaximizing(bool x) {
993   resource.xinerama_maximize = x;
994   config.setValue("session.xineramaSupport.windowMaximizing",
995                   resource.xinerama_maximize);
996   reconfigure();  // make sure all screens get this change
997 }
998
999
1000 void Blackbox::saveXineramaSnapping(bool x) {
1001   resource.xinerama_snap = x;
1002   config.setValue("session.xineramaSupport.windowSnapping",
1003                   resource.xinerama_snap);
1004   reconfigure();  // make sure all screens get this change
1005 }
1006 #endif // XINERAMA
1007
1008   
1009 /*
1010  * Save all values as they are so that the defaults will be written to the rc
1011  * file
1012  */
1013 void Blackbox::save_rc(void) {
1014   config.setAutoSave(false);
1015
1016   config.setValue("session.colorsPerChannel", resource.colors_per_channel);
1017   config.setValue("session.doubleClickInterval",
1018                   resource.double_click_interval);
1019   config.setValue("session.autoRaiseDelay",
1020                   ((resource.auto_raise_delay.tv_sec * 1000) +
1021                    (resource.auto_raise_delay.tv_usec / 1000)));
1022   config.setValue("session.cacheLife", resource.cache_life / 60000);
1023   config.setValue("session.cacheMax", resource.cache_max);
1024   config.setValue("session.styleFile", resource.style_file);
1025   config.setValue("session.titlebarLayout", resource.titlebar_layout);
1026
1027   string s;
1028   if (resource.mod_mask & Mod1Mask) s += "Mod1-";
1029   if (resource.mod_mask & Mod2Mask) s += "Mod2-";
1030   if (resource.mod_mask & Mod3Mask) s += "Mod3-";
1031   if (resource.mod_mask & Mod4Mask) s += "Mod4-";
1032   if (resource.mod_mask & Mod5Mask) s += "Mod5-";
1033   if (resource.mod_mask & ShiftMask) s += "Shift-";
1034   if (resource.mod_mask & ControlMask) s += "Control-";
1035   s.resize(s.size() - 1); // drop the last '-'
1036   config.setValue("session.modifierMask", s);
1037   
1038 #ifdef    XINERAMA
1039   saveXineramaPlacement(resource.xinerama_placement);
1040   saveXineramaMaximizing(resource.xinerama_maximize);
1041   saveXineramaSnapping(resource.xinerama_snap);
1042 #endif // XINERAMA
1043
1044   std::for_each(screenList.begin(), screenList.end(),
1045                 std::mem_fun(&BScreen::save_rc));
1046  
1047   config.setAutoSave(true);
1048   config.save();
1049 }
1050
1051
1052 void Blackbox::load_rc(void) {
1053   if (! config.load())
1054     config.create();
1055   
1056   string s;
1057
1058   if (! config.getValue("session.colorsPerChannel",
1059                         resource.colors_per_channel))
1060     resource.colors_per_channel = 4;
1061   if (resource.colors_per_channel < 2) resource.colors_per_channel = 2;
1062   else if (resource.colors_per_channel > 6) resource.colors_per_channel = 6;
1063
1064   if (config.getValue("session.styleFile", s))
1065     resource.style_file = otk::expandTilde(s);
1066   else
1067     resource.style_file = DEFAULTSTYLE;
1068
1069   if (! config.getValue("session.doubleClickInterval",
1070                        resource.double_click_interval));
1071     resource.double_click_interval = 250;
1072
1073   if (! config.getValue("session.autoRaiseDelay",
1074                        resource.auto_raise_delay.tv_usec))
1075     resource.auto_raise_delay.tv_usec = 400;
1076   resource.auto_raise_delay.tv_sec = resource.auto_raise_delay.tv_usec / 1000;
1077   resource.auto_raise_delay.tv_usec -=
1078     (resource.auto_raise_delay.tv_sec * 1000);
1079   resource.auto_raise_delay.tv_usec *= 1000;
1080
1081   if (! config.getValue("session.cacheLife", resource.cache_life))
1082     resource.cache_life = 5;
1083   resource.cache_life *= 60000;
1084
1085   if (! config.getValue("session.cacheMax", resource.cache_max))
1086     resource.cache_max = 200;
1087   
1088   if (! config.getValue("session.titlebarLayout", resource.titlebar_layout))
1089     resource.titlebar_layout = "ILMC";
1090
1091 #ifdef    XINERAMA
1092   if (! config.getValue("session.xineramaSupport.windowPlacement",
1093                         resource.xinerama_placement))
1094     resource.xinerama_placement = false;
1095
1096   if (! config.getValue("session.xineramaSupport.windowMaximizing",
1097                         resource.xinerama_maximize))
1098     resource.xinerama_maximize = false;
1099
1100   if (! config.getValue("session.xineramaSupport.windowSnapping",
1101                         resource.xinerama_snap))
1102     resource.xinerama_snap = false;
1103 #endif // XINERAMA
1104   
1105   resource.mod_mask = 0;
1106   if (config.getValue("session.modifierMask", s)) {
1107     if (s.find("Mod1") != string::npos)
1108       resource.mod_mask |= Mod1Mask;
1109     if (s.find("Mod2") != string::npos)
1110       resource.mod_mask |= Mod2Mask;
1111     if (s.find("Mod3") != string::npos)
1112       resource.mod_mask |= Mod3Mask;
1113     if (s.find("Mod4") != string::npos)
1114       resource.mod_mask |= Mod4Mask;
1115     if (s.find("Mod5") != string::npos)
1116       resource.mod_mask |= Mod5Mask;
1117     if (s.find("Shift") != string::npos)
1118       resource.mod_mask |= ShiftMask;
1119     if (s.find("Control") != string::npos)
1120       resource.mod_mask |= ControlMask;
1121   }
1122   if (! resource.mod_mask)
1123     resource.mod_mask = Mod1Mask;
1124 }
1125
1126
1127 void Blackbox::reconfigure(void) {
1128   // don't reconfigure while saving the initial rc file, it's a waste and it
1129   // breaks somethings (workspace names)
1130   if (state() == Openbox::State_Starting) return;
1131
1132   reconfigure_wait = True;
1133
1134   if (! timer->timing()) timer->start();
1135 }
1136
1137
1138 void Blackbox::real_reconfigure(void) {
1139   load_rc();
1140   
1141   otk::OBDisplay::gcCache()->purge();
1142
1143   std::for_each(screenList.begin(), screenList.end(),
1144                 std::mem_fun(&BScreen::reconfigure));
1145 }
1146
1147
1148 void Blackbox::saveStyleFilename(const string& filename) {
1149   assert(! filename.empty());
1150   resource.style_file = filename;
1151   config.setValue("session.styleFile", resource.style_file);
1152 }
1153
1154
1155 void Blackbox::timeout(Blackbox *t) {
1156   if (t->reconfigure_wait)
1157     t->real_reconfigure();
1158
1159   t->reconfigure_wait = False;
1160 }
1161
1162
1163 void Blackbox::setChangingWindow(BlackboxWindow *win) {
1164   // make sure one of the two is null and the other isn't
1165   assert((! changing_window && win) || (! win && changing_window));
1166   changing_window = win;
1167 }
1168
1169
1170 void Blackbox::setFocusedWindow(BlackboxWindow *win) {
1171   if (focused_window && focused_window == win) // nothing to do
1172     return;
1173
1174   BScreen *old_screen = 0;
1175
1176   if (focused_window) {
1177     focused_window->setFocusFlag(False);
1178     old_screen = focused_window->getScreen();
1179   }
1180
1181   if (win && ! win->isIconic()) {
1182     // the active screen is the one with the last focused window...
1183     // this will keep focus on this screen no matter where the mouse goes,
1184     // so multihead keybindings will continue to work on that screen until the
1185     // user focuses a window on a different screen.
1186     active_screen = win->getScreen();
1187     focused_window = win;
1188   } else {
1189     focused_window = 0;
1190     if (! old_screen) {
1191       if (active_screen) {
1192         // set input focus to the toolbar of the screen with mouse
1193         XSetInputFocus(otk::OBDisplay::display,
1194                        active_screen->getRootWindow(),
1195                        RevertToPointerRoot, CurrentTime);
1196       } else {
1197         // set input focus to the toolbar of the first managed screen
1198         XSetInputFocus(otk::OBDisplay::display,
1199                        screenList.front()->getRootWindow(),
1200                        RevertToPointerRoot, CurrentTime);
1201       }
1202     } else {
1203       // set input focus to the toolbar of the last screen
1204       XSetInputFocus(otk::OBDisplay::display, old_screen->getRootWindow(),
1205                      RevertToPointerRoot, CurrentTime);
1206     }
1207   }
1208
1209   if (active_screen && active_screen->isScreenManaged()) {
1210     active_screen->updateNetizenWindowFocus();
1211   }
1212
1213   if (old_screen && old_screen != active_screen) {
1214     old_screen->updateNetizenWindowFocus();
1215   }
1216 }
1217
1218
1219