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