]> icculus.org git repositories - dana/openbox.git/blob - src/client.cc
sync after sending focus
[dana/openbox.git] / src / client.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #ifdef HAVE_CONFIG_H
4 # include "../config.h"
5 #endif
6
7 #include "client.hh"
8 #include "frame.hh"
9 #include "screen.hh"
10 #include "openbox.hh"
11 #include "bindings.hh"
12 #include "otk/display.hh"
13 #include "otk/property.hh"
14
15 extern "C" {
16 #include <X11/Xlib.h>
17 #include <X11/Xutil.h>
18 #include <X11/Xatom.h>
19
20 #include <assert.h>
21
22 #include "gettext.h"
23 #define _(str) gettext(str)
24 }
25
26 #include <algorithm>
27
28 namespace ob {
29
30 Client::Client(int screen, Window window)
31   : otk::EventHandler(),
32     WidgetBase(WidgetBase::Type_Client),
33     frame(0), _screen(screen), _window(window)
34 {
35   assert(screen >= 0);
36   assert(window);
37
38   ignore_unmaps = 0;
39   
40   // update EVERYTHING the first time!!
41
42   // we default to NormalState, visible
43   _wmstate = NormalState;
44   // start unfocused
45   _focused = false;
46   // not a transient by default of course
47   _transient_for = 0;
48   // pick a layer to start from
49   _layer = Layer_Normal;
50   // default to not urgent
51   _urgent = false;
52   
53   getArea();
54   getDesktop();
55
56   updateTransientFor();
57   getType();
58   getMwmHints();
59
60   getState();
61   getShaped();
62
63   updateProtocols();
64
65   // got the type, the mwmhints, and the protocols, so we're ready to set up
66   // the decorations/functions
67   setupDecorAndFunctions();
68   
69   getGravity();        // get the attribute gravity
70   updateNormalHints(); // this may override the attribute gravity
71   // also get the initial_state and set _iconic if we aren't "starting"
72   // when we're "starting" that means we should use whatever state was already
73   // on the window over the initial map state, because it was already mapped
74   updateWMHints(openbox->state() != Openbox::State_Starting);
75   updateTitle();
76   updateIconTitle();
77   updateClass();
78   updateStrut();
79
80   // this makes sure that these windows appear on all desktops
81   if (/*_type == Type_Dock ||*/ _type == Type_Desktop)
82     _desktop = 0xffffffff;
83   
84   // set the desktop hint, to make sure that it always exists, and to reflect
85   // any changes we've made here
86   otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
87                      otk::Property::atoms.cardinal, (unsigned)_desktop);
88   
89   changeState();
90 }
91
92
93 Client::~Client()
94 {
95   // clean up childrens' references
96   while (!_transients.empty()) {
97     _transients.front()->_transient_for = 0;
98     _transients.pop_front();
99   }
100   
101   // clean up parents reference to this
102   if (_transient_for)
103     _transient_for->_transients.remove(this); // remove from old parent
104   
105   if (openbox->state() != Openbox::State_Exiting) {
106     // these values should not be persisted across a window unmapping/mapping
107     otk::Property::erase(_window, otk::Property::atoms.net_wm_desktop);
108     otk::Property::erase(_window, otk::Property::atoms.net_wm_state);
109   } else {
110     // if we're left in an iconic state, the client wont be mapped. this is
111     // bad, since we will no longer be managing the window on restart
112     if (_iconic)
113       XMapWindow(**otk::display, _window);
114   }
115 }
116
117
118 void Client::getGravity()
119 {
120   XWindowAttributes wattrib;
121   Status ret;
122
123   ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
124   assert(ret != BadWindow);
125   _gravity = wattrib.win_gravity;
126 }
127
128
129 void Client::getDesktop()
130 {
131   // defaults to the current desktop
132   _desktop = openbox->screen(_screen)->desktop();
133
134   if (otk::Property::get(_window, otk::Property::atoms.net_wm_desktop,
135                          otk::Property::atoms.cardinal,
136                          (long unsigned*)&_desktop)) {
137 #ifdef DEBUG
138 //    printf("Window requested desktop: %ld\n", _desktop);
139 #endif
140   }
141 }
142
143
144 void Client::getType()
145 {
146   _type = (WindowType) -1;
147   
148   unsigned long *val;
149   unsigned long num = (unsigned) -1;
150   if (otk::Property::get(_window, otk::Property::atoms.net_wm_window_type,
151                          otk::Property::atoms.atom, &num, &val)) {
152     // use the first value that we know about in the array
153     for (unsigned long i = 0; i < num; ++i) {
154       if (val[i] == otk::Property::atoms.net_wm_window_type_desktop)
155         _type = Type_Desktop;
156       else if (val[i] == otk::Property::atoms.net_wm_window_type_dock)
157         _type = Type_Dock;
158       else if (val[i] == otk::Property::atoms.net_wm_window_type_toolbar)
159         _type = Type_Toolbar;
160       else if (val[i] == otk::Property::atoms.net_wm_window_type_menu)
161         _type = Type_Menu;
162       else if (val[i] == otk::Property::atoms.net_wm_window_type_utility)
163         _type = Type_Utility;
164       else if (val[i] == otk::Property::atoms.net_wm_window_type_splash)
165         _type = Type_Splash;
166       else if (val[i] == otk::Property::atoms.net_wm_window_type_dialog)
167         _type = Type_Dialog;
168       else if (val[i] == otk::Property::atoms.net_wm_window_type_normal)
169         _type = Type_Normal;
170 //    XXX: make this work again
171 //    else if (val[i] == otk::Property::atoms.kde_net_wm_window_type_override)
172 //      mwm_decorations = 0; // prevent this window from getting any decor
173       if (_type != (WindowType) -1)
174         break; // grab the first known type
175     }
176     delete val;
177   }
178     
179   if (_type == (WindowType) -1) {
180     /*
181      * the window type hint was not set, which means we either classify ourself
182      * as a normal window or a dialog, depending on if we are a transient.
183      */
184     if (_transient_for)
185       _type = Type_Dialog;
186     else
187       _type = Type_Normal;
188   }
189 }
190
191
192 void Client::setupDecorAndFunctions()
193 {
194   // start with everything (cept fullscreen)
195   _decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
196     Decor_AllDesktops | Decor_Iconify | Decor_Maximize;
197   _functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize |
198     Func_Shade;
199   if (_delete_window) {
200     _decorations |= Decor_Close;
201     _functions |= Func_Close;
202   }
203
204   if (_min_size.x() > _max_size.x() || _min_size.y() > _max_size.y()) {
205     _decorations &= ~Decor_Maximize;
206     _functions &= ~(Func_Resize | Func_Maximize);
207   }
208   
209   switch (_type) {
210   case Type_Normal:
211     // normal windows retain all of the possible decorations and
212     // functionality, and are the only windows that you can fullscreen
213     _functions |= Func_Fullscreen;
214
215   case Type_Dialog:
216     // dialogs cannot be maximized
217     _decorations &= ~Decor_Maximize;
218     _functions &= ~Func_Maximize;
219     break;
220
221   case Type_Menu:
222   case Type_Toolbar:
223   case Type_Utility:
224     // these windows get less functionality
225     _decorations &= ~(Decor_Iconify | Decor_Handle);
226     _functions &= ~(Func_Iconify | Func_Resize);
227     break;
228
229   case Type_Desktop:
230   case Type_Dock:
231   case Type_Splash:
232     // none of these windows are manipulated by the window manager
233     _decorations = 0;
234     _functions = 0;
235     break;
236   }
237
238   // Mwm Hints are applied subtractively to what has already been chosen for
239   // decor and functionality
240   if (_mwmhints.flags & MwmFlag_Decorations) {
241     if (! (_mwmhints.decorations & MwmDecor_All)) {
242       if (! (_mwmhints.decorations & MwmDecor_Border))
243         _decorations &= ~Decor_Border;
244       if (! (_mwmhints.decorations & MwmDecor_Handle))
245         _decorations &= ~Decor_Handle;
246       if (! (_mwmhints.decorations & MwmDecor_Title)) {
247         _decorations &= ~Decor_Titlebar;
248         // if we don't have a titlebar, then we cannot shade!
249         _functions &= ~Func_Shade;
250       }
251       if (! (_mwmhints.decorations & MwmDecor_Iconify))
252         _decorations &= ~Decor_Iconify;
253       if (! (_mwmhints.decorations & MwmDecor_Maximize))
254         _decorations &= ~Decor_Maximize;
255     }
256   }
257
258   if (_mwmhints.flags & MwmFlag_Functions) {
259     if (! (_mwmhints.functions & MwmFunc_All)) {
260       if (! (_mwmhints.functions & MwmFunc_Resize))
261         _functions &= ~Func_Resize;
262       if (! (_mwmhints.functions & MwmFunc_Move))
263         _functions &= ~Func_Move;
264       if (! (_mwmhints.functions & MwmFunc_Iconify))
265         _functions &= ~Func_Iconify;
266       if (! (_mwmhints.functions & MwmFunc_Maximize))
267         _functions &= ~Func_Maximize;
268       // dont let mwm hints kill the close button
269       //if (! (_mwmhints.functions & MwmFunc_Close))
270       //  _functions &= ~Func_Close;
271     }
272   }
273
274   changeAllowedActions();
275 }
276
277
278 void Client::getMwmHints()
279 {
280   unsigned long num = MwmHints::elements;
281   unsigned long *hints;
282
283   _mwmhints.flags = 0; // default to none
284   
285   if (!otk::Property::get(_window, otk::Property::atoms.motif_wm_hints,
286                           otk::Property::atoms.motif_wm_hints, &num,
287                           (unsigned long **)&hints))
288     return;
289   
290   if (num >= MwmHints::elements) {
291     // retrieved the hints
292     _mwmhints.flags = hints[0];
293     _mwmhints.functions = hints[1];
294     _mwmhints.decorations = hints[2];
295   }
296
297   delete [] hints;
298 }
299
300
301 void Client::getArea()
302 {
303   XWindowAttributes wattrib;
304   Status ret;
305   
306   ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
307   assert(ret != BadWindow);
308
309   _area.setRect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
310   _border_width = wattrib.border_width;
311 }
312
313
314 void Client::getState()
315 {
316   _modal = _shaded = _max_horz = _max_vert = _fullscreen = _above = _below =
317     _iconic = _skip_taskbar = _skip_pager = false;
318   
319   unsigned long *state;
320   unsigned long num = (unsigned) -1;
321   
322   if (otk::Property::get(_window, otk::Property::atoms.net_wm_state,
323                          otk::Property::atoms.atom, &num, &state)) {
324     for (unsigned long i = 0; i < num; ++i) {
325       if (state[i] == otk::Property::atoms.net_wm_state_modal)
326         _modal = true;
327       else if (state[i] == otk::Property::atoms.net_wm_state_shaded)
328         _shaded = true;
329       else if (state[i] == otk::Property::atoms.net_wm_state_hidden)
330         _iconic = true;
331       else if (state[i] == otk::Property::atoms.net_wm_state_skip_taskbar)
332         _skip_taskbar = true;
333       else if (state[i] == otk::Property::atoms.net_wm_state_skip_pager)
334         _skip_pager = true;
335       else if (state[i] == otk::Property::atoms.net_wm_state_fullscreen)
336         _fullscreen = true;
337       else if (state[i] == otk::Property::atoms.net_wm_state_maximized_vert)
338         _max_vert = true;
339       else if (state[i] == otk::Property::atoms.net_wm_state_maximized_horz)
340         _max_horz = true;
341       else if (state[i] == otk::Property::atoms.net_wm_state_above)
342         _above = true;
343       else if (state[i] == otk::Property::atoms.net_wm_state_below)
344         _below = true;
345     }
346
347     delete [] state;
348   }
349 }
350
351
352 void Client::getShaped()
353 {
354   _shaped = false;
355 #ifdef   SHAPE
356   if (otk::display->shape()) {
357     int foo;
358     unsigned int ufoo;
359     int s;
360
361     XShapeSelectInput(**otk::display, _window, ShapeNotifyMask);
362
363     XShapeQueryExtents(**otk::display, _window, &s, &foo,
364                        &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo);
365     _shaped = (s != 0);
366   }
367 #endif // SHAPE
368 }
369
370
371 void Client::calcLayer() {
372   StackLayer l;
373
374   if (_iconic) l = Layer_Icon;
375   else if (_fullscreen) l = Layer_Fullscreen;
376   else if (_type == Type_Desktop) l = Layer_Desktop;
377   else if (_type == Type_Dock) {
378     if (!_below) l = Layer_Top;
379     else l = Layer_Normal;
380   }
381   else if (_above) l = Layer_Above;
382   else if (_below) l = Layer_Below;
383   else l = Layer_Normal;
384
385   if (l != _layer) {
386     _layer = l;
387     if (frame) {
388       /*
389         if we don't have a frame, then we aren't mapped yet (and this would
390         SIGSEGV :)
391       */
392       openbox->screen(_screen)->raiseWindow(this);
393     }
394   }
395 }
396
397
398 void Client::updateProtocols()
399 {
400   Atom *proto;
401   int num_return = 0;
402
403   _focus_notify = false;
404   _delete_window = false;
405
406   if (XGetWMProtocols(**otk::display, _window, &proto, &num_return)) {
407     for (int i = 0; i < num_return; ++i) {
408       if (proto[i] == otk::Property::atoms.wm_delete_window) {
409         // this means we can request the window to close
410         _delete_window = true;
411       } else if (proto[i] == otk::Property::atoms.wm_take_focus)
412         // if this protocol is requested, then the window will be notified
413         // by the window manager whenever it receives focus
414         _focus_notify = true;
415     }
416     XFree(proto);
417   }
418 }
419
420
421 void Client::updateNormalHints()
422 {
423   XSizeHints size;
424   long ret;
425   int oldgravity = _gravity;
426
427   // defaults
428   _min_ratio = 0.0;
429   _max_ratio = 0.0;
430   _size_inc.setPoint(1, 1);
431   _base_size.setPoint(0, 0);
432   _min_size.setPoint(0, 0);
433   _max_size.setPoint(INT_MAX, INT_MAX);
434
435   // XXX: might want to cancel any interactive resizing of the window at this
436   // point..
437
438   // get the hints from the window
439   if (XGetWMNormalHints(**otk::display, _window, &size, &ret)) {
440     _positioned = (size.flags & (PPosition|USPosition));
441
442     if (size.flags & PWinGravity) {
443       _gravity = size.win_gravity;
444       
445       // if the client has a frame, i.e. has already been mapped and is
446       // changing its gravity
447       if (frame && _gravity != oldgravity) {
448         // move our idea of the client's position based on its new gravity
449         int x, y;
450         frame->frameGravity(x, y);
451         _area.setPos(x, y);
452       }
453     }
454
455     if (size.flags & PAspect) {
456       if (size.min_aspect.y) _min_ratio = size.min_aspect.x/size.min_aspect.y;
457       if (size.max_aspect.y) _max_ratio = size.max_aspect.x/size.max_aspect.y;
458     }
459
460     if (size.flags & PMinSize)
461       _min_size.setPoint(size.min_width, size.min_height);
462     
463     if (size.flags & PMaxSize)
464       _max_size.setPoint(size.max_width, size.max_height);
465     
466     if (size.flags & PBaseSize)
467       _base_size.setPoint(size.base_width, size.base_height);
468     
469     if (size.flags & PResizeInc)
470       _size_inc.setPoint(size.width_inc, size.height_inc);
471   }
472 }
473
474
475 void Client::updateWMHints(bool initstate)
476 {
477   XWMHints *hints;
478
479   // assume a window takes input if it doesnt specify
480   _can_focus = true;
481   bool ur = false;
482   
483   if ((hints = XGetWMHints(**otk::display, _window)) != NULL) {
484     if (hints->flags & InputHint)
485       _can_focus = hints->input;
486
487     // only do this when initstate is true!
488     if (initstate && (hints->flags & StateHint))
489       _iconic = hints->initial_state == IconicState;
490
491     if (hints->flags & XUrgencyHint)
492       ur = true;
493
494     if (hints->flags & WindowGroupHint) {
495       if (hints->window_group != _group) {
496         // XXX: remove from the old group if there was one
497         _group = hints->window_group;
498         // XXX: do stuff with the group
499       }
500     } else // no group!
501       _group = None;
502
503     XFree(hints);
504   }
505
506   if (ur != _urgent) {
507     _urgent = ur;
508 #ifdef DEBUG
509     printf("DEBUG: Urgent Hint for 0x%lx: %s\n",
510            (long)_window, _urgent ? "ON" : "OFF");
511 #endif
512     // fire the urgent callback if we're mapped, otherwise, wait until after
513     // we're mapped
514     if (frame)
515       fireUrgent();
516   }
517 }
518
519
520 void Client::updateTitle()
521 {
522   _title = "";
523   
524   // try netwm
525   if (!otk::Property::get(_window, otk::Property::atoms.net_wm_name,
526                           otk::Property::utf8, &_title)) {
527     // try old x stuff
528     otk::Property::get(_window, otk::Property::atoms.wm_name,
529                        otk::Property::ascii, &_title);
530   }
531
532   if (_title.empty())
533     _title = _("Unnamed Window");
534
535   if (frame)
536     frame->setTitle(_title);
537 }
538
539
540 void Client::updateIconTitle()
541 {
542   _icon_title = "";
543   
544   // try netwm
545   if (!otk::Property::get(_window, otk::Property::atoms.net_wm_icon_name,
546                           otk::Property::utf8, &_icon_title)) {
547     // try old x stuff
548     otk::Property::get(_window, otk::Property::atoms.wm_icon_name,
549                        otk::Property::ascii, &_icon_title);
550   }
551
552   if (_title.empty())
553     _icon_title = _("Unnamed Window");
554 }
555
556
557 void Client::updateClass()
558 {
559   // set the defaults
560   _app_name = _app_class = _role = "";
561
562   otk::Property::StringVect v;
563   unsigned long num = 2;
564
565   if (otk::Property::get(_window, otk::Property::atoms.wm_class,
566                          otk::Property::ascii, &num, &v)) {
567     if (num > 0) _app_name = v[0].c_str();
568     if (num > 1) _app_class = v[1].c_str();
569   }
570
571   v.clear();
572   num = 1;
573   if (otk::Property::get(_window, otk::Property::atoms.wm_window_role,
574                          otk::Property::ascii, &num, &v)) {
575     if (num > 0) _role = v[0].c_str();
576   }
577 }
578
579
580 void Client::updateStrut()
581 {
582   unsigned long num = 4;
583   unsigned long *data;
584   if (!otk::Property::get(_window, otk::Property::atoms.net_wm_strut,
585                           otk::Property::atoms.cardinal, &num, &data))
586     return;
587
588   if (num == 4) {
589     _strut.left = data[0];
590     _strut.right = data[1];
591     _strut.top = data[2];
592     _strut.bottom = data[3]; 
593    
594     openbox->screen(_screen)->updateStrut();
595   }
596
597   delete [] data;
598 }
599
600
601 void Client::updateTransientFor()
602 {
603   Window t = 0;
604   Client *c = 0;
605
606   if (XGetTransientForHint(**otk::display, _window, &t) &&
607       t != _window) { // cant be transient to itself!
608     c = openbox->findClient(t);
609     assert(c != this); // if this happens then we need to check for it
610
611     if (!c /*XXX: && _group*/) {
612       // not transient to a client, see if it is transient for a group
613       if (//t == _group->leader() ||
614         t == None ||
615         t == otk::display->screenInfo(_screen)->rootWindow()) {
616         // window is a transient for its group!
617         // XXX: for now this is treated as non-transient.
618         //      this needs to be fixed!
619       }
620     }
621   }
622
623   // if anything has changed...
624   if (c != _transient_for) {
625     if (_transient_for)
626       _transient_for->_transients.remove(this); // remove from old parent
627     _transient_for = c;
628     if (_transient_for)
629       _transient_for->_transients.push_back(this); // add to new parent
630
631     // XXX: change decor status?
632   }
633 }
634
635
636 void Client::propertyHandler(const XPropertyEvent &e)
637 {
638   otk::EventHandler::propertyHandler(e);
639   
640   // compress changes to a single property into a single change
641   XEvent ce;
642   while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
643     // XXX: it would be nice to compress ALL changes to a property, not just
644     //      changes in a row without other props between.
645     if (ce.xproperty.atom != e.atom) {
646       XPutBackEvent(**otk::display, &ce);
647       break;
648     }
649   }
650
651   if (e.atom == XA_WM_NORMAL_HINTS) {
652     updateNormalHints();
653     setupDecorAndFunctions(); // normal hints can make a window non-resizable
654   } else if (e.atom == XA_WM_HINTS)
655     updateWMHints();
656   else if (e.atom == XA_WM_TRANSIENT_FOR) {
657     updateTransientFor();
658     getType();
659     calcLayer(); // type may have changed, so update the layer
660     setupDecorAndFunctions();
661     frame->adjustSize(); // this updates the frame for any new decor settings
662   }
663   else if (e.atom == otk::Property::atoms.net_wm_name ||
664            e.atom == otk::Property::atoms.wm_name)
665     updateTitle();
666   else if (e.atom == otk::Property::atoms.net_wm_icon_name ||
667            e.atom == otk::Property::atoms.wm_icon_name)
668     updateIconTitle();
669   else if (e.atom == otk::Property::atoms.wm_class)
670     updateClass();
671   else if (e.atom == otk::Property::atoms.wm_protocols) {
672     updateProtocols();
673     setupDecorAndFunctions();
674     frame->adjustSize(); // update the decorations
675   }
676   else if (e.atom == otk::Property::atoms.net_wm_strut)
677     updateStrut();
678 }
679
680
681 void Client::setWMState(long state)
682 {
683   if (state == _wmstate) return; // no change
684   
685   switch (state) {
686   case IconicState:
687     setDesktop(ICONIC_DESKTOP);
688     break;
689   case NormalState:
690     setDesktop(openbox->screen(_screen)->desktop());
691     break;
692   }
693 }
694
695
696 void Client::setDesktop(long target)
697 {
698   if (target == _desktop) return;
699   
700   printf("Setting desktop %ld\n", target);
701
702   if (!(target >= 0 || target == (signed)0xffffffff ||
703         target == ICONIC_DESKTOP))
704     return;
705   
706   _desktop = target;
707
708   // set the desktop hint, but not if we're iconifying
709   if (_desktop != ICONIC_DESKTOP)
710     otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
711                        otk::Property::atoms.cardinal, (unsigned)_desktop);
712   
713   // 'move' the window to the new desktop
714   if (_desktop == openbox->screen(_screen)->desktop() ||
715       _desktop == (signed)0xffffffff)
716     frame->show();
717   else
718     frame->hide();
719
720   // Handle Iconic state. Iconic state is maintained by the client being a
721   // member of the ICONIC_DESKTOP, so this is where we make iconifying and
722   // uniconifying happen.
723   bool i = _desktop == ICONIC_DESKTOP;
724   if (i != _iconic) { // has the state changed?
725     _iconic = i;
726     if (_iconic) {
727       _wmstate = IconicState;
728       ignore_unmaps++;
729       // we unmap the client itself so that we can get MapRequest events, and
730       // because the ICCCM tells us to!
731       XUnmapWindow(**otk::display, _window);
732     } else {
733       _wmstate = NormalState;
734       XMapWindow(**otk::display, _window);
735     }
736     changeState();
737   }
738   
739   frame->adjustState();
740 }
741
742
743 void Client::setState(StateAction action, long data1, long data2)
744 {
745   bool shadestate = _shaded;
746   bool fsstate = _fullscreen;
747
748   if (!(action == State_Add || action == State_Remove ||
749         action == State_Toggle))
750     return; // an invalid action was passed to the client message, ignore it
751
752   for (int i = 0; i < 2; ++i) {
753     Atom state = i == 0 ? data1 : data2;
754     
755     if (! state) continue;
756
757     // if toggling, then pick whether we're adding or removing
758     if (action == State_Toggle) {
759       if (state == otk::Property::atoms.net_wm_state_modal)
760         action = _modal ? State_Remove : State_Add;
761       else if (state == otk::Property::atoms.net_wm_state_maximized_vert)
762         action = _max_vert ? State_Remove : State_Add;
763       else if (state == otk::Property::atoms.net_wm_state_maximized_horz)
764         action = _max_horz ? State_Remove : State_Add;
765       else if (state == otk::Property::atoms.net_wm_state_shaded)
766         action = _shaded ? State_Remove : State_Add;
767       else if (state == otk::Property::atoms.net_wm_state_skip_taskbar)
768         action = _skip_taskbar ? State_Remove : State_Add;
769       else if (state == otk::Property::atoms.net_wm_state_skip_pager)
770         action = _skip_pager ? State_Remove : State_Add;
771       else if (state == otk::Property::atoms.net_wm_state_fullscreen)
772         action = _fullscreen ? State_Remove : State_Add;
773       else if (state == otk::Property::atoms.net_wm_state_above)
774         action = _above ? State_Remove : State_Add;
775       else if (state == otk::Property::atoms.net_wm_state_below)
776         action = _below ? State_Remove : State_Add;
777     }
778     
779     if (action == State_Add) {
780       if (state == otk::Property::atoms.net_wm_state_modal) {
781         if (_modal) continue;
782         _modal = true;
783         // XXX: give it focus if another window has focus that shouldnt now
784       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
785         if (_max_vert) continue;
786         _max_vert = true;
787         // XXX: resize the window etc
788       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
789         if (_max_horz) continue;
790         _max_horz = true;
791         // XXX: resize the window etc
792       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
793         shadestate = true;
794       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
795         _skip_taskbar = true;
796       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
797         _skip_pager = true;
798       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
799         fsstate = true;
800       } else if (state == otk::Property::atoms.net_wm_state_above) {
801         if (_above) continue;
802         _above = true;
803       } else if (state == otk::Property::atoms.net_wm_state_below) {
804         if (_below) continue;
805         _below = true;
806       }
807
808     } else { // action == State_Remove
809       if (state == otk::Property::atoms.net_wm_state_modal) {
810         if (!_modal) continue;
811         _modal = false;
812       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
813         if (!_max_vert) continue;
814         _max_vert = false;
815         // XXX: resize the window etc
816       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
817         if (!_max_horz) continue;
818         _max_horz = false;
819         // XXX: resize the window etc
820       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
821         shadestate = false;
822       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
823         _skip_taskbar = false;
824       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
825         _skip_pager = false;
826       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
827         fsstate = false;
828       } else if (state == otk::Property::atoms.net_wm_state_above) {
829         if (!_above) continue;
830         _above = false;
831       } else if (state == otk::Property::atoms.net_wm_state_below) {
832         if (!_below) continue;
833         _below = false;
834       }
835     }
836   }
837   // change fullscreen state before shading, as it will affect if the window
838   // can shade or not
839   if (fsstate != _fullscreen)
840     fullscreen(fsstate);
841   if (shadestate != _shaded)
842     shade(shadestate);
843   calcLayer();
844 }
845
846
847 void Client::toggleClientBorder(bool addborder)
848 {
849   // adjust our idea of where the client is, based on its border. When the
850   // border is removed, the client should now be considered to be in a
851   // different position.
852   // when re-adding the border to the client, the same operation needs to be
853   // reversed.
854   int x = _area.x(), y = _area.y();
855   switch(_gravity) {
856   default:
857   case NorthWestGravity:
858   case WestGravity:
859   case SouthWestGravity:
860     break;
861   case NorthEastGravity:
862   case EastGravity:
863   case SouthEastGravity:
864     if (addborder) x -= _border_width * 2;
865     else           x += _border_width * 2;
866     break;
867   case NorthGravity:
868   case SouthGravity:
869   case CenterGravity:
870   case ForgetGravity:
871   case StaticGravity:
872     if (addborder) x -= _border_width;
873     else           x += _border_width;
874     break;
875   }
876   switch(_gravity) {
877   default:
878   case NorthWestGravity:
879   case NorthGravity:
880   case NorthEastGravity:
881     break;
882   case SouthWestGravity:
883   case SouthGravity:
884   case SouthEastGravity:
885     if (addborder) y -= _border_width * 2;
886     else           y += _border_width * 2;
887     break;
888   case WestGravity:
889   case EastGravity:
890   case CenterGravity:
891   case ForgetGravity:
892   case StaticGravity:
893     if (addborder) y -= _border_width;
894     else           y += _border_width;
895     break;
896   }
897   _area.setPos(x, y);
898
899   if (addborder) {
900     XSetWindowBorderWidth(**otk::display, _window, _border_width);
901
902     // move the client so it is back it the right spot _with_ its border!
903     XMoveWindow(**otk::display, _window, x, y);
904   } else
905     XSetWindowBorderWidth(**otk::display, _window, 0);
906 }
907
908
909 void Client::clientMessageHandler(const XClientMessageEvent &e)
910 {
911   otk::EventHandler::clientMessageHandler(e);
912   
913   if (e.format != 32) return;
914
915   if (e.message_type == otk::Property::atoms.wm_change_state) {
916     // compress changes into a single change
917     bool compress = false;
918     XEvent ce;
919     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
920       // XXX: it would be nice to compress ALL messages of a type, not just
921       //      messages in a row without other message types between.
922       if (ce.xclient.message_type != e.message_type) {
923         XPutBackEvent(**otk::display, &ce);
924         break;
925       }
926       compress = true;
927     }
928     if (compress)
929       setWMState(ce.xclient.data.l[0]); // use the found event
930     else
931       setWMState(e.data.l[0]); // use the original event
932   } else if (e.message_type == otk::Property::atoms.net_wm_desktop) {
933     // compress changes into a single change 
934     bool compress = false;
935     XEvent ce;
936     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
937       // XXX: it would be nice to compress ALL messages of a type, not just
938       //      messages in a row without other message types between.
939       if (ce.xclient.message_type != e.message_type) {
940         XPutBackEvent(**otk::display, &ce);
941         break;
942       }
943       compress = true;
944     }
945     if (compress)
946       setDesktop(e.data.l[0]); // use the found event
947     else
948       setDesktop(e.data.l[0]); // use the original event
949   } else if (e.message_type == otk::Property::atoms.net_wm_state) {
950     // can't compress these
951 #ifdef DEBUG
952     printf("net_wm_state %s %ld %ld for 0x%lx\n",
953            (e.data.l[0] == 0 ? "Remove" : e.data.l[0] == 1 ? "Add" :
954             e.data.l[0] == 2 ? "Toggle" : "INVALID"),
955            e.data.l[1], e.data.l[2], _window);
956 #endif
957     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
958   } else if (e.message_type == otk::Property::atoms.net_close_window) {
959 #ifdef DEBUG
960     printf("net_close_window for 0x%lx\n", _window);
961 #endif
962     close();
963   } else if (e.message_type == otk::Property::atoms.net_active_window) {
964 #ifdef DEBUG
965     printf("net_active_window for 0x%lx\n", _window);
966 #endif
967     if (_iconic)
968       setDesktop(openbox->screen(_screen)->desktop());
969     if (_shaded)
970       shade(false);
971     // XXX: deiconify
972     focus();
973     openbox->screen(_screen)->raiseWindow(this);
974   }
975 }
976
977
978 #if defined(SHAPE)
979 void Client::shapeHandler(const XShapeEvent &e)
980 {
981   otk::EventHandler::shapeHandler(e);
982
983   if (e.kind == ShapeBounding) {
984     _shaped = e.shaped;
985     frame->adjustShape();
986   }
987 }
988 #endif
989
990
991 void Client::resize(Corner anchor, int w, int h)
992 {
993   if (!(_functions & Func_Resize)) return;
994   internal_resize(anchor, w, h);
995 }
996
997
998 void Client::internal_resize(Corner anchor, int w, int h, bool user,
999                              int x, int y)
1000 {
1001   w -= _base_size.x(); 
1002   h -= _base_size.y();
1003
1004   // for interactive resizing. have to move half an increment in each
1005   // direction.
1006   w += _size_inc.x() / 2;
1007   h += _size_inc.y() / 2;
1008
1009   if (user) {
1010     // if this is a user-requested resize, then check against min/max sizes
1011     // and aspect ratios
1012
1013     // smaller than min size or bigger than max size?
1014     if (w < _min_size.x()) w = _min_size.x();
1015     else if (w > _max_size.x()) w = _max_size.x();
1016     if (h < _min_size.y()) h = _min_size.y();
1017     else if (h > _max_size.y()) h = _max_size.y();
1018
1019     // adjust the height ot match the width for the aspect ratios
1020     if (_min_ratio)
1021       if (h * _min_ratio > w) h = static_cast<int>(w / _min_ratio);
1022     if (_max_ratio)
1023       if (h * _max_ratio < w) h = static_cast<int>(w / _max_ratio);
1024   }
1025
1026   // keep to the increments
1027   w /= _size_inc.x();
1028   h /= _size_inc.y();
1029
1030   // you cannot resize to nothing
1031   if (w < 1) w = 1;
1032   if (h < 1) h = 1;
1033   
1034   // store the logical size
1035   _logical_size.setPoint(w, h);
1036
1037   w *= _size_inc.x();
1038   h *= _size_inc.y();
1039
1040   w += _base_size.x();
1041   h += _base_size.y();
1042
1043   if (x == INT_MIN || y == INT_MIN) {
1044     x = _area.x();
1045     y = _area.y();
1046     switch (anchor) {
1047     case TopLeft:
1048       break;
1049     case TopRight:
1050       x -= w - _area.width();
1051       break;
1052     case BottomLeft:
1053       y -= h - _area.height();
1054       break;
1055     case BottomRight:
1056       x -= w - _area.width();
1057       y -= h - _area.height();
1058       break;
1059     }
1060   }
1061
1062   _area.setSize(w, h);
1063
1064   XResizeWindow(**otk::display, _window, w, h);
1065
1066   // resize the frame to match the request
1067   frame->adjustSize();
1068   internal_move(x, y);
1069 }
1070
1071
1072 void Client::move(int x, int y)
1073 {
1074   if (!(_functions & Func_Move)) return;
1075   internal_move(x, y);
1076 }
1077
1078
1079 void Client::internal_move(int x, int y)
1080 {
1081   _area.setPos(x, y);
1082
1083   // move the frame to be in the requested position
1084   if (frame) { // this can be called while mapping, before frame exists
1085     frame->adjustPosition();
1086
1087     // send synthetic configure notify (we don't need to if we aren't mapped
1088     // yet)
1089     XEvent event;
1090     event.type = ConfigureNotify;
1091     event.xconfigure.display = **otk::display;
1092     event.xconfigure.event = _window;
1093     event.xconfigure.window = _window;
1094     event.xconfigure.x = x;
1095     event.xconfigure.y = y;
1096     event.xconfigure.width = _area.width();
1097     event.xconfigure.height = _area.height();
1098     event.xconfigure.border_width = _border_width;
1099     event.xconfigure.above = frame->window();
1100     event.xconfigure.override_redirect = False;
1101     XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1102                StructureNotifyMask, &event);
1103   }
1104 }
1105
1106
1107 void Client::close()
1108 {
1109   XEvent ce;
1110
1111   if (!(_functions & Func_Close)) return;
1112
1113   // XXX: itd be cool to do timeouts and shit here for killing the client's
1114   //      process off
1115   // like... if the window is around after 5 seconds, then the close button
1116   // turns a nice red, and if this function is called again, the client is
1117   // explicitly killed.
1118
1119   ce.xclient.type = ClientMessage;
1120   ce.xclient.message_type =  otk::Property::atoms.wm_protocols;
1121   ce.xclient.display = **otk::display;
1122   ce.xclient.window = _window;
1123   ce.xclient.format = 32;
1124   ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1125   ce.xclient.data.l[1] = CurrentTime;
1126   ce.xclient.data.l[2] = 0l;
1127   ce.xclient.data.l[3] = 0l;
1128   ce.xclient.data.l[4] = 0l;
1129   XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1130 }
1131
1132
1133 void Client::changeState()
1134 {
1135   unsigned long state[2];
1136   state[0] = _wmstate;
1137   state[1] = None;
1138   otk::Property::set(_window, otk::Property::atoms.wm_state,
1139                      otk::Property::atoms.wm_state, state, 2);
1140   
1141   Atom netstate[10];
1142   int num = 0;
1143   if (_modal)
1144     netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1145   if (_shaded)
1146     netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1147   if (_iconic)
1148     netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1149   if (_skip_taskbar)
1150     netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1151   if (_skip_pager)
1152     netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1153   if (_fullscreen)
1154     netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1155   if (_max_vert)
1156     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1157   if (_max_horz)
1158     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1159   if (_above)
1160     netstate[num++] = otk::Property::atoms.net_wm_state_above;
1161   if (_below)
1162     netstate[num++] = otk::Property::atoms.net_wm_state_below;
1163   otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1164                      otk::Property::atoms.atom, netstate, num);
1165
1166   calcLayer();
1167
1168   if (frame)
1169     frame->adjustState();
1170 }
1171
1172
1173 void Client::changeAllowedActions(void)
1174 {
1175   Atom actions[9];
1176   int num = 0;
1177
1178   actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1179
1180   if (_functions & Func_Shade)
1181     actions[num++] = otk::Property::atoms.net_wm_action_shade;
1182   if (_functions & Func_Close)
1183     actions[num++] = otk::Property::atoms.net_wm_action_close;
1184   if (_functions & Func_Move)
1185     actions[num++] = otk::Property::atoms.net_wm_action_move;
1186   if (_functions & Func_Iconify)
1187     actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1188   if (_functions & Func_Resize)
1189     actions[num++] = otk::Property::atoms.net_wm_action_resize;
1190   if (_functions & Func_Fullscreen)
1191     actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1192   if (_functions & Func_Maximize) {
1193     actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1194     actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1195   }
1196
1197   otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1198                      otk::Property::atoms.atom, actions, num);
1199 }
1200
1201
1202 void Client::applyStartupState()
1203 {
1204   // these are in a carefully crafted order..
1205
1206   if (_iconic) {
1207     _iconic = false;
1208     setDesktop(ICONIC_DESKTOP);
1209   }
1210   if (_fullscreen) {
1211     _fullscreen = false;
1212     fullscreen(true);
1213   }
1214   if (_shaded) {
1215     _shaded = false;
1216     shade(true);
1217   }
1218   if (_urgent)
1219     fireUrgent();
1220   
1221   if (_max_vert); // XXX: incomplete
1222   if (_max_horz); // XXX: incomplete
1223
1224   if (_skip_taskbar); // nothing to do for this
1225   if (_skip_pager);   // nothing to do for this
1226   if (_modal);        // nothing to do for this
1227   if (_above);        // nothing to do for this
1228   if (_below);        // nothing to do for this
1229 }
1230
1231
1232 void Client::fireUrgent()
1233 {
1234   // call the python UrgentWindow callbacks
1235   EventData data(_screen, this, EventAction::UrgentWindow, 0);
1236   openbox->bindings()->fireEvent(&data);
1237 }
1238
1239
1240 void Client::shade(bool shade)
1241 {
1242   if (!(_functions & Func_Shade) || // can't
1243       _shaded == shade) return;     // already done
1244
1245   // when we're iconic, don't change the wmstate
1246   if (!_iconic)
1247     _wmstate = shade ? IconicState : NormalState;
1248   _shaded = shade;
1249   changeState();
1250   frame->adjustSize();
1251 }
1252
1253
1254 void Client::fullscreen(bool fs)
1255 {
1256   static FunctionFlags saved_func;
1257   static DecorationFlags saved_decor;
1258   static otk::Rect saved_area;
1259   static otk::Point saved_logical_size;
1260
1261   if (!(_functions & Func_Fullscreen) || // can't
1262       _fullscreen == fs) return;         // already done
1263
1264   _fullscreen = fs;
1265   changeState(); // change the state hints on the client
1266
1267   if (fs) {
1268     // save the functions and remove them
1269     saved_func = _functions;
1270     _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1271     // save the decorations and remove them
1272     saved_decor = _decorations;
1273     _decorations = 0;
1274     // save the area and adjust it (we don't call internal resize here for
1275     // constraints on the size, etc, we just make it fullscreen).
1276     saved_area = _area;
1277     const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1278     _area.setRect(0, 0, info->width(), info->height());
1279     saved_logical_size = _logical_size;
1280     _logical_size.setPoint((info->width() - _base_size.x()) / _size_inc.x(),
1281                            (info->height() - _base_size.y()) / _size_inc.y());
1282   } else {
1283     _functions = saved_func;
1284     _decorations = saved_decor;
1285     _area = saved_area;
1286     _logical_size = saved_logical_size;
1287   }
1288   
1289   changeAllowedActions();  // based on the new _functions
1290   
1291   frame->adjustSize();     // drop/replace the decor's and resize
1292   frame->adjustPosition(); // get (back) in position!
1293
1294   // raise (back) into our stacking layer
1295   openbox->screen(_screen)->raiseWindow(this);
1296
1297   // try focus us when we go into fullscreen mode
1298   if (fs) focus();
1299 }
1300
1301
1302 bool Client::focus()
1303 {
1304   // won't try focus if the client doesn't want it, or if the window isn't
1305   // visible on the screen
1306   if (!(frame->isVisible() && (_can_focus || _focus_notify))) return false;
1307
1308   if (_focused) return true;
1309
1310   // do a check to see if the window has already been unmapped or destroyed
1311   // do this intelligently while watching out for unmaps we've generated
1312   // (ignore_unmaps > 0)
1313   XEvent ev;
1314   if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1315     XPutBackEvent(**otk::display, &ev);
1316     return false;
1317   }
1318   while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1319     if (ignore_unmaps) {
1320       unmapHandler(ev.xunmap);
1321     } else {
1322       XPutBackEvent(**otk::display, &ev);
1323       return false;
1324     }
1325   }
1326
1327   if (_can_focus)
1328     XSetInputFocus(**otk::display, _window,
1329                    RevertToNone, CurrentTime);
1330
1331   if (_focus_notify) {
1332     XEvent ce;
1333     ce.xclient.type = ClientMessage;
1334     ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1335     ce.xclient.display = **otk::display;
1336     ce.xclient.window = _window;
1337     ce.xclient.format = 32;
1338     ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1339     ce.xclient.data.l[1] = openbox->lastTime();
1340     ce.xclient.data.l[2] = 0l;
1341     ce.xclient.data.l[3] = 0l;
1342     ce.xclient.data.l[4] = 0l;
1343     XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1344   }
1345
1346   XSync(**otk::display, False);
1347   return true;
1348 }
1349
1350
1351 void Client::unfocus() const
1352 {
1353   if (!_focused) return;
1354
1355   assert(openbox->focusedClient() == this);
1356   openbox->setFocusedClient(0);
1357 }
1358
1359
1360 void Client::focusHandler(const XFocusChangeEvent &e)
1361 {
1362 #ifdef    DEBUG
1363 //  printf("FocusIn for 0x%lx\n", e.window);
1364 #endif // DEBUG
1365   
1366   otk::EventHandler::focusHandler(e);
1367
1368   frame->focus();
1369   _focused = true;
1370
1371   openbox->setFocusedClient(this);
1372 }
1373
1374
1375 void Client::unfocusHandler(const XFocusChangeEvent &e)
1376 {
1377 #ifdef    DEBUG
1378 //  printf("FocusOut for 0x%lx\n", e.window);
1379 #endif // DEBUG
1380   
1381   otk::EventHandler::unfocusHandler(e);
1382
1383   frame->unfocus();
1384   _focused = false;
1385
1386   if (openbox->focusedClient() == this)
1387     openbox->setFocusedClient(0);
1388 }
1389
1390
1391 void Client::configureRequestHandler(const XConfigureRequestEvent &e)
1392 {
1393 #ifdef    DEBUG
1394   printf("ConfigureRequest for 0x%lx\n", e.window);
1395 #endif // DEBUG
1396   
1397   otk::EventHandler::configureRequestHandler(e);
1398
1399   // if we are iconic (or shaded (fvwm does this)) ignore the event
1400   if (_iconic || _shaded) return;
1401
1402   if (e.value_mask & CWBorderWidth)
1403     _border_width = e.border_width;
1404
1405   // resize, then move, as specified in the EWMH section 7.7
1406   if (e.value_mask & (CWWidth | CWHeight)) {
1407     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1408     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1409
1410     Corner corner;
1411     switch (_gravity) {
1412     case NorthEastGravity:
1413     case EastGravity:
1414       corner = TopRight;
1415       break;
1416     case SouthWestGravity:
1417     case SouthGravity:
1418       corner = BottomLeft;
1419       break;
1420     case SouthEastGravity:
1421       corner = BottomRight;
1422       break;
1423     default:     // NorthWest, Static, etc
1424       corner = TopLeft;
1425     }
1426
1427     // if moving AND resizing ...
1428     if (e.value_mask & (CWX | CWY)) {
1429       int x = (e.value_mask & CWX) ? e.x : _area.x();
1430       int y = (e.value_mask & CWY) ? e.y : _area.y();
1431       internal_resize(corner, w, h, false, x, y);
1432     } else // if JUST resizing...
1433       internal_resize(corner, w, h, false);
1434   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1435     int x = (e.value_mask & CWX) ? e.x : _area.x();
1436     int y = (e.value_mask & CWY) ? e.y : _area.y();
1437     internal_move(x, y);
1438   }
1439
1440   if (e.value_mask & CWStackMode) {
1441     switch (e.detail) {
1442     case Below:
1443     case BottomIf:
1444       openbox->screen(_screen)->lowerWindow(this);
1445       break;
1446
1447     case Above:
1448     case TopIf:
1449     default:
1450       openbox->screen(_screen)->raiseWindow(this);
1451       break;
1452     }
1453   }
1454 }
1455
1456
1457 void Client::unmapHandler(const XUnmapEvent &e)
1458 {
1459   if (ignore_unmaps) {
1460 #ifdef    DEBUG
1461 //  printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1462 #endif // DEBUG
1463     ignore_unmaps--;
1464     return;
1465   }
1466   
1467 #ifdef    DEBUG
1468   printf("UnmapNotify for 0x%lx\n", e.window);
1469 #endif // DEBUG
1470
1471   otk::EventHandler::unmapHandler(e);
1472
1473   // this deletes us etc
1474   openbox->screen(_screen)->unmanageWindow(this);
1475 }
1476
1477
1478 void Client::destroyHandler(const XDestroyWindowEvent &e)
1479 {
1480 #ifdef    DEBUG
1481   printf("DestroyNotify for 0x%lx\n", e.window);
1482 #endif // DEBUG
1483
1484   otk::EventHandler::destroyHandler(e);
1485
1486   // this deletes us etc
1487   openbox->screen(_screen)->unmanageWindow(this);
1488 }
1489
1490
1491 void Client::reparentHandler(const XReparentEvent &e)
1492 {
1493   // this is when the client is first taken captive in the frame
1494   if (e.parent == frame->plate()) return;
1495
1496 #ifdef    DEBUG
1497   printf("ReparentNotify for 0x%lx\n", e.window);
1498 #endif // DEBUG
1499
1500   otk::EventHandler::reparentHandler(e);
1501
1502   /*
1503     This event is quite rare and is usually handled in unmapHandler.
1504     However, if the window is unmapped when the reparent event occurs,
1505     the window manager never sees it because an unmap event is not sent
1506     to an already unmapped window.
1507   */
1508
1509   // we don't want the reparent event, put it back on the stack for the X
1510   // server to deal with after we unmanage the window
1511   XEvent ev;
1512   ev.xreparent = e;
1513   XPutBackEvent(**otk::display, &ev);
1514   
1515   // this deletes us etc
1516   openbox->screen(_screen)->unmanageWindow(this);
1517 }
1518
1519 void Client::mapRequestHandler(const XMapRequestEvent &e)
1520 {
1521 #ifdef    DEBUG
1522   printf("MapRequest for already managed 0x%lx\n", e.window);
1523 #endif // DEBUG
1524
1525   assert(_iconic); // we shouldn't be able to get this unless we're iconic
1526
1527   // move to the current desktop (uniconify)
1528   setDesktop(openbox->screen(_screen)->desktop());
1529   // XXX: should we focus/raise the window? (basically a net_wm_active_window)
1530 }
1531
1532 }