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