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