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