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