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