]> icculus.org git repositories - mikachu/openbox.git/blob - src/client.cc
keep things in min/max bounds properly
[mikachu/openbox.git] / src / client.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #include "config.h"
4
5 #include "client.hh"
6 #include "frame.hh"
7 #include "screen.hh"
8 #include "openbox.hh"
9 #include "bindings.hh"
10 #include "otk/display.hh"
11 #include "otk/property.hh"
12
13 extern "C" {
14 #include <X11/Xlib.h>
15 #include <X11/Xutil.h>
16 #include <X11/Xatom.h>
17
18 #include "gettext.h"
19 #define _(str) gettext(str)
20 }
21
22 #include <climits>
23 #include <cassert>
24 #include <algorithm>
25
26 namespace ob {
27
28 Client::Client(int screen, Window window)
29   : otk::EventHandler(),
30     frame(0), _screen(screen), _window(window)
31 {
32   assert(screen >= 0);
33   assert(window);
34
35   ignore_unmaps = 0;
36   
37   // update EVERYTHING the first time!!
38
39   // defaults
40   _wmstate = NormalState;
41   _focused = false;
42   _transient_for = 0;
43   _layer = Layer_Normal;
44   _urgent = false;
45   _positioned = false;
46   _disabled_decorations = 0;
47   _modal_child = 0;
48   _group = None;
49   _desktop = 0;
50   
51   getArea();
52   getDesktop();
53   getState();  // do this before updateTransientFor! (for _modal)
54   getShaped();
55
56   updateTransientFor();
57   getMwmHints();
58   getType(); // this can change the mwmhints for special cases
59
60   updateProtocols();
61
62   getGravity();        // get the attribute gravity
63   updateNormalHints(); // this may override the attribute gravity
64
65   // got the type, the mwmhints, the protocols, and the normal hints (min/max
66   // sizes), so we're ready to set up
67   // the decorations/functions
68   setupDecorAndFunctions();
69   
70   // also get the initial_state and set _iconic if we aren't "starting"
71   // when we're "starting" that means we should use whatever state was already
72   // on the window over the initial map state, because it was already mapped
73   updateWMHints(openbox->state() != Openbox::State_Starting);
74   updateTitle();
75   updateIconTitle();
76   updateClass();
77   updateStrut();
78
79   // this makes sure that these windows appear on all desktops
80   if (/*_type == Type_Dock ||*/ _type == Type_Desktop)
81     _desktop = 0xffffffff;
82   
83   // set the desktop hint, to make sure that it always exists, and to reflect
84   // any changes we've made here
85   otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
86                      otk::Property::atoms.cardinal, (unsigned)_desktop);
87   
88   changeState();
89 }
90
91
92 Client::~Client()
93 {
94   // clean up childrens' references
95   while (!_transients.empty()) {
96     _transients.front()->_transient_for = 0;
97     _transients.pop_front();
98   }
99
100   // clean up parents reference to this
101   if (_transient_for)
102     _transient_for->_transients.remove(this); // remove from old parent
103   
104   if (openbox->state() != Openbox::State_Exiting) {
105     // these values should not be persisted across a window unmapping/mapping
106     otk::Property::erase(_window, otk::Property::atoms.net_wm_desktop);
107     otk::Property::erase(_window, otk::Property::atoms.net_wm_state);
108   } else {
109     // if we're left in an iconic state, the client wont be mapped. this is
110     // bad, since we will no longer be managing the window on restart
111     if (_iconic)
112       XMapWindow(**otk::display, _window);
113   }
114 }
115
116
117 bool Client::validate() const
118 {
119   XSync(**otk::display, false); // get all events on the server
120
121   XEvent e;
122   if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &e) ||
123       XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &e)) {
124     XPutBackEvent(**otk::display, &e);
125     return false;
126   }
127
128   return true;
129 }
130
131
132 void Client::getGravity()
133 {
134   XWindowAttributes wattrib;
135   Status ret;
136
137   ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
138   assert(ret != BadWindow);
139   _gravity = wattrib.win_gravity;
140 }
141
142
143 void Client::getDesktop()
144 {
145   // defaults to the current desktop
146   _desktop = openbox->screen(_screen)->desktop();
147
148   if (otk::Property::get(_window, otk::Property::atoms.net_wm_desktop,
149                          otk::Property::atoms.cardinal,
150                          (long unsigned*)&_desktop)) {
151 #ifdef DEBUG
152 //    printf("Window requested desktop: %ld\n", _desktop);
153 #endif
154   }
155 }
156
157
158 void Client::getType()
159 {
160   _type = (WindowType) -1;
161   
162   unsigned long *val;
163   unsigned long num = (unsigned) -1;
164   if (otk::Property::get(_window, otk::Property::atoms.net_wm_window_type,
165                          otk::Property::atoms.atom, &num, &val)) {
166     // use the first value that we know about in the array
167     for (unsigned long i = 0; i < num; ++i) {
168       if (val[i] == otk::Property::atoms.net_wm_window_type_desktop)
169         _type = Type_Desktop;
170       else if (val[i] == otk::Property::atoms.net_wm_window_type_dock)
171         _type = Type_Dock;
172       else if (val[i] == otk::Property::atoms.net_wm_window_type_toolbar)
173         _type = Type_Toolbar;
174       else if (val[i] == otk::Property::atoms.net_wm_window_type_menu)
175         _type = Type_Menu;
176       else if (val[i] == otk::Property::atoms.net_wm_window_type_utility)
177         _type = Type_Utility;
178       else if (val[i] == otk::Property::atoms.net_wm_window_type_splash)
179         _type = Type_Splash;
180       else if (val[i] == otk::Property::atoms.net_wm_window_type_dialog)
181         _type = Type_Dialog;
182       else if (val[i] == otk::Property::atoms.net_wm_window_type_normal)
183         _type = Type_Normal;
184       else if (val[i] == otk::Property::atoms.kde_net_wm_window_type_override){
185         // prevent this window from getting any decor or functionality
186         _mwmhints.flags &= MwmFlag_Functions | MwmFlag_Decorations;
187         _mwmhints.decorations = 0;
188         _mwmhints.functions = 0;
189       }
190       if (_type != (WindowType) -1)
191         break; // grab the first known type
192     }
193     delete val;
194   }
195     
196   if (_type == (WindowType) -1) {
197     /*
198      * the window type hint was not set, which means we either classify ourself
199      * as a normal window or a dialog, depending on if we are a transient.
200      */
201     if (_transient_for)
202       _type = Type_Dialog;
203     else
204       _type = Type_Normal;
205   }
206 }
207
208
209 void Client::setupDecorAndFunctions()
210 {
211   // start with everything (cept fullscreen)
212   _decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
213     Decor_AllDesktops | Decor_Iconify | Decor_Maximize;
214   _functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize |
215     Func_Shade;
216   if (_delete_window) {
217     _decorations |= Decor_Close;
218     _functions |= Func_Close;
219   }
220
221   if (!(_min_size.width() < _max_size.width() ||
222         _min_size.height() < _max_size.height())) {
223     _decorations &= ~(Decor_Maximize | Decor_Handle);
224     _functions &= ~(Func_Resize | Func_Maximize);
225   }
226   
227   switch (_type) {
228   case Type_Normal:
229     // normal windows retain all of the possible decorations and
230     // functionality, and are the only windows that you can fullscreen
231     _functions |= Func_Fullscreen;
232     break;
233
234   case Type_Dialog:
235     // dialogs cannot be maximized
236     _decorations &= ~Decor_Maximize;
237     _functions &= ~Func_Maximize;
238     break;
239
240   case Type_Menu:
241   case Type_Toolbar:
242   case Type_Utility:
243     // these windows get less functionality
244     _decorations &= ~(Decor_Iconify | Decor_Handle);
245     _functions &= ~(Func_Iconify | Func_Resize);
246     break;
247
248   case Type_Desktop:
249   case Type_Dock:
250   case Type_Splash:
251     // none of these windows are manipulated by the window manager
252     _decorations = 0;
253     _functions = 0;
254     break;
255   }
256
257   // Mwm Hints are applied subtractively to what has already been chosen for
258   // decor and functionality
259   if (_mwmhints.flags & MwmFlag_Decorations) {
260     if (! (_mwmhints.decorations & MwmDecor_All)) {
261       if (! (_mwmhints.decorations & MwmDecor_Border))
262         _decorations &= ~Decor_Border;
263       if (! (_mwmhints.decorations & MwmDecor_Handle))
264         _decorations &= ~Decor_Handle;
265       if (! (_mwmhints.decorations & MwmDecor_Title))
266         _decorations &= ~Decor_Titlebar;
267       if (! (_mwmhints.decorations & MwmDecor_Iconify))
268         _decorations &= ~Decor_Iconify;
269       if (! (_mwmhints.decorations & MwmDecor_Maximize))
270         _decorations &= ~Decor_Maximize;
271     }
272   }
273
274   if (_mwmhints.flags & MwmFlag_Functions) {
275     if (! (_mwmhints.functions & MwmFunc_All)) {
276       if (! (_mwmhints.functions & MwmFunc_Resize))
277         _functions &= ~Func_Resize;
278       if (! (_mwmhints.functions & MwmFunc_Move))
279         _functions &= ~Func_Move;
280       if (! (_mwmhints.functions & MwmFunc_Iconify))
281         _functions &= ~Func_Iconify;
282       if (! (_mwmhints.functions & MwmFunc_Maximize))
283         _functions &= ~Func_Maximize;
284       // dont let mwm hints kill the close button
285       //if (! (_mwmhints.functions & MwmFunc_Close))
286       //  _functions &= ~Func_Close;
287     }
288   }
289
290   // can't maximize without moving/resizing
291   if (!((_functions & Func_Move) && (_functions & Func_Resize)))
292     _functions &= ~Func_Maximize;
293
294   // finally, user specified disabled decorations are applied to subtract
295   // decorations
296   if (_disabled_decorations & Decor_Titlebar)
297     _decorations &= ~Decor_Titlebar;
298   if (_disabled_decorations & Decor_Handle)
299     _decorations &= ~Decor_Handle;
300   if (_disabled_decorations & Decor_Border)
301     _decorations &= ~Decor_Border;
302   if (_disabled_decorations & Decor_Iconify)
303     _decorations &= ~Decor_Iconify;
304   if (_disabled_decorations & Decor_Maximize)
305     _decorations &= ~Decor_Maximize;
306   if (_disabled_decorations & Decor_AllDesktops)
307     _decorations &= ~Decor_AllDesktops;
308   if (_disabled_decorations & Decor_Close)
309     _decorations &= ~Decor_Close;
310
311   // if we don't have a titlebar, then we cannot shade!
312   if (!(_decorations & Decor_Titlebar))
313     _functions &= ~Func_Shade;
314
315   changeAllowedActions();
316
317   if (frame) {
318     frame->adjustSize(); // change the decors on the frame
319     frame->adjustPosition(); // with more/less decorations, we may need to be
320                              // moved
321     remaximize(); // with new decor, the window's maximized size may change
322   }
323 }
324
325
326 void Client::getMwmHints()
327 {
328   unsigned long num = MwmHints::elements;
329   unsigned long *hints;
330
331   _mwmhints.flags = 0; // default to none
332   
333   if (!otk::Property::get(_window, otk::Property::atoms.motif_wm_hints,
334                           otk::Property::atoms.motif_wm_hints, &num,
335                           (unsigned long **)&hints))
336     return;
337   
338   if (num >= MwmHints::elements) {
339     // retrieved the hints
340     _mwmhints.flags = hints[0];
341     _mwmhints.functions = hints[1];
342     _mwmhints.decorations = hints[2];
343   }
344
345   delete [] hints;
346 }
347
348
349 void Client::getArea()
350 {
351   XWindowAttributes wattrib;
352   Status ret;
353   
354   ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
355   assert(ret != BadWindow);
356
357   _area = otk::Rect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
358   _border_width = wattrib.border_width;
359 }
360
361
362 void Client::getState()
363 {
364   _modal = _shaded = _max_horz = _max_vert = _fullscreen = _above = _below =
365     _iconic = _skip_taskbar = _skip_pager = false;
366   
367   unsigned long *state;
368   unsigned long num = (unsigned) -1;
369   
370   if (otk::Property::get(_window, otk::Property::atoms.net_wm_state,
371                          otk::Property::atoms.atom, &num, &state)) {
372     for (unsigned long i = 0; i < num; ++i) {
373       if (state[i] == otk::Property::atoms.net_wm_state_modal)
374         _modal = true;
375       else if (state[i] == otk::Property::atoms.net_wm_state_shaded)
376         _shaded = true;
377       else if (state[i] == otk::Property::atoms.net_wm_state_hidden)
378         _iconic = true;
379       else if (state[i] == otk::Property::atoms.net_wm_state_skip_taskbar)
380         _skip_taskbar = true;
381       else if (state[i] == otk::Property::atoms.net_wm_state_skip_pager)
382         _skip_pager = true;
383       else if (state[i] == otk::Property::atoms.net_wm_state_fullscreen)
384         _fullscreen = true;
385       else if (state[i] == otk::Property::atoms.net_wm_state_maximized_vert)
386         _max_vert = true;
387       else if (state[i] == otk::Property::atoms.net_wm_state_maximized_horz)
388         _max_horz = true;
389       else if (state[i] == otk::Property::atoms.net_wm_state_above)
390         _above = true;
391       else if (state[i] == otk::Property::atoms.net_wm_state_below)
392         _below = true;
393     }
394
395     delete [] state;
396   }
397 }
398
399
400 void Client::getShaped()
401 {
402   _shaped = false;
403 #ifdef   SHAPE
404   if (otk::display->shape()) {
405     int foo;
406     unsigned int ufoo;
407     int s;
408
409     XShapeSelectInput(**otk::display, _window, ShapeNotifyMask);
410
411     XShapeQueryExtents(**otk::display, _window, &s, &foo,
412                        &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo);
413     _shaped = (s != 0);
414   }
415 #endif // SHAPE
416 }
417
418
419 void Client::calcLayer() {
420   StackLayer l;
421
422   if (_iconic) l = Layer_Icon;
423   else if (_fullscreen) l = Layer_Fullscreen;
424   else if (_type == Type_Desktop) l = Layer_Desktop;
425   else if (_type == Type_Dock) {
426     if (!_below) l = Layer_Top;
427     else l = Layer_Normal;
428   }
429   else if (_above) l = Layer_Above;
430   else if (_below) l = Layer_Below;
431   else l = Layer_Normal;
432
433   if (l != _layer) {
434     _layer = l;
435     if (frame) {
436       /*
437         if we don't have a frame, then we aren't mapped yet (and this would
438         SIGSEGV :)
439       */
440       openbox->screen(_screen)->raiseWindow(this);
441     }
442   }
443 }
444
445
446 void Client::updateProtocols()
447 {
448   Atom *proto;
449   int num_return = 0;
450
451   _focus_notify = false;
452   _delete_window = false;
453
454   if (XGetWMProtocols(**otk::display, _window, &proto, &num_return)) {
455     for (int i = 0; i < num_return; ++i) {
456       if (proto[i] == otk::Property::atoms.wm_delete_window) {
457         // this means we can request the window to close
458         _delete_window = true;
459       } else if (proto[i] == otk::Property::atoms.wm_take_focus)
460         // if this protocol is requested, then the window will be notified
461         // by the window manager whenever it receives focus
462         _focus_notify = true;
463     }
464     XFree(proto);
465   }
466 }
467
468
469 void Client::updateNormalHints()
470 {
471   XSizeHints size;
472   long ret;
473   int oldgravity = _gravity;
474
475   // defaults
476   _min_ratio = 0.0;
477   _max_ratio = 0.0;
478   _size_inc = otk::Size(1, 1);
479   _base_size = otk::Size(0, 0);
480   _min_size = otk::Size(0, 0);
481   _max_size = otk::Size(INT_MAX, INT_MAX);
482
483   // get the hints from the window
484   if (XGetWMNormalHints(**otk::display, _window, &size, &ret)) {
485     _positioned = (size.flags & (PPosition|USPosition));
486
487     if (size.flags & PWinGravity) {
488       _gravity = size.win_gravity;
489       
490       // if the client has a frame, i.e. has already been mapped and is
491       // changing its gravity
492       if (frame && _gravity != oldgravity) {
493         // move our idea of the client's position based on its new gravity
494         int x = frame->area().x(), y = frame->area().y();
495         frame->frameGravity(x, y);
496         _area = otk::Rect(otk::Point(x, y), _area.size());
497       }
498     }
499
500     if (size.flags & PAspect) {
501       if (size.min_aspect.y) _min_ratio = size.min_aspect.x/size.min_aspect.y;
502       if (size.max_aspect.y) _max_ratio = size.max_aspect.x/size.max_aspect.y;
503     }
504
505     if (size.flags & PMinSize)
506       _min_size = otk::Size(size.min_width, size.min_height);
507     
508     if (size.flags & PMaxSize)
509       _max_size = otk::Size(size.max_width, size.max_height);
510     
511     if (size.flags & PBaseSize)
512       _base_size = otk::Size(size.base_width, size.base_height);
513     
514     if (size.flags & PResizeInc)
515       _size_inc = otk::Size(size.width_inc, size.height_inc);
516   }
517 }
518
519
520 void Client::updateWMHints(bool initstate)
521 {
522   XWMHints *hints;
523
524   // assume a window takes input if it doesnt specify
525   _can_focus = true;
526   bool ur = false;
527   
528   if ((hints = XGetWMHints(**otk::display, _window)) != NULL) {
529     if (hints->flags & InputHint)
530       _can_focus = hints->input;
531
532     // only do this when initstate is true!
533     if (initstate && (hints->flags & StateHint))
534       _iconic = hints->initial_state == IconicState;
535
536     if (hints->flags & XUrgencyHint)
537       ur = true;
538
539     if (hints->flags & WindowGroupHint) {
540       if (hints->window_group != _group) {
541         // XXX: remove from the old group if there was one
542         _group = hints->window_group;
543         // XXX: do stuff with the group
544       }
545     } else // no group!
546       _group = None;
547
548     XFree(hints);
549   }
550
551   if (ur != _urgent) {
552     _urgent = ur;
553 #ifdef DEBUG
554     printf("DEBUG: Urgent Hint for 0x%lx: %s\n",
555            (long)_window, _urgent ? "ON" : "OFF");
556 #endif
557     // fire the urgent callback if we're mapped, otherwise, wait until after
558     // we're mapped
559     if (frame)
560       fireUrgent();
561   }
562 }
563
564
565 void Client::updateTitle()
566 {
567   _title = "";
568   
569   // try netwm
570   if (!otk::Property::get(_window, otk::Property::atoms.net_wm_name,
571                           otk::Property::utf8, &_title)) {
572     // try old x stuff
573     otk::Property::get(_window, otk::Property::atoms.wm_name,
574                        otk::Property::ascii, &_title);
575   }
576
577   if (_title.empty())
578     _title = _("Unnamed Window");
579
580   if (frame)
581     frame->adjustTitle();
582 }
583
584
585 void Client::updateIconTitle()
586 {
587   _icon_title = "";
588   
589   // try netwm
590   if (!otk::Property::get(_window, otk::Property::atoms.net_wm_icon_name,
591                           otk::Property::utf8, &_icon_title)) {
592     // try old x stuff
593     otk::Property::get(_window, otk::Property::atoms.wm_icon_name,
594                        otk::Property::ascii, &_icon_title);
595   }
596
597   if (_title.empty())
598     _icon_title = _("Unnamed Window");
599 }
600
601
602 void Client::updateClass()
603 {
604   // set the defaults
605   _app_name = _app_class = _role = "";
606
607   otk::Property::StringVect v;
608   unsigned long num = 2;
609
610   if (otk::Property::get(_window, otk::Property::atoms.wm_class,
611                          otk::Property::ascii, &num, &v)) {
612     if (num > 0) _app_name = v[0].c_str();
613     if (num > 1) _app_class = v[1].c_str();
614   }
615
616   v.clear();
617   num = 1;
618   if (otk::Property::get(_window, otk::Property::atoms.wm_window_role,
619                          otk::Property::ascii, &num, &v)) {
620     if (num > 0) _role = v[0].c_str();
621   }
622 }
623
624
625 void Client::updateStrut()
626 {
627   unsigned long num = 4;
628   unsigned long *data;
629   if (!otk::Property::get(_window, otk::Property::atoms.net_wm_strut,
630                           otk::Property::atoms.cardinal, &num, &data))
631     return;
632
633   if (num == 4) {
634     _strut.left = data[0];
635     _strut.right = data[1];
636     _strut.top = data[2];
637     _strut.bottom = data[3]; 
638
639     // updating here is pointless while we're being mapped cuz we're not in
640     // the screen's client list yet
641     if (frame)
642       openbox->screen(_screen)->updateStrut();
643   }
644
645   delete [] data;
646 }
647
648
649 void Client::updateTransientFor()
650 {
651   Window t = 0;
652   Client *c = 0;
653
654   if (XGetTransientForHint(**otk::display, _window, &t) &&
655       t != _window) { // cant be transient to itself!
656     c = openbox->findClient(t);
657     assert(c != this); // if this happens then we need to check for it
658
659     if (!c /*XXX: && _group*/) {
660       // not transient to a client, see if it is transient for a group
661       if (//t == _group->leader() ||
662         t == None ||
663         t == otk::display->screenInfo(_screen)->rootWindow()) {
664         // window is a transient for its group!
665         // XXX: for now this is treated as non-transient.
666         //      this needs to be fixed!
667       }
668     }
669   }
670
671   // if anything has changed...
672   if (c != _transient_for) {
673     bool m = _modal;
674     if (_modal)
675       setModal(false);
676     
677     if (_transient_for)
678       _transient_for->_transients.remove(this); // remove from old parent
679     _transient_for = c;
680     if (_transient_for)
681       _transient_for->_transients.push_back(this); // add to new parent
682
683     if (m)
684       setModal(true);
685   }
686 }
687
688
689 void Client::propertyHandler(const XPropertyEvent &e)
690 {
691   otk::EventHandler::propertyHandler(e);
692
693   // validate cuz we query stuff off the client here
694   if (!validate()) return;
695   
696   // compress changes to a single property into a single change
697   XEvent ce;
698   while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
699     // XXX: it would be nice to compress ALL changes to a property, not just
700     //      changes in a row without other props between.
701     if (ce.xproperty.atom != e.atom) {
702       XPutBackEvent(**otk::display, &ce);
703       break;
704     }
705   }
706
707   if (e.atom == XA_WM_NORMAL_HINTS) {
708     updateNormalHints();
709     setupDecorAndFunctions(); // normal hints can make a window non-resizable
710   } else if (e.atom == XA_WM_HINTS)
711     updateWMHints();
712   else if (e.atom == XA_WM_TRANSIENT_FOR) {
713     updateTransientFor();
714     getType();
715     calcLayer(); // type may have changed, so update the layer
716     setupDecorAndFunctions();
717   }
718   else if (e.atom == otk::Property::atoms.net_wm_name ||
719            e.atom == otk::Property::atoms.wm_name)
720     updateTitle();
721   else if (e.atom == otk::Property::atoms.net_wm_icon_name ||
722            e.atom == otk::Property::atoms.wm_icon_name)
723     updateIconTitle();
724   else if (e.atom == otk::Property::atoms.wm_class)
725     updateClass();
726   else if (e.atom == otk::Property::atoms.wm_protocols) {
727     updateProtocols();
728     setupDecorAndFunctions();
729   }
730   else if (e.atom == otk::Property::atoms.net_wm_strut)
731     updateStrut();
732 }
733
734
735 void Client::setWMState(long state)
736 {
737   if (state == _wmstate) return; // no change
738   
739   switch (state) {
740   case IconicState:
741     setDesktop(ICONIC_DESKTOP);
742     break;
743   case NormalState:
744     setDesktop(openbox->screen(_screen)->desktop());
745     break;
746   }
747 }
748
749
750 void Client::setDesktop(long target)
751 {
752   if (target == _desktop) return;
753   
754   printf("Setting desktop %ld\n", target);
755
756   if (!(target >= 0 || target == (signed)0xffffffff ||
757         target == ICONIC_DESKTOP))
758     return;
759   
760   _desktop = target;
761
762   // set the desktop hint, but not if we're iconifying
763   if (_desktop != ICONIC_DESKTOP)
764     otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
765                        otk::Property::atoms.cardinal, (unsigned)_desktop);
766   
767   // 'move' the window to the new desktop
768   if (_desktop == openbox->screen(_screen)->desktop() ||
769       _desktop == (signed)0xffffffff)
770     frame->show();
771   else
772     frame->hide();
773
774   // Handle Iconic state. Iconic state is maintained by the client being a
775   // member of the ICONIC_DESKTOP, so this is where we make iconifying and
776   // uniconifying happen.
777   bool i = _desktop == ICONIC_DESKTOP;
778   if (i != _iconic) { // has the state changed?
779     _iconic = i;
780     if (_iconic) {
781       _wmstate = IconicState;
782       ignore_unmaps++;
783       // we unmap the client itself so that we can get MapRequest events, and
784       // because the ICCCM tells us to!
785       XUnmapWindow(**otk::display, _window);
786     } else {
787       _wmstate = NormalState;
788       XMapWindow(**otk::display, _window);
789     }
790     changeState();
791   }
792   
793   frame->adjustState();
794 }
795
796
797 Client *Client::findModalChild(Client *skip) const
798 {
799   Client *ret = 0;
800   
801   // find a modal child recursively and try focus it
802   List::const_iterator it, end = _transients.end();
803   for (it = _transients.begin(); it != end; ++it)
804     if ((*it)->_modal && *it != skip)
805       return *it; // got one
806   // none of our direct children are modal, let them try check
807   for (it = _transients.begin(); it != end; ++it)
808     if ((ret = (*it)->findModalChild()))
809       return ret; // got one
810   return ret;
811 }
812
813
814 void Client::setModal(bool modal)
815 {
816   if (modal == _modal) return;
817   
818   if (modal) {
819     Client *c = this;
820     while (c->_transient_for) {
821       c = c->_transient_for;
822       if (c == this) break; // circular?
823       if (c->_modal_child) break; // already has a modal child
824       c->_modal_child = this;
825     }
826   } else {
827     // try find a replacement modal dialog
828     Client *replacement = 0;
829     
830     Client *c = this;
831     while (c->_transient_for) // go up the tree
832       c = c->_transient_for;
833     replacement = c->findModalChild(this); // find a modal child, skipping this
834     assert(replacement != this);
835
836     c = this;
837     while (c->_transient_for) {
838       c = c->_transient_for;
839       if (c == this) break; // circular?
840       if (c->_modal_child != this) break; // has a different modal child
841       if (c == replacement) break; // found the replacement itself
842       c->_modal_child = replacement;
843     }
844   }
845   _modal = modal;
846 }
847
848
849 void Client::setState(StateAction action, long data1, long data2)
850 {
851   bool shadestate = _shaded;
852   bool fsstate = _fullscreen;
853   bool maxh = _max_horz;
854   bool maxv = _max_vert;
855   bool modal = _modal;
856
857   if (!(action == State_Add || action == State_Remove ||
858         action == State_Toggle))
859     return; // an invalid action was passed to the client message, ignore it
860
861   for (int i = 0; i < 2; ++i) {
862     Atom state = i == 0 ? data1 : data2;
863     
864     if (! state) continue;
865
866     // if toggling, then pick whether we're adding or removing
867     if (action == State_Toggle) {
868       if (state == otk::Property::atoms.net_wm_state_modal)
869         action = _modal ? State_Remove : State_Add;
870       else if (state == otk::Property::atoms.net_wm_state_maximized_vert)
871         action = _max_vert ? State_Remove : State_Add;
872       else if (state == otk::Property::atoms.net_wm_state_maximized_horz)
873         action = _max_horz ? State_Remove : State_Add;
874       else if (state == otk::Property::atoms.net_wm_state_shaded)
875         action = _shaded ? State_Remove : State_Add;
876       else if (state == otk::Property::atoms.net_wm_state_skip_taskbar)
877         action = _skip_taskbar ? State_Remove : State_Add;
878       else if (state == otk::Property::atoms.net_wm_state_skip_pager)
879         action = _skip_pager ? State_Remove : State_Add;
880       else if (state == otk::Property::atoms.net_wm_state_fullscreen)
881         action = _fullscreen ? State_Remove : State_Add;
882       else if (state == otk::Property::atoms.net_wm_state_above)
883         action = _above ? State_Remove : State_Add;
884       else if (state == otk::Property::atoms.net_wm_state_below)
885         action = _below ? State_Remove : State_Add;
886     }
887     
888     if (action == State_Add) {
889       if (state == otk::Property::atoms.net_wm_state_modal) {
890         if (_modal) continue;
891         modal = true;
892       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
893         maxv = true;
894       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
895         if (_max_horz) continue;
896         maxh = true;
897       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
898         shadestate = true;
899       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
900         _skip_taskbar = true;
901       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
902         _skip_pager = true;
903       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
904         fsstate = true;
905       } else if (state == otk::Property::atoms.net_wm_state_above) {
906         if (_above) continue;
907         _above = true;
908       } else if (state == otk::Property::atoms.net_wm_state_below) {
909         if (_below) continue;
910         _below = true;
911       }
912
913     } else { // action == State_Remove
914       if (state == otk::Property::atoms.net_wm_state_modal) {
915         if (!_modal) continue;
916         modal = false;
917       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
918         maxv = false;
919       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
920         maxh = false;
921       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
922         shadestate = false;
923       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
924         _skip_taskbar = false;
925       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
926         _skip_pager = false;
927       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
928         fsstate = false;
929       } else if (state == otk::Property::atoms.net_wm_state_above) {
930         if (!_above) continue;
931         _above = false;
932       } else if (state == otk::Property::atoms.net_wm_state_below) {
933         if (!_below) continue;
934         _below = false;
935       }
936     }
937   }
938   if (maxh != _max_horz || maxv != _max_vert) {
939     if (maxh != _max_horz && maxv != _max_vert) { // toggling both
940       if (maxh == maxv) { // both going the same way
941         maximize(maxh, 0, true);
942       } else {
943         maximize(maxh, 1, true);
944         maximize(maxv, 2, true);
945       }
946     } else { // toggling one
947       if (maxh != _max_horz)
948         maximize(maxh, 1, true);
949       else
950         maximize(maxv, 2, true);
951     }
952   }
953   if (modal != _modal)
954     setModal(modal);
955   // change fullscreen state before shading, as it will affect if the window
956   // can shade or not
957   if (fsstate != _fullscreen)
958     fullscreen(fsstate, true);
959   if (shadestate != _shaded)
960     shade(shadestate);
961   calcLayer();
962   changeState(); // change the hint to relect these changes
963 }
964
965
966 void Client::toggleClientBorder(bool addborder)
967 {
968   // adjust our idea of where the client is, based on its border. When the
969   // border is removed, the client should now be considered to be in a
970   // different position.
971   // when re-adding the border to the client, the same operation needs to be
972   // reversed.
973   int oldx = _area.x(), oldy = _area.y();
974   int x = oldx, y = oldy;
975   switch(_gravity) {
976   default:
977   case NorthWestGravity:
978   case WestGravity:
979   case SouthWestGravity:
980     break;
981   case NorthEastGravity:
982   case EastGravity:
983   case SouthEastGravity:
984     if (addborder) x -= _border_width * 2;
985     else           x += _border_width * 2;
986     break;
987   case NorthGravity:
988   case SouthGravity:
989   case CenterGravity:
990   case ForgetGravity:
991   case StaticGravity:
992     if (addborder) x -= _border_width;
993     else           x += _border_width;
994     break;
995   }
996   switch(_gravity) {
997   default:
998   case NorthWestGravity:
999   case NorthGravity:
1000   case NorthEastGravity:
1001     break;
1002   case SouthWestGravity:
1003   case SouthGravity:
1004   case SouthEastGravity:
1005     if (addborder) y -= _border_width * 2;
1006     else           y += _border_width * 2;
1007     break;
1008   case WestGravity:
1009   case EastGravity:
1010   case CenterGravity:
1011   case ForgetGravity:
1012   case StaticGravity:
1013     if (addborder) y -= _border_width;
1014     else           y += _border_width;
1015     break;
1016   }
1017   _area = otk::Rect(otk::Point(x, y), _area.size());
1018
1019   if (addborder) {
1020     XSetWindowBorderWidth(**otk::display, _window, _border_width);
1021
1022     // move the client so it is back it the right spot _with_ its border!
1023     if (x != oldx || y != oldy)
1024       XMoveWindow(**otk::display, _window, x, y);
1025   } else
1026     XSetWindowBorderWidth(**otk::display, _window, 0);
1027 }
1028
1029
1030 void Client::clientMessageHandler(const XClientMessageEvent &e)
1031 {
1032   otk::EventHandler::clientMessageHandler(e);
1033   
1034   // validate cuz we query stuff off the client here
1035   if (!validate()) return;
1036   
1037   if (e.format != 32) return;
1038
1039   if (e.message_type == otk::Property::atoms.wm_change_state) {
1040     // compress changes into a single change
1041     bool compress = false;
1042     XEvent ce;
1043     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
1044       // XXX: it would be nice to compress ALL messages of a type, not just
1045       //      messages in a row without other message types between.
1046       if (ce.xclient.message_type != e.message_type) {
1047         XPutBackEvent(**otk::display, &ce);
1048         break;
1049       }
1050       compress = true;
1051     }
1052     if (compress)
1053       setWMState(ce.xclient.data.l[0]); // use the found event
1054     else
1055       setWMState(e.data.l[0]); // use the original event
1056   } else if (e.message_type == otk::Property::atoms.net_wm_desktop) {
1057     // compress changes into a single change 
1058     bool compress = false;
1059     XEvent ce;
1060     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
1061       // XXX: it would be nice to compress ALL messages of a type, not just
1062       //      messages in a row without other message types between.
1063       if (ce.xclient.message_type != e.message_type) {
1064         XPutBackEvent(**otk::display, &ce);
1065         break;
1066       }
1067       compress = true;
1068     }
1069     if (compress)
1070       setDesktop(e.data.l[0]); // use the found event
1071     else
1072       setDesktop(e.data.l[0]); // use the original event
1073   } else if (e.message_type == otk::Property::atoms.net_wm_state) {
1074     // can't compress these
1075 #ifdef DEBUG
1076     printf("net_wm_state %s %ld %ld for 0x%lx\n",
1077            (e.data.l[0] == 0 ? "Remove" : e.data.l[0] == 1 ? "Add" :
1078             e.data.l[0] == 2 ? "Toggle" : "INVALID"),
1079            e.data.l[1], e.data.l[2], _window);
1080 #endif
1081     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
1082   } else if (e.message_type == otk::Property::atoms.net_close_window) {
1083 #ifdef DEBUG
1084     printf("net_close_window for 0x%lx\n", _window);
1085 #endif
1086     close();
1087   } else if (e.message_type == otk::Property::atoms.net_active_window) {
1088 #ifdef DEBUG
1089     printf("net_active_window for 0x%lx\n", _window);
1090 #endif
1091     if (_iconic)
1092       setDesktop(openbox->screen(_screen)->desktop());
1093     if (_shaded)
1094       shade(false);
1095     focus();
1096     openbox->screen(_screen)->raiseWindow(this);
1097   } else if (e.message_type == otk::Property::atoms.openbox_active_window) {
1098     if (_iconic)
1099       setDesktop(openbox->screen(_screen)->desktop());
1100     if (e.data.l[0] && _shaded)
1101       shade(false);
1102     focus();
1103     if (e.data.l[1])
1104       openbox->screen(_screen)->raiseWindow(this);
1105   }
1106 }
1107
1108
1109 #if defined(SHAPE)
1110 void Client::shapeHandler(const XShapeEvent &e)
1111 {
1112   otk::EventHandler::shapeHandler(e);
1113
1114   if (e.kind == ShapeBounding) {
1115     _shaped = e.shaped;
1116     frame->adjustShape();
1117   }
1118 }
1119 #endif
1120
1121
1122 void Client::resize(Corner anchor, int w, int h)
1123 {
1124   if (!(_functions & Func_Resize)) return;
1125   internal_resize(anchor, w, h);
1126 }
1127
1128
1129 void Client::internal_resize(Corner anchor, int w, int h,
1130                              bool user, int x, int y)
1131 {
1132   w -= _base_size.width();
1133   h -= _base_size.height();
1134
1135   if (user) {
1136     // for interactive resizing. have to move half an increment in each
1137     // direction.
1138     int mw = w % _size_inc.width(); // how far we are towards the next size inc
1139     int mh = h % _size_inc.height();
1140     int aw = _size_inc.width() / 2; // amount to add
1141     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 > _max_size.width()) w = _max_size.width();
1153     if (w < _min_size.width()) w = _min_size.width();
1154     if (h > _max_size.height()) h = _max_size.height();
1155     if (h < _min_size.height()) h = _min_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   _focused = true;
1718   frame->adjustFocus();
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   _focused = false;
1733   frame->adjustFocus();
1734
1735   if (openbox->focusedClient() == this)
1736     openbox->setFocusedClient(0);
1737 }
1738
1739
1740 void Client::configureRequestHandler(const XConfigureRequestEvent &ec)
1741 {
1742 #ifdef    DEBUG
1743   printf("ConfigureRequest for 0x%lx\n", ec.window);
1744 #endif // DEBUG
1745   
1746   otk::EventHandler::configureRequestHandler(ec);
1747
1748   // compress these
1749   XConfigureRequestEvent e = ec;
1750   XEvent ev;
1751   while (XCheckTypedWindowEvent(**otk::display, window(), ConfigureRequest,
1752                                 &ev)) {
1753     // XXX if this causes bad things.. we can compress config req's with the
1754     //     same mask.
1755     e.value_mask |= ev.xconfigurerequest.value_mask;
1756     if (ev.xconfigurerequest.value_mask & CWX)
1757       e.x = ev.xconfigurerequest.x;
1758     if (ev.xconfigurerequest.value_mask & CWY)
1759       e.y = ev.xconfigurerequest.y;
1760     if (ev.xconfigurerequest.value_mask & CWWidth)
1761       e.width = ev.xconfigurerequest.width;
1762     if (ev.xconfigurerequest.value_mask & CWHeight)
1763       e.height = ev.xconfigurerequest.height;
1764     if (ev.xconfigurerequest.value_mask & CWBorderWidth)
1765       e.border_width = ev.xconfigurerequest.border_width;
1766     if (ev.xconfigurerequest.value_mask & CWStackMode)
1767       e.detail = ev.xconfigurerequest.detail;
1768   }
1769
1770   // if we are iconic (or shaded (fvwm does this)) ignore the event
1771   if (_iconic || _shaded) return;
1772
1773   if (e.value_mask & CWBorderWidth)
1774     _border_width = e.border_width;
1775
1776   // resize, then move, as specified in the EWMH section 7.7
1777   if (e.value_mask & (CWWidth | CWHeight)) {
1778     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1779     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1780
1781     Corner corner;
1782     switch (_gravity) {
1783     case NorthEastGravity:
1784     case EastGravity:
1785       corner = TopRight;
1786       break;
1787     case SouthWestGravity:
1788     case SouthGravity:
1789       corner = BottomLeft;
1790       break;
1791     case SouthEastGravity:
1792       corner = BottomRight;
1793       break;
1794     default:     // NorthWest, Static, etc
1795       corner = TopLeft;
1796     }
1797
1798     // if moving AND resizing ...
1799     if (e.value_mask & (CWX | CWY)) {
1800       int x = (e.value_mask & CWX) ? e.x : _area.x();
1801       int y = (e.value_mask & CWY) ? e.y : _area.y();
1802       internal_resize(corner, w, h, false, x, y);
1803     } else // if JUST resizing...
1804       internal_resize(corner, w, h, false);
1805   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1806     int x = (e.value_mask & CWX) ? e.x : _area.x();
1807     int y = (e.value_mask & CWY) ? e.y : _area.y();
1808     internal_move(x, y);
1809   }
1810
1811   if (e.value_mask & CWStackMode) {
1812     switch (e.detail) {
1813     case Below:
1814     case BottomIf:
1815       openbox->screen(_screen)->lowerWindow(this);
1816       break;
1817
1818     case Above:
1819     case TopIf:
1820     default:
1821       openbox->screen(_screen)->raiseWindow(this);
1822       break;
1823     }
1824   }
1825 }
1826
1827
1828 void Client::unmapHandler(const XUnmapEvent &e)
1829 {
1830   if (ignore_unmaps) {
1831 #ifdef    DEBUG
1832 //  printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1833 #endif // DEBUG
1834     ignore_unmaps--;
1835     return;
1836   }
1837   
1838 #ifdef    DEBUG
1839   printf("UnmapNotify for 0x%lx\n", e.window);
1840 #endif // DEBUG
1841
1842   otk::EventHandler::unmapHandler(e);
1843
1844   // this deletes us etc
1845   openbox->screen(_screen)->unmanageWindow(this);
1846 }
1847
1848
1849 void Client::destroyHandler(const XDestroyWindowEvent &e)
1850 {
1851 #ifdef    DEBUG
1852   printf("DestroyNotify for 0x%lx\n", e.window);
1853 #endif // DEBUG
1854
1855   otk::EventHandler::destroyHandler(e);
1856
1857   // this deletes us etc
1858   openbox->screen(_screen)->unmanageWindow(this);
1859 }
1860
1861
1862 void Client::reparentHandler(const XReparentEvent &e)
1863 {
1864   // this is when the client is first taken captive in the frame
1865   if (e.parent == frame->plate()) return;
1866
1867 #ifdef    DEBUG
1868   printf("ReparentNotify for 0x%lx\n", e.window);
1869 #endif // DEBUG
1870
1871   otk::EventHandler::reparentHandler(e);
1872
1873   /*
1874     This event is quite rare and is usually handled in unmapHandler.
1875     However, if the window is unmapped when the reparent event occurs,
1876     the window manager never sees it because an unmap event is not sent
1877     to an already unmapped window.
1878   */
1879
1880   // we don't want the reparent event, put it back on the stack for the X
1881   // server to deal with after we unmanage the window
1882   XEvent ev;
1883   ev.xreparent = e;
1884   XPutBackEvent(**otk::display, &ev);
1885   
1886   // this deletes us etc
1887   openbox->screen(_screen)->unmanageWindow(this);
1888 }
1889
1890 void Client::mapRequestHandler(const XMapRequestEvent &e)
1891 {
1892 #ifdef    DEBUG
1893   printf("MapRequest for already managed 0x%lx\n", e.window);
1894 #endif // DEBUG
1895
1896   assert(_iconic); // we shouldn't be able to get this unless we're iconic
1897
1898   // move to the current desktop (uniconify)
1899   setDesktop(openbox->screen(_screen)->desktop());
1900   // XXX: should we focus/raise the window? (basically a net_wm_active_window)
1901 }
1902
1903 }