]> icculus.org git repositories - mikachu/openbox.git/blob - src/client.cc
add support for the openbox_restack_window message.
[mikachu/openbox.git] / src / client.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #include "config.h"
4
5 #include "client.hh"
6 #include "frame.hh"
7 #include "screen.hh"
8 #include "openbox.hh"
9 #include "bindings.hh"
10 #include "otk/display.hh"
11 #include "otk/property.hh"
12
13 extern "C" {
14 #include <X11/Xlib.h>
15 #include <X11/Xutil.h>
16 #include <X11/Xatom.h>
17
18 #include "gettext.h"
19 #define _(str) gettext(str)
20 }
21
22 #include <cstring> // for memcpy
23 #include <climits>
24 #include <cassert>
25 #include <algorithm>
26
27 namespace ob {
28
29 Client::Client(int screen, Window window)
30   : otk::EventHandler(),
31     frame(0), _screen(screen), _window(window)
32 {
33   assert(screen >= 0);
34   assert(window);
35
36   ignore_unmaps = 0;
37   
38   // update EVERYTHING the first time!!
39
40   // defaults
41   _wmstate = NormalState;
42   _focused = false;
43   _transient_for = 0;
44   _layer = Layer_Normal;
45   _urgent = false;
46   _positioned = false;
47   _disabled_decorations = 0;
48   _group = None;
49   _desktop = 0;
50   _nicons = 0;
51   
52   getArea();
53   getDesktop();
54   getState();  // do this before updateTransientFor! (for _modal)
55   getShaped();
56
57   updateTransientFor();
58   getMwmHints();
59   getType(); // this can change the mwmhints for special cases
60
61   updateProtocols();
62
63   getGravity();        // get the attribute gravity
64   updateNormalHints(); // this may override the attribute gravity
65
66   // got the type, the mwmhints, the protocols, and the normal hints (min/max
67   // sizes), so we're ready to set up
68   // the decorations/functions
69   setupDecorAndFunctions();
70   
71   // also get the initial_state and set _iconic if we aren't "starting"
72   // when we're "starting" that means we should use whatever state was already
73   // on the window over the initial map state, because it was already mapped
74   updateWMHints(openbox->state() != Openbox::State_Starting);
75   updateTitle();
76   updateIconTitle();
77   updateClass();
78   updateStrut();
79   updateIcons();
80   updateKwmIcon();
81   
82   // this makes sure that these windows appear on all desktops
83   if (/*_type == Type_Dock ||*/ _type == Type_Desktop)
84     _desktop = 0xffffffff;
85   
86   // set the desktop hint, to make sure that it always exists, and to reflect
87   // any changes we've made here
88   otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
89                      otk::Property::atoms.cardinal, (unsigned)_desktop);
90   changeState();
91 }
92
93
94 Client::~Client()
95 {
96   assert(_nicons > 0); // there should always be a default..
97   for (int j = 0; j < _nicons; ++j)
98     delete [] _icons[j].data;
99   delete [] _icons;
100   
101   // clean up childrens' references
102   while (!_transients.empty()) {
103     _transients.front()->_transient_for = 0;
104     _transients.pop_front();
105   }
106
107   // clean up parents reference to this
108   if (_transient_for)
109     _transient_for->_transients.remove(this); // remove from old parent
110   
111   if (openbox->state() != Openbox::State_Exiting) {
112     // these values should not be persisted across a window unmapping/mapping
113     otk::Property::erase(_window, otk::Property::atoms.net_wm_desktop);
114     otk::Property::erase(_window, otk::Property::atoms.net_wm_state);
115   } else {
116     // if we're left in an iconic state, the client wont be mapped. this is
117     // bad, since we will no longer be managing the window on restart
118     if (_iconic)
119       XMapWindow(**otk::display, _window);
120   }
121 }
122
123
124 bool Client::validate() const
125 {
126   XSync(**otk::display, false); // get all events on the server
127
128   XEvent e;
129   if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &e) ||
130       XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &e)) {
131     XPutBackEvent(**otk::display, &e);
132     return false;
133   }
134
135   return true;
136 }
137
138
139 void Client::getGravity()
140 {
141   XWindowAttributes wattrib;
142   Status ret;
143
144   ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
145   assert(ret != BadWindow);
146   _gravity = wattrib.win_gravity;
147 }
148
149
150 void Client::getDesktop()
151 {
152   // defaults to the current desktop
153   _desktop = openbox->screen(_screen)->desktop();
154
155   if (otk::Property::get(_window, otk::Property::atoms.net_wm_desktop,
156                          otk::Property::atoms.cardinal,
157                          (long unsigned*)&_desktop)) {
158 #ifdef DEBUG
159 //    printf("Window requested desktop: %ld\n", _desktop);
160 #endif
161   }
162 }
163
164
165 void Client::getType()
166 {
167   _type = (WindowType) -1;
168   
169   unsigned long *val;
170   unsigned long num = (unsigned) -1;
171   if (otk::Property::get(_window, otk::Property::atoms.net_wm_window_type,
172                          otk::Property::atoms.atom, &num, &val)) {
173     // use the first value that we know about in the array
174     for (unsigned long i = 0; i < num; ++i) {
175       if (val[i] == otk::Property::atoms.net_wm_window_type_desktop)
176         _type = Type_Desktop;
177       else if (val[i] == otk::Property::atoms.net_wm_window_type_dock)
178         _type = Type_Dock;
179       else if (val[i] == otk::Property::atoms.net_wm_window_type_toolbar)
180         _type = Type_Toolbar;
181       else if (val[i] == otk::Property::atoms.net_wm_window_type_menu)
182         _type = Type_Menu;
183       else if (val[i] == otk::Property::atoms.net_wm_window_type_utility)
184         _type = Type_Utility;
185       else if (val[i] == otk::Property::atoms.net_wm_window_type_splash)
186         _type = Type_Splash;
187       else if (val[i] == otk::Property::atoms.net_wm_window_type_dialog)
188         _type = Type_Dialog;
189       else if (val[i] == otk::Property::atoms.net_wm_window_type_normal)
190         _type = Type_Normal;
191       else if (val[i] == otk::Property::atoms.kde_net_wm_window_type_override){
192         // prevent this window from getting any decor or functionality
193         _mwmhints.flags &= MwmFlag_Functions | MwmFlag_Decorations;
194         _mwmhints.decorations = 0;
195         _mwmhints.functions = 0;
196       }
197       if (_type != (WindowType) -1)
198         break; // grab the first known type
199     }
200     delete val;
201   }
202     
203   if (_type == (WindowType) -1) {
204     /*
205      * the window type hint was not set, which means we either classify ourself
206      * as a normal window or a dialog, depending on if we are a transient.
207      */
208     if (_transient_for)
209       _type = Type_Dialog;
210     else
211       _type = Type_Normal;
212   }
213 }
214
215
216 void Client::setupDecorAndFunctions()
217 {
218   // start with everything (cept fullscreen)
219   _decorations = Decor_Titlebar | Decor_Handle | Decor_Border | Decor_Icon |
220     Decor_AllDesktops | Decor_Iconify | Decor_Maximize;
221   _functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize |
222     Func_Shade;
223   if (_delete_window) {
224     _decorations |= Decor_Close;
225     _functions |= Func_Close;
226   }
227
228   if (!(_min_size.width() < _max_size.width() ||
229         _min_size.height() < _max_size.height())) {
230     _decorations &= ~(Decor_Maximize | Decor_Handle);
231     _functions &= ~(Func_Resize | Func_Maximize);
232   }
233   
234   switch (_type) {
235   case Type_Normal:
236     // normal windows retain all of the possible decorations and
237     // functionality, and are the only windows that you can fullscreen
238     _functions |= Func_Fullscreen;
239     break;
240
241   case Type_Dialog:
242     // dialogs cannot be maximized
243     _decorations &= ~Decor_Maximize;
244     _functions &= ~Func_Maximize;
245     break;
246
247   case Type_Menu:
248   case Type_Toolbar:
249   case Type_Utility:
250     // these windows get less functionality
251     _decorations &= ~(Decor_Iconify | Decor_Handle);
252     _functions &= ~(Func_Iconify | Func_Resize);
253     break;
254
255   case Type_Desktop:
256   case Type_Dock:
257   case Type_Splash:
258     // none of these windows are manipulated by the window manager
259     _decorations = 0;
260     _functions = 0;
261     break;
262   }
263
264   // Mwm Hints are applied subtractively to what has already been chosen for
265   // decor and functionality
266   if (_mwmhints.flags & MwmFlag_Decorations) {
267     if (! (_mwmhints.decorations & MwmDecor_All)) {
268       if (! (_mwmhints.decorations & MwmDecor_Border))
269         _decorations &= ~Decor_Border;
270       if (! (_mwmhints.decorations & MwmDecor_Handle))
271         _decorations &= ~Decor_Handle;
272       if (! (_mwmhints.decorations & MwmDecor_Title))
273         _decorations &= ~Decor_Titlebar;
274       if (! (_mwmhints.decorations & MwmDecor_Iconify))
275         _decorations &= ~Decor_Iconify;
276       if (! (_mwmhints.decorations & MwmDecor_Maximize))
277         _decorations &= ~Decor_Maximize;
278     }
279   }
280
281   if (_mwmhints.flags & MwmFlag_Functions) {
282     if (! (_mwmhints.functions & MwmFunc_All)) {
283       if (! (_mwmhints.functions & MwmFunc_Resize))
284         _functions &= ~Func_Resize;
285       if (! (_mwmhints.functions & MwmFunc_Move))
286         _functions &= ~Func_Move;
287       if (! (_mwmhints.functions & MwmFunc_Iconify))
288         _functions &= ~Func_Iconify;
289       if (! (_mwmhints.functions & MwmFunc_Maximize))
290         _functions &= ~Func_Maximize;
291       // dont let mwm hints kill the close button
292       //if (! (_mwmhints.functions & MwmFunc_Close))
293       //  _functions &= ~Func_Close;
294     }
295   }
296
297   // can't maximize without moving/resizing
298   if (!((_functions & Func_Move) && (_functions & Func_Resize)))
299     _functions &= ~Func_Maximize;
300
301   // finally, user specified disabled decorations are applied to subtract
302   // decorations
303   if (_disabled_decorations & Decor_Titlebar)
304     _decorations &= ~Decor_Titlebar;
305   if (_disabled_decorations & Decor_Handle)
306     _decorations &= ~Decor_Handle;
307   if (_disabled_decorations & Decor_Border)
308     _decorations &= ~Decor_Border;
309   if (_disabled_decorations & Decor_Iconify)
310     _decorations &= ~Decor_Iconify;
311   if (_disabled_decorations & Decor_Maximize)
312     _decorations &= ~Decor_Maximize;
313   if (_disabled_decorations & Decor_AllDesktops)
314     _decorations &= ~Decor_AllDesktops;
315   if (_disabled_decorations & Decor_Close)
316     _decorations &= ~Decor_Close;
317
318   // if we don't have a titlebar, then we cannot shade!
319   if (!(_decorations & Decor_Titlebar))
320     _functions &= ~Func_Shade;
321
322   changeAllowedActions();
323
324   if (frame) {
325     frame->adjustSize(); // change the decors on the frame
326     frame->adjustPosition(); // with more/less decorations, we may need to be
327                              // moved
328     remaximize(); // with new decor, the window's maximized size may change
329   }
330 }
331
332
333 void Client::getMwmHints()
334 {
335   unsigned long num = MwmHints::elements;
336   unsigned long *hints;
337
338   _mwmhints.flags = 0; // default to none
339   
340   if (!otk::Property::get(_window, otk::Property::atoms.motif_wm_hints,
341                           otk::Property::atoms.motif_wm_hints, &num,
342                           (unsigned long **)&hints))
343     return;
344   
345   if (num >= MwmHints::elements) {
346     // retrieved the hints
347     _mwmhints.flags = hints[0];
348     _mwmhints.functions = hints[1];
349     _mwmhints.decorations = hints[2];
350   }
351
352   delete [] hints;
353 }
354
355
356 void Client::getArea()
357 {
358   XWindowAttributes wattrib;
359   Status ret;
360   
361   ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
362   assert(ret != BadWindow);
363
364   _area = otk::Rect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
365   _border_width = wattrib.border_width;
366 }
367
368
369 void Client::getState()
370 {
371   _modal = _shaded = _max_horz = _max_vert = _fullscreen = _above = _below =
372     _iconic = _skip_taskbar = _skip_pager = false;
373   
374   unsigned long *state;
375   unsigned long num = (unsigned) -1;
376   
377   if (otk::Property::get(_window, otk::Property::atoms.net_wm_state,
378                          otk::Property::atoms.atom, &num, &state)) {
379     for (unsigned long i = 0; i < num; ++i) {
380       if (state[i] == otk::Property::atoms.net_wm_state_modal)
381         _modal = true;
382       else if (state[i] == otk::Property::atoms.net_wm_state_shaded)
383         _shaded = true;
384       else if (state[i] == otk::Property::atoms.net_wm_state_hidden)
385         _iconic = true;
386       else if (state[i] == otk::Property::atoms.net_wm_state_skip_taskbar)
387         _skip_taskbar = true;
388       else if (state[i] == otk::Property::atoms.net_wm_state_skip_pager)
389         _skip_pager = true;
390       else if (state[i] == otk::Property::atoms.net_wm_state_fullscreen)
391         _fullscreen = true;
392       else if (state[i] == otk::Property::atoms.net_wm_state_maximized_vert)
393         _max_vert = true;
394       else if (state[i] == otk::Property::atoms.net_wm_state_maximized_horz)
395         _max_horz = true;
396       else if (state[i] == otk::Property::atoms.net_wm_state_above)
397         _above = true;
398       else if (state[i] == otk::Property::atoms.net_wm_state_below)
399         _below = true;
400     }
401
402     delete [] state;
403   }
404 }
405
406 void Client::getShaped()
407 {
408   _shaped = false;
409 #ifdef   SHAPE
410   if (otk::display->shape()) {
411     int foo;
412     unsigned int ufoo;
413     int s;
414
415     XShapeSelectInput(**otk::display, _window, ShapeNotifyMask);
416
417     XShapeQueryExtents(**otk::display, _window, &s, &foo,
418                        &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo);
419     _shaped = (s != 0);
420   }
421 #endif // SHAPE
422 }
423
424 Client *Client::searchFocusTree(Client *node, Client *skip)
425 {
426   List::const_iterator it, end = node->_transients.end();
427   Client *ret;
428
429   for (it = node->_transients.begin(); it != end; ++it) {
430     if (*it == skip) continue; // circular?
431     if ((ret = searchModalTree(*it, skip))) return ret; // got one
432     if ((*it)->_focused) return *it; // got one
433   }
434   return 0;
435 }
436
437 void Client::calcLayer() {
438   StackLayer l;
439   bool fs = false;
440
441   // are we fullscreen, or do we have a fullscreen transient parent?
442   Client *c = this;
443   while (c) {
444     if (c->_fullscreen) {
445       fs =true;
446       break;
447     }
448     c = c->_transient_for;
449   }
450   if (!fs && _fullscreen) {
451     // is one of our transients focused?
452     c = searchFocusTree(this, this);
453     if (c) fs = true;
454   }
455   
456   if (_iconic) l = Layer_Icon;
457   else if (fs) l = Layer_Fullscreen;
458   else if (_type == Type_Desktop) l = Layer_Desktop;
459   else if (_type == Type_Dock) {
460     if (!_below) l = Layer_Top;
461     else l = Layer_Normal;
462   }
463   else if (_above) l = Layer_Above;
464   else if (_below) l = Layer_Below;
465   else l = Layer_Normal;
466
467   if (l != _layer) {
468     _layer = l;
469     if (frame) {
470       /*
471         if we don't have a frame, then we aren't mapped yet (and this would
472         SIGSEGV :)
473       */
474       openbox->screen(_screen)->raiseWindow(this);
475     }
476   }
477 }
478
479 void Client::updateProtocols()
480 {
481   Atom *proto;
482   int num_return = 0;
483
484   _focus_notify = false;
485   _delete_window = false;
486
487   if (XGetWMProtocols(**otk::display, _window, &proto, &num_return)) {
488     for (int i = 0; i < num_return; ++i) {
489       if (proto[i] == otk::Property::atoms.wm_delete_window) {
490         // this means we can request the window to close
491         _delete_window = true;
492       } else if (proto[i] == otk::Property::atoms.wm_take_focus)
493         // if this protocol is requested, then the window will be notified
494         // by the window manager whenever it receives focus
495         _focus_notify = true;
496     }
497     XFree(proto);
498   }
499 }
500
501 void Client::updateNormalHints()
502 {
503   XSizeHints size;
504   long ret;
505   int oldgravity = _gravity;
506
507   // defaults
508   _min_ratio = 0.0;
509   _max_ratio = 0.0;
510   _size_inc = otk::Size(1, 1);
511   _base_size = otk::Size(0, 0);
512   _min_size = otk::Size(0, 0);
513   _max_size = otk::Size(INT_MAX, INT_MAX);
514
515   // get the hints from the window
516   if (XGetWMNormalHints(**otk::display, _window, &size, &ret)) {
517     _positioned = (size.flags & (PPosition|USPosition));
518
519     if (size.flags & PWinGravity) {
520       _gravity = size.win_gravity;
521       
522       // if the client has a frame, i.e. has already been mapped and is
523       // changing its gravity
524       if (frame && _gravity != oldgravity) {
525         // move our idea of the client's position based on its new gravity
526         int x = frame->area().x(), y = frame->area().y();
527         frame->frameGravity(x, y);
528         _area = otk::Rect(otk::Point(x, y), _area.size());
529       }
530     }
531
532     if (size.flags & PAspect) {
533       if (size.min_aspect.y) _min_ratio = size.min_aspect.x/size.min_aspect.y;
534       if (size.max_aspect.y) _max_ratio = size.max_aspect.x/size.max_aspect.y;
535     }
536
537     if (size.flags & PMinSize)
538       _min_size = otk::Size(size.min_width, size.min_height);
539     
540     if (size.flags & PMaxSize)
541       _max_size = otk::Size(size.max_width, size.max_height);
542     
543     if (size.flags & PBaseSize)
544       _base_size = otk::Size(size.base_width, size.base_height);
545     
546     if (size.flags & PResizeInc)
547       _size_inc = otk::Size(size.width_inc, size.height_inc);
548   }
549 }
550
551 void Client::updateWMHints(bool initstate)
552 {
553   XWMHints *hints;
554
555   // assume a window takes input if it doesnt specify
556   _can_focus = true;
557   bool ur = false;
558   
559   if ((hints = XGetWMHints(**otk::display, _window)) != NULL) {
560     if (hints->flags & InputHint)
561       _can_focus = hints->input;
562
563     // only do this when initstate is true!
564     if (initstate && (hints->flags & StateHint))
565       _iconic = hints->initial_state == IconicState;
566
567     if (hints->flags & XUrgencyHint)
568       ur = true;
569
570     if (hints->flags & WindowGroupHint) {
571       if (hints->window_group != _group) {
572         // XXX: remove from the old group if there was one
573         _group = hints->window_group;
574         // XXX: do stuff with the group
575       }
576     } else // no group!
577       _group = None;
578
579     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 = (unsigned) -1;
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)
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);
1292 }
1293
1294 void Client::internal_move(int x, int y)
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     XEvent event;
1305     event.type = ConfigureNotify;
1306     event.xconfigure.display = **otk::display;
1307     event.xconfigure.event = _window;
1308     event.xconfigure.window = _window;
1309     
1310     // root window coords with border in mind
1311     event.xconfigure.x = x - _border_width + frame->size().left;
1312     event.xconfigure.y = y - _border_width + frame->size().top;
1313     
1314     event.xconfigure.width = _area.width();
1315     event.xconfigure.height = _area.height();
1316     event.xconfigure.border_width = _border_width;
1317     event.xconfigure.above = frame->plate();
1318     event.xconfigure.override_redirect = False;
1319     XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1320                StructureNotifyMask, &event);
1321 #if 0//def DEBUG
1322     printf("Sent synthetic ConfigureNotify %d,%d %d,%d to 0x%lx\n",
1323            event.xconfigure.x, event.xconfigure.y, event.xconfigure.width,
1324            event.xconfigure.height, event.xconfigure.window);
1325 #endif
1326   }
1327 }
1328
1329 void Client::close()
1330 {
1331   XEvent ce;
1332
1333   if (!(_functions & Func_Close)) return;
1334
1335   // XXX: itd be cool to do timeouts and shit here for killing the client's
1336   //      process off
1337   // like... if the window is around after 5 seconds, then the close button
1338   // turns a nice red, and if this function is called again, the client is
1339   // explicitly killed.
1340
1341   ce.xclient.type = ClientMessage;
1342   ce.xclient.message_type =  otk::Property::atoms.wm_protocols;
1343   ce.xclient.display = **otk::display;
1344   ce.xclient.window = _window;
1345   ce.xclient.format = 32;
1346   ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1347   ce.xclient.data.l[1] = CurrentTime;
1348   ce.xclient.data.l[2] = 0l;
1349   ce.xclient.data.l[3] = 0l;
1350   ce.xclient.data.l[4] = 0l;
1351   XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1352 }
1353
1354 void Client::changeState()
1355 {
1356   unsigned long state[2];
1357   state[0] = _wmstate;
1358   state[1] = None;
1359   otk::Property::set(_window, otk::Property::atoms.wm_state,
1360                      otk::Property::atoms.wm_state, state, 2);
1361
1362   Atom netstate[10];
1363   int num = 0;
1364   if (_modal)
1365     netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1366   if (_shaded)
1367     netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1368   if (_iconic)
1369     netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1370   if (_skip_taskbar)
1371     netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1372   if (_skip_pager)
1373     netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1374   if (_fullscreen)
1375     netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1376   if (_max_vert)
1377     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1378   if (_max_horz)
1379     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1380   if (_above)
1381     netstate[num++] = otk::Property::atoms.net_wm_state_above;
1382   if (_below)
1383     netstate[num++] = otk::Property::atoms.net_wm_state_below;
1384   otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1385                      otk::Property::atoms.atom, netstate, num);
1386
1387   calcLayer();
1388
1389   if (frame)
1390     frame->adjustState();
1391 }
1392
1393 void Client::changeAllowedActions(void)
1394 {
1395   Atom actions[9];
1396   int num = 0;
1397
1398   actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1399
1400   if (_functions & Func_Shade)
1401     actions[num++] = otk::Property::atoms.net_wm_action_shade;
1402   if (_functions & Func_Close)
1403     actions[num++] = otk::Property::atoms.net_wm_action_close;
1404   if (_functions & Func_Move)
1405     actions[num++] = otk::Property::atoms.net_wm_action_move;
1406   if (_functions & Func_Iconify)
1407     actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1408   if (_functions & Func_Resize)
1409     actions[num++] = otk::Property::atoms.net_wm_action_resize;
1410   if (_functions & Func_Fullscreen)
1411     actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1412   if (_functions & Func_Maximize) {
1413     actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1414     actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1415   }
1416
1417   otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1418                      otk::Property::atoms.atom, actions, num);
1419
1420   // make sure the window isn't breaking any rules now
1421   
1422   if (!(_functions & Func_Shade) && _shaded)
1423     if (frame) shade(false);
1424     else _shaded = false;
1425   if (!(_functions & Func_Iconify) && _iconic)
1426     if (frame) setDesktop(openbox->screen(_screen)->desktop());
1427     else _iconic = false;
1428   if (!(_functions & Func_Fullscreen) && _fullscreen)
1429     if (frame) fullscreen(false);
1430     else _fullscreen = false;
1431   if (!(_functions & Func_Maximize) && (_max_horz || _max_vert))
1432     if (frame) maximize(false, 0);
1433     else _max_vert = _max_horz = false;
1434 }
1435
1436 void Client::remaximize()
1437 {
1438   int dir;
1439   if (_max_horz && _max_vert)
1440     dir = 0;
1441   else if (_max_horz)
1442     dir = 1;
1443   else if (_max_vert)
1444     dir = 2;
1445   else
1446     return; // not maximized
1447   _max_horz = _max_vert = false;
1448   maximize(true, dir, false);
1449 }
1450
1451 void Client::applyStartupState()
1452 {
1453   // these are in a carefully crafted order..
1454
1455   if (_iconic) {
1456     _iconic = false;
1457     iconify(true);
1458   }
1459   if (_fullscreen) {
1460     _fullscreen = false;
1461     fullscreen(true, false);
1462   }
1463   if (_shaded) {
1464     _shaded = false;
1465     shade(true);
1466   }
1467   if (_urgent)
1468     fireUrgent();
1469   
1470   if (_max_vert && _max_horz) {
1471     _max_vert = _max_horz = false;
1472     maximize(true, 0, false);
1473   } else if (_max_vert) {
1474     _max_vert = false;
1475     maximize(true, 2, false);
1476   } else if (_max_horz) {
1477     _max_horz = false;
1478     maximize(true, 1, false);
1479   }
1480
1481   if (_skip_taskbar); // nothing to do for this
1482   if (_skip_pager);   // nothing to do for this
1483   if (_modal);        // nothing to do for this
1484   if (_above);        // nothing to do for this
1485   if (_below);        // nothing to do for this
1486 }
1487
1488 void Client::fireUrgent()
1489 {
1490   // call the python UrgentWindow callbacks
1491   EventData data(_screen, this, EventAction::UrgentWindow, 0);
1492   openbox->bindings()->fireEvent(&data);
1493 }
1494
1495 void Client::shade(bool shade)
1496 {
1497   if (!(_functions & Func_Shade) || // can't
1498       _shaded == shade) return;     // already done
1499
1500   // when we're iconic, don't change the wmstate
1501   if (!_iconic)
1502     _wmstate = shade ? IconicState : NormalState;
1503   _shaded = shade;
1504   changeState();
1505   frame->adjustSize();
1506 }
1507
1508 void Client::maximize(bool max, int dir, bool savearea)
1509 {
1510   assert(dir == 0 || dir == 1 || dir == 2);
1511   if (!(_functions & Func_Maximize)) return; // can't
1512
1513   // check if already done
1514   if (max) {
1515     if (dir == 0 && _max_horz && _max_vert) return;
1516     if (dir == 1 && _max_horz) return;
1517     if (dir == 2 && _max_vert) return;
1518   } else {
1519     if (dir == 0 && !_max_horz && !_max_vert) return;
1520     if (dir == 1 && !_max_horz) return;
1521     if (dir == 2 && !_max_vert) return;
1522   }
1523
1524   const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1525   int x = frame->area().x(), y = frame->area().y(),
1526     w = _area.width(), h = _area.height();
1527   
1528   if (max) {
1529     if (savearea) {
1530       long dimensions[4];
1531       long *readdim;
1532       unsigned long n = 4;
1533
1534       dimensions[0] = x;
1535       dimensions[1] = y;
1536       dimensions[2] = w;
1537       dimensions[3] = h;
1538
1539       // get the property off the window and use it for the dimentions we are
1540       // already maxed on
1541       if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1542                              otk::Property::atoms.cardinal, &n,
1543                              (long unsigned**) &readdim)) {
1544         if (n >= 4) {
1545           if (_max_horz) {
1546             dimensions[0] = readdim[0];
1547             dimensions[2] = readdim[2];
1548           }
1549           if (_max_vert) {
1550             dimensions[1] = readdim[1];
1551             dimensions[3] = readdim[3];
1552           }
1553         }
1554         delete readdim;
1555       }
1556       
1557       otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1558                          otk::Property::atoms.cardinal,
1559                          (long unsigned*)dimensions, 4);
1560     }
1561     if (dir == 0 || dir == 1) { // horz
1562       x = a.x();
1563       w = a.width();
1564     }
1565     if (dir == 0 || dir == 2) { // vert
1566       y = a.y();
1567       h = a.height() - frame->size().top - frame->size().bottom;
1568     }
1569   } else {
1570     long *dimensions;
1571     long unsigned n = 4;
1572       
1573     if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1574                            otk::Property::atoms.cardinal, &n,
1575                            (long unsigned**) &dimensions)) {
1576       if (n >= 4) {
1577         if (dir == 0 || dir == 1) { // horz
1578           x = (signed int)dimensions[0];
1579           w = (signed int)dimensions[2];
1580         }
1581         if (dir == 0 || dir == 2) { // vert
1582           y = (signed int)dimensions[1];
1583           h = (signed int)dimensions[3];
1584         }
1585       }
1586       delete dimensions;
1587     } else {
1588       // pick some fallbacks...
1589       if (dir == 0 || dir == 1) { // horz
1590         x = a.x() + a.width() / 4;
1591         w = a.width() / 2;
1592       }
1593       if (dir == 0 || dir == 2) { // vert
1594         y = a.y() + a.height() / 4;
1595         h = a.height() / 2;
1596       }
1597     }
1598   }
1599
1600   if (dir == 0 || dir == 1) // horz
1601     _max_horz = max;
1602   if (dir == 0 || dir == 2) // vert
1603     _max_vert = max;
1604
1605   if (!_max_horz && !_max_vert)
1606     otk::Property::erase(_window, otk::Property::atoms.openbox_premax);
1607
1608   changeState(); // change the state hints on the client
1609
1610   frame->frameGravity(x, y); // figure out where the client should be going
1611   internal_resize(TopLeft, w, h, true, x, y);
1612 }
1613
1614 void Client::fullscreen(bool fs, bool savearea)
1615 {
1616   static FunctionFlags saved_func;
1617   static DecorationFlags saved_decor;
1618
1619   if (!(_functions & Func_Fullscreen) || // can't
1620       _fullscreen == fs) return;         // already done
1621
1622   _fullscreen = fs;
1623   changeState(); // change the state hints on the client
1624
1625   int x = _area.x(), y = _area.y(), w = _area.width(), h = _area.height();
1626   
1627   if (fs) {
1628     // save the functions and remove them
1629     saved_func = _functions;
1630     _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1631     // save the decorations and remove them
1632     saved_decor = _decorations;
1633     _decorations = 0;
1634     if (savearea) {
1635       long dimensions[4];
1636       dimensions[0] = _area.x();
1637       dimensions[1] = _area.y();
1638       dimensions[2] = _area.width();
1639       dimensions[3] = _area.height();
1640       otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1641                          otk::Property::atoms.cardinal,
1642                          (long unsigned*)dimensions, 4);
1643     }
1644     const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1645     x = 0;
1646     y = 0;
1647     w = info->size().width();
1648     h = info->size().height();
1649   } else {
1650     _functions = saved_func;
1651     _decorations = saved_decor;
1652
1653     long *dimensions;
1654     long unsigned n = 4;
1655       
1656     if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1657                            otk::Property::atoms.cardinal, &n,
1658                            (long unsigned**) &dimensions)) {
1659       if (n >= 4) {
1660         x = dimensions[0];
1661         y = dimensions[1];
1662         w = dimensions[2];
1663         h = dimensions[3];
1664       }
1665       delete dimensions;
1666     } else {
1667       // pick some fallbacks...
1668       const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1669       x = a.x() + a.width() / 4;
1670       y = a.y() + a.height() / 4;
1671       w = a.width() / 2;
1672         h = a.height() / 2;
1673     }    
1674   }
1675   
1676   changeAllowedActions();  // based on the new _functions
1677
1678   // when fullscreening, don't obey things like increments, fill the screen
1679   internal_resize(TopLeft, w, h, !fs, x, y);
1680
1681   // raise (back) into our stacking layer
1682   openbox->screen(_screen)->raiseWindow(this);
1683
1684   // try focus us when we go into fullscreen mode
1685   if (fs) focus();
1686 }
1687
1688 void Client::iconify(bool iconic, bool curdesk)
1689 {
1690   if (_iconic == iconic) return; // nothing to do
1691
1692 #ifdef DEBUG
1693     printf("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"), _window);
1694 #endif
1695
1696   _iconic = iconic;
1697
1698   if (_iconic) {
1699     _wmstate = IconicState;
1700     ignore_unmaps++;
1701     // we unmap the client itself so that we can get MapRequest events, and
1702     // because the ICCCM tells us to!
1703     XUnmapWindow(**otk::display, _window);
1704   } else {
1705     if (curdesk)
1706       setDesktop(openbox->screen(_screen)->desktop());
1707     _wmstate = NormalState;
1708     XMapWindow(**otk::display, _window);
1709   }
1710   changeState();
1711   showhide();
1712   openbox->screen(_screen)->updateStruts();
1713 }
1714
1715 void Client::disableDecorations(DecorationFlags flags)
1716 {
1717   _disabled_decorations = flags;
1718   setupDecorAndFunctions();
1719 }
1720
1721 void Client::installColormap(bool install) const
1722 {
1723   XWindowAttributes wa;
1724   if (XGetWindowAttributes(**otk::display, _window, &wa)) {
1725     if (install)
1726       XInstallColormap(**otk::display, wa.colormap);
1727     else
1728       XUninstallColormap(**otk::display, wa.colormap);
1729   }
1730 }
1731
1732 Client *Client::searchModalTree(Client *node, Client *skip)
1733 {
1734   List::const_iterator it, end = node->_transients.end();
1735   Client *ret;
1736   
1737   for (it = node->_transients.begin(); it != end; ++it) {
1738     if (*it == skip) continue; // circular?
1739     if ((ret = searchModalTree(*it, skip))) return ret; // got one
1740     if ((*it)->_modal) return *it; // got one
1741   }
1742   return 0;
1743 }
1744
1745 Client *Client::findModalChild()
1746 {
1747   return searchModalTree(this, this);
1748 }
1749
1750
1751 bool Client::focus()
1752 {
1753   // if we have a modal child, then focus it, not us
1754   Client *c = findModalChild();
1755   if (c) return c->focus();
1756
1757   // won't try focus if the client doesn't want it, or if the window isn't
1758   // visible on the screen
1759   if (!(frame->visible() && (_can_focus || _focus_notify))) return false;
1760
1761   // do a check to see if the window has already been unmapped or destroyed
1762   // do this intelligently while watching out for unmaps we've generated
1763   // (ignore_unmaps > 0)
1764   XEvent ev;
1765   if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1766     XPutBackEvent(**otk::display, &ev);
1767     return false;
1768   }
1769   while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1770     if (ignore_unmaps) {
1771       unmapHandler(ev.xunmap);
1772     } else {
1773       XPutBackEvent(**otk::display, &ev);
1774       return false;
1775     }
1776   }
1777
1778   if (_can_focus)
1779     XSetInputFocus(**otk::display, _window,
1780                    RevertToNone, CurrentTime);
1781
1782   if (_focus_notify) {
1783     XEvent ce;
1784     ce.xclient.type = ClientMessage;
1785     ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1786     ce.xclient.display = **otk::display;
1787     ce.xclient.window = _window;
1788     ce.xclient.format = 32;
1789     ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1790     ce.xclient.data.l[1] = openbox->lastTime();
1791     ce.xclient.data.l[2] = 0l;
1792     ce.xclient.data.l[3] = 0l;
1793     ce.xclient.data.l[4] = 0l;
1794     XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1795   }
1796
1797   XSync(**otk::display, False);
1798   return true;
1799 }
1800
1801
1802 void Client::unfocus() const
1803 {
1804   assert(openbox->focusedClient() == this);
1805   openbox->setFocusedClient(0);
1806 }
1807
1808
1809 void Client::focusHandler(const XFocusChangeEvent &e)
1810 {
1811 #ifdef    DEBUG
1812 //  printf("FocusIn for 0x%lx\n", e.window);
1813 #endif // DEBUG
1814   
1815   otk::EventHandler::focusHandler(e);
1816
1817   _focused = true;
1818   frame->adjustFocus();
1819
1820   calcLayer(); // focus state can affect the stacking layer
1821
1822   openbox->setFocusedClient(this);
1823 }
1824
1825
1826 void Client::unfocusHandler(const XFocusChangeEvent &e)
1827 {
1828 #ifdef    DEBUG
1829 //  printf("FocusOut for 0x%lx\n", e.window);
1830 #endif // DEBUG
1831   
1832   otk::EventHandler::unfocusHandler(e);
1833
1834   _focused = false;
1835   frame->adjustFocus();
1836
1837   calcLayer(); // focus state can affect the stacking layer
1838
1839   if (openbox->focusedClient() == this)
1840     openbox->setFocusedClient(0);
1841 }
1842
1843
1844 void Client::configureRequestHandler(const XConfigureRequestEvent &ec)
1845 {
1846 #ifdef    DEBUG
1847   printf("ConfigureRequest for 0x%lx\n", ec.window);
1848 #endif // DEBUG
1849   
1850   otk::EventHandler::configureRequestHandler(ec);
1851
1852   // compress these
1853   XConfigureRequestEvent e = ec;
1854   XEvent ev;
1855   while (XCheckTypedWindowEvent(**otk::display, window(), ConfigureRequest,
1856                                 &ev)) {
1857     // XXX if this causes bad things.. we can compress config req's with the
1858     //     same mask.
1859     e.value_mask |= ev.xconfigurerequest.value_mask;
1860     if (ev.xconfigurerequest.value_mask & CWX)
1861       e.x = ev.xconfigurerequest.x;
1862     if (ev.xconfigurerequest.value_mask & CWY)
1863       e.y = ev.xconfigurerequest.y;
1864     if (ev.xconfigurerequest.value_mask & CWWidth)
1865       e.width = ev.xconfigurerequest.width;
1866     if (ev.xconfigurerequest.value_mask & CWHeight)
1867       e.height = ev.xconfigurerequest.height;
1868     if (ev.xconfigurerequest.value_mask & CWBorderWidth)
1869       e.border_width = ev.xconfigurerequest.border_width;
1870     if (ev.xconfigurerequest.value_mask & CWStackMode)
1871       e.detail = ev.xconfigurerequest.detail;
1872   }
1873
1874   // if we are iconic (or shaded (fvwm does this)) ignore the event
1875   if (_iconic || _shaded) return;
1876
1877   if (e.value_mask & CWBorderWidth)
1878     _border_width = e.border_width;
1879
1880   // resize, then move, as specified in the EWMH section 7.7
1881   if (e.value_mask & (CWWidth | CWHeight)) {
1882     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1883     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1884
1885     Corner corner;
1886     switch (_gravity) {
1887     case NorthEastGravity:
1888     case EastGravity:
1889       corner = TopRight;
1890       break;
1891     case SouthWestGravity:
1892     case SouthGravity:
1893       corner = BottomLeft;
1894       break;
1895     case SouthEastGravity:
1896       corner = BottomRight;
1897       break;
1898     default:     // NorthWest, Static, etc
1899       corner = TopLeft;
1900     }
1901
1902     // if moving AND resizing ...
1903     if (e.value_mask & (CWX | CWY)) {
1904       int x = (e.value_mask & CWX) ? e.x : _area.x();
1905       int y = (e.value_mask & CWY) ? e.y : _area.y();
1906       internal_resize(corner, w, h, false, x, y);
1907     } else // if JUST resizing...
1908       internal_resize(corner, w, h, false);
1909   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1910     int x = (e.value_mask & CWX) ? e.x : _area.x();
1911     int y = (e.value_mask & CWY) ? e.y : _area.y();
1912     internal_move(x, y);
1913   }
1914
1915   if (e.value_mask & CWStackMode) {
1916     switch (e.detail) {
1917     case Below:
1918     case BottomIf:
1919       openbox->screen(_screen)->lowerWindow(this);
1920       break;
1921
1922     case Above:
1923     case TopIf:
1924     default:
1925       openbox->screen(_screen)->raiseWindow(this);
1926       break;
1927     }
1928   }
1929 }
1930
1931
1932 void Client::unmapHandler(const XUnmapEvent &e)
1933 {
1934   if (ignore_unmaps) {
1935 #ifdef    DEBUG
1936 //  printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1937 #endif // DEBUG
1938     ignore_unmaps--;
1939     return;
1940   }
1941   
1942 #ifdef    DEBUG
1943   printf("UnmapNotify for 0x%lx\n", e.window);
1944 #endif // DEBUG
1945
1946   otk::EventHandler::unmapHandler(e);
1947
1948   // this deletes us etc
1949   openbox->screen(_screen)->unmanageWindow(this);
1950 }
1951
1952
1953 void Client::destroyHandler(const XDestroyWindowEvent &e)
1954 {
1955 #ifdef    DEBUG
1956   printf("DestroyNotify for 0x%lx\n", e.window);
1957 #endif // DEBUG
1958
1959   otk::EventHandler::destroyHandler(e);
1960
1961   // this deletes us etc
1962   openbox->screen(_screen)->unmanageWindow(this);
1963 }
1964
1965
1966 void Client::reparentHandler(const XReparentEvent &e)
1967 {
1968   // this is when the client is first taken captive in the frame
1969   if (e.parent == frame->plate()) return;
1970
1971 #ifdef    DEBUG
1972   printf("ReparentNotify for 0x%lx\n", e.window);
1973 #endif // DEBUG
1974
1975   otk::EventHandler::reparentHandler(e);
1976
1977   /*
1978     This event is quite rare and is usually handled in unmapHandler.
1979     However, if the window is unmapped when the reparent event occurs,
1980     the window manager never sees it because an unmap event is not sent
1981     to an already unmapped window.
1982   */
1983
1984   // we don't want the reparent event, put it back on the stack for the X
1985   // server to deal with after we unmanage the window
1986   XEvent ev;
1987   ev.xreparent = e;
1988   XPutBackEvent(**otk::display, &ev);
1989   
1990   // this deletes us etc
1991   openbox->screen(_screen)->unmanageWindow(this);
1992 }
1993
1994 void Client::mapRequestHandler(const XMapRequestEvent &e)
1995 {
1996 #ifdef    DEBUG
1997   printf("MapRequest for already managed 0x%lx\n", e.window);
1998 #endif // DEBUG
1999
2000   assert(_iconic); // we shouldn't be able to get this unless we're iconic
2001
2002   // move to the current desktop (uniconify)
2003   iconify(false);
2004   // XXX: should we focus/raise the window? (basically a net_wm_active_window)
2005 }
2006
2007 }