]> icculus.org git repositories - mikachu/openbox.git/blob - src/client.cc
remove includes for widgetbase.hh. fix bug with circular modal pointer.
[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 "gettext.h"
21 #define _(str) gettext(str)
22 }
23
24 #include <climits>
25 #include <cassert>
26 #include <algorithm>
27
28 namespace ob {
29
30 Client::Client(int screen, Window window)
31   : otk::EventHandler(),
32     frame(0), _screen(screen), _window(window)
33 {
34   assert(screen >= 0);
35   assert(window);
36
37   ignore_unmaps = 0;
38   
39   // update EVERYTHING the first time!!
40
41   // defaults
42   _wmstate = NormalState;
43   _focused = false;
44   _transient_for = 0;
45   _layer = Layer_Normal;
46   _urgent = false;
47   _positioned = false;
48   _disabled_decorations = 0;
49   _modal_child = 0;
50   _group = None;
51   _desktop = 0;
52   
53   getArea();
54   getDesktop();
55   getState();  // do this before updateTransientFor! (for _modal)
56   getShaped();
57
58   updateTransientFor();
59   getMwmHints();
60   getType(); // this can change the mwmhints for special cases
61
62   updateProtocols();
63
64   getGravity();        // get the attribute gravity
65   updateNormalHints(); // this may override the attribute gravity
66
67   // got the type, the mwmhints, the protocols, and the normal hints (min/max
68   // sizes), so we're ready to set up
69   // the decorations/functions
70   setupDecorAndFunctions();
71   
72   // also get the initial_state and set _iconic if we aren't "starting"
73   // when we're "starting" that means we should use whatever state was already
74   // on the window over the initial map state, because it was already mapped
75   updateWMHints(openbox->state() != Openbox::State_Starting);
76   updateTitle();
77   updateIconTitle();
78   updateClass();
79   updateStrut();
80
81   // this makes sure that these windows appear on all desktops
82   if (/*_type == Type_Dock ||*/ _type == Type_Desktop)
83     _desktop = 0xffffffff;
84   
85   // set the desktop hint, to make sure that it always exists, and to reflect
86   // any changes we've made here
87   otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
88                      otk::Property::atoms.cardinal, (unsigned)_desktop);
89   
90   changeState();
91 }
92
93
94 Client::~Client()
95 {
96   // clean up childrens' references
97   while (!_transients.empty()) {
98     _transients.front()->_transient_for = 0;
99     _transients.pop_front();
100   }
101
102   // clean up parents reference to this
103   if (_transient_for)
104     _transient_for->_transients.remove(this); // remove from old parent
105   
106   if (openbox->state() != Openbox::State_Exiting) {
107     // these values should not be persisted across a window unmapping/mapping
108     otk::Property::erase(_window, otk::Property::atoms.net_wm_desktop);
109     otk::Property::erase(_window, otk::Property::atoms.net_wm_state);
110   } else {
111     // if we're left in an iconic state, the client wont be mapped. this is
112     // bad, since we will no longer be managing the window on restart
113     if (_iconic)
114       XMapWindow(**otk::display, _window);
115   }
116 }
117
118
119 bool Client::validate() const
120 {
121   XSync(**otk::display, false); // get all events on the server
122
123   XEvent e;
124   if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &e) ||
125       XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &e)) {
126     XPutBackEvent(**otk::display, &e);
127     return false;
128   }
129
130   return true;
131 }
132
133
134 void Client::getGravity()
135 {
136   XWindowAttributes wattrib;
137   Status ret;
138
139   ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
140   assert(ret != BadWindow);
141   _gravity = wattrib.win_gravity;
142 }
143
144
145 void Client::getDesktop()
146 {
147   // defaults to the current desktop
148   _desktop = openbox->screen(_screen)->desktop();
149
150   if (otk::Property::get(_window, otk::Property::atoms.net_wm_desktop,
151                          otk::Property::atoms.cardinal,
152                          (long unsigned*)&_desktop)) {
153 #ifdef DEBUG
154 //    printf("Window requested desktop: %ld\n", _desktop);
155 #endif
156   }
157 }
158
159
160 void Client::getType()
161 {
162   _type = (WindowType) -1;
163   
164   unsigned long *val;
165   unsigned long num = (unsigned) -1;
166   if (otk::Property::get(_window, otk::Property::atoms.net_wm_window_type,
167                          otk::Property::atoms.atom, &num, &val)) {
168     // use the first value that we know about in the array
169     for (unsigned long i = 0; i < num; ++i) {
170       if (val[i] == otk::Property::atoms.net_wm_window_type_desktop)
171         _type = Type_Desktop;
172       else if (val[i] == otk::Property::atoms.net_wm_window_type_dock)
173         _type = Type_Dock;
174       else if (val[i] == otk::Property::atoms.net_wm_window_type_toolbar)
175         _type = Type_Toolbar;
176       else if (val[i] == otk::Property::atoms.net_wm_window_type_menu)
177         _type = Type_Menu;
178       else if (val[i] == otk::Property::atoms.net_wm_window_type_utility)
179         _type = Type_Utility;
180       else if (val[i] == otk::Property::atoms.net_wm_window_type_splash)
181         _type = Type_Splash;
182       else if (val[i] == otk::Property::atoms.net_wm_window_type_dialog)
183         _type = Type_Dialog;
184       else if (val[i] == otk::Property::atoms.net_wm_window_type_normal)
185         _type = Type_Normal;
186       else if (val[i] == otk::Property::atoms.kde_net_wm_window_type_override){
187         // prevent this window from getting any decor or functionality
188         _mwmhints.flags &= MwmFlag_Functions | MwmFlag_Decorations;
189         _mwmhints.decorations = 0;
190         _mwmhints.functions = 0;
191       }
192       if (_type != (WindowType) -1)
193         break; // grab the first known type
194     }
195     delete val;
196   }
197     
198   if (_type == (WindowType) -1) {
199     /*
200      * the window type hint was not set, which means we either classify ourself
201      * as a normal window or a dialog, depending on if we are a transient.
202      */
203     if (_transient_for)
204       _type = Type_Dialog;
205     else
206       _type = Type_Normal;
207   }
208 }
209
210
211 void Client::setupDecorAndFunctions()
212 {
213   // start with everything (cept fullscreen)
214   _decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
215     Decor_AllDesktops | Decor_Iconify | Decor_Maximize;
216   _functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize |
217     Func_Shade;
218   if (_delete_window) {
219     _decorations |= Decor_Close;
220     _functions |= Func_Close;
221   }
222
223   if (!(_min_size.width() < _max_size.width() ||
224         _min_size.height() < _max_size.height())) {
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 = otk::Rect(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 = otk::Size(1, 1);
481   _base_size = otk::Size(0, 0);
482   _min_size = otk::Size(0, 0);
483   _max_size = otk::Size(UINT_MAX, UINT_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->area().x(), y = frame->area().y();
497         frame->frameGravity(x, y);
498         _area = otk::Rect(otk::Point(x, y), _area.size());
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 = otk::Size(size.min_width, size.min_height);
509     
510     if (size.flags & PMaxSize)
511       _max_size = otk::Size(size.max_width, size.max_height);
512     
513     if (size.flags & PBaseSize)
514       _base_size = otk::Size(size.base_width, size.base_height);
515     
516     if (size.flags & PResizeInc)
517       _size_inc = otk::Size(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->adjustTitle();
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 == this) break; // circular?
825       if (c->_modal_child) break; // already has a modal child
826       c->_modal_child = this;
827     }
828   } else {
829     // try find a replacement modal dialog
830     Client *replacement = 0;
831     
832     Client *c = this;
833     while (c->_transient_for) // go up the tree
834       c = c->_transient_for;
835     replacement = c->findModalChild(this); // find a modal child, skipping this
836
837     c = this;
838     while (c->_transient_for) {
839       c = c->_transient_for;
840       if (c == this) break; // circular?
841       if (c->_modal_child != this) break; // has a different modal child
842       if (c == replacement) break; // found the replacement itself
843       c->_modal_child = replacement;
844     }
845   }
846   _modal = modal;
847 }
848
849
850 void Client::setState(StateAction action, long data1, long data2)
851 {
852   bool shadestate = _shaded;
853   bool fsstate = _fullscreen;
854   bool maxh = _max_horz;
855   bool maxv = _max_vert;
856   bool modal = _modal;
857
858   if (!(action == State_Add || action == State_Remove ||
859         action == State_Toggle))
860     return; // an invalid action was passed to the client message, ignore it
861
862   for (int i = 0; i < 2; ++i) {
863     Atom state = i == 0 ? data1 : data2;
864     
865     if (! state) continue;
866
867     // if toggling, then pick whether we're adding or removing
868     if (action == State_Toggle) {
869       if (state == otk::Property::atoms.net_wm_state_modal)
870         action = _modal ? State_Remove : State_Add;
871       else if (state == otk::Property::atoms.net_wm_state_maximized_vert)
872         action = _max_vert ? State_Remove : State_Add;
873       else if (state == otk::Property::atoms.net_wm_state_maximized_horz)
874         action = _max_horz ? State_Remove : State_Add;
875       else if (state == otk::Property::atoms.net_wm_state_shaded)
876         action = _shaded ? State_Remove : State_Add;
877       else if (state == otk::Property::atoms.net_wm_state_skip_taskbar)
878         action = _skip_taskbar ? State_Remove : State_Add;
879       else if (state == otk::Property::atoms.net_wm_state_skip_pager)
880         action = _skip_pager ? State_Remove : State_Add;
881       else if (state == otk::Property::atoms.net_wm_state_fullscreen)
882         action = _fullscreen ? State_Remove : State_Add;
883       else if (state == otk::Property::atoms.net_wm_state_above)
884         action = _above ? State_Remove : State_Add;
885       else if (state == otk::Property::atoms.net_wm_state_below)
886         action = _below ? State_Remove : State_Add;
887     }
888     
889     if (action == State_Add) {
890       if (state == otk::Property::atoms.net_wm_state_modal) {
891         if (_modal) continue;
892         modal = true;
893       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
894         maxv = true;
895       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
896         if (_max_horz) continue;
897         maxh = true;
898       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
899         shadestate = true;
900       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
901         _skip_taskbar = true;
902       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
903         _skip_pager = true;
904       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
905         fsstate = true;
906       } else if (state == otk::Property::atoms.net_wm_state_above) {
907         if (_above) continue;
908         _above = true;
909       } else if (state == otk::Property::atoms.net_wm_state_below) {
910         if (_below) continue;
911         _below = true;
912       }
913
914     } else { // action == State_Remove
915       if (state == otk::Property::atoms.net_wm_state_modal) {
916         if (!_modal) continue;
917         modal = false;
918       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
919         maxv = false;
920       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
921         maxh = false;
922       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
923         shadestate = false;
924       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
925         _skip_taskbar = false;
926       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
927         _skip_pager = false;
928       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
929         fsstate = false;
930       } else if (state == otk::Property::atoms.net_wm_state_above) {
931         if (!_above) continue;
932         _above = false;
933       } else if (state == otk::Property::atoms.net_wm_state_below) {
934         if (!_below) continue;
935         _below = false;
936       }
937     }
938   }
939   if (maxh != _max_horz || maxv != _max_vert) {
940     if (maxh != _max_horz && maxv != _max_vert) { // toggling both
941       if (maxh == maxv) { // both going the same way
942         maximize(maxh, 0, true);
943       } else {
944         maximize(maxh, 1, true);
945         maximize(maxv, 2, true);
946       }
947     } else { // toggling one
948       if (maxh != _max_horz)
949         maximize(maxh, 1, true);
950       else
951         maximize(maxv, 2, true);
952     }
953   }
954   if (modal != _modal)
955     setModal(modal);
956   // change fullscreen state before shading, as it will affect if the window
957   // can shade or not
958   if (fsstate != _fullscreen)
959     fullscreen(fsstate, true);
960   if (shadestate != _shaded)
961     shade(shadestate);
962   calcLayer();
963   changeState(); // change the hint to relect these changes
964 }
965
966
967 void Client::toggleClientBorder(bool addborder)
968 {
969   // adjust our idea of where the client is, based on its border. When the
970   // border is removed, the client should now be considered to be in a
971   // different position.
972   // when re-adding the border to the client, the same operation needs to be
973   // reversed.
974   int oldx = _area.x(), oldy = _area.y();
975   int x = oldx, y = oldy;
976   switch(_gravity) {
977   default:
978   case NorthWestGravity:
979   case WestGravity:
980   case SouthWestGravity:
981     break;
982   case NorthEastGravity:
983   case EastGravity:
984   case SouthEastGravity:
985     if (addborder) x -= _border_width * 2;
986     else           x += _border_width * 2;
987     break;
988   case NorthGravity:
989   case SouthGravity:
990   case CenterGravity:
991   case ForgetGravity:
992   case StaticGravity:
993     if (addborder) x -= _border_width;
994     else           x += _border_width;
995     break;
996   }
997   switch(_gravity) {
998   default:
999   case NorthWestGravity:
1000   case NorthGravity:
1001   case NorthEastGravity:
1002     break;
1003   case SouthWestGravity:
1004   case SouthGravity:
1005   case SouthEastGravity:
1006     if (addborder) y -= _border_width * 2;
1007     else           y += _border_width * 2;
1008     break;
1009   case WestGravity:
1010   case EastGravity:
1011   case CenterGravity:
1012   case ForgetGravity:
1013   case StaticGravity:
1014     if (addborder) y -= _border_width;
1015     else           y += _border_width;
1016     break;
1017   }
1018   _area = otk::Rect(otk::Point(x, y), _area.size());
1019
1020   if (addborder) {
1021     XSetWindowBorderWidth(**otk::display, _window, _border_width);
1022
1023     // move the client so it is back it the right spot _with_ its border!
1024     if (x != oldx || y != oldy)
1025       XMoveWindow(**otk::display, _window, x, y);
1026   } else
1027     XSetWindowBorderWidth(**otk::display, _window, 0);
1028 }
1029
1030
1031 void Client::clientMessageHandler(const XClientMessageEvent &e)
1032 {
1033   otk::EventHandler::clientMessageHandler(e);
1034   
1035   // validate cuz we query stuff off the client here
1036   if (!validate()) return;
1037   
1038   if (e.format != 32) return;
1039
1040   if (e.message_type == otk::Property::atoms.wm_change_state) {
1041     // compress changes into a single change
1042     bool compress = false;
1043     XEvent ce;
1044     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
1045       // XXX: it would be nice to compress ALL messages of a type, not just
1046       //      messages in a row without other message types between.
1047       if (ce.xclient.message_type != e.message_type) {
1048         XPutBackEvent(**otk::display, &ce);
1049         break;
1050       }
1051       compress = true;
1052     }
1053     if (compress)
1054       setWMState(ce.xclient.data.l[0]); // use the found event
1055     else
1056       setWMState(e.data.l[0]); // use the original event
1057   } else if (e.message_type == otk::Property::atoms.net_wm_desktop) {
1058     // compress changes into a single change 
1059     bool compress = false;
1060     XEvent ce;
1061     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
1062       // XXX: it would be nice to compress ALL messages of a type, not just
1063       //      messages in a row without other message types between.
1064       if (ce.xclient.message_type != e.message_type) {
1065         XPutBackEvent(**otk::display, &ce);
1066         break;
1067       }
1068       compress = true;
1069     }
1070     if (compress)
1071       setDesktop(e.data.l[0]); // use the found event
1072     else
1073       setDesktop(e.data.l[0]); // use the original event
1074   } else if (e.message_type == otk::Property::atoms.net_wm_state) {
1075     // can't compress these
1076 #ifdef DEBUG
1077     printf("net_wm_state %s %ld %ld for 0x%lx\n",
1078            (e.data.l[0] == 0 ? "Remove" : e.data.l[0] == 1 ? "Add" :
1079             e.data.l[0] == 2 ? "Toggle" : "INVALID"),
1080            e.data.l[1], e.data.l[2], _window);
1081 #endif
1082     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
1083   } else if (e.message_type == otk::Property::atoms.net_close_window) {
1084 #ifdef DEBUG
1085     printf("net_close_window for 0x%lx\n", _window);
1086 #endif
1087     close();
1088   } else if (e.message_type == otk::Property::atoms.net_active_window) {
1089 #ifdef DEBUG
1090     printf("net_active_window for 0x%lx\n", _window);
1091 #endif
1092     if (_iconic)
1093       setDesktop(openbox->screen(_screen)->desktop());
1094     if (_shaded)
1095       shade(false);
1096     focus();
1097     openbox->screen(_screen)->raiseWindow(this);
1098   } else if (e.message_type == otk::Property::atoms.openbox_active_window) {
1099     if (_iconic)
1100       setDesktop(openbox->screen(_screen)->desktop());
1101     if (e.data.l[0] && _shaded)
1102       shade(false);
1103     focus();
1104     if (e.data.l[1])
1105       openbox->screen(_screen)->raiseWindow(this);
1106   }
1107 }
1108
1109
1110 #if defined(SHAPE)
1111 void Client::shapeHandler(const XShapeEvent &e)
1112 {
1113   otk::EventHandler::shapeHandler(e);
1114
1115   if (e.kind == ShapeBounding) {
1116     _shaped = e.shaped;
1117     frame->adjustShape();
1118   }
1119 }
1120 #endif
1121
1122
1123 void Client::resize(Corner anchor, unsigned int w, unsigned int h)
1124 {
1125   if (!(_functions & Func_Resize)) return;
1126   internal_resize(anchor, w, h);
1127 }
1128
1129
1130 void Client::internal_resize(Corner anchor, unsigned int w, unsigned int h,
1131                              bool user, int x, int y)
1132 {
1133   if (_base_size.width() < w)
1134     w -= _base_size.width();
1135   else
1136     w = 0;
1137   if (_base_size.height() < h)
1138     h -= _base_size.height();
1139   else
1140     h = 0;
1141
1142   if (user) {
1143     // for interactive resizing. have to move half an increment in each
1144     // direction.
1145     unsigned int mw = w % _size_inc.width(); // how far we are towards the next
1146                                              // size inc
1147     unsigned int mh = h % _size_inc.height();
1148     unsigned int aw = _size_inc.width() / 2; // amount to add
1149     unsigned int ah = _size_inc.height() / 2;
1150     // don't let us move into a new size increment
1151     if (mw + aw >= _size_inc.width()) aw = _size_inc.width() - mw - 1;
1152     if (mh + ah >= _size_inc.height()) ah = _size_inc.height() - mh - 1;
1153     w += aw;
1154     h += ah;
1155     
1156     // if this is a user-requested resize, then check against min/max sizes
1157     // and aspect ratios
1158
1159     // smaller than min size or bigger than max size?
1160     if (w < _min_size.width()) w = _min_size.width();
1161     else if (w > _max_size.width()) w = _max_size.width();
1162     if (h < _min_size.height()) h = _min_size.height();
1163     else if (h > _max_size.height()) h = _max_size.height();
1164
1165     // adjust the height ot match the width for the aspect ratios
1166     if (_min_ratio)
1167       if (h * _min_ratio > w) h = static_cast<int>(w / _min_ratio);
1168     if (_max_ratio)
1169       if (h * _max_ratio < w) h = static_cast<int>(w / _max_ratio);
1170   }
1171
1172   // keep to the increments
1173   w /= _size_inc.width();
1174   h /= _size_inc.height();
1175
1176   // you cannot resize to nothing
1177   if (w < 1) w = 1;
1178   if (h < 1) h = 1;
1179   
1180   // store the logical size
1181   _logical_size = otk::Size(w, h);
1182
1183   w *= _size_inc.width();
1184   h *= _size_inc.height();
1185
1186   w += _base_size.width();
1187   h += _base_size.height();
1188
1189   if (x == INT_MIN || y == INT_MIN) {
1190     x = _area.x();
1191     y = _area.y();
1192     switch (anchor) {
1193     case TopLeft:
1194       break;
1195     case TopRight:
1196       x -= w - _area.width();
1197       break;
1198     case BottomLeft:
1199       y -= h - _area.height();
1200       break;
1201     case BottomRight:
1202       x -= w - _area.width();
1203       y -= h - _area.height();
1204       break;
1205     }
1206   }
1207
1208   _area = otk::Rect(_area.position(), otk::Size(w, h));
1209
1210   XResizeWindow(**otk::display, _window, w, h);
1211
1212   // resize the frame to match the request
1213   frame->adjustSize();
1214   internal_move(x, y);
1215 }
1216
1217
1218 void Client::move(int x, int y)
1219 {
1220   if (!(_functions & Func_Move)) return;
1221   frame->frameGravity(x, y); // get the client's position based on x,y for the
1222                              // frame
1223   internal_move(x, y);
1224 }
1225
1226
1227 void Client::internal_move(int x, int y)
1228 {
1229   _area = otk::Rect(otk::Point(x, y), _area.size());
1230
1231   // move the frame to be in the requested position
1232   if (frame) { // this can be called while mapping, before frame exists
1233     frame->adjustPosition();
1234
1235     // send synthetic configure notify (we don't need to if we aren't mapped
1236     // yet)
1237     XEvent event;
1238     event.type = ConfigureNotify;
1239     event.xconfigure.display = **otk::display;
1240     event.xconfigure.event = _window;
1241     event.xconfigure.window = _window;
1242     
1243     // root window coords with border in mind
1244     event.xconfigure.x = x - _border_width + frame->size().left;
1245     event.xconfigure.y = y - _border_width + frame->size().top;
1246     
1247     event.xconfigure.width = _area.width();
1248     event.xconfigure.height = _area.height();
1249     event.xconfigure.border_width = _border_width;
1250     event.xconfigure.above = frame->plate();
1251     event.xconfigure.override_redirect = False;
1252     XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1253                StructureNotifyMask, &event);
1254 #if 0//def DEBUG
1255     printf("Sent synthetic ConfigureNotify %d,%d %d,%d to 0x%lx\n",
1256            event.xconfigure.x, event.xconfigure.y, event.xconfigure.width,
1257            event.xconfigure.height, event.xconfigure.window);
1258 #endif
1259   }
1260 }
1261
1262
1263 void Client::close()
1264 {
1265   XEvent ce;
1266
1267   if (!(_functions & Func_Close)) return;
1268
1269   // XXX: itd be cool to do timeouts and shit here for killing the client's
1270   //      process off
1271   // like... if the window is around after 5 seconds, then the close button
1272   // turns a nice red, and if this function is called again, the client is
1273   // explicitly killed.
1274
1275   ce.xclient.type = ClientMessage;
1276   ce.xclient.message_type =  otk::Property::atoms.wm_protocols;
1277   ce.xclient.display = **otk::display;
1278   ce.xclient.window = _window;
1279   ce.xclient.format = 32;
1280   ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1281   ce.xclient.data.l[1] = CurrentTime;
1282   ce.xclient.data.l[2] = 0l;
1283   ce.xclient.data.l[3] = 0l;
1284   ce.xclient.data.l[4] = 0l;
1285   XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1286 }
1287
1288
1289 void Client::changeState()
1290 {
1291   unsigned long state[2];
1292   state[0] = _wmstate;
1293   state[1] = None;
1294   otk::Property::set(_window, otk::Property::atoms.wm_state,
1295                      otk::Property::atoms.wm_state, state, 2);
1296
1297   Atom netstate[10];
1298   int num = 0;
1299   if (_modal)
1300     netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1301   if (_shaded)
1302     netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1303   if (_iconic)
1304     netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1305   if (_skip_taskbar)
1306     netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1307   if (_skip_pager)
1308     netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1309   if (_fullscreen)
1310     netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1311   if (_max_vert)
1312     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1313   if (_max_horz)
1314     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1315   if (_above)
1316     netstate[num++] = otk::Property::atoms.net_wm_state_above;
1317   if (_below)
1318     netstate[num++] = otk::Property::atoms.net_wm_state_below;
1319   otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1320                      otk::Property::atoms.atom, netstate, num);
1321
1322   calcLayer();
1323
1324   if (frame)
1325     frame->adjustState();
1326 }
1327
1328
1329 void Client::changeAllowedActions(void)
1330 {
1331   Atom actions[9];
1332   int num = 0;
1333
1334   actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1335
1336   if (_functions & Func_Shade)
1337     actions[num++] = otk::Property::atoms.net_wm_action_shade;
1338   if (_functions & Func_Close)
1339     actions[num++] = otk::Property::atoms.net_wm_action_close;
1340   if (_functions & Func_Move)
1341     actions[num++] = otk::Property::atoms.net_wm_action_move;
1342   if (_functions & Func_Iconify)
1343     actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1344   if (_functions & Func_Resize)
1345     actions[num++] = otk::Property::atoms.net_wm_action_resize;
1346   if (_functions & Func_Fullscreen)
1347     actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1348   if (_functions & Func_Maximize) {
1349     actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1350     actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1351   }
1352
1353   otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1354                      otk::Property::atoms.atom, actions, num);
1355
1356   // make sure the window isn't breaking any rules now
1357   
1358   if (!(_functions & Func_Shade) && _shaded)
1359     if (frame) shade(false);
1360     else _shaded = false;
1361   if (!(_functions & Func_Iconify) && _iconic)
1362     if (frame) setDesktop(openbox->screen(_screen)->desktop());
1363     else _iconic = false;
1364   if (!(_functions & Func_Fullscreen) && _fullscreen)
1365     if (frame) fullscreen(false);
1366     else _fullscreen = false;
1367   if (!(_functions & Func_Maximize) && (_max_horz || _max_vert))
1368     if (frame) maximize(false, 0);
1369     else _max_vert = _max_horz = false;
1370 }
1371
1372
1373 void Client::remaximize()
1374 {
1375   int dir;
1376   if (_max_horz && _max_vert)
1377     dir = 0;
1378   else if (_max_horz)
1379     dir = 1;
1380   else if (_max_vert)
1381     dir = 2;
1382   else
1383     return; // not maximized
1384   _max_horz = _max_vert = false;
1385   maximize(true, dir, false);
1386 }
1387
1388
1389 void Client::applyStartupState()
1390 {
1391   // these are in a carefully crafted order..
1392
1393   if (_modal) {
1394     _modal = false;
1395     setModal(true);
1396   }
1397   
1398   if (_iconic) {
1399     _iconic = false;
1400     setDesktop(ICONIC_DESKTOP);
1401   }
1402   if (_fullscreen) {
1403     _fullscreen = false;
1404     fullscreen(true, false);
1405   }
1406   if (_shaded) {
1407     _shaded = false;
1408     shade(true);
1409   }
1410   if (_urgent)
1411     fireUrgent();
1412   
1413   if (_max_vert && _max_horz) {
1414     _max_vert = _max_horz = false;
1415     maximize(true, 0, false);
1416   } else if (_max_vert) {
1417     _max_vert = false;
1418     maximize(true, 2, false);
1419   } else if (_max_horz) {
1420     _max_horz = false;
1421     maximize(true, 1, false);
1422   }
1423
1424   if (_skip_taskbar); // nothing to do for this
1425   if (_skip_pager);   // nothing to do for this
1426   if (_modal);        // nothing to do for this
1427   if (_above);        // nothing to do for this
1428   if (_below);        // nothing to do for this
1429 }
1430
1431
1432 void Client::fireUrgent()
1433 {
1434   // call the python UrgentWindow callbacks
1435   EventData data(_screen, this, EventAction::UrgentWindow, 0);
1436   openbox->bindings()->fireEvent(&data);
1437 }
1438
1439
1440 void Client::shade(bool shade)
1441 {
1442   if (!(_functions & Func_Shade) || // can't
1443       _shaded == shade) return;     // already done
1444
1445   // when we're iconic, don't change the wmstate
1446   if (!_iconic)
1447     _wmstate = shade ? IconicState : NormalState;
1448   _shaded = shade;
1449   changeState();
1450   frame->adjustSize();
1451 }
1452
1453
1454 void Client::maximize(bool max, int dir, bool savearea)
1455 {
1456   assert(dir == 0 || dir == 1 || dir == 2);
1457   if (!(_functions & Func_Maximize)) return; // can't
1458
1459   // check if already done
1460   if (max) {
1461     if (dir == 0 && _max_horz && _max_vert) return;
1462     if (dir == 1 && _max_horz) return;
1463     if (dir == 2 && _max_vert) return;
1464   } else {
1465     if (dir == 0 && !_max_horz && !_max_vert) return;
1466     if (dir == 1 && !_max_horz) return;
1467     if (dir == 2 && !_max_vert) return;
1468   }
1469
1470   const otk::Rect &a = openbox->screen(_screen)->area();
1471   int x = frame->area().x(), y = frame->area().y(),
1472     w = _area.width(), h = _area.height();
1473   
1474   if (max) {
1475     if (savearea) {
1476       long dimensions[4];
1477       long *readdim;
1478       unsigned long n = 4;
1479
1480       dimensions[0] = x;
1481       dimensions[1] = y;
1482       dimensions[2] = w;
1483       dimensions[3] = h;
1484
1485       // get the property off the window and use it for the dimentions we are
1486       // already maxed on
1487       if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1488                              otk::Property::atoms.cardinal, &n,
1489                              (long unsigned**) &readdim)) {
1490         if (n >= 4) {
1491           if (_max_horz) {
1492             dimensions[0] = readdim[0];
1493             dimensions[2] = readdim[2];
1494           }
1495           if (_max_vert) {
1496             dimensions[1] = readdim[1];
1497             dimensions[3] = readdim[3];
1498           }
1499         }
1500         delete readdim;
1501       }
1502       
1503       otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1504                          otk::Property::atoms.cardinal,
1505                          (long unsigned*)dimensions, 4);
1506     }
1507     if (dir == 0 || dir == 1) { // horz
1508       x = a.x();
1509       w = a.width();
1510     }
1511     if (dir == 0 || dir == 2) { // vert
1512       y = a.y();
1513       h = a.height() - frame->size().top - frame->size().bottom;
1514     }
1515   } else {
1516     long *dimensions;
1517     long unsigned n = 4;
1518       
1519     if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1520                            otk::Property::atoms.cardinal, &n,
1521                            (long unsigned**) &dimensions)) {
1522       if (n >= 4) {
1523         if (dir == 0 || dir == 1) { // horz
1524           x = (signed int)dimensions[0];
1525           w = (signed int)dimensions[2];
1526         }
1527         if (dir == 0 || dir == 2) { // vert
1528           y = (signed int)dimensions[1];
1529           h = (signed int)dimensions[3];
1530         }
1531       }
1532       delete dimensions;
1533     } else {
1534       // pick some fallbacks...
1535       if (dir == 0 || dir == 1) { // horz
1536         x = a.x() + a.width() / 4;
1537         w = a.width() / 2;
1538       }
1539       if (dir == 0 || dir == 2) { // vert
1540         y = a.y() + a.height() / 4;
1541         h = a.height() / 2;
1542       }
1543     }
1544   }
1545
1546   if (dir == 0 || dir == 1) // horz
1547     _max_horz = max;
1548   if (dir == 0 || dir == 2) // vert
1549     _max_vert = max;
1550
1551   if (!_max_horz && !_max_vert)
1552     otk::Property::erase(_window, otk::Property::atoms.openbox_premax);
1553
1554   changeState(); // change the state hints on the client
1555
1556   frame->frameGravity(x, y); // figure out where the client should be going
1557   internal_resize(TopLeft, w, h, true, x, y);
1558 }
1559
1560
1561 void Client::fullscreen(bool fs, bool savearea)
1562 {
1563   static FunctionFlags saved_func;
1564   static DecorationFlags saved_decor;
1565
1566   if (!(_functions & Func_Fullscreen) || // can't
1567       _fullscreen == fs) return;         // already done
1568
1569   _fullscreen = fs;
1570   changeState(); // change the state hints on the client
1571
1572   int x = _area.x(), y = _area.y(), w = _area.width(), h = _area.height();
1573   
1574   if (fs) {
1575     // save the functions and remove them
1576     saved_func = _functions;
1577     _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1578     // save the decorations and remove them
1579     saved_decor = _decorations;
1580     _decorations = 0;
1581     if (savearea) {
1582       long dimensions[4];
1583       dimensions[0] = _area.x();
1584       dimensions[1] = _area.y();
1585       dimensions[2] = _area.width();
1586       dimensions[3] = _area.height();
1587       otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1588                          otk::Property::atoms.cardinal,
1589                          (long unsigned*)dimensions, 4);
1590     }
1591     const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1592     x = 0;
1593     y = 0;
1594     w = info->size().width();
1595     h = info->size().height();
1596   } else {
1597     _functions = saved_func;
1598     _decorations = saved_decor;
1599
1600     long *dimensions;
1601     long unsigned n = 4;
1602       
1603     if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1604                            otk::Property::atoms.cardinal, &n,
1605                            (long unsigned**) &dimensions)) {
1606       if (n >= 4) {
1607         x = dimensions[0];
1608         y = dimensions[1];
1609         w = dimensions[2];
1610         h = dimensions[3];
1611       }
1612       delete dimensions;
1613     } else {
1614       // pick some fallbacks...
1615       const otk::Rect &a = openbox->screen(_screen)->area();
1616       x = a.x() + a.width() / 4;
1617       y = a.y() + a.height() / 4;
1618       w = a.width() / 2;
1619         h = a.height() / 2;
1620     }    
1621   }
1622   
1623   changeAllowedActions();  // based on the new _functions
1624
1625   // when fullscreening, don't obey things like increments, fill the screen
1626   internal_resize(TopLeft, w, h, !fs, x, y);
1627
1628   // raise (back) into our stacking layer
1629   openbox->screen(_screen)->raiseWindow(this);
1630
1631   // try focus us when we go into fullscreen mode
1632   if (fs) focus();
1633 }
1634
1635
1636 void Client::disableDecorations(DecorationFlags flags)
1637 {
1638   _disabled_decorations = flags;
1639   setupDecorAndFunctions();
1640 }
1641
1642
1643 void Client::installColormap(bool install) const
1644 {
1645   XWindowAttributes wa;
1646   if (XGetWindowAttributes(**otk::display, _window, &wa)) {
1647     if (install)
1648       XInstallColormap(**otk::display, wa.colormap);
1649     else
1650       XUninstallColormap(**otk::display, wa.colormap);
1651   }
1652 }
1653
1654
1655 bool Client::focus()
1656 {
1657   // if we have a modal child, then focus it, not us
1658   if (_modal_child)
1659     return _modal_child->focus();
1660
1661   // won't try focus if the client doesn't want it, or if the window isn't
1662   // visible on the screen
1663   if (!(frame->visible() && (_can_focus || _focus_notify))) return false;
1664
1665   if (_focused) return true;
1666
1667   // do a check to see if the window has already been unmapped or destroyed
1668   // do this intelligently while watching out for unmaps we've generated
1669   // (ignore_unmaps > 0)
1670   XEvent ev;
1671   if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1672     XPutBackEvent(**otk::display, &ev);
1673     return false;
1674   }
1675   while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1676     if (ignore_unmaps) {
1677       unmapHandler(ev.xunmap);
1678     } else {
1679       XPutBackEvent(**otk::display, &ev);
1680       return false;
1681     }
1682   }
1683
1684   if (_can_focus)
1685     XSetInputFocus(**otk::display, _window,
1686                    RevertToNone, CurrentTime);
1687
1688   if (_focus_notify) {
1689     XEvent ce;
1690     ce.xclient.type = ClientMessage;
1691     ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1692     ce.xclient.display = **otk::display;
1693     ce.xclient.window = _window;
1694     ce.xclient.format = 32;
1695     ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1696     ce.xclient.data.l[1] = openbox->lastTime();
1697     ce.xclient.data.l[2] = 0l;
1698     ce.xclient.data.l[3] = 0l;
1699     ce.xclient.data.l[4] = 0l;
1700     XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1701   }
1702
1703   XSync(**otk::display, False);
1704   return true;
1705 }
1706
1707
1708 void Client::unfocus() const
1709 {
1710   if (!_focused) return;
1711
1712   assert(openbox->focusedClient() == this);
1713   openbox->setFocusedClient(0);
1714 }
1715
1716
1717 void Client::focusHandler(const XFocusChangeEvent &e)
1718 {
1719 #ifdef    DEBUG
1720 //  printf("FocusIn for 0x%lx\n", e.window);
1721 #endif // DEBUG
1722   
1723   otk::EventHandler::focusHandler(e);
1724
1725   _focused = true;
1726   frame->adjustFocus();
1727
1728   openbox->setFocusedClient(this);
1729 }
1730
1731
1732 void Client::unfocusHandler(const XFocusChangeEvent &e)
1733 {
1734 #ifdef    DEBUG
1735 //  printf("FocusOut for 0x%lx\n", e.window);
1736 #endif // DEBUG
1737   
1738   otk::EventHandler::unfocusHandler(e);
1739
1740   _focused = false;
1741   frame->adjustFocus();
1742
1743   if (openbox->focusedClient() == this)
1744     openbox->setFocusedClient(0);
1745 }
1746
1747
1748 void Client::configureRequestHandler(const XConfigureRequestEvent &ec)
1749 {
1750 #ifdef    DEBUG
1751   printf("ConfigureRequest for 0x%lx\n", ec.window);
1752 #endif // DEBUG
1753   
1754   otk::EventHandler::configureRequestHandler(ec);
1755
1756   // compress these
1757   XConfigureRequestEvent e = ec;
1758   XEvent ev;
1759   while (XCheckTypedWindowEvent(**otk::display, window(), ConfigureRequest,
1760                                 &ev)) {
1761     // XXX if this causes bad things.. we can compress config req's with the
1762     //     same mask.
1763     e.value_mask |= ev.xconfigurerequest.value_mask;
1764     if (ev.xconfigurerequest.value_mask & CWX)
1765       e.x = ev.xconfigurerequest.x;
1766     if (ev.xconfigurerequest.value_mask & CWY)
1767       e.y = ev.xconfigurerequest.y;
1768     if (ev.xconfigurerequest.value_mask & CWWidth)
1769       e.width = ev.xconfigurerequest.width;
1770     if (ev.xconfigurerequest.value_mask & CWHeight)
1771       e.height = ev.xconfigurerequest.height;
1772     if (ev.xconfigurerequest.value_mask & CWBorderWidth)
1773       e.border_width = ev.xconfigurerequest.border_width;
1774     if (ev.xconfigurerequest.value_mask & CWStackMode)
1775       e.detail = ev.xconfigurerequest.detail;
1776   }
1777
1778   // if we are iconic (or shaded (fvwm does this)) ignore the event
1779   if (_iconic || _shaded) return;
1780
1781   if (e.value_mask & CWBorderWidth)
1782     _border_width = e.border_width;
1783
1784   // resize, then move, as specified in the EWMH section 7.7
1785   if (e.value_mask & (CWWidth | CWHeight)) {
1786     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1787     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1788
1789     Corner corner;
1790     switch (_gravity) {
1791     case NorthEastGravity:
1792     case EastGravity:
1793       corner = TopRight;
1794       break;
1795     case SouthWestGravity:
1796     case SouthGravity:
1797       corner = BottomLeft;
1798       break;
1799     case SouthEastGravity:
1800       corner = BottomRight;
1801       break;
1802     default:     // NorthWest, Static, etc
1803       corner = TopLeft;
1804     }
1805
1806     // if moving AND resizing ...
1807     if (e.value_mask & (CWX | CWY)) {
1808       int x = (e.value_mask & CWX) ? e.x : _area.x();
1809       int y = (e.value_mask & CWY) ? e.y : _area.y();
1810       internal_resize(corner, w, h, false, x, y);
1811     } else // if JUST resizing...
1812       internal_resize(corner, w, h, false);
1813   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1814     int x = (e.value_mask & CWX) ? e.x : _area.x();
1815     int y = (e.value_mask & CWY) ? e.y : _area.y();
1816     internal_move(x, y);
1817   }
1818
1819   if (e.value_mask & CWStackMode) {
1820     switch (e.detail) {
1821     case Below:
1822     case BottomIf:
1823       openbox->screen(_screen)->lowerWindow(this);
1824       break;
1825
1826     case Above:
1827     case TopIf:
1828     default:
1829       openbox->screen(_screen)->raiseWindow(this);
1830       break;
1831     }
1832   }
1833 }
1834
1835
1836 void Client::unmapHandler(const XUnmapEvent &e)
1837 {
1838   if (ignore_unmaps) {
1839 #ifdef    DEBUG
1840 //  printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1841 #endif // DEBUG
1842     ignore_unmaps--;
1843     return;
1844   }
1845   
1846 #ifdef    DEBUG
1847   printf("UnmapNotify for 0x%lx\n", e.window);
1848 #endif // DEBUG
1849
1850   otk::EventHandler::unmapHandler(e);
1851
1852   // this deletes us etc
1853   openbox->screen(_screen)->unmanageWindow(this);
1854 }
1855
1856
1857 void Client::destroyHandler(const XDestroyWindowEvent &e)
1858 {
1859 #ifdef    DEBUG
1860   printf("DestroyNotify for 0x%lx\n", e.window);
1861 #endif // DEBUG
1862
1863   otk::EventHandler::destroyHandler(e);
1864
1865   // this deletes us etc
1866   openbox->screen(_screen)->unmanageWindow(this);
1867 }
1868
1869
1870 void Client::reparentHandler(const XReparentEvent &e)
1871 {
1872   // this is when the client is first taken captive in the frame
1873   if (e.parent == frame->plate()) return;
1874
1875 #ifdef    DEBUG
1876   printf("ReparentNotify for 0x%lx\n", e.window);
1877 #endif // DEBUG
1878
1879   otk::EventHandler::reparentHandler(e);
1880
1881   /*
1882     This event is quite rare and is usually handled in unmapHandler.
1883     However, if the window is unmapped when the reparent event occurs,
1884     the window manager never sees it because an unmap event is not sent
1885     to an already unmapped window.
1886   */
1887
1888   // we don't want the reparent event, put it back on the stack for the X
1889   // server to deal with after we unmanage the window
1890   XEvent ev;
1891   ev.xreparent = e;
1892   XPutBackEvent(**otk::display, &ev);
1893   
1894   // this deletes us etc
1895   openbox->screen(_screen)->unmanageWindow(this);
1896 }
1897
1898 void Client::mapRequestHandler(const XMapRequestEvent &e)
1899 {
1900 #ifdef    DEBUG
1901   printf("MapRequest for already managed 0x%lx\n", e.window);
1902 #endif // DEBUG
1903
1904   assert(_iconic); // we shouldn't be able to get this unless we're iconic
1905
1906   // move to the current desktop (uniconify)
1907   setDesktop(openbox->screen(_screen)->desktop());
1908   // XXX: should we focus/raise the window? (basically a net_wm_active_window)
1909 }
1910
1911 }