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