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