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