]> icculus.org git repositories - dana/openbox.git/blob - src/client.cc
fix a signed/unsigned thing
[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   // find a modal child recursively and try focus it
1554   List::iterator it, end = _transients.end();
1555   for (it = _transients.begin(); it != end; ++it)
1556     if ((*it)->focusModalChild())
1557       return true; // got one
1558   // none of our grand-children are modal, try our direct children
1559   for (it = _transients.begin(); it != end; ++it)
1560     if ((*it)->modal() && (*it)->focus())
1561       return true; // got one
1562   return false;
1563 }
1564
1565
1566 bool Client::focus()
1567 {
1568   // won't try focus if the client doesn't want it, or if the window isn't
1569   // visible on the screen
1570   if (!(frame->isVisible() && (_can_focus || _focus_notify))) return false;
1571
1572   if (_focused) return true;
1573
1574   if (!_modal)
1575     if (focusModalChild())
1576       return true;
1577
1578   // do a check to see if the window has already been unmapped or destroyed
1579   // do this intelligently while watching out for unmaps we've generated
1580   // (ignore_unmaps > 0)
1581   XEvent ev;
1582   if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1583     XPutBackEvent(**otk::display, &ev);
1584     return false;
1585   }
1586   while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1587     if (ignore_unmaps) {
1588       unmapHandler(ev.xunmap);
1589     } else {
1590       XPutBackEvent(**otk::display, &ev);
1591       return false;
1592     }
1593   }
1594
1595   if (_can_focus)
1596     XSetInputFocus(**otk::display, _window,
1597                    RevertToNone, CurrentTime);
1598
1599   if (_focus_notify) {
1600     XEvent ce;
1601     ce.xclient.type = ClientMessage;
1602     ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1603     ce.xclient.display = **otk::display;
1604     ce.xclient.window = _window;
1605     ce.xclient.format = 32;
1606     ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1607     ce.xclient.data.l[1] = openbox->lastTime();
1608     ce.xclient.data.l[2] = 0l;
1609     ce.xclient.data.l[3] = 0l;
1610     ce.xclient.data.l[4] = 0l;
1611     XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1612   }
1613
1614   XSync(**otk::display, False);
1615   return true;
1616 }
1617
1618
1619 void Client::unfocus() const
1620 {
1621   if (!_focused) return;
1622
1623   assert(openbox->focusedClient() == this);
1624   openbox->setFocusedClient(0);
1625 }
1626
1627
1628 void Client::focusHandler(const XFocusChangeEvent &e)
1629 {
1630 #ifdef    DEBUG
1631 //  printf("FocusIn for 0x%lx\n", e.window);
1632 #endif // DEBUG
1633   
1634   otk::EventHandler::focusHandler(e);
1635
1636   frame->focus();
1637   _focused = true;
1638
1639   openbox->setFocusedClient(this);
1640 }
1641
1642
1643 void Client::unfocusHandler(const XFocusChangeEvent &e)
1644 {
1645 #ifdef    DEBUG
1646 //  printf("FocusOut for 0x%lx\n", e.window);
1647 #endif // DEBUG
1648   
1649   otk::EventHandler::unfocusHandler(e);
1650
1651   frame->unfocus();
1652   _focused = false;
1653
1654   if (openbox->focusedClient() == this)
1655     openbox->setFocusedClient(0);
1656 }
1657
1658
1659 void Client::configureRequestHandler(const XConfigureRequestEvent &e)
1660 {
1661 #ifdef    DEBUG
1662   printf("ConfigureRequest for 0x%lx\n", e.window);
1663 #endif // DEBUG
1664   
1665   otk::EventHandler::configureRequestHandler(e);
1666
1667   // if we are iconic (or shaded (fvwm does this)) ignore the event
1668   if (_iconic || _shaded) return;
1669
1670   if (e.value_mask & CWBorderWidth)
1671     _border_width = e.border_width;
1672
1673   // resize, then move, as specified in the EWMH section 7.7
1674   if (e.value_mask & (CWWidth | CWHeight)) {
1675     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1676     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1677
1678     Corner corner;
1679     switch (_gravity) {
1680     case NorthEastGravity:
1681     case EastGravity:
1682       corner = TopRight;
1683       break;
1684     case SouthWestGravity:
1685     case SouthGravity:
1686       corner = BottomLeft;
1687       break;
1688     case SouthEastGravity:
1689       corner = BottomRight;
1690       break;
1691     default:     // NorthWest, Static, etc
1692       corner = TopLeft;
1693     }
1694
1695     // if moving AND resizing ...
1696     if (e.value_mask & (CWX | CWY)) {
1697       int x = (e.value_mask & CWX) ? e.x : _area.x();
1698       int y = (e.value_mask & CWY) ? e.y : _area.y();
1699       internal_resize(corner, w, h, false, x, y);
1700     } else // if JUST resizing...
1701       internal_resize(corner, w, h, false);
1702   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1703     int x = (e.value_mask & CWX) ? e.x : _area.x();
1704     int y = (e.value_mask & CWY) ? e.y : _area.y();
1705     internal_move(x, y);
1706   }
1707
1708   if (e.value_mask & CWStackMode) {
1709     switch (e.detail) {
1710     case Below:
1711     case BottomIf:
1712       openbox->screen(_screen)->lowerWindow(this);
1713       break;
1714
1715     case Above:
1716     case TopIf:
1717     default:
1718       openbox->screen(_screen)->raiseWindow(this);
1719       break;
1720     }
1721   }
1722 }
1723
1724
1725 void Client::unmapHandler(const XUnmapEvent &e)
1726 {
1727   if (ignore_unmaps) {
1728 #ifdef    DEBUG
1729 //  printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1730 #endif // DEBUG
1731     ignore_unmaps--;
1732     return;
1733   }
1734   
1735 #ifdef    DEBUG
1736   printf("UnmapNotify for 0x%lx\n", e.window);
1737 #endif // DEBUG
1738
1739   otk::EventHandler::unmapHandler(e);
1740
1741   // this deletes us etc
1742   openbox->screen(_screen)->unmanageWindow(this);
1743 }
1744
1745
1746 void Client::destroyHandler(const XDestroyWindowEvent &e)
1747 {
1748 #ifdef    DEBUG
1749   printf("DestroyNotify for 0x%lx\n", e.window);
1750 #endif // DEBUG
1751
1752   otk::EventHandler::destroyHandler(e);
1753
1754   // this deletes us etc
1755   openbox->screen(_screen)->unmanageWindow(this);
1756 }
1757
1758
1759 void Client::reparentHandler(const XReparentEvent &e)
1760 {
1761   // this is when the client is first taken captive in the frame
1762   if (e.parent == frame->plate()) return;
1763
1764 #ifdef    DEBUG
1765   printf("ReparentNotify for 0x%lx\n", e.window);
1766 #endif // DEBUG
1767
1768   otk::EventHandler::reparentHandler(e);
1769
1770   /*
1771     This event is quite rare and is usually handled in unmapHandler.
1772     However, if the window is unmapped when the reparent event occurs,
1773     the window manager never sees it because an unmap event is not sent
1774     to an already unmapped window.
1775   */
1776
1777   // we don't want the reparent event, put it back on the stack for the X
1778   // server to deal with after we unmanage the window
1779   XEvent ev;
1780   ev.xreparent = e;
1781   XPutBackEvent(**otk::display, &ev);
1782   
1783   // this deletes us etc
1784   openbox->screen(_screen)->unmanageWindow(this);
1785 }
1786
1787 void Client::mapRequestHandler(const XMapRequestEvent &e)
1788 {
1789 #ifdef    DEBUG
1790   printf("MapRequest for already managed 0x%lx\n", e.window);
1791 #endif // DEBUG
1792
1793   assert(_iconic); // we shouldn't be able to get this unless we're iconic
1794
1795   // move to the current desktop (uniconify)
1796   setDesktop(openbox->screen(_screen)->desktop());
1797   // XXX: should we focus/raise the window? (basically a net_wm_active_window)
1798 }
1799
1800 }