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