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