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