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