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