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