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