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