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