]> icculus.org git repositories - mikachu/openbox.git/blob - src/client.cc
dont let maximizing occur if the window cant move or resize
[mikachu/openbox.git] / src / client.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #ifdef HAVE_CONFIG_H
4 # include "../config.h"
5 #endif
6
7 #include "client.hh"
8 #include "frame.hh"
9 #include "screen.hh"
10 #include "openbox.hh"
11 #include "bindings.hh"
12 #include "otk/display.hh"
13 #include "otk/property.hh"
14
15 extern "C" {
16 #include <X11/Xlib.h>
17 #include <X11/Xutil.h>
18 #include <X11/Xatom.h>
19
20 #include <assert.h>
21
22 #include "gettext.h"
23 #define _(str) gettext(str)
24 }
25
26 #include <algorithm>
27
28 namespace ob {
29
30 Client::Client(int screen, Window window)
31   : otk::EventHandler(),
32     WidgetBase(WidgetBase::Type_Client),
33     frame(0), _screen(screen), _window(window)
34 {
35   assert(screen >= 0);
36   assert(window);
37
38   ignore_unmaps = 0;
39   
40   // update EVERYTHING the first time!!
41
42   // we default to NormalState, visible
43   _wmstate = NormalState;
44   // start unfocused
45   _focused = false;
46   // not a transient by default of course
47   _transient_for = 0;
48   // pick a layer to start from
49   _layer = Layer_Normal;
50   // default to not urgent
51   _urgent = false;
52   // not positioned unless specified
53   _positioned = false;
54   // nothing is disabled unless specified
55   _disabled_decorations = 0;
56   // 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   // can't maximize without moving/resizing
304   if (!((_functions & Func_Move) && (_functions & Func_Resize)))
305     _functions &= ~Func_Maximize;
306
307   // finally, user specified disabled decorations are applied to subtract
308   // decorations
309   if (_disabled_decorations & Decor_Titlebar)
310     _decorations &= ~Decor_Titlebar;
311   if (_disabled_decorations & Decor_Handle)
312     _decorations &= ~Decor_Handle;
313   if (_disabled_decorations & Decor_Border)
314     _decorations &= ~Decor_Border;
315   if (_disabled_decorations & Decor_Iconify)
316     _decorations &= ~Decor_Iconify;
317   if (_disabled_decorations & Decor_Maximize)
318     _decorations &= ~Decor_Maximize;
319   if (_disabled_decorations & Decor_AllDesktops)
320     _decorations &= ~Decor_AllDesktops;
321   if (_disabled_decorations & Decor_Close)
322     _decorations &= ~Decor_Close;
323
324   // You can't shade without a titlebar
325   if (!(_decorations & Decor_Titlebar))
326     _functions &= ~Func_Shade;
327   
328   changeAllowedActions();
329
330   if (frame) {
331     frame->adjustSize(); // change the decors on the frame
332     frame->adjustPosition(); // with more/less decorations, we may need to be
333                              // moved
334   }
335 }
336
337
338 void Client::getMwmHints()
339 {
340   unsigned long num = MwmHints::elements;
341   unsigned long *hints;
342
343   _mwmhints.flags = 0; // default to none
344   
345   if (!otk::Property::get(_window, otk::Property::atoms.motif_wm_hints,
346                           otk::Property::atoms.motif_wm_hints, &num,
347                           (unsigned long **)&hints))
348     return;
349   
350   if (num >= MwmHints::elements) {
351     // retrieved the hints
352     _mwmhints.flags = hints[0];
353     _mwmhints.functions = hints[1];
354     _mwmhints.decorations = hints[2];
355   }
356
357   delete [] hints;
358 }
359
360
361 void Client::getArea()
362 {
363   XWindowAttributes wattrib;
364   Status ret;
365   
366   ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
367   assert(ret != BadWindow);
368
369   _area.setRect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
370   _border_width = wattrib.border_width;
371 }
372
373
374 void Client::getState()
375 {
376   _modal = _shaded = _max_horz = _max_vert = _fullscreen = _above = _below =
377     _iconic = _skip_taskbar = _skip_pager = false;
378   
379   unsigned long *state;
380   unsigned long num = (unsigned) -1;
381   
382   if (otk::Property::get(_window, otk::Property::atoms.net_wm_state,
383                          otk::Property::atoms.atom, &num, &state)) {
384     for (unsigned long i = 0; i < num; ++i) {
385       if (state[i] == otk::Property::atoms.net_wm_state_modal)
386         _modal = true;
387       else if (state[i] == otk::Property::atoms.net_wm_state_shaded)
388         _shaded = true;
389       else if (state[i] == otk::Property::atoms.net_wm_state_hidden)
390         _iconic = true;
391       else if (state[i] == otk::Property::atoms.net_wm_state_skip_taskbar)
392         _skip_taskbar = true;
393       else if (state[i] == otk::Property::atoms.net_wm_state_skip_pager)
394         _skip_pager = true;
395       else if (state[i] == otk::Property::atoms.net_wm_state_fullscreen)
396         _fullscreen = true;
397       else if (state[i] == otk::Property::atoms.net_wm_state_maximized_vert)
398         _max_vert = true;
399       else if (state[i] == otk::Property::atoms.net_wm_state_maximized_horz)
400         _max_horz = true;
401       else if (state[i] == otk::Property::atoms.net_wm_state_above)
402         _above = true;
403       else if (state[i] == otk::Property::atoms.net_wm_state_below)
404         _below = true;
405     }
406
407     delete [] state;
408   }
409 }
410
411
412 void Client::getShaped()
413 {
414   _shaped = false;
415 #ifdef   SHAPE
416   if (otk::display->shape()) {
417     int foo;
418     unsigned int ufoo;
419     int s;
420
421     XShapeSelectInput(**otk::display, _window, ShapeNotifyMask);
422
423     XShapeQueryExtents(**otk::display, _window, &s, &foo,
424                        &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo);
425     _shaped = (s != 0);
426   }
427 #endif // SHAPE
428 }
429
430
431 void Client::calcLayer() {
432   StackLayer l;
433
434   if (_iconic) l = Layer_Icon;
435   else if (_fullscreen) l = Layer_Fullscreen;
436   else if (_type == Type_Desktop) l = Layer_Desktop;
437   else if (_type == Type_Dock) {
438     if (!_below) l = Layer_Top;
439     else l = Layer_Normal;
440   }
441   else if (_above) l = Layer_Above;
442   else if (_below) l = Layer_Below;
443   else l = Layer_Normal;
444
445   if (l != _layer) {
446     _layer = l;
447     if (frame) {
448       /*
449         if we don't have a frame, then we aren't mapped yet (and this would
450         SIGSEGV :)
451       */
452       openbox->screen(_screen)->raiseWindow(this);
453     }
454   }
455 }
456
457
458 void Client::updateProtocols()
459 {
460   Atom *proto;
461   int num_return = 0;
462
463   _focus_notify = false;
464   _delete_window = false;
465
466   if (XGetWMProtocols(**otk::display, _window, &proto, &num_return)) {
467     for (int i = 0; i < num_return; ++i) {
468       if (proto[i] == otk::Property::atoms.wm_delete_window) {
469         // this means we can request the window to close
470         _delete_window = true;
471       } else if (proto[i] == otk::Property::atoms.wm_take_focus)
472         // if this protocol is requested, then the window will be notified
473         // by the window manager whenever it receives focus
474         _focus_notify = true;
475     }
476     XFree(proto);
477   }
478 }
479
480
481 void Client::updateNormalHints()
482 {
483   XSizeHints size;
484   long ret;
485   int oldgravity = _gravity;
486
487   // defaults
488   _min_ratio = 0.0;
489   _max_ratio = 0.0;
490   _size_inc.setPoint(1, 1);
491   _base_size.setPoint(0, 0);
492   _min_size.setPoint(0, 0);
493   _max_size.setPoint(INT_MAX, INT_MAX);
494
495   // get the hints from the window
496   if (XGetWMNormalHints(**otk::display, _window, &size, &ret)) {
497     _positioned = (size.flags & (PPosition|USPosition));
498
499     if (size.flags & PWinGravity) {
500       _gravity = size.win_gravity;
501       
502       // if the client has a frame, i.e. has already been mapped and is
503       // changing its gravity
504       if (frame && _gravity != oldgravity) {
505         // move our idea of the client's position based on its new gravity
506         int x = frame->rect().x(), y = frame->rect().y();
507         frame->frameGravity(x, y);
508         _area.setPos(x, y);
509       }
510     }
511
512     if (size.flags & PAspect) {
513       if (size.min_aspect.y) _min_ratio = size.min_aspect.x/size.min_aspect.y;
514       if (size.max_aspect.y) _max_ratio = size.max_aspect.x/size.max_aspect.y;
515     }
516
517     if (size.flags & PMinSize)
518       _min_size.setPoint(size.min_width, size.min_height);
519     
520     if (size.flags & PMaxSize)
521       _max_size.setPoint(size.max_width, size.max_height);
522     
523     if (size.flags & PBaseSize)
524       _base_size.setPoint(size.base_width, size.base_height);
525     
526     if (size.flags & PResizeInc)
527       _size_inc.setPoint(size.width_inc, size.height_inc);
528   }
529 }
530
531
532 void Client::updateWMHints(bool initstate)
533 {
534   XWMHints *hints;
535
536   // assume a window takes input if it doesnt specify
537   _can_focus = true;
538   bool ur = false;
539   
540   if ((hints = XGetWMHints(**otk::display, _window)) != NULL) {
541     if (hints->flags & InputHint)
542       _can_focus = hints->input;
543
544     // only do this when initstate is true!
545     if (initstate && (hints->flags & StateHint))
546       _iconic = hints->initial_state == IconicState;
547
548     if (hints->flags & XUrgencyHint)
549       ur = true;
550
551     if (hints->flags & WindowGroupHint) {
552       if (hints->window_group != _group) {
553         // XXX: remove from the old group if there was one
554         _group = hints->window_group;
555         // XXX: do stuff with the group
556       }
557     } else // no group!
558       _group = None;
559
560     XFree(hints);
561   }
562
563   if (ur != _urgent) {
564     _urgent = ur;
565 #ifdef DEBUG
566     printf("DEBUG: Urgent Hint for 0x%lx: %s\n",
567            (long)_window, _urgent ? "ON" : "OFF");
568 #endif
569     // fire the urgent callback if we're mapped, otherwise, wait until after
570     // we're mapped
571     if (frame)
572       fireUrgent();
573   }
574 }
575
576
577 void Client::updateTitle()
578 {
579   _title = "";
580   
581   // try netwm
582   if (!otk::Property::get(_window, otk::Property::atoms.net_wm_name,
583                           otk::Property::utf8, &_title)) {
584     // try old x stuff
585     otk::Property::get(_window, otk::Property::atoms.wm_name,
586                        otk::Property::ascii, &_title);
587   }
588
589   if (_title.empty())
590     _title = _("Unnamed Window");
591
592   if (frame)
593     frame->setTitle(_title);
594 }
595
596
597 void Client::updateIconTitle()
598 {
599   _icon_title = "";
600   
601   // try netwm
602   if (!otk::Property::get(_window, otk::Property::atoms.net_wm_icon_name,
603                           otk::Property::utf8, &_icon_title)) {
604     // try old x stuff
605     otk::Property::get(_window, otk::Property::atoms.wm_icon_name,
606                        otk::Property::ascii, &_icon_title);
607   }
608
609   if (_title.empty())
610     _icon_title = _("Unnamed Window");
611 }
612
613
614 void Client::updateClass()
615 {
616   // set the defaults
617   _app_name = _app_class = _role = "";
618
619   otk::Property::StringVect v;
620   unsigned long num = 2;
621
622   if (otk::Property::get(_window, otk::Property::atoms.wm_class,
623                          otk::Property::ascii, &num, &v)) {
624     if (num > 0) _app_name = v[0].c_str();
625     if (num > 1) _app_class = v[1].c_str();
626   }
627
628   v.clear();
629   num = 1;
630   if (otk::Property::get(_window, otk::Property::atoms.wm_window_role,
631                          otk::Property::ascii, &num, &v)) {
632     if (num > 0) _role = v[0].c_str();
633   }
634 }
635
636
637 void Client::updateStrut()
638 {
639   unsigned long num = 4;
640   unsigned long *data;
641   if (!otk::Property::get(_window, otk::Property::atoms.net_wm_strut,
642                           otk::Property::atoms.cardinal, &num, &data))
643     return;
644
645   if (num == 4) {
646     _strut.left = data[0];
647     _strut.right = data[1];
648     _strut.top = data[2];
649     _strut.bottom = data[3]; 
650
651     // updating here is pointless while we're being mapped cuz we're not in
652     // the screen's client list yet
653     if (frame)
654       openbox->screen(_screen)->updateStrut();
655   }
656
657   delete [] data;
658 }
659
660
661 void Client::updateTransientFor()
662 {
663   Window t = 0;
664   Client *c = 0;
665
666   if (XGetTransientForHint(**otk::display, _window, &t) &&
667       t != _window) { // cant be transient to itself!
668     c = openbox->findClient(t);
669     assert(c != this); // if this happens then we need to check for it
670
671     if (!c /*XXX: && _group*/) {
672       // not transient to a client, see if it is transient for a group
673       if (//t == _group->leader() ||
674         t == None ||
675         t == otk::display->screenInfo(_screen)->rootWindow()) {
676         // window is a transient for its group!
677         // XXX: for now this is treated as non-transient.
678         //      this needs to be fixed!
679       }
680     }
681   }
682
683   // if anything has changed...
684   if (c != _transient_for) {
685     bool m = _modal;
686     if (_modal)
687       setModal(false);
688     
689     if (_transient_for)
690       _transient_for->_transients.remove(this); // remove from old parent
691     _transient_for = c;
692     if (_transient_for)
693       _transient_for->_transients.push_back(this); // add to new parent
694
695     if (m)
696       setModal(true);
697   }
698 }
699
700
701 void Client::propertyHandler(const XPropertyEvent &e)
702 {
703   otk::EventHandler::propertyHandler(e);
704
705   // validate cuz we query stuff off the client here
706   if (!validate()) return;
707   
708   // compress changes to a single property into a single change
709   XEvent ce;
710   while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
711     // XXX: it would be nice to compress ALL changes to a property, not just
712     //      changes in a row without other props between.
713     if (ce.xproperty.atom != e.atom) {
714       XPutBackEvent(**otk::display, &ce);
715       break;
716     }
717   }
718
719   if (e.atom == XA_WM_NORMAL_HINTS) {
720     updateNormalHints();
721     setupDecorAndFunctions(); // normal hints can make a window non-resizable
722   } else if (e.atom == XA_WM_HINTS)
723     updateWMHints();
724   else if (e.atom == XA_WM_TRANSIENT_FOR) {
725     updateTransientFor();
726     getType();
727     calcLayer(); // type may have changed, so update the layer
728     setupDecorAndFunctions();
729   }
730   else if (e.atom == otk::Property::atoms.net_wm_name ||
731            e.atom == otk::Property::atoms.wm_name)
732     updateTitle();
733   else if (e.atom == otk::Property::atoms.net_wm_icon_name ||
734            e.atom == otk::Property::atoms.wm_icon_name)
735     updateIconTitle();
736   else if (e.atom == otk::Property::atoms.wm_class)
737     updateClass();
738   else if (e.atom == otk::Property::atoms.wm_protocols) {
739     updateProtocols();
740     setupDecorAndFunctions();
741   }
742   else if (e.atom == otk::Property::atoms.net_wm_strut)
743     updateStrut();
744 }
745
746
747 void Client::setWMState(long state)
748 {
749   if (state == _wmstate) return; // no change
750   
751   switch (state) {
752   case IconicState:
753     setDesktop(ICONIC_DESKTOP);
754     break;
755   case NormalState:
756     setDesktop(openbox->screen(_screen)->desktop());
757     break;
758   }
759 }
760
761
762 void Client::setDesktop(long target)
763 {
764   if (target == _desktop) return;
765   
766   printf("Setting desktop %ld\n", target);
767
768   if (!(target >= 0 || target == (signed)0xffffffff ||
769         target == ICONIC_DESKTOP))
770     return;
771   
772   _desktop = target;
773
774   // set the desktop hint, but not if we're iconifying
775   if (_desktop != ICONIC_DESKTOP)
776     otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
777                        otk::Property::atoms.cardinal, (unsigned)_desktop);
778   
779   // 'move' the window to the new desktop
780   if (_desktop == openbox->screen(_screen)->desktop() ||
781       _desktop == (signed)0xffffffff)
782     frame->show();
783   else
784     frame->hide();
785
786   // Handle Iconic state. Iconic state is maintained by the client being a
787   // member of the ICONIC_DESKTOP, so this is where we make iconifying and
788   // uniconifying happen.
789   bool i = _desktop == ICONIC_DESKTOP;
790   if (i != _iconic) { // has the state changed?
791     _iconic = i;
792     if (_iconic) {
793       _wmstate = IconicState;
794       ignore_unmaps++;
795       // we unmap the client itself so that we can get MapRequest events, and
796       // because the ICCCM tells us to!
797       XUnmapWindow(**otk::display, _window);
798     } else {
799       _wmstate = NormalState;
800       XMapWindow(**otk::display, _window);
801     }
802     changeState();
803   }
804   
805   frame->adjustState();
806 }
807
808
809 Client *Client::findModalChild(Client *skip) const
810 {
811   Client *ret = 0;
812   
813   // find a modal child recursively and try focus it
814   List::const_iterator it, end = _transients.end();
815   for (it = _transients.begin(); it != end; ++it)
816     if ((*it)->_modal && *it != skip)
817       return *it; // got one
818   // none of our direct children are modal, let them try check
819   for (it = _transients.begin(); it != end; ++it)
820     if ((ret = (*it)->findModalChild()))
821       return ret; // got one
822   return ret;
823 }
824
825
826 void Client::setModal(bool modal)
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 bool Client::focus()
1620 {
1621   // if we have a modal child, then focus it, not us
1622   if (_modal_child)
1623     return _modal_child->focus();
1624
1625   // won't try focus if the client doesn't want it, or if the window isn't
1626   // visible on the screen
1627   if (!(frame->isVisible() && (_can_focus || _focus_notify))) return false;
1628
1629   if (_focused) return true;
1630
1631   // do a check to see if the window has already been unmapped or destroyed
1632   // do this intelligently while watching out for unmaps we've generated
1633   // (ignore_unmaps > 0)
1634   XEvent ev;
1635   if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1636     XPutBackEvent(**otk::display, &ev);
1637     return false;
1638   }
1639   while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1640     if (ignore_unmaps) {
1641       unmapHandler(ev.xunmap);
1642     } else {
1643       XPutBackEvent(**otk::display, &ev);
1644       return false;
1645     }
1646   }
1647
1648   if (_can_focus)
1649     XSetInputFocus(**otk::display, _window,
1650                    RevertToNone, CurrentTime);
1651
1652   if (_focus_notify) {
1653     XEvent ce;
1654     ce.xclient.type = ClientMessage;
1655     ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1656     ce.xclient.display = **otk::display;
1657     ce.xclient.window = _window;
1658     ce.xclient.format = 32;
1659     ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1660     ce.xclient.data.l[1] = openbox->lastTime();
1661     ce.xclient.data.l[2] = 0l;
1662     ce.xclient.data.l[3] = 0l;
1663     ce.xclient.data.l[4] = 0l;
1664     XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1665   }
1666
1667   XSync(**otk::display, False);
1668   return true;
1669 }
1670
1671
1672 void Client::unfocus() const
1673 {
1674   if (!_focused) return;
1675
1676   assert(openbox->focusedClient() == this);
1677   openbox->setFocusedClient(0);
1678 }
1679
1680
1681 void Client::focusHandler(const XFocusChangeEvent &e)
1682 {
1683 #ifdef    DEBUG
1684 //  printf("FocusIn for 0x%lx\n", e.window);
1685 #endif // DEBUG
1686   
1687   otk::EventHandler::focusHandler(e);
1688
1689   frame->focus();
1690   _focused = true;
1691
1692   openbox->setFocusedClient(this);
1693 }
1694
1695
1696 void Client::unfocusHandler(const XFocusChangeEvent &e)
1697 {
1698 #ifdef    DEBUG
1699 //  printf("FocusOut for 0x%lx\n", e.window);
1700 #endif // DEBUG
1701   
1702   otk::EventHandler::unfocusHandler(e);
1703
1704   frame->unfocus();
1705   _focused = false;
1706
1707   if (openbox->focusedClient() == this)
1708     openbox->setFocusedClient(0);
1709 }
1710
1711
1712 void Client::configureRequestHandler(const XConfigureRequestEvent &e)
1713 {
1714 #ifdef    DEBUG
1715   printf("ConfigureRequest for 0x%lx\n", e.window);
1716 #endif // DEBUG
1717   
1718   otk::EventHandler::configureRequestHandler(e);
1719
1720   // if we are iconic (or shaded (fvwm does this)) ignore the event
1721   if (_iconic || _shaded) return;
1722
1723   if (e.value_mask & CWBorderWidth)
1724     _border_width = e.border_width;
1725
1726   // resize, then move, as specified in the EWMH section 7.7
1727   if (e.value_mask & (CWWidth | CWHeight)) {
1728     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1729     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1730
1731     Corner corner;
1732     switch (_gravity) {
1733     case NorthEastGravity:
1734     case EastGravity:
1735       corner = TopRight;
1736       break;
1737     case SouthWestGravity:
1738     case SouthGravity:
1739       corner = BottomLeft;
1740       break;
1741     case SouthEastGravity:
1742       corner = BottomRight;
1743       break;
1744     default:     // NorthWest, Static, etc
1745       corner = TopLeft;
1746     }
1747
1748     // if moving AND resizing ...
1749     if (e.value_mask & (CWX | CWY)) {
1750       int x = (e.value_mask & CWX) ? e.x : _area.x();
1751       int y = (e.value_mask & CWY) ? e.y : _area.y();
1752       internal_resize(corner, w, h, false, x, y);
1753     } else // if JUST resizing...
1754       internal_resize(corner, w, h, false);
1755   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1756     int x = (e.value_mask & CWX) ? e.x : _area.x();
1757     int y = (e.value_mask & CWY) ? e.y : _area.y();
1758     internal_move(x, y);
1759   }
1760
1761   if (e.value_mask & CWStackMode) {
1762     switch (e.detail) {
1763     case Below:
1764     case BottomIf:
1765       openbox->screen(_screen)->lowerWindow(this);
1766       break;
1767
1768     case Above:
1769     case TopIf:
1770     default:
1771       openbox->screen(_screen)->raiseWindow(this);
1772       break;
1773     }
1774   }
1775 }
1776
1777
1778 void Client::unmapHandler(const XUnmapEvent &e)
1779 {
1780   if (ignore_unmaps) {
1781 #ifdef    DEBUG
1782 //  printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1783 #endif // DEBUG
1784     ignore_unmaps--;
1785     return;
1786   }
1787   
1788 #ifdef    DEBUG
1789   printf("UnmapNotify for 0x%lx\n", e.window);
1790 #endif // DEBUG
1791
1792   otk::EventHandler::unmapHandler(e);
1793
1794   // this deletes us etc
1795   openbox->screen(_screen)->unmanageWindow(this);
1796 }
1797
1798
1799 void Client::destroyHandler(const XDestroyWindowEvent &e)
1800 {
1801 #ifdef    DEBUG
1802   printf("DestroyNotify for 0x%lx\n", e.window);
1803 #endif // DEBUG
1804
1805   otk::EventHandler::destroyHandler(e);
1806
1807   // this deletes us etc
1808   openbox->screen(_screen)->unmanageWindow(this);
1809 }
1810
1811
1812 void Client::reparentHandler(const XReparentEvent &e)
1813 {
1814   // this is when the client is first taken captive in the frame
1815   if (e.parent == frame->plate()) return;
1816
1817 #ifdef    DEBUG
1818   printf("ReparentNotify for 0x%lx\n", e.window);
1819 #endif // DEBUG
1820
1821   otk::EventHandler::reparentHandler(e);
1822
1823   /*
1824     This event is quite rare and is usually handled in unmapHandler.
1825     However, if the window is unmapped when the reparent event occurs,
1826     the window manager never sees it because an unmap event is not sent
1827     to an already unmapped window.
1828   */
1829
1830   // we don't want the reparent event, put it back on the stack for the X
1831   // server to deal with after we unmanage the window
1832   XEvent ev;
1833   ev.xreparent = e;
1834   XPutBackEvent(**otk::display, &ev);
1835   
1836   // this deletes us etc
1837   openbox->screen(_screen)->unmanageWindow(this);
1838 }
1839
1840 void Client::mapRequestHandler(const XMapRequestEvent &e)
1841 {
1842 #ifdef    DEBUG
1843   printf("MapRequest for already managed 0x%lx\n", e.window);
1844 #endif // DEBUG
1845
1846   assert(_iconic); // we shouldn't be able to get this unless we're iconic
1847
1848   // move to the current desktop (uniconify)
1849   setDesktop(openbox->screen(_screen)->desktop());
1850   // XXX: should we focus/raise the window? (basically a net_wm_active_window)
1851 }
1852
1853 }