]> icculus.org git repositories - dana/openbox.git/blob - src/client.cc
add a Config class with config data from the scripts.
[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 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("DEBUG: 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     // set the default icon(s) XXX load these from the py
747     _nicons = 1;
748     _icons = new Icon[1];
749     _icons[i].w = 0;
750     _icons[i].h = 0;
751     _icons[i].data = 0;
752   }
753
754   assert(_nicons > 0); // there should always be a default..
755   
756   if (frame) frame->adjustIcon();
757 }
758
759 void Client::propertyHandler(const XPropertyEvent &e)
760 {
761   otk::EventHandler::propertyHandler(e);
762
763   // validate cuz we query stuff off the client here
764   if (!validate()) return;
765   
766   // compress changes to a single property into a single change
767   XEvent ce;
768   while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
769     // XXX: it would be nice to compress ALL changes to a property, not just
770     //      changes in a row without other props between.
771     if (ce.xproperty.atom != e.atom) {
772       XPutBackEvent(**otk::display, &ce);
773       break;
774     }
775   }
776
777   if (e.atom == XA_WM_NORMAL_HINTS) {
778     updateNormalHints();
779     setupDecorAndFunctions(); // normal hints can make a window non-resizable
780   } else if (e.atom == XA_WM_HINTS)
781     updateWMHints();
782   else if (e.atom == XA_WM_TRANSIENT_FOR) {
783     updateTransientFor();
784     getType();
785     calcLayer(); // type may have changed, so update the layer
786     setupDecorAndFunctions();
787   }
788   else if (e.atom == otk::Property::atoms.net_wm_name ||
789            e.atom == otk::Property::atoms.wm_name)
790     updateTitle();
791   else if (e.atom == otk::Property::atoms.net_wm_icon_name ||
792            e.atom == otk::Property::atoms.wm_icon_name)
793     updateIconTitle();
794   else if (e.atom == otk::Property::atoms.wm_class)
795     updateClass();
796   else if (e.atom == otk::Property::atoms.wm_protocols) {
797     updateProtocols();
798     setupDecorAndFunctions();
799   }
800   else if (e.atom == otk::Property::atoms.net_wm_strut)
801     updateStrut();
802   else if (e.atom == otk::Property::atoms.net_wm_icon)
803     updateIcons();
804 }
805
806 void Client::setWMState(long state)
807 {
808   if (state == _wmstate) return; // no change
809   
810   switch (state) {
811   case IconicState:
812     iconify(true);
813     break;
814   case NormalState:
815     iconify(false);
816     break;
817   }
818 }
819
820 void Client::setDesktop(unsigned int target)
821 {
822   if (target == _desktop) return;
823   
824   printf("Setting desktop %u\n", target);
825
826   if (!(target < openbox->screen(_screen)->numDesktops() ||
827         target == 0xffffffff))
828     return;
829
830   _desktop = target;
831   // set the desktop hint
832   otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
833                      otk::Property::atoms.cardinal, _desktop);
834   frame->adjustState(); // the frame can display the current desktop state
835   // 'move' the window to the new desktop
836   showhide();
837   openbox->screen(_screen)->updateStruts();
838 }
839
840 void Client::showhide()
841 {
842   bool show;
843   Screen *s = openbox->screen(_screen);
844
845   if (_iconic) show = false;
846   else if (!(_desktop == s->desktop() ||
847              _desktop == 0xffffffff)) show = false;
848   else if (normal() && s->showingDesktop()) show = false;
849   else show = true;
850
851   if (show) frame->show();
852   else      frame->hide();
853 }
854
855 void Client::setState(StateAction action, long data1, long data2)
856 {
857   bool shadestate = _shaded;
858   bool fsstate = _fullscreen;
859   bool maxh = _max_horz;
860   bool maxv = _max_vert;
861
862   if (!(action == State_Add || action == State_Remove ||
863         action == State_Toggle))
864     return; // an invalid action was passed to the client message, ignore it
865
866   for (int i = 0; i < 2; ++i) {
867     Atom state = i == 0 ? data1 : data2;
868     
869     if (! state) continue;
870
871     // if toggling, then pick whether we're adding or removing
872     if (action == State_Toggle) {
873       if (state == otk::Property::atoms.net_wm_state_modal)
874         action = _modal ? State_Remove : State_Add;
875       else if (state == otk::Property::atoms.net_wm_state_maximized_vert)
876         action = _max_vert ? State_Remove : State_Add;
877       else if (state == otk::Property::atoms.net_wm_state_maximized_horz)
878         action = _max_horz ? State_Remove : State_Add;
879       else if (state == otk::Property::atoms.net_wm_state_shaded)
880         action = _shaded ? State_Remove : State_Add;
881       else if (state == otk::Property::atoms.net_wm_state_skip_taskbar)
882         action = _skip_taskbar ? State_Remove : State_Add;
883       else if (state == otk::Property::atoms.net_wm_state_skip_pager)
884         action = _skip_pager ? State_Remove : State_Add;
885       else if (state == otk::Property::atoms.net_wm_state_fullscreen)
886         action = _fullscreen ? State_Remove : State_Add;
887       else if (state == otk::Property::atoms.net_wm_state_above)
888         action = _above ? State_Remove : State_Add;
889       else if (state == otk::Property::atoms.net_wm_state_below)
890         action = _below ? State_Remove : State_Add;
891     }
892     
893     if (action == State_Add) {
894       if (state == otk::Property::atoms.net_wm_state_modal) {
895         if (_modal) continue;
896         _modal = true;
897       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
898         maxv = true;
899       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
900         if (_max_horz) continue;
901         maxh = true;
902       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
903         shadestate = true;
904       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
905         _skip_taskbar = true;
906       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
907         _skip_pager = true;
908       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
909         fsstate = true;
910       } else if (state == otk::Property::atoms.net_wm_state_above) {
911         if (_above) continue;
912         _above = true;
913       } else if (state == otk::Property::atoms.net_wm_state_below) {
914         if (_below) continue;
915         _below = true;
916       }
917
918     } else { // action == State_Remove
919       if (state == otk::Property::atoms.net_wm_state_modal) {
920         if (!_modal) continue;
921         _modal = false;
922       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
923         maxv = false;
924       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
925         maxh = false;
926       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
927         shadestate = false;
928       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
929         _skip_taskbar = false;
930       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
931         _skip_pager = false;
932       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
933         fsstate = false;
934       } else if (state == otk::Property::atoms.net_wm_state_above) {
935         if (!_above) continue;
936         _above = false;
937       } else if (state == otk::Property::atoms.net_wm_state_below) {
938         if (!_below) continue;
939         _below = false;
940       }
941     }
942   }
943   if (maxh != _max_horz || maxv != _max_vert) {
944     if (maxh != _max_horz && maxv != _max_vert) { // toggling both
945       if (maxh == maxv) { // both going the same way
946         maximize(maxh, 0, true);
947       } else {
948         maximize(maxh, 1, true);
949         maximize(maxv, 2, true);
950       }
951     } else { // toggling one
952       if (maxh != _max_horz)
953         maximize(maxh, 1, true);
954       else
955         maximize(maxv, 2, true);
956     }
957   }
958   // change fullscreen state before shading, as it will affect if the window
959   // can shade or not
960   if (fsstate != _fullscreen)
961     fullscreen(fsstate, true);
962   if (shadestate != _shaded)
963     shade(shadestate);
964   calcLayer();
965   changeState(); // change the hint to relect these changes
966 }
967
968 void Client::toggleClientBorder(bool addborder)
969 {
970   // adjust our idea of where the client is, based on its border. When the
971   // border is removed, the client should now be considered to be in a
972   // different position.
973   // when re-adding the border to the client, the same operation needs to be
974   // reversed.
975   int oldx = _area.x(), oldy = _area.y();
976   int x = oldx, y = oldy;
977   switch(_gravity) {
978   default:
979   case NorthWestGravity:
980   case WestGravity:
981   case SouthWestGravity:
982     break;
983   case NorthEastGravity:
984   case EastGravity:
985   case SouthEastGravity:
986     if (addborder) x -= _border_width * 2;
987     else           x += _border_width * 2;
988     break;
989   case NorthGravity:
990   case SouthGravity:
991   case CenterGravity:
992   case ForgetGravity:
993   case StaticGravity:
994     if (addborder) x -= _border_width;
995     else           x += _border_width;
996     break;
997   }
998   switch(_gravity) {
999   default:
1000   case NorthWestGravity:
1001   case NorthGravity:
1002   case NorthEastGravity:
1003     break;
1004   case SouthWestGravity:
1005   case SouthGravity:
1006   case SouthEastGravity:
1007     if (addborder) y -= _border_width * 2;
1008     else           y += _border_width * 2;
1009     break;
1010   case WestGravity:
1011   case EastGravity:
1012   case CenterGravity:
1013   case ForgetGravity:
1014   case StaticGravity:
1015     if (addborder) y -= _border_width;
1016     else           y += _border_width;
1017     break;
1018   }
1019   _area = otk::Rect(otk::Point(x, y), _area.size());
1020
1021   if (addborder) {
1022     XSetWindowBorderWidth(**otk::display, _window, _border_width);
1023
1024     // move the client so it is back it the right spot _with_ its border!
1025     if (x != oldx || y != oldy)
1026       XMoveWindow(**otk::display, _window, x, y);
1027   } else
1028     XSetWindowBorderWidth(**otk::display, _window, 0);
1029 }
1030
1031 void Client::clientMessageHandler(const XClientMessageEvent &e)
1032 {
1033   otk::EventHandler::clientMessageHandler(e);
1034   
1035   // validate cuz we query stuff off the client here
1036   if (!validate()) return;
1037   
1038   if (e.format != 32) return;
1039
1040   if (e.message_type == otk::Property::atoms.wm_change_state) {
1041     // compress changes into a single change
1042     bool compress = false;
1043     XEvent ce;
1044     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
1045       // XXX: it would be nice to compress ALL messages of a type, not just
1046       //      messages in a row without other message types between.
1047       if (ce.xclient.message_type != e.message_type) {
1048         XPutBackEvent(**otk::display, &ce);
1049         break;
1050       }
1051       compress = true;
1052     }
1053     if (compress)
1054       setWMState(ce.xclient.data.l[0]); // use the found event
1055     else
1056       setWMState(e.data.l[0]); // use the original event
1057   } else if (e.message_type == otk::Property::atoms.net_wm_desktop) {
1058     // compress changes into a single change 
1059     bool compress = false;
1060     XEvent ce;
1061     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
1062       // XXX: it would be nice to compress ALL messages of a type, not just
1063       //      messages in a row without other message types between.
1064       if (ce.xclient.message_type != e.message_type) {
1065         XPutBackEvent(**otk::display, &ce);
1066         break;
1067       }
1068       compress = true;
1069     }
1070     if (compress)
1071       setDesktop(e.data.l[0]); // use the found event
1072     else
1073       setDesktop(e.data.l[0]); // use the original event
1074   } else if (e.message_type == otk::Property::atoms.net_wm_state) {
1075     // can't compress these
1076 #ifdef DEBUG
1077     printf("net_wm_state %s %ld %ld for 0x%lx\n",
1078            (e.data.l[0] == 0 ? "Remove" : e.data.l[0] == 1 ? "Add" :
1079             e.data.l[0] == 2 ? "Toggle" : "INVALID"),
1080            e.data.l[1], e.data.l[2], _window);
1081 #endif
1082     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
1083   } else if (e.message_type == otk::Property::atoms.net_close_window) {
1084 #ifdef DEBUG
1085     printf("net_close_window for 0x%lx\n", _window);
1086 #endif
1087     close();
1088   } else if (e.message_type == otk::Property::atoms.net_active_window) {
1089 #ifdef DEBUG
1090     printf("net_active_window for 0x%lx\n", _window);
1091 #endif
1092     if (openbox->screen(_screen)->showingDesktop())
1093       openbox->screen(_screen)->showDesktop(false);
1094     if (_iconic)
1095       iconify(false);
1096     else if (!frame->visible()) // if its not visible for other reasons, then
1097       return;                   // don't mess with it
1098     if (_shaded)
1099       shade(false);
1100     focus();
1101     openbox->screen(_screen)->raiseWindow(this);
1102   } else if (e.message_type == otk::Property::atoms.openbox_active_window) {
1103     if (openbox->screen(_screen)->showingDesktop())
1104       openbox->screen(_screen)->showDesktop(false);
1105     if (_iconic)
1106       iconify(false);
1107     else if (!frame->visible()) // if its not visible for other reasons, then
1108       return;                   // don't mess with it
1109     if (e.data.l[0] && _shaded)
1110       shade(false);
1111     focus();
1112     if (e.data.l[1])
1113       openbox->screen(_screen)->raiseWindow(this);
1114   }
1115 }
1116
1117 #if defined(SHAPE)
1118 void Client::shapeHandler(const XShapeEvent &e)
1119 {
1120   otk::EventHandler::shapeHandler(e);
1121
1122   if (e.kind == ShapeBounding) {
1123     _shaped = e.shaped;
1124     frame->adjustShape();
1125   }
1126 }
1127 #endif
1128
1129 void Client::resize(Corner anchor, int w, int h)
1130 {
1131   if (!(_functions & Func_Resize)) return;
1132   internal_resize(anchor, w, h);
1133 }
1134
1135 void Client::internal_resize(Corner anchor, int w, int h,
1136                              bool user, int x, int y)
1137 {
1138   w -= _base_size.width();
1139   h -= _base_size.height();
1140
1141   if (user) {
1142     // for interactive resizing. have to move half an increment in each
1143     // direction.
1144     int mw = w % _size_inc.width(); // how far we are towards the next size inc
1145     int mh = h % _size_inc.height();
1146     int aw = _size_inc.width() / 2; // amount to add
1147     int ah = _size_inc.height() / 2;
1148     // don't let us move into a new size increment
1149     if (mw + aw >= _size_inc.width()) aw = _size_inc.width() - mw - 1;
1150     if (mh + ah >= _size_inc.height()) ah = _size_inc.height() - mh - 1;
1151     w += aw;
1152     h += ah;
1153     
1154     // if this is a user-requested resize, then check against min/max sizes
1155     // and aspect ratios
1156
1157     // smaller than min size or bigger than max size?
1158     if (w > _max_size.width()) w = _max_size.width();
1159     if (w < _min_size.width()) w = _min_size.width();
1160     if (h > _max_size.height()) h = _max_size.height();
1161     if (h < _min_size.height()) h = _min_size.height();
1162
1163     // adjust the height ot match the width for the aspect ratios
1164     if (_min_ratio)
1165       if (h * _min_ratio > w) h = static_cast<int>(w / _min_ratio);
1166     if (_max_ratio)
1167       if (h * _max_ratio < w) h = static_cast<int>(w / _max_ratio);
1168   }
1169
1170   // keep to the increments
1171   w /= _size_inc.width();
1172   h /= _size_inc.height();
1173
1174   // you cannot resize to nothing
1175   if (w < 1) w = 1;
1176   if (h < 1) h = 1;
1177   
1178   // store the logical size
1179   _logical_size = otk::Size(w, h);
1180
1181   w *= _size_inc.width();
1182   h *= _size_inc.height();
1183
1184   w += _base_size.width();
1185   h += _base_size.height();
1186
1187   if (x == INT_MIN || y == INT_MIN) {
1188     x = _area.x();
1189     y = _area.y();
1190     switch (anchor) {
1191     case TopLeft:
1192       break;
1193     case TopRight:
1194       x -= w - _area.width();
1195       break;
1196     case BottomLeft:
1197       y -= h - _area.height();
1198       break;
1199     case BottomRight:
1200       x -= w - _area.width();
1201       y -= h - _area.height();
1202       break;
1203     }
1204   }
1205
1206   _area = otk::Rect(_area.position(), otk::Size(w, h));
1207
1208   XResizeWindow(**otk::display, _window, w, h);
1209
1210   // resize the frame to match the request
1211   frame->adjustSize();
1212   internal_move(x, y);
1213 }
1214
1215 const Icon *Client::icon(const otk::Size &s) const
1216 {
1217   unsigned long req = s.width() * s.height();
1218   // si is the smallest image >= req
1219   // li is the largest image < req
1220   unsigned long smallest = 0xffffffff, largest = 0, si = 0, li = 0;
1221
1222   assert(_nicons > 0); // there should always be a default..
1223   for (int i = 0; i < _nicons; ++i) {
1224     unsigned long size = _icons[i].w * _icons[i].h;
1225     if (size < smallest && size >= req) {
1226       smallest = size;
1227       si = i;
1228     }
1229     if (size > largest && size <= req) {
1230       largest = size;
1231       li = i;
1232     }
1233   }
1234   if (smallest == 0xffffffff) // didnt find one bigger than us...
1235     return &_icons[li];
1236   return &_icons[si];
1237 }
1238
1239 void Client::move(int x, int y)
1240 {
1241   if (!(_functions & Func_Move)) return;
1242   frame->frameGravity(x, y); // get the client's position based on x,y for the
1243                              // frame
1244   internal_move(x, y);
1245 }
1246
1247 void Client::internal_move(int x, int y)
1248 {
1249   _area = otk::Rect(otk::Point(x, y), _area.size());
1250
1251   // move the frame to be in the requested position
1252   if (frame) { // this can be called while mapping, before frame exists
1253     frame->adjustPosition();
1254
1255     // send synthetic configure notify (we don't need to if we aren't mapped
1256     // yet)
1257     XEvent event;
1258     event.type = ConfigureNotify;
1259     event.xconfigure.display = **otk::display;
1260     event.xconfigure.event = _window;
1261     event.xconfigure.window = _window;
1262     
1263     // root window coords with border in mind
1264     event.xconfigure.x = x - _border_width + frame->size().left;
1265     event.xconfigure.y = y - _border_width + frame->size().top;
1266     
1267     event.xconfigure.width = _area.width();
1268     event.xconfigure.height = _area.height();
1269     event.xconfigure.border_width = _border_width;
1270     event.xconfigure.above = frame->plate();
1271     event.xconfigure.override_redirect = False;
1272     XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1273                StructureNotifyMask, &event);
1274 #if 0//def DEBUG
1275     printf("Sent synthetic ConfigureNotify %d,%d %d,%d to 0x%lx\n",
1276            event.xconfigure.x, event.xconfigure.y, event.xconfigure.width,
1277            event.xconfigure.height, event.xconfigure.window);
1278 #endif
1279   }
1280 }
1281
1282 void Client::close()
1283 {
1284   XEvent ce;
1285
1286   if (!(_functions & Func_Close)) return;
1287
1288   // XXX: itd be cool to do timeouts and shit here for killing the client's
1289   //      process off
1290   // like... if the window is around after 5 seconds, then the close button
1291   // turns a nice red, and if this function is called again, the client is
1292   // explicitly killed.
1293
1294   ce.xclient.type = ClientMessage;
1295   ce.xclient.message_type =  otk::Property::atoms.wm_protocols;
1296   ce.xclient.display = **otk::display;
1297   ce.xclient.window = _window;
1298   ce.xclient.format = 32;
1299   ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1300   ce.xclient.data.l[1] = CurrentTime;
1301   ce.xclient.data.l[2] = 0l;
1302   ce.xclient.data.l[3] = 0l;
1303   ce.xclient.data.l[4] = 0l;
1304   XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1305 }
1306
1307 void Client::changeState()
1308 {
1309   unsigned long state[2];
1310   state[0] = _wmstate;
1311   state[1] = None;
1312   otk::Property::set(_window, otk::Property::atoms.wm_state,
1313                      otk::Property::atoms.wm_state, state, 2);
1314
1315   Atom netstate[10];
1316   int num = 0;
1317   if (_modal)
1318     netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1319   if (_shaded)
1320     netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1321   if (_iconic)
1322     netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1323   if (_skip_taskbar)
1324     netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1325   if (_skip_pager)
1326     netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1327   if (_fullscreen)
1328     netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1329   if (_max_vert)
1330     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1331   if (_max_horz)
1332     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1333   if (_above)
1334     netstate[num++] = otk::Property::atoms.net_wm_state_above;
1335   if (_below)
1336     netstate[num++] = otk::Property::atoms.net_wm_state_below;
1337   otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1338                      otk::Property::atoms.atom, netstate, num);
1339
1340   calcLayer();
1341
1342   if (frame)
1343     frame->adjustState();
1344 }
1345
1346 void Client::changeAllowedActions(void)
1347 {
1348   Atom actions[9];
1349   int num = 0;
1350
1351   actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1352
1353   if (_functions & Func_Shade)
1354     actions[num++] = otk::Property::atoms.net_wm_action_shade;
1355   if (_functions & Func_Close)
1356     actions[num++] = otk::Property::atoms.net_wm_action_close;
1357   if (_functions & Func_Move)
1358     actions[num++] = otk::Property::atoms.net_wm_action_move;
1359   if (_functions & Func_Iconify)
1360     actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1361   if (_functions & Func_Resize)
1362     actions[num++] = otk::Property::atoms.net_wm_action_resize;
1363   if (_functions & Func_Fullscreen)
1364     actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1365   if (_functions & Func_Maximize) {
1366     actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1367     actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1368   }
1369
1370   otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1371                      otk::Property::atoms.atom, actions, num);
1372
1373   // make sure the window isn't breaking any rules now
1374   
1375   if (!(_functions & Func_Shade) && _shaded)
1376     if (frame) shade(false);
1377     else _shaded = false;
1378   if (!(_functions & Func_Iconify) && _iconic)
1379     if (frame) setDesktop(openbox->screen(_screen)->desktop());
1380     else _iconic = false;
1381   if (!(_functions & Func_Fullscreen) && _fullscreen)
1382     if (frame) fullscreen(false);
1383     else _fullscreen = false;
1384   if (!(_functions & Func_Maximize) && (_max_horz || _max_vert))
1385     if (frame) maximize(false, 0);
1386     else _max_vert = _max_horz = false;
1387 }
1388
1389 void Client::remaximize()
1390 {
1391   int dir;
1392   if (_max_horz && _max_vert)
1393     dir = 0;
1394   else if (_max_horz)
1395     dir = 1;
1396   else if (_max_vert)
1397     dir = 2;
1398   else
1399     return; // not maximized
1400   _max_horz = _max_vert = false;
1401   maximize(true, dir, false);
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 void Client::fireUrgent()
1442 {
1443   // call the python UrgentWindow callbacks
1444   EventData data(_screen, this, EventAction::UrgentWindow, 0);
1445   openbox->bindings()->fireEvent(&data);
1446 }
1447
1448 void Client::shade(bool shade)
1449 {
1450   if (!(_functions & Func_Shade) || // can't
1451       _shaded == shade) return;     // already done
1452
1453   // when we're iconic, don't change the wmstate
1454   if (!_iconic)
1455     _wmstate = shade ? IconicState : NormalState;
1456   _shaded = shade;
1457   changeState();
1458   frame->adjustSize();
1459 }
1460
1461 void Client::maximize(bool max, int dir, bool savearea)
1462 {
1463   assert(dir == 0 || dir == 1 || dir == 2);
1464   if (!(_functions & Func_Maximize)) return; // can't
1465
1466   // check if already done
1467   if (max) {
1468     if (dir == 0 && _max_horz && _max_vert) return;
1469     if (dir == 1 && _max_horz) return;
1470     if (dir == 2 && _max_vert) return;
1471   } else {
1472     if (dir == 0 && !_max_horz && !_max_vert) return;
1473     if (dir == 1 && !_max_horz) return;
1474     if (dir == 2 && !_max_vert) return;
1475   }
1476
1477   const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1478   int x = frame->area().x(), y = frame->area().y(),
1479     w = _area.width(), h = _area.height();
1480   
1481   if (max) {
1482     if (savearea) {
1483       long dimensions[4];
1484       long *readdim;
1485       unsigned long n = 4;
1486
1487       dimensions[0] = x;
1488       dimensions[1] = y;
1489       dimensions[2] = w;
1490       dimensions[3] = h;
1491
1492       // get the property off the window and use it for the dimentions we are
1493       // already maxed on
1494       if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1495                              otk::Property::atoms.cardinal, &n,
1496                              (long unsigned**) &readdim)) {
1497         if (n >= 4) {
1498           if (_max_horz) {
1499             dimensions[0] = readdim[0];
1500             dimensions[2] = readdim[2];
1501           }
1502           if (_max_vert) {
1503             dimensions[1] = readdim[1];
1504             dimensions[3] = readdim[3];
1505           }
1506         }
1507         delete readdim;
1508       }
1509       
1510       otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1511                          otk::Property::atoms.cardinal,
1512                          (long unsigned*)dimensions, 4);
1513     }
1514     if (dir == 0 || dir == 1) { // horz
1515       x = a.x();
1516       w = a.width();
1517     }
1518     if (dir == 0 || dir == 2) { // vert
1519       y = a.y();
1520       h = a.height() - frame->size().top - frame->size().bottom;
1521     }
1522   } else {
1523     long *dimensions;
1524     long unsigned n = 4;
1525       
1526     if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1527                            otk::Property::atoms.cardinal, &n,
1528                            (long unsigned**) &dimensions)) {
1529       if (n >= 4) {
1530         if (dir == 0 || dir == 1) { // horz
1531           x = (signed int)dimensions[0];
1532           w = (signed int)dimensions[2];
1533         }
1534         if (dir == 0 || dir == 2) { // vert
1535           y = (signed int)dimensions[1];
1536           h = (signed int)dimensions[3];
1537         }
1538       }
1539       delete dimensions;
1540     } else {
1541       // pick some fallbacks...
1542       if (dir == 0 || dir == 1) { // horz
1543         x = a.x() + a.width() / 4;
1544         w = a.width() / 2;
1545       }
1546       if (dir == 0 || dir == 2) { // vert
1547         y = a.y() + a.height() / 4;
1548         h = a.height() / 2;
1549       }
1550     }
1551   }
1552
1553   if (dir == 0 || dir == 1) // horz
1554     _max_horz = max;
1555   if (dir == 0 || dir == 2) // vert
1556     _max_vert = max;
1557
1558   if (!_max_horz && !_max_vert)
1559     otk::Property::erase(_window, otk::Property::atoms.openbox_premax);
1560
1561   changeState(); // change the state hints on the client
1562
1563   frame->frameGravity(x, y); // figure out where the client should be going
1564   internal_resize(TopLeft, w, h, true, x, y);
1565 }
1566
1567 void Client::fullscreen(bool fs, bool savearea)
1568 {
1569   static FunctionFlags saved_func;
1570   static DecorationFlags saved_decor;
1571
1572   if (!(_functions & Func_Fullscreen) || // can't
1573       _fullscreen == fs) return;         // already done
1574
1575   _fullscreen = fs;
1576   changeState(); // change the state hints on the client
1577
1578   int x = _area.x(), y = _area.y(), w = _area.width(), h = _area.height();
1579   
1580   if (fs) {
1581     // save the functions and remove them
1582     saved_func = _functions;
1583     _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1584     // save the decorations and remove them
1585     saved_decor = _decorations;
1586     _decorations = 0;
1587     if (savearea) {
1588       long dimensions[4];
1589       dimensions[0] = _area.x();
1590       dimensions[1] = _area.y();
1591       dimensions[2] = _area.width();
1592       dimensions[3] = _area.height();
1593       otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1594                          otk::Property::atoms.cardinal,
1595                          (long unsigned*)dimensions, 4);
1596     }
1597     const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1598     x = 0;
1599     y = 0;
1600     w = info->size().width();
1601     h = info->size().height();
1602   } else {
1603     _functions = saved_func;
1604     _decorations = saved_decor;
1605
1606     long *dimensions;
1607     long unsigned n = 4;
1608       
1609     if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1610                            otk::Property::atoms.cardinal, &n,
1611                            (long unsigned**) &dimensions)) {
1612       if (n >= 4) {
1613         x = dimensions[0];
1614         y = dimensions[1];
1615         w = dimensions[2];
1616         h = dimensions[3];
1617       }
1618       delete dimensions;
1619     } else {
1620       // pick some fallbacks...
1621       const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1622       x = a.x() + a.width() / 4;
1623       y = a.y() + a.height() / 4;
1624       w = a.width() / 2;
1625         h = a.height() / 2;
1626     }    
1627   }
1628   
1629   changeAllowedActions();  // based on the new _functions
1630
1631   // when fullscreening, don't obey things like increments, fill the screen
1632   internal_resize(TopLeft, w, h, !fs, x, y);
1633
1634   // raise (back) into our stacking layer
1635   openbox->screen(_screen)->raiseWindow(this);
1636
1637   // try focus us when we go into fullscreen mode
1638   if (fs) focus();
1639 }
1640
1641 void Client::iconify(bool iconic, bool curdesk)
1642 {
1643   if (_iconic == iconic) return; // nothing to do
1644
1645 #ifdef DEBUG
1646     printf("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"), _window);
1647 #endif
1648
1649   _iconic = iconic;
1650
1651   if (_iconic) {
1652     _wmstate = IconicState;
1653     ignore_unmaps++;
1654     // we unmap the client itself so that we can get MapRequest events, and
1655     // because the ICCCM tells us to!
1656     XUnmapWindow(**otk::display, _window);
1657   } else {
1658     if (curdesk)
1659       setDesktop(openbox->screen(_screen)->desktop());
1660     _wmstate = NormalState;
1661     XMapWindow(**otk::display, _window);
1662   }
1663   changeState();
1664   showhide();
1665   openbox->screen(_screen)->updateStruts();
1666 }
1667
1668 void Client::disableDecorations(DecorationFlags flags)
1669 {
1670   _disabled_decorations = flags;
1671   setupDecorAndFunctions();
1672 }
1673
1674 void Client::installColormap(bool install) const
1675 {
1676   XWindowAttributes wa;
1677   if (XGetWindowAttributes(**otk::display, _window, &wa)) {
1678     if (install)
1679       XInstallColormap(**otk::display, wa.colormap);
1680     else
1681       XUninstallColormap(**otk::display, wa.colormap);
1682   }
1683 }
1684
1685 Client *Client::searchModalTree(Client *node, Client *skip)
1686 {
1687   List::const_iterator it, end = node->_transients.end();
1688   Client *ret;
1689   
1690   for (it = node->_transients.begin(); it != end; ++it) {
1691     if (*it == skip) continue; // circular?
1692     if ((ret = searchModalTree(*it, skip))) return ret; // got one
1693     if ((*it)->_modal) return *it; // got one
1694   }
1695   return 0;
1696 }
1697
1698 Client *Client::findModalChild()
1699 {
1700   return searchModalTree(this, this);
1701 }
1702
1703
1704 bool Client::focus()
1705 {
1706   // if we have a modal child, then focus it, not us
1707   Client *c = findModalChild();
1708   if (c) return c->focus();
1709
1710   // won't try focus if the client doesn't want it, or if the window isn't
1711   // visible on the screen
1712   if (!(frame->visible() && (_can_focus || _focus_notify))) return false;
1713
1714   if (_focused) return true;
1715
1716   // do a check to see if the window has already been unmapped or destroyed
1717   // do this intelligently while watching out for unmaps we've generated
1718   // (ignore_unmaps > 0)
1719   XEvent ev;
1720   if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1721     XPutBackEvent(**otk::display, &ev);
1722     return false;
1723   }
1724   while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1725     if (ignore_unmaps) {
1726       unmapHandler(ev.xunmap);
1727     } else {
1728       XPutBackEvent(**otk::display, &ev);
1729       return false;
1730     }
1731   }
1732
1733   if (_can_focus)
1734     XSetInputFocus(**otk::display, _window,
1735                    RevertToNone, CurrentTime);
1736
1737   if (_focus_notify) {
1738     XEvent ce;
1739     ce.xclient.type = ClientMessage;
1740     ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1741     ce.xclient.display = **otk::display;
1742     ce.xclient.window = _window;
1743     ce.xclient.format = 32;
1744     ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1745     ce.xclient.data.l[1] = openbox->lastTime();
1746     ce.xclient.data.l[2] = 0l;
1747     ce.xclient.data.l[3] = 0l;
1748     ce.xclient.data.l[4] = 0l;
1749     XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1750   }
1751
1752   XSync(**otk::display, False);
1753   return true;
1754 }
1755
1756
1757 void Client::unfocus() const
1758 {
1759   if (!_focused) return;
1760
1761   assert(openbox->focusedClient() == this);
1762   openbox->setFocusedClient(0);
1763 }
1764
1765
1766 void Client::focusHandler(const XFocusChangeEvent &e)
1767 {
1768 #ifdef    DEBUG
1769 //  printf("FocusIn for 0x%lx\n", e.window);
1770 #endif // DEBUG
1771   
1772   otk::EventHandler::focusHandler(e);
1773
1774   _focused = true;
1775   frame->adjustFocus();
1776
1777   calcLayer(); // focus state can affect the stacking layer
1778
1779   openbox->setFocusedClient(this);
1780 }
1781
1782
1783 void Client::unfocusHandler(const XFocusChangeEvent &e)
1784 {
1785 #ifdef    DEBUG
1786 //  printf("FocusOut for 0x%lx\n", e.window);
1787 #endif // DEBUG
1788   
1789   otk::EventHandler::unfocusHandler(e);
1790
1791   _focused = false;
1792   frame->adjustFocus();
1793
1794   calcLayer(); // focus state can affect the stacking layer
1795
1796   if (openbox->focusedClient() == this)
1797     openbox->setFocusedClient(0);
1798 }
1799
1800
1801 void Client::configureRequestHandler(const XConfigureRequestEvent &ec)
1802 {
1803 #ifdef    DEBUG
1804   printf("ConfigureRequest for 0x%lx\n", ec.window);
1805 #endif // DEBUG
1806   
1807   otk::EventHandler::configureRequestHandler(ec);
1808
1809   // compress these
1810   XConfigureRequestEvent e = ec;
1811   XEvent ev;
1812   while (XCheckTypedWindowEvent(**otk::display, window(), ConfigureRequest,
1813                                 &ev)) {
1814     // XXX if this causes bad things.. we can compress config req's with the
1815     //     same mask.
1816     e.value_mask |= ev.xconfigurerequest.value_mask;
1817     if (ev.xconfigurerequest.value_mask & CWX)
1818       e.x = ev.xconfigurerequest.x;
1819     if (ev.xconfigurerequest.value_mask & CWY)
1820       e.y = ev.xconfigurerequest.y;
1821     if (ev.xconfigurerequest.value_mask & CWWidth)
1822       e.width = ev.xconfigurerequest.width;
1823     if (ev.xconfigurerequest.value_mask & CWHeight)
1824       e.height = ev.xconfigurerequest.height;
1825     if (ev.xconfigurerequest.value_mask & CWBorderWidth)
1826       e.border_width = ev.xconfigurerequest.border_width;
1827     if (ev.xconfigurerequest.value_mask & CWStackMode)
1828       e.detail = ev.xconfigurerequest.detail;
1829   }
1830
1831   // if we are iconic (or shaded (fvwm does this)) ignore the event
1832   if (_iconic || _shaded) return;
1833
1834   if (e.value_mask & CWBorderWidth)
1835     _border_width = e.border_width;
1836
1837   // resize, then move, as specified in the EWMH section 7.7
1838   if (e.value_mask & (CWWidth | CWHeight)) {
1839     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1840     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1841
1842     Corner corner;
1843     switch (_gravity) {
1844     case NorthEastGravity:
1845     case EastGravity:
1846       corner = TopRight;
1847       break;
1848     case SouthWestGravity:
1849     case SouthGravity:
1850       corner = BottomLeft;
1851       break;
1852     case SouthEastGravity:
1853       corner = BottomRight;
1854       break;
1855     default:     // NorthWest, Static, etc
1856       corner = TopLeft;
1857     }
1858
1859     // if moving AND resizing ...
1860     if (e.value_mask & (CWX | CWY)) {
1861       int x = (e.value_mask & CWX) ? e.x : _area.x();
1862       int y = (e.value_mask & CWY) ? e.y : _area.y();
1863       internal_resize(corner, w, h, false, x, y);
1864     } else // if JUST resizing...
1865       internal_resize(corner, w, h, false);
1866   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1867     int x = (e.value_mask & CWX) ? e.x : _area.x();
1868     int y = (e.value_mask & CWY) ? e.y : _area.y();
1869     internal_move(x, y);
1870   }
1871
1872   if (e.value_mask & CWStackMode) {
1873     switch (e.detail) {
1874     case Below:
1875     case BottomIf:
1876       openbox->screen(_screen)->lowerWindow(this);
1877       break;
1878
1879     case Above:
1880     case TopIf:
1881     default:
1882       openbox->screen(_screen)->raiseWindow(this);
1883       break;
1884     }
1885   }
1886 }
1887
1888
1889 void Client::unmapHandler(const XUnmapEvent &e)
1890 {
1891   if (ignore_unmaps) {
1892 #ifdef    DEBUG
1893 //  printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1894 #endif // DEBUG
1895     ignore_unmaps--;
1896     return;
1897   }
1898   
1899 #ifdef    DEBUG
1900   printf("UnmapNotify for 0x%lx\n", e.window);
1901 #endif // DEBUG
1902
1903   otk::EventHandler::unmapHandler(e);
1904
1905   // this deletes us etc
1906   openbox->screen(_screen)->unmanageWindow(this);
1907 }
1908
1909
1910 void Client::destroyHandler(const XDestroyWindowEvent &e)
1911 {
1912 #ifdef    DEBUG
1913   printf("DestroyNotify for 0x%lx\n", e.window);
1914 #endif // DEBUG
1915
1916   otk::EventHandler::destroyHandler(e);
1917
1918   // this deletes us etc
1919   openbox->screen(_screen)->unmanageWindow(this);
1920 }
1921
1922
1923 void Client::reparentHandler(const XReparentEvent &e)
1924 {
1925   // this is when the client is first taken captive in the frame
1926   if (e.parent == frame->plate()) return;
1927
1928 #ifdef    DEBUG
1929   printf("ReparentNotify for 0x%lx\n", e.window);
1930 #endif // DEBUG
1931
1932   otk::EventHandler::reparentHandler(e);
1933
1934   /*
1935     This event is quite rare and is usually handled in unmapHandler.
1936     However, if the window is unmapped when the reparent event occurs,
1937     the window manager never sees it because an unmap event is not sent
1938     to an already unmapped window.
1939   */
1940
1941   // we don't want the reparent event, put it back on the stack for the X
1942   // server to deal with after we unmanage the window
1943   XEvent ev;
1944   ev.xreparent = e;
1945   XPutBackEvent(**otk::display, &ev);
1946   
1947   // this deletes us etc
1948   openbox->screen(_screen)->unmanageWindow(this);
1949 }
1950
1951 void Client::mapRequestHandler(const XMapRequestEvent &e)
1952 {
1953 #ifdef    DEBUG
1954   printf("MapRequest for already managed 0x%lx\n", e.window);
1955 #endif // DEBUG
1956
1957   assert(_iconic); // we shouldn't be able to get this unless we're iconic
1958
1959   // move to the current desktop (uniconify)
1960   iconify(false);
1961   // XXX: should we focus/raise the window? (basically a net_wm_active_window)
1962 }
1963
1964 }