]> icculus.org git repositories - mikachu/openbox.git/blob - src/client.cc
Client::move moves the client to the position so that its *frame* will be at the...
[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   getType();
62   getMwmHints();
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 //    XXX: make this work again
192 //    else if (val[i] == otk::Property::atoms.kde_net_wm_window_type_override)
193 //      mwm_decorations = 0; // prevent this window from getting any decor
194       if (_type != (WindowType) -1)
195         break; // grab the first known type
196     }
197     delete val;
198   }
199     
200   if (_type == (WindowType) -1) {
201     /*
202      * the window type hint was not set, which means we either classify ourself
203      * as a normal window or a dialog, depending on if we are a transient.
204      */
205     if (_transient_for)
206       _type = Type_Dialog;
207     else
208       _type = Type_Normal;
209   }
210 }
211
212
213 void Client::setupDecorAndFunctions()
214 {
215   // start with everything (cept fullscreen)
216   _decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
217     Decor_AllDesktops | Decor_Iconify | Decor_Maximize;
218   _functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize |
219     Func_Shade;
220   if (_delete_window) {
221     _decorations |= Decor_Close;
222     _functions |= Func_Close;
223   }
224
225   if (!(_min_size.x() < _max_size.x() || _min_size.y() < _max_size.y())) {
226     _decorations &= ~(Decor_Maximize | Decor_Handle);
227     _functions &= ~(Func_Resize | Func_Maximize);
228   }
229   
230   switch (_type) {
231   case Type_Normal:
232     // normal windows retain all of the possible decorations and
233     // functionality, and are the only windows that you can fullscreen
234     _functions |= Func_Fullscreen;
235     break;
236
237   case Type_Dialog:
238     // dialogs cannot be maximized
239     _decorations &= ~Decor_Maximize;
240     _functions &= ~Func_Maximize;
241     break;
242
243   case Type_Menu:
244   case Type_Toolbar:
245   case Type_Utility:
246     // these windows get less functionality
247     _decorations &= ~(Decor_Iconify | Decor_Handle);
248     _functions &= ~(Func_Iconify | Func_Resize);
249     break;
250
251   case Type_Desktop:
252   case Type_Dock:
253   case Type_Splash:
254     // none of these windows are manipulated by the window manager
255     _decorations = 0;
256     _functions = 0;
257     break;
258   }
259
260   // Mwm Hints are applied subtractively to what has already been chosen for
261   // decor and functionality
262   if (_mwmhints.flags & MwmFlag_Decorations) {
263     if (! (_mwmhints.decorations & MwmDecor_All)) {
264       if (! (_mwmhints.decorations & MwmDecor_Border))
265         _decorations &= ~Decor_Border;
266       if (! (_mwmhints.decorations & MwmDecor_Handle))
267         _decorations &= ~Decor_Handle;
268       if (! (_mwmhints.decorations & MwmDecor_Title)) {
269         _decorations &= ~Decor_Titlebar;
270         // if we don't have a titlebar, then we cannot shade!
271         _functions &= ~Func_Shade;
272       }
273       if (! (_mwmhints.decorations & MwmDecor_Iconify))
274         _decorations &= ~Decor_Iconify;
275       if (! (_mwmhints.decorations & MwmDecor_Maximize))
276         _decorations &= ~Decor_Maximize;
277     }
278   }
279
280   if (_mwmhints.flags & MwmFlag_Functions) {
281     if (! (_mwmhints.functions & MwmFunc_All)) {
282       if (! (_mwmhints.functions & MwmFunc_Resize))
283         _functions &= ~Func_Resize;
284       if (! (_mwmhints.functions & MwmFunc_Move))
285         _functions &= ~Func_Move;
286       if (! (_mwmhints.functions & MwmFunc_Iconify))
287         _functions &= ~Func_Iconify;
288       if (! (_mwmhints.functions & MwmFunc_Maximize))
289         _functions &= ~Func_Maximize;
290       // dont let mwm hints kill the close button
291       //if (! (_mwmhints.functions & MwmFunc_Close))
292       //  _functions &= ~Func_Close;
293     }
294   }
295
296   // finally, user specified disabled decorations are applied to subtract
297   // decorations
298   if (_disabled_decorations & Decor_Titlebar)
299     _decorations &= ~Decor_Titlebar;
300   if (_disabled_decorations & Decor_Handle)
301     _decorations &= ~Decor_Handle;
302   if (_disabled_decorations & Decor_Border)
303     _decorations &= ~Decor_Border;
304   if (_disabled_decorations & Decor_Iconify)
305     _decorations &= ~Decor_Iconify;
306   if (_disabled_decorations & Decor_Maximize)
307     _decorations &= ~Decor_Maximize;
308   if (_disabled_decorations & Decor_AllDesktops)
309     _decorations &= ~Decor_AllDesktops;
310   if (_disabled_decorations & Decor_Close)
311     _decorations &= ~Decor_Close;
312
313   // You can't shade without a titlebar
314   if (!(_decorations & Decor_Titlebar))
315     _functions &= ~Func_Shade;
316   
317   changeAllowedActions();
318
319   if (frame) {
320     frame->adjustSize(); // change the decors on the frame
321     frame->adjustPosition(); // with more/less decorations, we may need to be
322                              // moved
323   }
324 }
325
326
327 void Client::getMwmHints()
328 {
329   unsigned long num = MwmHints::elements;
330   unsigned long *hints;
331
332   _mwmhints.flags = 0; // default to none
333   
334   if (!otk::Property::get(_window, otk::Property::atoms.motif_wm_hints,
335                           otk::Property::atoms.motif_wm_hints, &num,
336                           (unsigned long **)&hints))
337     return;
338   
339   if (num >= MwmHints::elements) {
340     // retrieved the hints
341     _mwmhints.flags = hints[0];
342     _mwmhints.functions = hints[1];
343     _mwmhints.decorations = hints[2];
344   }
345
346   delete [] hints;
347 }
348
349
350 void Client::getArea()
351 {
352   XWindowAttributes wattrib;
353   Status ret;
354   
355   ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
356   assert(ret != BadWindow);
357
358   _area.setRect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
359   _border_width = wattrib.border_width;
360 }
361
362
363 void Client::getState()
364 {
365   _modal = _shaded = _max_horz = _max_vert = _fullscreen = _above = _below =
366     _iconic = _skip_taskbar = _skip_pager = false;
367   
368   unsigned long *state;
369   unsigned long num = (unsigned) -1;
370   
371   if (otk::Property::get(_window, otk::Property::atoms.net_wm_state,
372                          otk::Property::atoms.atom, &num, &state)) {
373     for (unsigned long i = 0; i < num; ++i) {
374       if (state[i] == otk::Property::atoms.net_wm_state_modal)
375         _modal = true;
376       else if (state[i] == otk::Property::atoms.net_wm_state_shaded)
377         _shaded = true;
378       else if (state[i] == otk::Property::atoms.net_wm_state_hidden)
379         _iconic = true;
380       else if (state[i] == otk::Property::atoms.net_wm_state_skip_taskbar)
381         _skip_taskbar = true;
382       else if (state[i] == otk::Property::atoms.net_wm_state_skip_pager)
383         _skip_pager = true;
384       else if (state[i] == otk::Property::atoms.net_wm_state_fullscreen)
385         _fullscreen = true;
386       else if (state[i] == otk::Property::atoms.net_wm_state_maximized_vert)
387         _max_vert = true;
388       else if (state[i] == otk::Property::atoms.net_wm_state_maximized_horz)
389         _max_horz = true;
390       else if (state[i] == otk::Property::atoms.net_wm_state_above)
391         _above = true;
392       else if (state[i] == otk::Property::atoms.net_wm_state_below)
393         _below = true;
394     }
395
396     delete [] state;
397   }
398 }
399
400
401 void Client::getShaped()
402 {
403   _shaped = false;
404 #ifdef   SHAPE
405   if (otk::display->shape()) {
406     int foo;
407     unsigned int ufoo;
408     int s;
409
410     XShapeSelectInput(**otk::display, _window, ShapeNotifyMask);
411
412     XShapeQueryExtents(**otk::display, _window, &s, &foo,
413                        &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo);
414     _shaped = (s != 0);
415   }
416 #endif // SHAPE
417 }
418
419
420 void Client::calcLayer() {
421   StackLayer l;
422
423   if (_iconic) l = Layer_Icon;
424   else if (_fullscreen) l = Layer_Fullscreen;
425   else if (_type == Type_Desktop) l = Layer_Desktop;
426   else if (_type == Type_Dock) {
427     if (!_below) l = Layer_Top;
428     else l = Layer_Normal;
429   }
430   else if (_above) l = Layer_Above;
431   else if (_below) l = Layer_Below;
432   else l = Layer_Normal;
433
434   if (l != _layer) {
435     _layer = l;
436     if (frame) {
437       /*
438         if we don't have a frame, then we aren't mapped yet (and this would
439         SIGSEGV :)
440       */
441       openbox->screen(_screen)->raiseWindow(this);
442     }
443   }
444 }
445
446
447 void Client::updateProtocols()
448 {
449   Atom *proto;
450   int num_return = 0;
451
452   _focus_notify = false;
453   _delete_window = false;
454
455   if (XGetWMProtocols(**otk::display, _window, &proto, &num_return)) {
456     for (int i = 0; i < num_return; ++i) {
457       if (proto[i] == otk::Property::atoms.wm_delete_window) {
458         // this means we can request the window to close
459         _delete_window = true;
460       } else if (proto[i] == otk::Property::atoms.wm_take_focus)
461         // if this protocol is requested, then the window will be notified
462         // by the window manager whenever it receives focus
463         _focus_notify = true;
464     }
465     XFree(proto);
466   }
467 }
468
469
470 void Client::updateNormalHints()
471 {
472   XSizeHints size;
473   long ret;
474   int oldgravity = _gravity;
475
476   // defaults
477   _min_ratio = 0.0;
478   _max_ratio = 0.0;
479   _size_inc.setPoint(1, 1);
480   _base_size.setPoint(0, 0);
481   _min_size.setPoint(0, 0);
482   _max_size.setPoint(INT_MAX, INT_MAX);
483
484   // XXX: might want to cancel any interactive resizing of the window at this
485   // point..
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     if (_transient_for)
678       _transient_for->_transients.remove(this); // remove from old parent
679     _transient_for = c;
680     if (_transient_for)
681       _transient_for->_transients.push_back(this); // add to new parent
682
683     // XXX: change decor status?
684   }
685 }
686
687
688 void Client::propertyHandler(const XPropertyEvent &e)
689 {
690   otk::EventHandler::propertyHandler(e);
691
692   // validate cuz we query stuff off the client here
693   if (!validate()) return;
694   
695   // compress changes to a single property into a single change
696   XEvent ce;
697   while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
698     // XXX: it would be nice to compress ALL changes to a property, not just
699     //      changes in a row without other props between.
700     if (ce.xproperty.atom != e.atom) {
701       XPutBackEvent(**otk::display, &ce);
702       break;
703     }
704   }
705
706   if (e.atom == XA_WM_NORMAL_HINTS) {
707     updateNormalHints();
708     setupDecorAndFunctions(); // normal hints can make a window non-resizable
709   } else if (e.atom == XA_WM_HINTS)
710     updateWMHints();
711   else if (e.atom == XA_WM_TRANSIENT_FOR) {
712     updateTransientFor();
713     getType();
714     calcLayer(); // type may have changed, so update the layer
715     setupDecorAndFunctions();
716   }
717   else if (e.atom == otk::Property::atoms.net_wm_name ||
718            e.atom == otk::Property::atoms.wm_name)
719     updateTitle();
720   else if (e.atom == otk::Property::atoms.net_wm_icon_name ||
721            e.atom == otk::Property::atoms.wm_icon_name)
722     updateIconTitle();
723   else if (e.atom == otk::Property::atoms.wm_class)
724     updateClass();
725   else if (e.atom == otk::Property::atoms.wm_protocols) {
726     updateProtocols();
727     setupDecorAndFunctions();
728   }
729   else if (e.atom == otk::Property::atoms.net_wm_strut)
730     updateStrut();
731 }
732
733
734 void Client::setWMState(long state)
735 {
736   if (state == _wmstate) return; // no change
737   
738   switch (state) {
739   case IconicState:
740     setDesktop(ICONIC_DESKTOP);
741     break;
742   case NormalState:
743     setDesktop(openbox->screen(_screen)->desktop());
744     break;
745   }
746 }
747
748
749 void Client::setDesktop(long target)
750 {
751   if (target == _desktop) return;
752   
753   printf("Setting desktop %ld\n", target);
754
755   if (!(target >= 0 || target == (signed)0xffffffff ||
756         target == ICONIC_DESKTOP))
757     return;
758   
759   _desktop = target;
760
761   // set the desktop hint, but not if we're iconifying
762   if (_desktop != ICONIC_DESKTOP)
763     otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
764                        otk::Property::atoms.cardinal, (unsigned)_desktop);
765   
766   // 'move' the window to the new desktop
767   if (_desktop == openbox->screen(_screen)->desktop() ||
768       _desktop == (signed)0xffffffff)
769     frame->show();
770   else
771     frame->hide();
772
773   // Handle Iconic state. Iconic state is maintained by the client being a
774   // member of the ICONIC_DESKTOP, so this is where we make iconifying and
775   // uniconifying happen.
776   bool i = _desktop == ICONIC_DESKTOP;
777   if (i != _iconic) { // has the state changed?
778     _iconic = i;
779     if (_iconic) {
780       _wmstate = IconicState;
781       ignore_unmaps++;
782       // we unmap the client itself so that we can get MapRequest events, and
783       // because the ICCCM tells us to!
784       XUnmapWindow(**otk::display, _window);
785     } else {
786       _wmstate = NormalState;
787       XMapWindow(**otk::display, _window);
788     }
789     changeState();
790   }
791   
792   frame->adjustState();
793 }
794
795
796 void Client::setState(StateAction action, long data1, long data2)
797 {
798   bool shadestate = _shaded;
799   bool fsstate = _fullscreen;
800   bool maxh = _max_horz;
801   bool maxv = _max_vert;
802
803   if (!(action == State_Add || action == State_Remove ||
804         action == State_Toggle))
805     return; // an invalid action was passed to the client message, ignore it
806
807   for (int i = 0; i < 2; ++i) {
808     Atom state = i == 0 ? data1 : data2;
809     
810     if (! state) continue;
811
812     // if toggling, then pick whether we're adding or removing
813     if (action == State_Toggle) {
814       if (state == otk::Property::atoms.net_wm_state_modal)
815         action = _modal ? State_Remove : State_Add;
816       else if (state == otk::Property::atoms.net_wm_state_maximized_vert)
817         action = _max_vert ? State_Remove : State_Add;
818       else if (state == otk::Property::atoms.net_wm_state_maximized_horz)
819         action = _max_horz ? State_Remove : State_Add;
820       else if (state == otk::Property::atoms.net_wm_state_shaded)
821         action = _shaded ? State_Remove : State_Add;
822       else if (state == otk::Property::atoms.net_wm_state_skip_taskbar)
823         action = _skip_taskbar ? State_Remove : State_Add;
824       else if (state == otk::Property::atoms.net_wm_state_skip_pager)
825         action = _skip_pager ? State_Remove : State_Add;
826       else if (state == otk::Property::atoms.net_wm_state_fullscreen)
827         action = _fullscreen ? State_Remove : State_Add;
828       else if (state == otk::Property::atoms.net_wm_state_above)
829         action = _above ? State_Remove : State_Add;
830       else if (state == otk::Property::atoms.net_wm_state_below)
831         action = _below ? State_Remove : State_Add;
832     }
833     
834     if (action == State_Add) {
835       if (state == otk::Property::atoms.net_wm_state_modal) {
836         if (_modal) continue;
837         _modal = true;
838         // XXX: give it focus if another window has focus that shouldnt now
839       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
840         maxv = true;
841       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
842         if (_max_horz) continue;
843         maxh = true;
844         // XXX: resize the window etc
845       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
846         shadestate = true;
847       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
848         _skip_taskbar = true;
849       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
850         _skip_pager = true;
851       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
852         fsstate = true;
853       } else if (state == otk::Property::atoms.net_wm_state_above) {
854         if (_above) continue;
855         _above = true;
856       } else if (state == otk::Property::atoms.net_wm_state_below) {
857         if (_below) continue;
858         _below = true;
859       }
860
861     } else { // action == State_Remove
862       if (state == otk::Property::atoms.net_wm_state_modal) {
863         if (!_modal) continue;
864         _modal = false;
865       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
866         maxv = false;
867       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
868         maxh = false;
869       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
870         shadestate = false;
871       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
872         _skip_taskbar = false;
873       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
874         _skip_pager = false;
875       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
876         fsstate = false;
877       } else if (state == otk::Property::atoms.net_wm_state_above) {
878         if (!_above) continue;
879         _above = false;
880       } else if (state == otk::Property::atoms.net_wm_state_below) {
881         if (!_below) continue;
882         _below = false;
883       }
884     }
885   }
886   if (maxh != _max_horz || maxv != _max_vert) {
887     if (maxh != _max_horz && maxv != _max_vert) { // toggling both
888       if (maxh == maxv) { // both going the same way
889         maximize(maxh, 0, true);
890       } else {
891         maximize(maxh, 1, true);
892         maximize(maxv, 2, true);
893       }
894     } else { // toggling one
895       if (maxh != _max_horz)
896         maximize(maxh, 1, true);
897       else
898         maximize(maxv, 2, true);
899     }
900   }
901   // change fullscreen state before shading, as it will affect if the window
902   // can shade or not
903   if (fsstate != _fullscreen)
904     fullscreen(fsstate, true);
905   if (shadestate != _shaded)
906     shade(shadestate);
907   calcLayer();
908   changeState(); // change the hint to relect these changes
909 }
910
911
912 void Client::toggleClientBorder(bool addborder)
913 {
914   // adjust our idea of where the client is, based on its border. When the
915   // border is removed, the client should now be considered to be in a
916   // different position.
917   // when re-adding the border to the client, the same operation needs to be
918   // reversed.
919   int oldx = _area.x(), oldy = _area.y();
920   int x = oldx, y = oldy;
921   switch(_gravity) {
922   default:
923   case NorthWestGravity:
924   case WestGravity:
925   case SouthWestGravity:
926     break;
927   case NorthEastGravity:
928   case EastGravity:
929   case SouthEastGravity:
930     if (addborder) x -= _border_width * 2;
931     else           x += _border_width * 2;
932     break;
933   case NorthGravity:
934   case SouthGravity:
935   case CenterGravity:
936   case ForgetGravity:
937   case StaticGravity:
938     if (addborder) x -= _border_width;
939     else           x += _border_width;
940     break;
941   }
942   switch(_gravity) {
943   default:
944   case NorthWestGravity:
945   case NorthGravity:
946   case NorthEastGravity:
947     break;
948   case SouthWestGravity:
949   case SouthGravity:
950   case SouthEastGravity:
951     if (addborder) y -= _border_width * 2;
952     else           y += _border_width * 2;
953     break;
954   case WestGravity:
955   case EastGravity:
956   case CenterGravity:
957   case ForgetGravity:
958   case StaticGravity:
959     if (addborder) y -= _border_width;
960     else           y += _border_width;
961     break;
962   }
963   _area.setPos(x, y);
964
965   if (addborder) {
966     XSetWindowBorderWidth(**otk::display, _window, _border_width);
967
968     // move the client so it is back it the right spot _with_ its border!
969     if (x != oldx || y != oldy)
970       XMoveWindow(**otk::display, _window, x, y);
971   } else
972     XSetWindowBorderWidth(**otk::display, _window, 0);
973 }
974
975
976 void Client::clientMessageHandler(const XClientMessageEvent &e)
977 {
978   otk::EventHandler::clientMessageHandler(e);
979   
980   // validate cuz we query stuff off the client here
981   if (!validate()) return;
982   
983   if (e.format != 32) return;
984
985   if (e.message_type == otk::Property::atoms.wm_change_state) {
986     // compress changes into a single change
987     bool compress = false;
988     XEvent ce;
989     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
990       // XXX: it would be nice to compress ALL messages of a type, not just
991       //      messages in a row without other message types between.
992       if (ce.xclient.message_type != e.message_type) {
993         XPutBackEvent(**otk::display, &ce);
994         break;
995       }
996       compress = true;
997     }
998     if (compress)
999       setWMState(ce.xclient.data.l[0]); // use the found event
1000     else
1001       setWMState(e.data.l[0]); // use the original event
1002   } else if (e.message_type == otk::Property::atoms.net_wm_desktop) {
1003     // compress changes into a single change 
1004     bool compress = false;
1005     XEvent ce;
1006     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
1007       // XXX: it would be nice to compress ALL messages of a type, not just
1008       //      messages in a row without other message types between.
1009       if (ce.xclient.message_type != e.message_type) {
1010         XPutBackEvent(**otk::display, &ce);
1011         break;
1012       }
1013       compress = true;
1014     }
1015     if (compress)
1016       setDesktop(e.data.l[0]); // use the found event
1017     else
1018       setDesktop(e.data.l[0]); // use the original event
1019   } else if (e.message_type == otk::Property::atoms.net_wm_state) {
1020     // can't compress these
1021 #ifdef DEBUG
1022     printf("net_wm_state %s %ld %ld for 0x%lx\n",
1023            (e.data.l[0] == 0 ? "Remove" : e.data.l[0] == 1 ? "Add" :
1024             e.data.l[0] == 2 ? "Toggle" : "INVALID"),
1025            e.data.l[1], e.data.l[2], _window);
1026 #endif
1027     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
1028   } else if (e.message_type == otk::Property::atoms.net_close_window) {
1029 #ifdef DEBUG
1030     printf("net_close_window for 0x%lx\n", _window);
1031 #endif
1032     close();
1033   } else if (e.message_type == otk::Property::atoms.net_active_window) {
1034 #ifdef DEBUG
1035     printf("net_active_window for 0x%lx\n", _window);
1036 #endif
1037     if (_iconic)
1038       setDesktop(openbox->screen(_screen)->desktop());
1039     if (_shaded)
1040       shade(false);
1041     // XXX: deiconify
1042     focus();
1043     openbox->screen(_screen)->raiseWindow(this);
1044   }
1045 }
1046
1047
1048 #if defined(SHAPE)
1049 void Client::shapeHandler(const XShapeEvent &e)
1050 {
1051   otk::EventHandler::shapeHandler(e);
1052
1053   if (e.kind == ShapeBounding) {
1054     _shaped = e.shaped;
1055     frame->adjustShape();
1056   }
1057 }
1058 #endif
1059
1060
1061 void Client::resize(Corner anchor, int w, int h)
1062 {
1063   if (!(_functions & Func_Resize)) return;
1064   internal_resize(anchor, w, h);
1065 }
1066
1067
1068 void Client::internal_resize(Corner anchor, int w, int h, bool user,
1069                              int x, int y)
1070 {
1071   w -= _base_size.x(); 
1072   h -= _base_size.y();
1073
1074   if (user) {
1075     // for interactive resizing. have to move half an increment in each
1076     // direction.
1077     int mw = w % _size_inc.x(); // how far we are towards the next size inc
1078     int mh = h % _size_inc.y();
1079     int aw = _size_inc.x() / 2; // amount to add
1080     int ah = _size_inc.y() / 2;
1081     // don't let us move into a new size increment
1082     if (mw + aw >= _size_inc.x()) aw = _size_inc.x() - mw - 1;
1083     if (mh + ah >= _size_inc.y()) ah = _size_inc.y() - mh - 1;
1084     w += aw;
1085     h += ah;
1086     
1087     // if this is a user-requested resize, then check against min/max sizes
1088     // and aspect ratios
1089
1090     // smaller than min size or bigger than max size?
1091     if (w < _min_size.x()) w = _min_size.x();
1092     else if (w > _max_size.x()) w = _max_size.x();
1093     if (h < _min_size.y()) h = _min_size.y();
1094     else if (h > _max_size.y()) h = _max_size.y();
1095
1096     // adjust the height ot match the width for the aspect ratios
1097     if (_min_ratio)
1098       if (h * _min_ratio > w) h = static_cast<int>(w / _min_ratio);
1099     if (_max_ratio)
1100       if (h * _max_ratio < w) h = static_cast<int>(w / _max_ratio);
1101   }
1102
1103   // keep to the increments
1104   w /= _size_inc.x();
1105   h /= _size_inc.y();
1106
1107   // you cannot resize to nothing
1108   if (w < 1) w = 1;
1109   if (h < 1) h = 1;
1110   
1111   // store the logical size
1112   _logical_size.setPoint(w, h);
1113
1114   w *= _size_inc.x();
1115   h *= _size_inc.y();
1116
1117   w += _base_size.x();
1118   h += _base_size.y();
1119
1120   if (x == INT_MIN || y == INT_MIN) {
1121     x = _area.x();
1122     y = _area.y();
1123     switch (anchor) {
1124     case TopLeft:
1125       break;
1126     case TopRight:
1127       x -= w - _area.width();
1128       break;
1129     case BottomLeft:
1130       y -= h - _area.height();
1131       break;
1132     case BottomRight:
1133       x -= w - _area.width();
1134       y -= h - _area.height();
1135       break;
1136     }
1137   }
1138
1139   _area.setSize(w, h);
1140
1141   XResizeWindow(**otk::display, _window, w, h);
1142
1143   // resize the frame to match the request
1144   frame->adjustSize();
1145   internal_move(x, y);
1146 }
1147
1148
1149 void Client::move(int x, int y)
1150 {
1151   if (!(_functions & Func_Move)) return;
1152   frame->frameGravity(x, y); // get the client's position based on x,y for the
1153                              // frame
1154   internal_move(x, y);
1155 }
1156
1157
1158 void Client::internal_move(int x, int y)
1159 {
1160   _area.setPos(x, y);
1161
1162   // move the frame to be in the requested position
1163   if (frame) { // this can be called while mapping, before frame exists
1164     frame->adjustPosition();
1165
1166     // send synthetic configure notify (we don't need to if we aren't mapped
1167     // yet)
1168     XEvent event;
1169     event.type = ConfigureNotify;
1170     event.xconfigure.display = **otk::display;
1171     event.xconfigure.event = _window;
1172     event.xconfigure.window = _window;
1173     
1174     // root window coords with border in mind
1175     event.xconfigure.x = x - _border_width + frame->size().left;
1176     event.xconfigure.y = y - _border_width + frame->size().top;
1177     
1178     event.xconfigure.width = _area.width();
1179     event.xconfigure.height = _area.height();
1180     event.xconfigure.border_width = _border_width;
1181     event.xconfigure.above = frame->plate();
1182     event.xconfigure.override_redirect = False;
1183     XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1184                StructureNotifyMask, &event);
1185 #if 0//def DEBUG
1186     printf("Sent synthetic ConfigureNotify %d,%d %d,%d to 0x%lx\n",
1187            event.xconfigure.x, event.xconfigure.y, event.xconfigure.width,
1188            event.xconfigure.height, event.xconfigure.window);
1189 #endif
1190   }
1191 }
1192
1193
1194 void Client::close()
1195 {
1196   XEvent ce;
1197
1198   if (!(_functions & Func_Close)) return;
1199
1200   // XXX: itd be cool to do timeouts and shit here for killing the client's
1201   //      process off
1202   // like... if the window is around after 5 seconds, then the close button
1203   // turns a nice red, and if this function is called again, the client is
1204   // explicitly killed.
1205
1206   ce.xclient.type = ClientMessage;
1207   ce.xclient.message_type =  otk::Property::atoms.wm_protocols;
1208   ce.xclient.display = **otk::display;
1209   ce.xclient.window = _window;
1210   ce.xclient.format = 32;
1211   ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1212   ce.xclient.data.l[1] = CurrentTime;
1213   ce.xclient.data.l[2] = 0l;
1214   ce.xclient.data.l[3] = 0l;
1215   ce.xclient.data.l[4] = 0l;
1216   XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1217 }
1218
1219
1220 void Client::changeState()
1221 {
1222   unsigned long state[2];
1223   state[0] = _wmstate;
1224   state[1] = None;
1225   otk::Property::set(_window, otk::Property::atoms.wm_state,
1226                      otk::Property::atoms.wm_state, state, 2);
1227
1228   Atom netstate[10];
1229   int num = 0;
1230   if (_modal)
1231     netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1232   if (_shaded)
1233     netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1234   if (_iconic)
1235     netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1236   if (_skip_taskbar)
1237     netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1238   if (_skip_pager)
1239     netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1240   if (_fullscreen)
1241     netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1242   if (_max_vert)
1243     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1244   if (_max_horz)
1245     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1246   if (_above)
1247     netstate[num++] = otk::Property::atoms.net_wm_state_above;
1248   if (_below)
1249     netstate[num++] = otk::Property::atoms.net_wm_state_below;
1250   otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1251                      otk::Property::atoms.atom, netstate, num);
1252
1253   calcLayer();
1254
1255   if (frame)
1256     frame->adjustState();
1257 }
1258
1259
1260 void Client::changeAllowedActions(void)
1261 {
1262   Atom actions[9];
1263   int num = 0;
1264
1265   actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1266
1267   if (_functions & Func_Shade)
1268     actions[num++] = otk::Property::atoms.net_wm_action_shade;
1269   if (_functions & Func_Close)
1270     actions[num++] = otk::Property::atoms.net_wm_action_close;
1271   if (_functions & Func_Move)
1272     actions[num++] = otk::Property::atoms.net_wm_action_move;
1273   if (_functions & Func_Iconify)
1274     actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1275   if (_functions & Func_Resize)
1276     actions[num++] = otk::Property::atoms.net_wm_action_resize;
1277   if (_functions & Func_Fullscreen)
1278     actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1279   if (_functions & Func_Maximize) {
1280     actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1281     actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1282   }
1283
1284   otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1285                      otk::Property::atoms.atom, actions, num);
1286 }
1287
1288
1289 void Client::remaximize()
1290 {
1291   int dir;
1292   if (_max_horz && _max_vert)
1293     dir = 0;
1294   else if (_max_horz)
1295     dir = 1;
1296   else if (_max_vert)
1297     dir = 2;
1298   else
1299     return; // not maximized
1300   _max_horz = _max_vert = false;
1301   maximize(true, dir, false);
1302 }
1303
1304
1305 void Client::applyStartupState()
1306 {
1307   // these are in a carefully crafted order..
1308
1309   if (_iconic) {
1310     _iconic = false;
1311     setDesktop(ICONIC_DESKTOP);
1312   }
1313   if (_fullscreen) {
1314     _fullscreen = false;
1315     fullscreen(true, false);
1316   }
1317   if (_shaded) {
1318     _shaded = false;
1319     shade(true);
1320   }
1321   if (_urgent)
1322     fireUrgent();
1323   
1324   if (_max_vert && _max_horz) {
1325     _max_vert = _max_horz = false;
1326     maximize(true, 0, false);
1327   } else if (_max_vert) {
1328     _max_vert = false;
1329     maximize(true, 2, false);
1330   } else if (_max_horz) {
1331     _max_horz = false;
1332     maximize(true, 1, false);
1333   }
1334
1335   if (_skip_taskbar); // nothing to do for this
1336   if (_skip_pager);   // nothing to do for this
1337   if (_modal);        // nothing to do for this
1338   if (_above);        // nothing to do for this
1339   if (_below);        // nothing to do for this
1340 }
1341
1342
1343 void Client::fireUrgent()
1344 {
1345   // call the python UrgentWindow callbacks
1346   EventData data(_screen, this, EventAction::UrgentWindow, 0);
1347   openbox->bindings()->fireEvent(&data);
1348 }
1349
1350
1351 void Client::shade(bool shade)
1352 {
1353   if (!(_functions & Func_Shade) || // can't
1354       _shaded == shade) return;     // already done
1355
1356   // when we're iconic, don't change the wmstate
1357   if (!_iconic)
1358     _wmstate = shade ? IconicState : NormalState;
1359   _shaded = shade;
1360   changeState();
1361   frame->adjustSize();
1362 }
1363
1364
1365 void Client::maximize(bool max, int dir, bool savearea)
1366 {
1367   assert(dir == 0 || dir == 1 || dir == 2);
1368   if (!(_functions & Func_Maximize)) return; // can't
1369
1370   // check if already done
1371   if (max) {
1372     if (dir == 0 && _max_horz && _max_vert) return;
1373     if (dir == 1 && _max_horz) return;
1374     if (dir == 2 && _max_vert) return;
1375   } else {
1376     if (dir == 0 && !_max_horz && !_max_vert) return;
1377     if (dir == 1 && !_max_horz) return;
1378     if (dir == 2 && !_max_vert) return;
1379   }
1380
1381   int g = _gravity;
1382   
1383   const otk::Rect &a = openbox->screen(_screen)->area();
1384   int x = _area.x(), y = _area.y(), w = _area.width(), h = _area.height();
1385   
1386   if (max) {
1387     // when maximizing, put the client where we want, NOT the frame!
1388     _gravity = StaticGravity;
1389     // adjust our idea of position based on StaticGravity, so we stay put
1390     // unless asked
1391     x = frame->rect().x();
1392     y = frame->rect().y();
1393     frame->frameGravity(x, y);
1394
1395     if (savearea) {
1396       long dimensions[4];
1397       long *readdim;
1398       unsigned long n = 4;
1399
1400       dimensions[0] = x;
1401       dimensions[1] = y;
1402       dimensions[2] = w;
1403       dimensions[3] = h;
1404
1405       // get the property off the window and use it for the dimentions we are
1406       // already maxed on
1407       if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1408                              otk::Property::atoms.cardinal, &n,
1409                              (long unsigned**) &readdim)) {
1410         if (n >= 4) {
1411           if (_max_horz) {
1412             dimensions[0] = readdim[0];
1413             dimensions[2] = readdim[2];
1414           }
1415           if (_max_vert) {
1416             dimensions[1] = readdim[1];
1417             dimensions[3] = readdim[3];
1418           }
1419         }
1420         delete readdim;
1421       }
1422       
1423       otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1424                          otk::Property::atoms.cardinal,
1425                          (long unsigned*)dimensions, 4);
1426     }
1427     if (dir == 0 || dir == 1) { // horz
1428       x = a.x();
1429       w = a.width();
1430     }
1431     if (dir == 0 || dir == 2) { // vert
1432       y = a.y() + frame->size().top;
1433       h = a.height() - frame->size().top - frame->size().bottom;
1434     }
1435   } else {
1436     long *dimensions;
1437     long unsigned n = 4;
1438       
1439     if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1440                            otk::Property::atoms.cardinal, &n,
1441                            (long unsigned**) &dimensions)) {
1442       if (n >= 4) {
1443         if (dir == 0 || dir == 1) { // horz
1444           x = (signed int)dimensions[0];
1445           w = (signed int)dimensions[2];
1446         }
1447         if (dir == 0 || dir == 2) { // vert
1448           y = (signed int)dimensions[1];
1449           h = (signed int)dimensions[3];
1450         }
1451       }
1452       delete dimensions;
1453     } else {
1454       // pick some fallbacks...
1455       if (dir == 0 || dir == 1) { // horz
1456         x = a.x() + a.width() / 4;
1457         w = a.width() / 2;
1458       }
1459       if (dir == 0 || dir == 2) { // vert
1460         y = a.y() + a.height() / 4;
1461         h = a.height() / 2;
1462       }
1463     }
1464     otk::Property::erase(_window, otk::Property::atoms.openbox_premax);
1465   }
1466
1467   if (dir == 0 || dir == 1) // horz
1468     _max_horz = max;
1469   if (dir == 0 || dir == 2) // vert
1470     _max_vert = max;
1471   changeState(); // change the state hints on the client
1472
1473   internal_resize(TopLeft, w, h, true, x, y);
1474   _gravity = g;
1475   if (max) {
1476     // because of my little gravity trick in here, we have to set the position
1477     // of the client to what it really is
1478     int x = frame->rect().x(), y = frame->rect().y();
1479     frame->frameGravity(x, y);
1480     _area.setPos(x, y);
1481   }
1482 }
1483
1484
1485 void Client::fullscreen(bool fs, bool savearea)
1486 {
1487   static FunctionFlags saved_func;
1488   static DecorationFlags saved_decor;
1489
1490   if (!(_functions & Func_Fullscreen) || // can't
1491       _fullscreen == fs) return;         // already done
1492
1493   _fullscreen = fs;
1494   changeState(); // change the state hints on the client
1495
1496   int x = _area.x(), y = _area.y(), w = _area.width(), h = _area.height();
1497   
1498   if (fs) {
1499     // save the functions and remove them
1500     saved_func = _functions;
1501     _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1502     // save the decorations and remove them
1503     saved_decor = _decorations;
1504     _decorations = 0;
1505     if (savearea) {
1506       long dimensions[4];
1507       dimensions[0] = _area.x();
1508       dimensions[1] = _area.y();
1509       dimensions[2] = _area.width();
1510       dimensions[3] = _area.height();
1511       otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1512                          otk::Property::atoms.cardinal,
1513                          (long unsigned*)dimensions, 4);
1514     }
1515     const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1516     x = 0;
1517     y = 0;
1518     w = info->width();
1519     h = info->height();
1520   } else {
1521     _functions = saved_func;
1522     _decorations = saved_decor;
1523
1524     long *dimensions;
1525     long unsigned n = 4;
1526       
1527     if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1528                            otk::Property::atoms.cardinal, &n,
1529                            (long unsigned**) &dimensions)) {
1530       if (n >= 4) {
1531         x = dimensions[0];
1532         y = dimensions[1];
1533         w = dimensions[2];
1534         h = dimensions[3];
1535       }
1536       delete dimensions;
1537     } else {
1538       // pick some fallbacks...
1539       const otk::Rect &a = openbox->screen(_screen)->area();
1540       x = a.x() + a.width() / 4;
1541       y = a.y() + a.height() / 4;
1542       w = a.width() / 2;
1543         h = a.height() / 2;
1544     }    
1545   }
1546   
1547   changeAllowedActions();  // based on the new _functions
1548
1549   // when fullscreening, don't obey things like increments, fill the screen
1550   internal_resize(TopLeft, w, h, !fs, x, y);
1551
1552   // raise (back) into our stacking layer
1553   openbox->screen(_screen)->raiseWindow(this);
1554
1555   // try focus us when we go into fullscreen mode
1556   if (fs) focus();
1557 }
1558
1559
1560 void Client::disableDecorations(DecorationFlags flags)
1561 {
1562   _disabled_decorations = flags;
1563   setupDecorAndFunctions();
1564 }
1565
1566
1567 bool Client::focus()
1568 {
1569   // won't try focus if the client doesn't want it, or if the window isn't
1570   // visible on the screen
1571   if (!(frame->isVisible() && (_can_focus || _focus_notify))) return false;
1572
1573   if (_focused) return true;
1574
1575   // do a check to see if the window has already been unmapped or destroyed
1576   // do this intelligently while watching out for unmaps we've generated
1577   // (ignore_unmaps > 0)
1578   XEvent ev;
1579   if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1580     XPutBackEvent(**otk::display, &ev);
1581     return false;
1582   }
1583   while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1584     if (ignore_unmaps) {
1585       unmapHandler(ev.xunmap);
1586     } else {
1587       XPutBackEvent(**otk::display, &ev);
1588       return false;
1589     }
1590   }
1591
1592   if (_can_focus)
1593     XSetInputFocus(**otk::display, _window,
1594                    RevertToNone, CurrentTime);
1595
1596   if (_focus_notify) {
1597     XEvent ce;
1598     ce.xclient.type = ClientMessage;
1599     ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1600     ce.xclient.display = **otk::display;
1601     ce.xclient.window = _window;
1602     ce.xclient.format = 32;
1603     ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1604     ce.xclient.data.l[1] = openbox->lastTime();
1605     ce.xclient.data.l[2] = 0l;
1606     ce.xclient.data.l[3] = 0l;
1607     ce.xclient.data.l[4] = 0l;
1608     XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1609   }
1610
1611   XSync(**otk::display, False);
1612   return true;
1613 }
1614
1615
1616 void Client::unfocus() const
1617 {
1618   if (!_focused) return;
1619
1620   assert(openbox->focusedClient() == this);
1621   openbox->setFocusedClient(0);
1622 }
1623
1624
1625 void Client::focusHandler(const XFocusChangeEvent &e)
1626 {
1627 #ifdef    DEBUG
1628 //  printf("FocusIn for 0x%lx\n", e.window);
1629 #endif // DEBUG
1630   
1631   otk::EventHandler::focusHandler(e);
1632
1633   frame->focus();
1634   _focused = true;
1635
1636   openbox->setFocusedClient(this);
1637 }
1638
1639
1640 void Client::unfocusHandler(const XFocusChangeEvent &e)
1641 {
1642 #ifdef    DEBUG
1643 //  printf("FocusOut for 0x%lx\n", e.window);
1644 #endif // DEBUG
1645   
1646   otk::EventHandler::unfocusHandler(e);
1647
1648   frame->unfocus();
1649   _focused = false;
1650
1651   if (openbox->focusedClient() == this)
1652     openbox->setFocusedClient(0);
1653 }
1654
1655
1656 void Client::configureRequestHandler(const XConfigureRequestEvent &e)
1657 {
1658 #ifdef    DEBUG
1659   printf("ConfigureRequest for 0x%lx\n", e.window);
1660 #endif // DEBUG
1661   
1662   otk::EventHandler::configureRequestHandler(e);
1663
1664   // if we are iconic (or shaded (fvwm does this)) ignore the event
1665   if (_iconic || _shaded) return;
1666
1667   if (e.value_mask & CWBorderWidth)
1668     _border_width = e.border_width;
1669
1670   // resize, then move, as specified in the EWMH section 7.7
1671   if (e.value_mask & (CWWidth | CWHeight)) {
1672     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1673     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1674
1675     Corner corner;
1676     switch (_gravity) {
1677     case NorthEastGravity:
1678     case EastGravity:
1679       corner = TopRight;
1680       break;
1681     case SouthWestGravity:
1682     case SouthGravity:
1683       corner = BottomLeft;
1684       break;
1685     case SouthEastGravity:
1686       corner = BottomRight;
1687       break;
1688     default:     // NorthWest, Static, etc
1689       corner = TopLeft;
1690     }
1691
1692     // if moving AND resizing ...
1693     if (e.value_mask & (CWX | CWY)) {
1694       int x = (e.value_mask & CWX) ? e.x : _area.x();
1695       int y = (e.value_mask & CWY) ? e.y : _area.y();
1696       internal_resize(corner, w, h, false, x, y);
1697     } else // if JUST resizing...
1698       internal_resize(corner, w, h, false);
1699   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1700     int x = (e.value_mask & CWX) ? e.x : _area.x();
1701     int y = (e.value_mask & CWY) ? e.y : _area.y();
1702     internal_move(x, y);
1703   }
1704
1705   if (e.value_mask & CWStackMode) {
1706     switch (e.detail) {
1707     case Below:
1708     case BottomIf:
1709       openbox->screen(_screen)->lowerWindow(this);
1710       break;
1711
1712     case Above:
1713     case TopIf:
1714     default:
1715       openbox->screen(_screen)->raiseWindow(this);
1716       break;
1717     }
1718   }
1719 }
1720
1721
1722 void Client::unmapHandler(const XUnmapEvent &e)
1723 {
1724   if (ignore_unmaps) {
1725 #ifdef    DEBUG
1726 //  printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1727 #endif // DEBUG
1728     ignore_unmaps--;
1729     return;
1730   }
1731   
1732 #ifdef    DEBUG
1733   printf("UnmapNotify for 0x%lx\n", e.window);
1734 #endif // DEBUG
1735
1736   otk::EventHandler::unmapHandler(e);
1737
1738   // this deletes us etc
1739   openbox->screen(_screen)->unmanageWindow(this);
1740 }
1741
1742
1743 void Client::destroyHandler(const XDestroyWindowEvent &e)
1744 {
1745 #ifdef    DEBUG
1746   printf("DestroyNotify for 0x%lx\n", e.window);
1747 #endif // DEBUG
1748
1749   otk::EventHandler::destroyHandler(e);
1750
1751   // this deletes us etc
1752   openbox->screen(_screen)->unmanageWindow(this);
1753 }
1754
1755
1756 void Client::reparentHandler(const XReparentEvent &e)
1757 {
1758   // this is when the client is first taken captive in the frame
1759   if (e.parent == frame->plate()) return;
1760
1761 #ifdef    DEBUG
1762   printf("ReparentNotify for 0x%lx\n", e.window);
1763 #endif // DEBUG
1764
1765   otk::EventHandler::reparentHandler(e);
1766
1767   /*
1768     This event is quite rare and is usually handled in unmapHandler.
1769     However, if the window is unmapped when the reparent event occurs,
1770     the window manager never sees it because an unmap event is not sent
1771     to an already unmapped window.
1772   */
1773
1774   // we don't want the reparent event, put it back on the stack for the X
1775   // server to deal with after we unmanage the window
1776   XEvent ev;
1777   ev.xreparent = e;
1778   XPutBackEvent(**otk::display, &ev);
1779   
1780   // this deletes us etc
1781   openbox->screen(_screen)->unmanageWindow(this);
1782 }
1783
1784 void Client::mapRequestHandler(const XMapRequestEvent &e)
1785 {
1786 #ifdef    DEBUG
1787   printf("MapRequest for already managed 0x%lx\n", e.window);
1788 #endif // DEBUG
1789
1790   assert(_iconic); // we shouldn't be able to get this unless we're iconic
1791
1792   // move to the current desktop (uniconify)
1793   setDesktop(openbox->screen(_screen)->desktop());
1794   // XXX: should we focus/raise the window? (basically a net_wm_active_window)
1795 }
1796
1797 }