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