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