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