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