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