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