stop using a desktop number for iconic windows. woot!
[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   if (!_iconic &&
768       (_desktop == openbox->screen(_screen)->desktop() ||
769        _desktop == 0xffffffff))
770     frame->show();
771   else
772     frame->hide();
773 }
774
775
776 void Client::setState(StateAction action, long data1, long data2)
777 {
778   bool shadestate = _shaded;
779   bool fsstate = _fullscreen;
780   bool maxh = _max_horz;
781   bool maxv = _max_vert;
782
783   if (!(action == State_Add || action == State_Remove ||
784         action == State_Toggle))
785     return; // an invalid action was passed to the client message, ignore it
786
787   for (int i = 0; i < 2; ++i) {
788     Atom state = i == 0 ? data1 : data2;
789     
790     if (! state) continue;
791
792     // if toggling, then pick whether we're adding or removing
793     if (action == State_Toggle) {
794       if (state == otk::Property::atoms.net_wm_state_modal)
795         action = _modal ? State_Remove : State_Add;
796       else if (state == otk::Property::atoms.net_wm_state_maximized_vert)
797         action = _max_vert ? State_Remove : State_Add;
798       else if (state == otk::Property::atoms.net_wm_state_maximized_horz)
799         action = _max_horz ? State_Remove : State_Add;
800       else if (state == otk::Property::atoms.net_wm_state_shaded)
801         action = _shaded ? State_Remove : State_Add;
802       else if (state == otk::Property::atoms.net_wm_state_skip_taskbar)
803         action = _skip_taskbar ? State_Remove : State_Add;
804       else if (state == otk::Property::atoms.net_wm_state_skip_pager)
805         action = _skip_pager ? State_Remove : State_Add;
806       else if (state == otk::Property::atoms.net_wm_state_fullscreen)
807         action = _fullscreen ? State_Remove : State_Add;
808       else if (state == otk::Property::atoms.net_wm_state_above)
809         action = _above ? State_Remove : State_Add;
810       else if (state == otk::Property::atoms.net_wm_state_below)
811         action = _below ? State_Remove : State_Add;
812     }
813     
814     if (action == State_Add) {
815       if (state == otk::Property::atoms.net_wm_state_modal) {
816         if (_modal) continue;
817         _modal = true;
818       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
819         maxv = true;
820       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
821         if (_max_horz) continue;
822         maxh = true;
823       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
824         shadestate = true;
825       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
826         _skip_taskbar = true;
827       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
828         _skip_pager = true;
829       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
830         fsstate = true;
831       } else if (state == otk::Property::atoms.net_wm_state_above) {
832         if (_above) continue;
833         _above = true;
834       } else if (state == otk::Property::atoms.net_wm_state_below) {
835         if (_below) continue;
836         _below = true;
837       }
838
839     } else { // action == State_Remove
840       if (state == otk::Property::atoms.net_wm_state_modal) {
841         if (!_modal) continue;
842         _modal = false;
843       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
844         maxv = false;
845       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
846         maxh = false;
847       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
848         shadestate = false;
849       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
850         _skip_taskbar = false;
851       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
852         _skip_pager = false;
853       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
854         fsstate = false;
855       } else if (state == otk::Property::atoms.net_wm_state_above) {
856         if (!_above) continue;
857         _above = false;
858       } else if (state == otk::Property::atoms.net_wm_state_below) {
859         if (!_below) continue;
860         _below = false;
861       }
862     }
863   }
864   if (maxh != _max_horz || maxv != _max_vert) {
865     if (maxh != _max_horz && maxv != _max_vert) { // toggling both
866       if (maxh == maxv) { // both going the same way
867         maximize(maxh, 0, true);
868       } else {
869         maximize(maxh, 1, true);
870         maximize(maxv, 2, true);
871       }
872     } else { // toggling one
873       if (maxh != _max_horz)
874         maximize(maxh, 1, true);
875       else
876         maximize(maxv, 2, true);
877     }
878   }
879   // change fullscreen state before shading, as it will affect if the window
880   // can shade or not
881   if (fsstate != _fullscreen)
882     fullscreen(fsstate, true);
883   if (shadestate != _shaded)
884     shade(shadestate);
885   calcLayer();
886   changeState(); // change the hint to relect these changes
887 }
888
889
890 void Client::toggleClientBorder(bool addborder)
891 {
892   // adjust our idea of where the client is, based on its border. When the
893   // border is removed, the client should now be considered to be in a
894   // different position.
895   // when re-adding the border to the client, the same operation needs to be
896   // reversed.
897   int oldx = _area.x(), oldy = _area.y();
898   int x = oldx, y = oldy;
899   switch(_gravity) {
900   default:
901   case NorthWestGravity:
902   case WestGravity:
903   case SouthWestGravity:
904     break;
905   case NorthEastGravity:
906   case EastGravity:
907   case SouthEastGravity:
908     if (addborder) x -= _border_width * 2;
909     else           x += _border_width * 2;
910     break;
911   case NorthGravity:
912   case SouthGravity:
913   case CenterGravity:
914   case ForgetGravity:
915   case StaticGravity:
916     if (addborder) x -= _border_width;
917     else           x += _border_width;
918     break;
919   }
920   switch(_gravity) {
921   default:
922   case NorthWestGravity:
923   case NorthGravity:
924   case NorthEastGravity:
925     break;
926   case SouthWestGravity:
927   case SouthGravity:
928   case SouthEastGravity:
929     if (addborder) y -= _border_width * 2;
930     else           y += _border_width * 2;
931     break;
932   case WestGravity:
933   case EastGravity:
934   case CenterGravity:
935   case ForgetGravity:
936   case StaticGravity:
937     if (addborder) y -= _border_width;
938     else           y += _border_width;
939     break;
940   }
941   _area = otk::Rect(otk::Point(x, y), _area.size());
942
943   if (addborder) {
944     XSetWindowBorderWidth(**otk::display, _window, _border_width);
945
946     // move the client so it is back it the right spot _with_ its border!
947     if (x != oldx || y != oldy)
948       XMoveWindow(**otk::display, _window, x, y);
949   } else
950     XSetWindowBorderWidth(**otk::display, _window, 0);
951 }
952
953
954 void Client::clientMessageHandler(const XClientMessageEvent &e)
955 {
956   otk::EventHandler::clientMessageHandler(e);
957   
958   // validate cuz we query stuff off the client here
959   if (!validate()) return;
960   
961   if (e.format != 32) return;
962
963   if (e.message_type == otk::Property::atoms.wm_change_state) {
964     // compress changes into a single change
965     bool compress = false;
966     XEvent ce;
967     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
968       // XXX: it would be nice to compress ALL messages of a type, not just
969       //      messages in a row without other message types between.
970       if (ce.xclient.message_type != e.message_type) {
971         XPutBackEvent(**otk::display, &ce);
972         break;
973       }
974       compress = true;
975     }
976     if (compress)
977       setWMState(ce.xclient.data.l[0]); // use the found event
978     else
979       setWMState(e.data.l[0]); // use the original event
980   } else if (e.message_type == otk::Property::atoms.net_wm_desktop) {
981     // compress changes into a single change 
982     bool compress = false;
983     XEvent ce;
984     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
985       // XXX: it would be nice to compress ALL messages of a type, not just
986       //      messages in a row without other message types between.
987       if (ce.xclient.message_type != e.message_type) {
988         XPutBackEvent(**otk::display, &ce);
989         break;
990       }
991       compress = true;
992     }
993     if (compress)
994       setDesktop(e.data.l[0]); // use the found event
995     else
996       setDesktop(e.data.l[0]); // use the original event
997   } else if (e.message_type == otk::Property::atoms.net_wm_state) {
998     // can't compress these
999 #ifdef DEBUG
1000     printf("net_wm_state %s %ld %ld for 0x%lx\n",
1001            (e.data.l[0] == 0 ? "Remove" : e.data.l[0] == 1 ? "Add" :
1002             e.data.l[0] == 2 ? "Toggle" : "INVALID"),
1003            e.data.l[1], e.data.l[2], _window);
1004 #endif
1005     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
1006   } else if (e.message_type == otk::Property::atoms.net_close_window) {
1007 #ifdef DEBUG
1008     printf("net_close_window for 0x%lx\n", _window);
1009 #endif
1010     close();
1011   } else if (e.message_type == otk::Property::atoms.net_active_window) {
1012 #ifdef DEBUG
1013     printf("net_active_window for 0x%lx\n", _window);
1014 #endif
1015     if (_iconic)
1016       iconify(false);
1017     if (_shaded)
1018       shade(false);
1019     focus();
1020     openbox->screen(_screen)->raiseWindow(this);
1021   } else if (e.message_type == otk::Property::atoms.openbox_active_window) {
1022     if (_iconic)
1023       iconify(false);
1024     if (e.data.l[0] && _shaded)
1025       shade(false);
1026     focus();
1027     if (e.data.l[1])
1028       openbox->screen(_screen)->raiseWindow(this);
1029   }
1030 }
1031
1032
1033 #if defined(SHAPE)
1034 void Client::shapeHandler(const XShapeEvent &e)
1035 {
1036   otk::EventHandler::shapeHandler(e);
1037
1038   if (e.kind == ShapeBounding) {
1039     _shaped = e.shaped;
1040     frame->adjustShape();
1041   }
1042 }
1043 #endif
1044
1045
1046 void Client::resize(Corner anchor, int w, int h)
1047 {
1048   if (!(_functions & Func_Resize)) return;
1049   internal_resize(anchor, w, h);
1050 }
1051
1052
1053 void Client::internal_resize(Corner anchor, int w, int h,
1054                              bool user, int x, int y)
1055 {
1056   w -= _base_size.width();
1057   h -= _base_size.height();
1058
1059   if (user) {
1060     // for interactive resizing. have to move half an increment in each
1061     // direction.
1062     int mw = w % _size_inc.width(); // how far we are towards the next size inc
1063     int mh = h % _size_inc.height();
1064     int aw = _size_inc.width() / 2; // amount to add
1065     int ah = _size_inc.height() / 2;
1066     // don't let us move into a new size increment
1067     if (mw + aw >= _size_inc.width()) aw = _size_inc.width() - mw - 1;
1068     if (mh + ah >= _size_inc.height()) ah = _size_inc.height() - mh - 1;
1069     w += aw;
1070     h += ah;
1071     
1072     // if this is a user-requested resize, then check against min/max sizes
1073     // and aspect ratios
1074
1075     // smaller than min size or bigger than max size?
1076     if (w > _max_size.width()) w = _max_size.width();
1077     if (w < _min_size.width()) w = _min_size.width();
1078     if (h > _max_size.height()) h = _max_size.height();
1079     if (h < _min_size.height()) h = _min_size.height();
1080
1081     // adjust the height ot match the width for the aspect ratios
1082     if (_min_ratio)
1083       if (h * _min_ratio > w) h = static_cast<int>(w / _min_ratio);
1084     if (_max_ratio)
1085       if (h * _max_ratio < w) h = static_cast<int>(w / _max_ratio);
1086   }
1087
1088   // keep to the increments
1089   w /= _size_inc.width();
1090   h /= _size_inc.height();
1091
1092   // you cannot resize to nothing
1093   if (w < 1) w = 1;
1094   if (h < 1) h = 1;
1095   
1096   // store the logical size
1097   _logical_size = otk::Size(w, h);
1098
1099   w *= _size_inc.width();
1100   h *= _size_inc.height();
1101
1102   w += _base_size.width();
1103   h += _base_size.height();
1104
1105   if (x == INT_MIN || y == INT_MIN) {
1106     x = _area.x();
1107     y = _area.y();
1108     switch (anchor) {
1109     case TopLeft:
1110       break;
1111     case TopRight:
1112       x -= w - _area.width();
1113       break;
1114     case BottomLeft:
1115       y -= h - _area.height();
1116       break;
1117     case BottomRight:
1118       x -= w - _area.width();
1119       y -= h - _area.height();
1120       break;
1121     }
1122   }
1123
1124   _area = otk::Rect(_area.position(), otk::Size(w, h));
1125
1126   XResizeWindow(**otk::display, _window, w, h);
1127
1128   // resize the frame to match the request
1129   frame->adjustSize();
1130   internal_move(x, y);
1131 }
1132
1133
1134 void Client::move(int x, int y)
1135 {
1136   if (!(_functions & Func_Move)) return;
1137   frame->frameGravity(x, y); // get the client's position based on x,y for the
1138                              // frame
1139   internal_move(x, y);
1140 }
1141
1142
1143 void Client::internal_move(int x, int y)
1144 {
1145   _area = otk::Rect(otk::Point(x, y), _area.size());
1146
1147   // move the frame to be in the requested position
1148   if (frame) { // this can be called while mapping, before frame exists
1149     frame->adjustPosition();
1150
1151     // send synthetic configure notify (we don't need to if we aren't mapped
1152     // yet)
1153     XEvent event;
1154     event.type = ConfigureNotify;
1155     event.xconfigure.display = **otk::display;
1156     event.xconfigure.event = _window;
1157     event.xconfigure.window = _window;
1158     
1159     // root window coords with border in mind
1160     event.xconfigure.x = x - _border_width + frame->size().left;
1161     event.xconfigure.y = y - _border_width + frame->size().top;
1162     
1163     event.xconfigure.width = _area.width();
1164     event.xconfigure.height = _area.height();
1165     event.xconfigure.border_width = _border_width;
1166     event.xconfigure.above = frame->plate();
1167     event.xconfigure.override_redirect = False;
1168     XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1169                StructureNotifyMask, &event);
1170 #if 0//def DEBUG
1171     printf("Sent synthetic ConfigureNotify %d,%d %d,%d to 0x%lx\n",
1172            event.xconfigure.x, event.xconfigure.y, event.xconfigure.width,
1173            event.xconfigure.height, event.xconfigure.window);
1174 #endif
1175   }
1176 }
1177
1178
1179 void Client::close()
1180 {
1181   XEvent ce;
1182
1183   if (!(_functions & Func_Close)) return;
1184
1185   // XXX: itd be cool to do timeouts and shit here for killing the client's
1186   //      process off
1187   // like... if the window is around after 5 seconds, then the close button
1188   // turns a nice red, and if this function is called again, the client is
1189   // explicitly killed.
1190
1191   ce.xclient.type = ClientMessage;
1192   ce.xclient.message_type =  otk::Property::atoms.wm_protocols;
1193   ce.xclient.display = **otk::display;
1194   ce.xclient.window = _window;
1195   ce.xclient.format = 32;
1196   ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1197   ce.xclient.data.l[1] = CurrentTime;
1198   ce.xclient.data.l[2] = 0l;
1199   ce.xclient.data.l[3] = 0l;
1200   ce.xclient.data.l[4] = 0l;
1201   XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1202 }
1203
1204
1205 void Client::changeState()
1206 {
1207   unsigned long state[2];
1208   state[0] = _wmstate;
1209   state[1] = None;
1210   otk::Property::set(_window, otk::Property::atoms.wm_state,
1211                      otk::Property::atoms.wm_state, state, 2);
1212
1213   Atom netstate[10];
1214   int num = 0;
1215   if (_modal)
1216     netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1217   if (_shaded)
1218     netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1219   if (_iconic)
1220     netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1221   if (_skip_taskbar)
1222     netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1223   if (_skip_pager)
1224     netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1225   if (_fullscreen)
1226     netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1227   if (_max_vert)
1228     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1229   if (_max_horz)
1230     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1231   if (_above)
1232     netstate[num++] = otk::Property::atoms.net_wm_state_above;
1233   if (_below)
1234     netstate[num++] = otk::Property::atoms.net_wm_state_below;
1235   otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1236                      otk::Property::atoms.atom, netstate, num);
1237
1238   calcLayer();
1239
1240   if (frame)
1241     frame->adjustState();
1242 }
1243
1244
1245 void Client::changeAllowedActions(void)
1246 {
1247   Atom actions[9];
1248   int num = 0;
1249
1250   actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1251
1252   if (_functions & Func_Shade)
1253     actions[num++] = otk::Property::atoms.net_wm_action_shade;
1254   if (_functions & Func_Close)
1255     actions[num++] = otk::Property::atoms.net_wm_action_close;
1256   if (_functions & Func_Move)
1257     actions[num++] = otk::Property::atoms.net_wm_action_move;
1258   if (_functions & Func_Iconify)
1259     actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1260   if (_functions & Func_Resize)
1261     actions[num++] = otk::Property::atoms.net_wm_action_resize;
1262   if (_functions & Func_Fullscreen)
1263     actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1264   if (_functions & Func_Maximize) {
1265     actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1266     actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1267   }
1268
1269   otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1270                      otk::Property::atoms.atom, actions, num);
1271
1272   // make sure the window isn't breaking any rules now
1273   
1274   if (!(_functions & Func_Shade) && _shaded)
1275     if (frame) shade(false);
1276     else _shaded = false;
1277   if (!(_functions & Func_Iconify) && _iconic)
1278     if (frame) setDesktop(openbox->screen(_screen)->desktop());
1279     else _iconic = false;
1280   if (!(_functions & Func_Fullscreen) && _fullscreen)
1281     if (frame) fullscreen(false);
1282     else _fullscreen = false;
1283   if (!(_functions & Func_Maximize) && (_max_horz || _max_vert))
1284     if (frame) maximize(false, 0);
1285     else _max_vert = _max_horz = false;
1286 }
1287
1288
1289 void Client::remaximize()
1290 {
1291   printf("REMAXIMIZE!!!!!!!!!!!!!!!!!!!\n");
1292   int dir;
1293   if (_max_horz && _max_vert)
1294     dir = 0;
1295   else if (_max_horz)
1296     dir = 1;
1297   else if (_max_vert)
1298     dir = 2;
1299   else
1300     return; // not maximized
1301   _max_horz = _max_vert = false;
1302   maximize(true, dir, false);
1303 }
1304
1305
1306 void Client::applyStartupState()
1307 {
1308   // these are in a carefully crafted order..
1309
1310   if (_iconic) {
1311     _iconic = false;
1312     iconify(true);
1313   }
1314   if (_fullscreen) {
1315     _fullscreen = false;
1316     fullscreen(true, false);
1317   }
1318   if (_shaded) {
1319     _shaded = false;
1320     shade(true);
1321   }
1322   if (_urgent)
1323     fireUrgent();
1324   
1325   if (_max_vert && _max_horz) {
1326     _max_vert = _max_horz = false;
1327     maximize(true, 0, false);
1328   } else if (_max_vert) {
1329     _max_vert = false;
1330     maximize(true, 2, false);
1331   } else if (_max_horz) {
1332     _max_horz = false;
1333     maximize(true, 1, false);
1334   }
1335
1336   if (_skip_taskbar); // nothing to do for this
1337   if (_skip_pager);   // nothing to do for this
1338   if (_modal);        // nothing to do for this
1339   if (_above);        // nothing to do for this
1340   if (_below);        // nothing to do for this
1341 }
1342
1343
1344 void Client::fireUrgent()
1345 {
1346   // call the python UrgentWindow callbacks
1347   EventData data(_screen, this, EventAction::UrgentWindow, 0);
1348   openbox->bindings()->fireEvent(&data);
1349 }
1350
1351
1352 void Client::shade(bool shade)
1353 {
1354   if (!(_functions & Func_Shade) || // can't
1355       _shaded == shade) return;     // already done
1356
1357   // when we're iconic, don't change the wmstate
1358   if (!_iconic)
1359     _wmstate = shade ? IconicState : NormalState;
1360   _shaded = shade;
1361   changeState();
1362   frame->adjustSize();
1363 }
1364
1365
1366 void Client::maximize(bool max, int dir, bool savearea)
1367 {
1368   assert(dir == 0 || dir == 1 || dir == 2);
1369   if (!(_functions & Func_Maximize)) return; // can't
1370
1371   // check if already done
1372   if (max) {
1373     if (dir == 0 && _max_horz && _max_vert) return;
1374     if (dir == 1 && _max_horz) return;
1375     if (dir == 2 && _max_vert) return;
1376   } else {
1377     if (dir == 0 && !_max_horz && !_max_vert) return;
1378     if (dir == 1 && !_max_horz) return;
1379     if (dir == 2 && !_max_vert) return;
1380   }
1381
1382   const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1383   int x = frame->area().x(), y = frame->area().y(),
1384     w = _area.width(), h = _area.height();
1385   
1386   if (max) {
1387     if (savearea) {
1388       long dimensions[4];
1389       long *readdim;
1390       unsigned long n = 4;
1391
1392       dimensions[0] = x;
1393       dimensions[1] = y;
1394       dimensions[2] = w;
1395       dimensions[3] = h;
1396
1397       // get the property off the window and use it for the dimentions we are
1398       // already maxed on
1399       if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1400                              otk::Property::atoms.cardinal, &n,
1401                              (long unsigned**) &readdim)) {
1402         if (n >= 4) {
1403           if (_max_horz) {
1404             dimensions[0] = readdim[0];
1405             dimensions[2] = readdim[2];
1406           }
1407           if (_max_vert) {
1408             dimensions[1] = readdim[1];
1409             dimensions[3] = readdim[3];
1410           }
1411         }
1412         delete readdim;
1413       }
1414       
1415       otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1416                          otk::Property::atoms.cardinal,
1417                          (long unsigned*)dimensions, 4);
1418     }
1419     if (dir == 0 || dir == 1) { // horz
1420       x = a.x();
1421       w = a.width();
1422     }
1423     if (dir == 0 || dir == 2) { // vert
1424       y = a.y();
1425       h = a.height() - frame->size().top - frame->size().bottom;
1426     }
1427   } else {
1428     long *dimensions;
1429     long unsigned n = 4;
1430       
1431     if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1432                            otk::Property::atoms.cardinal, &n,
1433                            (long unsigned**) &dimensions)) {
1434       if (n >= 4) {
1435         if (dir == 0 || dir == 1) { // horz
1436           x = (signed int)dimensions[0];
1437           w = (signed int)dimensions[2];
1438         }
1439         if (dir == 0 || dir == 2) { // vert
1440           y = (signed int)dimensions[1];
1441           h = (signed int)dimensions[3];
1442         }
1443       }
1444       delete dimensions;
1445     } else {
1446       // pick some fallbacks...
1447       if (dir == 0 || dir == 1) { // horz
1448         x = a.x() + a.width() / 4;
1449         w = a.width() / 2;
1450       }
1451       if (dir == 0 || dir == 2) { // vert
1452         y = a.y() + a.height() / 4;
1453         h = a.height() / 2;
1454       }
1455     }
1456   }
1457
1458   if (dir == 0 || dir == 1) // horz
1459     _max_horz = max;
1460   if (dir == 0 || dir == 2) // vert
1461     _max_vert = max;
1462
1463   if (!_max_horz && !_max_vert)
1464     otk::Property::erase(_window, otk::Property::atoms.openbox_premax);
1465
1466   changeState(); // change the state hints on the client
1467
1468   frame->frameGravity(x, y); // figure out where the client should be going
1469   internal_resize(TopLeft, w, h, true, x, y);
1470 }
1471
1472
1473 void Client::fullscreen(bool fs, bool savearea)
1474 {
1475   static FunctionFlags saved_func;
1476   static DecorationFlags saved_decor;
1477
1478   if (!(_functions & Func_Fullscreen) || // can't
1479       _fullscreen == fs) return;         // already done
1480
1481   _fullscreen = fs;
1482   changeState(); // change the state hints on the client
1483
1484   int x = _area.x(), y = _area.y(), w = _area.width(), h = _area.height();
1485   
1486   if (fs) {
1487     // save the functions and remove them
1488     saved_func = _functions;
1489     _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1490     // save the decorations and remove them
1491     saved_decor = _decorations;
1492     _decorations = 0;
1493     if (savearea) {
1494       long dimensions[4];
1495       dimensions[0] = _area.x();
1496       dimensions[1] = _area.y();
1497       dimensions[2] = _area.width();
1498       dimensions[3] = _area.height();
1499       otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1500                          otk::Property::atoms.cardinal,
1501                          (long unsigned*)dimensions, 4);
1502     }
1503     const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1504     x = 0;
1505     y = 0;
1506     w = info->size().width();
1507     h = info->size().height();
1508   } else {
1509     _functions = saved_func;
1510     _decorations = saved_decor;
1511
1512     long *dimensions;
1513     long unsigned n = 4;
1514       
1515     if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1516                            otk::Property::atoms.cardinal, &n,
1517                            (long unsigned**) &dimensions)) {
1518       if (n >= 4) {
1519         x = dimensions[0];
1520         y = dimensions[1];
1521         w = dimensions[2];
1522         h = dimensions[3];
1523       }
1524       delete dimensions;
1525     } else {
1526       // pick some fallbacks...
1527       const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1528       x = a.x() + a.width() / 4;
1529       y = a.y() + a.height() / 4;
1530       w = a.width() / 2;
1531         h = a.height() / 2;
1532     }    
1533   }
1534   
1535   changeAllowedActions();  // based on the new _functions
1536
1537   // when fullscreening, don't obey things like increments, fill the screen
1538   internal_resize(TopLeft, w, h, !fs, x, y);
1539
1540   // raise (back) into our stacking layer
1541   openbox->screen(_screen)->raiseWindow(this);
1542
1543   // try focus us when we go into fullscreen mode
1544   if (fs) focus();
1545 }
1546
1547
1548 void Client::iconify(bool iconic, bool curdesk)
1549 {
1550   if (_iconic == iconic) return; // nothing to do
1551
1552   _iconic = iconic;
1553
1554   if (_iconic) {
1555     _wmstate = IconicState;
1556     ignore_unmaps++;
1557     // we unmap the client itself so that we can get MapRequest events, and
1558     // because the ICCCM tells us to!
1559     XUnmapWindow(**otk::display, _window);
1560   } else {
1561     if (curdesk)
1562       setDesktop(openbox->screen(_screen)->desktop());
1563     _wmstate = NormalState;
1564     XMapWindow(**otk::display, _window);
1565   }
1566   changeState();
1567
1568   showhide();
1569   
1570   openbox->screen(_screen)->updateStruts();
1571 }
1572
1573
1574 void Client::disableDecorations(DecorationFlags flags)
1575 {
1576   _disabled_decorations = flags;
1577   setupDecorAndFunctions();
1578 }
1579
1580
1581 void Client::installColormap(bool install) const
1582 {
1583   XWindowAttributes wa;
1584   if (XGetWindowAttributes(**otk::display, _window, &wa)) {
1585     if (install)
1586       XInstallColormap(**otk::display, wa.colormap);
1587     else
1588       XUninstallColormap(**otk::display, wa.colormap);
1589   }
1590 }
1591
1592
1593 // recursively searches the client 'tree' for a modal client, always skips the
1594 // topmost node (the window you're starting with)
1595 Client *Client::searchModalTree(Client *node, Client *skip)
1596 {
1597   List::const_iterator it, end = node->_transients.end();
1598   Client *ret;
1599   
1600   for (it = node->_transients.begin(); it != end; ++it) {
1601     if (*it == skip) continue; // circular?
1602     if ((ret = searchModalTree(*it, skip))) return ret; // got one
1603     if ((*it)->_modal) return *it; // got one
1604   }
1605   return 0;
1606 }
1607
1608 Client *Client::findModalChild()
1609 {
1610   return searchModalTree(this, this);
1611 }
1612
1613
1614 bool Client::focus()
1615 {
1616   // if we have a modal child, then focus it, not us
1617   Client *c = findModalChild();
1618   if (c) return c->focus();
1619
1620   // won't try focus if the client doesn't want it, or if the window isn't
1621   // visible on the screen
1622   if (!(frame->visible() && (_can_focus || _focus_notify))) return false;
1623
1624   if (_focused) return true;
1625
1626   // do a check to see if the window has already been unmapped or destroyed
1627   // do this intelligently while watching out for unmaps we've generated
1628   // (ignore_unmaps > 0)
1629   XEvent ev;
1630   if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1631     XPutBackEvent(**otk::display, &ev);
1632     return false;
1633   }
1634   while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1635     if (ignore_unmaps) {
1636       unmapHandler(ev.xunmap);
1637     } else {
1638       XPutBackEvent(**otk::display, &ev);
1639       return false;
1640     }
1641   }
1642
1643   if (_can_focus)
1644     XSetInputFocus(**otk::display, _window,
1645                    RevertToNone, CurrentTime);
1646
1647   if (_focus_notify) {
1648     XEvent ce;
1649     ce.xclient.type = ClientMessage;
1650     ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1651     ce.xclient.display = **otk::display;
1652     ce.xclient.window = _window;
1653     ce.xclient.format = 32;
1654     ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1655     ce.xclient.data.l[1] = openbox->lastTime();
1656     ce.xclient.data.l[2] = 0l;
1657     ce.xclient.data.l[3] = 0l;
1658     ce.xclient.data.l[4] = 0l;
1659     XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1660   }
1661
1662   XSync(**otk::display, False);
1663   return true;
1664 }
1665
1666
1667 void Client::unfocus() const
1668 {
1669   if (!_focused) return;
1670
1671   assert(openbox->focusedClient() == this);
1672   openbox->setFocusedClient(0);
1673 }
1674
1675
1676 void Client::focusHandler(const XFocusChangeEvent &e)
1677 {
1678 #ifdef    DEBUG
1679 //  printf("FocusIn for 0x%lx\n", e.window);
1680 #endif // DEBUG
1681   
1682   otk::EventHandler::focusHandler(e);
1683
1684   _focused = true;
1685   frame->adjustFocus();
1686
1687   openbox->setFocusedClient(this);
1688 }
1689
1690
1691 void Client::unfocusHandler(const XFocusChangeEvent &e)
1692 {
1693 #ifdef    DEBUG
1694 //  printf("FocusOut for 0x%lx\n", e.window);
1695 #endif // DEBUG
1696   
1697   otk::EventHandler::unfocusHandler(e);
1698
1699   _focused = false;
1700   frame->adjustFocus();
1701
1702   if (openbox->focusedClient() == this)
1703     openbox->setFocusedClient(0);
1704 }
1705
1706
1707 void Client::configureRequestHandler(const XConfigureRequestEvent &ec)
1708 {
1709 #ifdef    DEBUG
1710   printf("ConfigureRequest for 0x%lx\n", ec.window);
1711 #endif // DEBUG
1712   
1713   otk::EventHandler::configureRequestHandler(ec);
1714
1715   // compress these
1716   XConfigureRequestEvent e = ec;
1717   XEvent ev;
1718   while (XCheckTypedWindowEvent(**otk::display, window(), ConfigureRequest,
1719                                 &ev)) {
1720     // XXX if this causes bad things.. we can compress config req's with the
1721     //     same mask.
1722     e.value_mask |= ev.xconfigurerequest.value_mask;
1723     if (ev.xconfigurerequest.value_mask & CWX)
1724       e.x = ev.xconfigurerequest.x;
1725     if (ev.xconfigurerequest.value_mask & CWY)
1726       e.y = ev.xconfigurerequest.y;
1727     if (ev.xconfigurerequest.value_mask & CWWidth)
1728       e.width = ev.xconfigurerequest.width;
1729     if (ev.xconfigurerequest.value_mask & CWHeight)
1730       e.height = ev.xconfigurerequest.height;
1731     if (ev.xconfigurerequest.value_mask & CWBorderWidth)
1732       e.border_width = ev.xconfigurerequest.border_width;
1733     if (ev.xconfigurerequest.value_mask & CWStackMode)
1734       e.detail = ev.xconfigurerequest.detail;
1735   }
1736
1737   // if we are iconic (or shaded (fvwm does this)) ignore the event
1738   if (_iconic || _shaded) return;
1739
1740   if (e.value_mask & CWBorderWidth)
1741     _border_width = e.border_width;
1742
1743   // resize, then move, as specified in the EWMH section 7.7
1744   if (e.value_mask & (CWWidth | CWHeight)) {
1745     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1746     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1747
1748     Corner corner;
1749     switch (_gravity) {
1750     case NorthEastGravity:
1751     case EastGravity:
1752       corner = TopRight;
1753       break;
1754     case SouthWestGravity:
1755     case SouthGravity:
1756       corner = BottomLeft;
1757       break;
1758     case SouthEastGravity:
1759       corner = BottomRight;
1760       break;
1761     default:     // NorthWest, Static, etc
1762       corner = TopLeft;
1763     }
1764
1765     // if moving AND resizing ...
1766     if (e.value_mask & (CWX | CWY)) {
1767       int x = (e.value_mask & CWX) ? e.x : _area.x();
1768       int y = (e.value_mask & CWY) ? e.y : _area.y();
1769       internal_resize(corner, w, h, false, x, y);
1770     } else // if JUST resizing...
1771       internal_resize(corner, w, h, false);
1772   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1773     int x = (e.value_mask & CWX) ? e.x : _area.x();
1774     int y = (e.value_mask & CWY) ? e.y : _area.y();
1775     internal_move(x, y);
1776   }
1777
1778   if (e.value_mask & CWStackMode) {
1779     switch (e.detail) {
1780     case Below:
1781     case BottomIf:
1782       openbox->screen(_screen)->lowerWindow(this);
1783       break;
1784
1785     case Above:
1786     case TopIf:
1787     default:
1788       openbox->screen(_screen)->raiseWindow(this);
1789       break;
1790     }
1791   }
1792 }
1793
1794
1795 void Client::unmapHandler(const XUnmapEvent &e)
1796 {
1797   if (ignore_unmaps) {
1798 #ifdef    DEBUG
1799 //  printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1800 #endif // DEBUG
1801     ignore_unmaps--;
1802     return;
1803   }
1804   
1805 #ifdef    DEBUG
1806   printf("UnmapNotify for 0x%lx\n", e.window);
1807 #endif // DEBUG
1808
1809   otk::EventHandler::unmapHandler(e);
1810
1811   // this deletes us etc
1812   openbox->screen(_screen)->unmanageWindow(this);
1813 }
1814
1815
1816 void Client::destroyHandler(const XDestroyWindowEvent &e)
1817 {
1818 #ifdef    DEBUG
1819   printf("DestroyNotify for 0x%lx\n", e.window);
1820 #endif // DEBUG
1821
1822   otk::EventHandler::destroyHandler(e);
1823
1824   // this deletes us etc
1825   openbox->screen(_screen)->unmanageWindow(this);
1826 }
1827
1828
1829 void Client::reparentHandler(const XReparentEvent &e)
1830 {
1831   // this is when the client is first taken captive in the frame
1832   if (e.parent == frame->plate()) return;
1833
1834 #ifdef    DEBUG
1835   printf("ReparentNotify for 0x%lx\n", e.window);
1836 #endif // DEBUG
1837
1838   otk::EventHandler::reparentHandler(e);
1839
1840   /*
1841     This event is quite rare and is usually handled in unmapHandler.
1842     However, if the window is unmapped when the reparent event occurs,
1843     the window manager never sees it because an unmap event is not sent
1844     to an already unmapped window.
1845   */
1846
1847   // we don't want the reparent event, put it back on the stack for the X
1848   // server to deal with after we unmanage the window
1849   XEvent ev;
1850   ev.xreparent = e;
1851   XPutBackEvent(**otk::display, &ev);
1852   
1853   // this deletes us etc
1854   openbox->screen(_screen)->unmanageWindow(this);
1855 }
1856
1857 void Client::mapRequestHandler(const XMapRequestEvent &e)
1858 {
1859 #ifdef    DEBUG
1860   printf("MapRequest for already managed 0x%lx\n", e.window);
1861 #endif // DEBUG
1862
1863   assert(_iconic); // we shouldn't be able to get this unless we're iconic
1864
1865   // move to the current desktop (uniconify)
1866   iconify(false);
1867   // XXX: should we focus/raise the window? (basically a net_wm_active_window)
1868 }
1869
1870 }