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