rm debug prints
[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_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     // XXX: deiconify
1096     focus();
1097     openbox->screen(_screen)->raiseWindow(this);
1098   }
1099 }
1100
1101
1102 #if defined(SHAPE)
1103 void Client::shapeHandler(const XShapeEvent &e)
1104 {
1105   otk::EventHandler::shapeHandler(e);
1106
1107   if (e.kind == ShapeBounding) {
1108     _shaped = e.shaped;
1109     frame->adjustShape();
1110   }
1111 }
1112 #endif
1113
1114
1115 void Client::resize(Corner anchor, int w, int h)
1116 {
1117   if (!(_functions & Func_Resize)) return;
1118   internal_resize(anchor, w, h);
1119 }
1120
1121
1122 void Client::internal_resize(Corner anchor, int w, int h, bool user,
1123                              int x, int y)
1124 {
1125   w -= _base_size.x(); 
1126   h -= _base_size.y();
1127
1128   if (user) {
1129     // for interactive resizing. have to move half an increment in each
1130     // direction.
1131     int mw = w % _size_inc.x(); // how far we are towards the next size inc
1132     int mh = h % _size_inc.y();
1133     int aw = _size_inc.x() / 2; // amount to add
1134     int ah = _size_inc.y() / 2;
1135     // don't let us move into a new size increment
1136     if (mw + aw >= _size_inc.x()) aw = _size_inc.x() - mw - 1;
1137     if (mh + ah >= _size_inc.y()) ah = _size_inc.y() - mh - 1;
1138     w += aw;
1139     h += ah;
1140     
1141     // if this is a user-requested resize, then check against min/max sizes
1142     // and aspect ratios
1143
1144     // smaller than min size or bigger than max size?
1145     if (w < _min_size.x()) w = _min_size.x();
1146     else if (w > _max_size.x()) w = _max_size.x();
1147     if (h < _min_size.y()) h = _min_size.y();
1148     else if (h > _max_size.y()) h = _max_size.y();
1149
1150     // adjust the height ot match the width for the aspect ratios
1151     if (_min_ratio)
1152       if (h * _min_ratio > w) h = static_cast<int>(w / _min_ratio);
1153     if (_max_ratio)
1154       if (h * _max_ratio < w) h = static_cast<int>(w / _max_ratio);
1155   }
1156
1157   // keep to the increments
1158   w /= _size_inc.x();
1159   h /= _size_inc.y();
1160
1161   // you cannot resize to nothing
1162   if (w < 1) w = 1;
1163   if (h < 1) h = 1;
1164   
1165   // store the logical size
1166   _logical_size.setPoint(w, h);
1167
1168   w *= _size_inc.x();
1169   h *= _size_inc.y();
1170
1171   w += _base_size.x();
1172   h += _base_size.y();
1173
1174   if (x == INT_MIN || y == INT_MIN) {
1175     x = _area.x();
1176     y = _area.y();
1177     switch (anchor) {
1178     case TopLeft:
1179       break;
1180     case TopRight:
1181       x -= w - _area.width();
1182       break;
1183     case BottomLeft:
1184       y -= h - _area.height();
1185       break;
1186     case BottomRight:
1187       x -= w - _area.width();
1188       y -= h - _area.height();
1189       break;
1190     }
1191   }
1192
1193   _area.setSize(w, h);
1194
1195   XResizeWindow(**otk::display, _window, w, h);
1196
1197   // resize the frame to match the request
1198   frame->adjustSize();
1199   internal_move(x, y);
1200 }
1201
1202
1203 void Client::move(int x, int y)
1204 {
1205   if (!(_functions & Func_Move)) return;
1206   frame->frameGravity(x, y); // get the client's position based on x,y for the
1207                              // frame
1208   internal_move(x, y);
1209 }
1210
1211
1212 void Client::internal_move(int x, int y)
1213 {
1214   _area.setPos(x, y);
1215
1216   // move the frame to be in the requested position
1217   if (frame) { // this can be called while mapping, before frame exists
1218     frame->adjustPosition();
1219
1220     // send synthetic configure notify (we don't need to if we aren't mapped
1221     // yet)
1222     XEvent event;
1223     event.type = ConfigureNotify;
1224     event.xconfigure.display = **otk::display;
1225     event.xconfigure.event = _window;
1226     event.xconfigure.window = _window;
1227     
1228     // root window coords with border in mind
1229     event.xconfigure.x = x - _border_width + frame->size().left;
1230     event.xconfigure.y = y - _border_width + frame->size().top;
1231     
1232     event.xconfigure.width = _area.width();
1233     event.xconfigure.height = _area.height();
1234     event.xconfigure.border_width = _border_width;
1235     event.xconfigure.above = frame->plate();
1236     event.xconfigure.override_redirect = False;
1237     XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1238                StructureNotifyMask, &event);
1239 #if 0//def DEBUG
1240     printf("Sent synthetic ConfigureNotify %d,%d %d,%d to 0x%lx\n",
1241            event.xconfigure.x, event.xconfigure.y, event.xconfigure.width,
1242            event.xconfigure.height, event.xconfigure.window);
1243 #endif
1244   }
1245 }
1246
1247
1248 void Client::close()
1249 {
1250   XEvent ce;
1251
1252   if (!(_functions & Func_Close)) return;
1253
1254   // XXX: itd be cool to do timeouts and shit here for killing the client's
1255   //      process off
1256   // like... if the window is around after 5 seconds, then the close button
1257   // turns a nice red, and if this function is called again, the client is
1258   // explicitly killed.
1259
1260   ce.xclient.type = ClientMessage;
1261   ce.xclient.message_type =  otk::Property::atoms.wm_protocols;
1262   ce.xclient.display = **otk::display;
1263   ce.xclient.window = _window;
1264   ce.xclient.format = 32;
1265   ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1266   ce.xclient.data.l[1] = CurrentTime;
1267   ce.xclient.data.l[2] = 0l;
1268   ce.xclient.data.l[3] = 0l;
1269   ce.xclient.data.l[4] = 0l;
1270   XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1271 }
1272
1273
1274 void Client::changeState()
1275 {
1276   unsigned long state[2];
1277   state[0] = _wmstate;
1278   state[1] = None;
1279   otk::Property::set(_window, otk::Property::atoms.wm_state,
1280                      otk::Property::atoms.wm_state, state, 2);
1281
1282   Atom netstate[10];
1283   int num = 0;
1284   if (_modal)
1285     netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1286   if (_shaded)
1287     netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1288   if (_iconic)
1289     netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1290   if (_skip_taskbar)
1291     netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1292   if (_skip_pager)
1293     netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1294   if (_fullscreen)
1295     netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1296   if (_max_vert)
1297     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1298   if (_max_horz)
1299     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1300   if (_above)
1301     netstate[num++] = otk::Property::atoms.net_wm_state_above;
1302   if (_below)
1303     netstate[num++] = otk::Property::atoms.net_wm_state_below;
1304   otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1305                      otk::Property::atoms.atom, netstate, num);
1306
1307   calcLayer();
1308
1309   if (frame)
1310     frame->adjustState();
1311 }
1312
1313
1314 void Client::changeAllowedActions(void)
1315 {
1316   Atom actions[9];
1317   int num = 0;
1318
1319   actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1320
1321   if (_functions & Func_Shade)
1322     actions[num++] = otk::Property::atoms.net_wm_action_shade;
1323   if (_functions & Func_Close)
1324     actions[num++] = otk::Property::atoms.net_wm_action_close;
1325   if (_functions & Func_Move)
1326     actions[num++] = otk::Property::atoms.net_wm_action_move;
1327   if (_functions & Func_Iconify)
1328     actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1329   if (_functions & Func_Resize)
1330     actions[num++] = otk::Property::atoms.net_wm_action_resize;
1331   if (_functions & Func_Fullscreen)
1332     actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1333   if (_functions & Func_Maximize) {
1334     actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1335     actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1336   }
1337
1338   otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1339                      otk::Property::atoms.atom, actions, num);
1340 }
1341
1342
1343 void Client::remaximize()
1344 {
1345   int dir;
1346   if (_max_horz && _max_vert)
1347     dir = 0;
1348   else if (_max_horz)
1349     dir = 1;
1350   else if (_max_vert)
1351     dir = 2;
1352   else
1353     return; // not maximized
1354   _max_horz = _max_vert = false;
1355   maximize(true, dir, false);
1356 }
1357
1358
1359 void Client::applyStartupState()
1360 {
1361   // these are in a carefully crafted order..
1362
1363   if (_modal) {
1364     _modal = false;
1365     setModal(true);
1366   }
1367   
1368   if (_iconic) {
1369     _iconic = false;
1370     setDesktop(ICONIC_DESKTOP);
1371   }
1372   if (_fullscreen) {
1373     _fullscreen = false;
1374     fullscreen(true, false);
1375   }
1376   if (_shaded) {
1377     _shaded = false;
1378     shade(true);
1379   }
1380   if (_urgent)
1381     fireUrgent();
1382   
1383   if (_max_vert && _max_horz) {
1384     _max_vert = _max_horz = false;
1385     maximize(true, 0, false);
1386   } else if (_max_vert) {
1387     _max_vert = false;
1388     maximize(true, 2, false);
1389   } else if (_max_horz) {
1390     _max_horz = false;
1391     maximize(true, 1, false);
1392   }
1393
1394   if (_skip_taskbar); // nothing to do for this
1395   if (_skip_pager);   // nothing to do for this
1396   if (_modal);        // nothing to do for this
1397   if (_above);        // nothing to do for this
1398   if (_below);        // nothing to do for this
1399 }
1400
1401
1402 void Client::fireUrgent()
1403 {
1404   // call the python UrgentWindow callbacks
1405   EventData data(_screen, this, EventAction::UrgentWindow, 0);
1406   openbox->bindings()->fireEvent(&data);
1407 }
1408
1409
1410 void Client::shade(bool shade)
1411 {
1412   if (!(_functions & Func_Shade) || // can't
1413       _shaded == shade) return;     // already done
1414
1415   // when we're iconic, don't change the wmstate
1416   if (!_iconic)
1417     _wmstate = shade ? IconicState : NormalState;
1418   _shaded = shade;
1419   changeState();
1420   frame->adjustSize();
1421 }
1422
1423
1424 void Client::maximize(bool max, int dir, bool savearea)
1425 {
1426   assert(dir == 0 || dir == 1 || dir == 2);
1427   if (!(_functions & Func_Maximize)) return; // can't
1428
1429   // check if already done
1430   if (max) {
1431     if (dir == 0 && _max_horz && _max_vert) return;
1432     if (dir == 1 && _max_horz) return;
1433     if (dir == 2 && _max_vert) return;
1434   } else {
1435     if (dir == 0 && !_max_horz && !_max_vert) return;
1436     if (dir == 1 && !_max_horz) return;
1437     if (dir == 2 && !_max_vert) return;
1438   }
1439
1440   const otk::Rect &a = openbox->screen(_screen)->area();
1441   int x = frame->rect().x(), y = frame->rect().y(),
1442     w = _area.width(), h = _area.height();
1443   
1444   if (max) {
1445     if (savearea) {
1446       long dimensions[4];
1447       long *readdim;
1448       unsigned long n = 4;
1449
1450       dimensions[0] = x;
1451       dimensions[1] = y;
1452       dimensions[2] = w;
1453       dimensions[3] = h;
1454
1455       // get the property off the window and use it for the dimentions we are
1456       // already maxed on
1457       if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1458                              otk::Property::atoms.cardinal, &n,
1459                              (long unsigned**) &readdim)) {
1460         if (n >= 4) {
1461           if (_max_horz) {
1462             dimensions[0] = readdim[0];
1463             dimensions[2] = readdim[2];
1464           }
1465           if (_max_vert) {
1466             dimensions[1] = readdim[1];
1467             dimensions[3] = readdim[3];
1468           }
1469         }
1470         delete readdim;
1471       }
1472       
1473       otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1474                          otk::Property::atoms.cardinal,
1475                          (long unsigned*)dimensions, 4);
1476     }
1477     if (dir == 0 || dir == 1) { // horz
1478       x = a.x();
1479       w = a.width();
1480     }
1481     if (dir == 0 || dir == 2) { // vert
1482       y = a.y();
1483       h = a.height() - frame->size().top - frame->size().bottom;
1484     }
1485   } else {
1486     long *dimensions;
1487     long unsigned n = 4;
1488       
1489     if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1490                            otk::Property::atoms.cardinal, &n,
1491                            (long unsigned**) &dimensions)) {
1492       if (n >= 4) {
1493         if (dir == 0 || dir == 1) { // horz
1494           x = (signed int)dimensions[0];
1495           w = (signed int)dimensions[2];
1496         }
1497         if (dir == 0 || dir == 2) { // vert
1498           y = (signed int)dimensions[1];
1499           h = (signed int)dimensions[3];
1500         }
1501       }
1502       delete dimensions;
1503     } else {
1504       // pick some fallbacks...
1505       if (dir == 0 || dir == 1) { // horz
1506         x = a.x() + a.width() / 4;
1507         w = a.width() / 2;
1508       }
1509       if (dir == 0 || dir == 2) { // vert
1510         y = a.y() + a.height() / 4;
1511         h = a.height() / 2;
1512       }
1513     }
1514   }
1515
1516   if (dir == 0 || dir == 1) // horz
1517     _max_horz = max;
1518   if (dir == 0 || dir == 2) // vert
1519     _max_vert = max;
1520
1521   if (!_max_horz && !_max_vert)
1522     otk::Property::erase(_window, otk::Property::atoms.openbox_premax);
1523
1524   changeState(); // change the state hints on the client
1525
1526   frame->frameGravity(x, y); // figure out where the client should be going
1527   internal_resize(TopLeft, w, h, true, x, y);
1528 }
1529
1530
1531 void Client::fullscreen(bool fs, bool savearea)
1532 {
1533   static FunctionFlags saved_func;
1534   static DecorationFlags saved_decor;
1535
1536   if (!(_functions & Func_Fullscreen) || // can't
1537       _fullscreen == fs) return;         // already done
1538
1539   _fullscreen = fs;
1540   changeState(); // change the state hints on the client
1541
1542   int x = _area.x(), y = _area.y(), w = _area.width(), h = _area.height();
1543   
1544   if (fs) {
1545     // save the functions and remove them
1546     saved_func = _functions;
1547     _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1548     // save the decorations and remove them
1549     saved_decor = _decorations;
1550     _decorations = 0;
1551     if (savearea) {
1552       long dimensions[4];
1553       dimensions[0] = _area.x();
1554       dimensions[1] = _area.y();
1555       dimensions[2] = _area.width();
1556       dimensions[3] = _area.height();
1557       otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1558                          otk::Property::atoms.cardinal,
1559                          (long unsigned*)dimensions, 4);
1560     }
1561     const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1562     x = 0;
1563     y = 0;
1564     w = info->width();
1565     h = info->height();
1566   } else {
1567     _functions = saved_func;
1568     _decorations = saved_decor;
1569
1570     long *dimensions;
1571     long unsigned n = 4;
1572       
1573     if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1574                            otk::Property::atoms.cardinal, &n,
1575                            (long unsigned**) &dimensions)) {
1576       if (n >= 4) {
1577         x = dimensions[0];
1578         y = dimensions[1];
1579         w = dimensions[2];
1580         h = dimensions[3];
1581       }
1582       delete dimensions;
1583     } else {
1584       // pick some fallbacks...
1585       const otk::Rect &a = openbox->screen(_screen)->area();
1586       x = a.x() + a.width() / 4;
1587       y = a.y() + a.height() / 4;
1588       w = a.width() / 2;
1589         h = a.height() / 2;
1590     }    
1591   }
1592   
1593   changeAllowedActions();  // based on the new _functions
1594
1595   // when fullscreening, don't obey things like increments, fill the screen
1596   internal_resize(TopLeft, w, h, !fs, x, y);
1597
1598   // raise (back) into our stacking layer
1599   openbox->screen(_screen)->raiseWindow(this);
1600
1601   // try focus us when we go into fullscreen mode
1602   if (fs) focus();
1603 }
1604
1605
1606 void Client::disableDecorations(DecorationFlags flags)
1607 {
1608   _disabled_decorations = flags;
1609   setupDecorAndFunctions();
1610 }
1611
1612
1613 void Client::installColormap(bool install) const
1614 {
1615   XWindowAttributes wa;
1616   if (XGetWindowAttributes(**otk::display, _window, &wa)) {
1617     if (install)
1618       XInstallColormap(**otk::display, wa.colormap);
1619     else
1620       XUninstallColormap(**otk::display, wa.colormap);
1621   }
1622 }
1623
1624
1625 bool Client::focus()
1626 {
1627   // if we have a modal child, then focus it, not us
1628   if (_modal_child)
1629     return _modal_child->focus();
1630
1631   // won't try focus if the client doesn't want it, or if the window isn't
1632   // visible on the screen
1633   if (!(frame->isVisible() && (_can_focus || _focus_notify))) return false;
1634
1635   if (_focused) return true;
1636
1637   // do a check to see if the window has already been unmapped or destroyed
1638   // do this intelligently while watching out for unmaps we've generated
1639   // (ignore_unmaps > 0)
1640   XEvent ev;
1641   if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1642     XPutBackEvent(**otk::display, &ev);
1643     return false;
1644   }
1645   while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1646     if (ignore_unmaps) {
1647       unmapHandler(ev.xunmap);
1648     } else {
1649       XPutBackEvent(**otk::display, &ev);
1650       return false;
1651     }
1652   }
1653
1654   if (_can_focus)
1655     XSetInputFocus(**otk::display, _window,
1656                    RevertToNone, CurrentTime);
1657
1658   if (_focus_notify) {
1659     XEvent ce;
1660     ce.xclient.type = ClientMessage;
1661     ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1662     ce.xclient.display = **otk::display;
1663     ce.xclient.window = _window;
1664     ce.xclient.format = 32;
1665     ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1666     ce.xclient.data.l[1] = openbox->lastTime();
1667     ce.xclient.data.l[2] = 0l;
1668     ce.xclient.data.l[3] = 0l;
1669     ce.xclient.data.l[4] = 0l;
1670     XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1671   }
1672
1673   XSync(**otk::display, False);
1674   return true;
1675 }
1676
1677
1678 void Client::unfocus() const
1679 {
1680   if (!_focused) return;
1681
1682   assert(openbox->focusedClient() == this);
1683   openbox->setFocusedClient(0);
1684 }
1685
1686
1687 void Client::focusHandler(const XFocusChangeEvent &e)
1688 {
1689 #ifdef    DEBUG
1690 //  printf("FocusIn for 0x%lx\n", e.window);
1691 #endif // DEBUG
1692   
1693   otk::EventHandler::focusHandler(e);
1694
1695   frame->focus();
1696   _focused = true;
1697
1698   openbox->setFocusedClient(this);
1699 }
1700
1701
1702 void Client::unfocusHandler(const XFocusChangeEvent &e)
1703 {
1704 #ifdef    DEBUG
1705 //  printf("FocusOut for 0x%lx\n", e.window);
1706 #endif // DEBUG
1707   
1708   otk::EventHandler::unfocusHandler(e);
1709
1710   frame->unfocus();
1711   _focused = false;
1712
1713   if (openbox->focusedClient() == this)
1714     openbox->setFocusedClient(0);
1715 }
1716
1717
1718 void Client::configureRequestHandler(const XConfigureRequestEvent &e)
1719 {
1720 #ifdef    DEBUG
1721   printf("ConfigureRequest for 0x%lx\n", e.window);
1722 #endif // DEBUG
1723   
1724   otk::EventHandler::configureRequestHandler(e);
1725
1726   // if we are iconic (or shaded (fvwm does this)) ignore the event
1727   if (_iconic || _shaded) return;
1728
1729   if (e.value_mask & CWBorderWidth)
1730     _border_width = e.border_width;
1731
1732   // resize, then move, as specified in the EWMH section 7.7
1733   if (e.value_mask & (CWWidth | CWHeight)) {
1734     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1735     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1736
1737     Corner corner;
1738     switch (_gravity) {
1739     case NorthEastGravity:
1740     case EastGravity:
1741       corner = TopRight;
1742       break;
1743     case SouthWestGravity:
1744     case SouthGravity:
1745       corner = BottomLeft;
1746       break;
1747     case SouthEastGravity:
1748       corner = BottomRight;
1749       break;
1750     default:     // NorthWest, Static, etc
1751       corner = TopLeft;
1752     }
1753
1754     // if moving AND resizing ...
1755     if (e.value_mask & (CWX | CWY)) {
1756       int x = (e.value_mask & CWX) ? e.x : _area.x();
1757       int y = (e.value_mask & CWY) ? e.y : _area.y();
1758       internal_resize(corner, w, h, false, x, y);
1759     } else // if JUST resizing...
1760       internal_resize(corner, w, h, false);
1761   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1762     int x = (e.value_mask & CWX) ? e.x : _area.x();
1763     int y = (e.value_mask & CWY) ? e.y : _area.y();
1764     internal_move(x, y);
1765   }
1766
1767   if (e.value_mask & CWStackMode) {
1768     switch (e.detail) {
1769     case Below:
1770     case BottomIf:
1771       openbox->screen(_screen)->lowerWindow(this);
1772       break;
1773
1774     case Above:
1775     case TopIf:
1776     default:
1777       openbox->screen(_screen)->raiseWindow(this);
1778       break;
1779     }
1780   }
1781 }
1782
1783
1784 void Client::unmapHandler(const XUnmapEvent &e)
1785 {
1786   if (ignore_unmaps) {
1787 #ifdef    DEBUG
1788 //  printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1789 #endif // DEBUG
1790     ignore_unmaps--;
1791     return;
1792   }
1793   
1794 #ifdef    DEBUG
1795   printf("UnmapNotify for 0x%lx\n", e.window);
1796 #endif // DEBUG
1797
1798   otk::EventHandler::unmapHandler(e);
1799
1800   // this deletes us etc
1801   openbox->screen(_screen)->unmanageWindow(this);
1802 }
1803
1804
1805 void Client::destroyHandler(const XDestroyWindowEvent &e)
1806 {
1807 #ifdef    DEBUG
1808   printf("DestroyNotify for 0x%lx\n", e.window);
1809 #endif // DEBUG
1810
1811   otk::EventHandler::destroyHandler(e);
1812
1813   // this deletes us etc
1814   openbox->screen(_screen)->unmanageWindow(this);
1815 }
1816
1817
1818 void Client::reparentHandler(const XReparentEvent &e)
1819 {
1820   // this is when the client is first taken captive in the frame
1821   if (e.parent == frame->plate()) return;
1822
1823 #ifdef    DEBUG
1824   printf("ReparentNotify for 0x%lx\n", e.window);
1825 #endif // DEBUG
1826
1827   otk::EventHandler::reparentHandler(e);
1828
1829   /*
1830     This event is quite rare and is usually handled in unmapHandler.
1831     However, if the window is unmapped when the reparent event occurs,
1832     the window manager never sees it because an unmap event is not sent
1833     to an already unmapped window.
1834   */
1835
1836   // we don't want the reparent event, put it back on the stack for the X
1837   // server to deal with after we unmanage the window
1838   XEvent ev;
1839   ev.xreparent = e;
1840   XPutBackEvent(**otk::display, &ev);
1841   
1842   // this deletes us etc
1843   openbox->screen(_screen)->unmanageWindow(this);
1844 }
1845
1846 void Client::mapRequestHandler(const XMapRequestEvent &e)
1847 {
1848 #ifdef    DEBUG
1849   printf("MapRequest for already managed 0x%lx\n", e.window);
1850 #endif // DEBUG
1851
1852   assert(_iconic); // we shouldn't be able to get this unless we're iconic
1853
1854   // move to the current desktop (uniconify)
1855   setDesktop(openbox->screen(_screen)->desktop());
1856   // XXX: should we focus/raise the window? (basically a net_wm_active_window)
1857 }
1858
1859 }