]> icculus.org git repositories - mikachu/openbox.git/blob - src/client.cc
clean up transient's parent's reference in destructor
[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 "otk/display.hh"
12 #include "otk/property.hh"
13
14 extern "C" {
15 #include <X11/Xlib.h>
16 #include <X11/Xutil.h>
17
18 #include <assert.h>
19
20 #include "gettext.h"
21 #define _(str) gettext(str)
22 }
23
24 namespace ob {
25
26 OBClient::OBClient(int screen, Window window)
27   : otk::OtkEventHandler(),
28     OBWidget(OBWidget::Type_Client),
29     frame(0), _screen(screen), _window(window)
30 {
31   assert(screen >= 0);
32   assert(window);
33
34   ignore_unmaps = 0;
35   
36   // update EVERYTHING the first time!!
37
38   // the state is kinda assumed to be normal. is this right? XXX
39   _wmstate = NormalState; _iconic = false;
40   // no default decors or functions, each has to be enabled
41   _decorations = _functions = 0;
42   // start unfocused
43   _focused = false;
44   // not a transient by default of course
45   _transient_for = 0;
46   
47   getArea();
48   getDesktop();
49
50   updateTransientFor();
51   getType();
52   getMwmHints();
53
54   setupDecorAndFunctions();
55   
56   getState();
57   getShaped();
58
59   updateProtocols();
60   updateNormalHints();
61   updateWMHints();
62   updateTitle();
63   updateIconTitle();
64   updateClass();
65   updateStrut();
66
67   changeState();
68 }
69
70
71 OBClient::~OBClient()
72 {
73   const otk::OBProperty *property = Openbox::instance->property();
74
75   // clean up parents reference to this
76   if (_transient_for)
77     _transient_for->_transients.remove(this); // remove from old parent
78   
79   if (Openbox::instance->state() != Openbox::State_Exiting) {
80     // these values should not be persisted across a window unmapping/mapping
81     property->erase(_window, otk::OBProperty::net_wm_desktop);
82     property->erase(_window, otk::OBProperty::net_wm_state);
83   }
84 }
85
86
87 void OBClient::getDesktop()
88 {
89   const otk::OBProperty *property = Openbox::instance->property();
90
91   // defaults to the current desktop
92   _desktop = 0; // XXX: change this to the current desktop!
93
94   property->get(_window, otk::OBProperty::net_wm_desktop,
95                 otk::OBProperty::Atom_Cardinal,
96                 &_desktop);
97 }
98
99
100 void OBClient::getType()
101 {
102   const otk::OBProperty *property = Openbox::instance->property();
103
104   _type = (WindowType) -1;
105   
106   unsigned long *val;
107   unsigned long num = (unsigned) -1;
108   if (property->get(_window, otk::OBProperty::net_wm_window_type,
109                     otk::OBProperty::Atom_Atom,
110                     &num, &val)) {
111     // use the first value that we know about in the array
112     for (unsigned long i = 0; i < num; ++i) {
113       if (val[i] ==
114           property->atom(otk::OBProperty::net_wm_window_type_desktop))
115         _type = Type_Desktop;
116       else if (val[i] ==
117                property->atom(otk::OBProperty::net_wm_window_type_dock))
118         _type = Type_Dock;
119       else if (val[i] ==
120                property->atom(otk::OBProperty::net_wm_window_type_toolbar))
121         _type = Type_Toolbar;
122       else if (val[i] ==
123                property->atom(otk::OBProperty::net_wm_window_type_menu))
124         _type = Type_Menu;
125       else if (val[i] ==
126                property->atom(otk::OBProperty::net_wm_window_type_utility))
127         _type = Type_Utility;
128       else if (val[i] ==
129                property->atom(otk::OBProperty::net_wm_window_type_splash))
130         _type = Type_Splash;
131       else if (val[i] ==
132                property->atom(otk::OBProperty::net_wm_window_type_dialog))
133         _type = Type_Dialog;
134       else if (val[i] ==
135                property->atom(otk::OBProperty::net_wm_window_type_normal))
136         _type = Type_Normal;
137 //      else if (val[i] ==
138 //               property->atom(otk::OBProperty::kde_net_wm_window_type_override))
139 //        mwm_decorations = 0; // prevent this window from getting any decor
140       // XXX: make this work again
141     }
142     delete val;
143   }
144     
145   if (_type == (WindowType) -1) {
146     /*
147      * the window type hint was not set, which means we either classify ourself
148      * as a normal window or a dialog, depending on if we are a transient.
149      */
150     if (_transient_for)
151       _type = Type_Dialog;
152     else
153       _type = Type_Normal;
154   }
155 }
156
157
158 void OBClient::setupDecorAndFunctions()
159 {
160   // start with everything
161   _decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
162     Decor_Iconify | Decor_Maximize;
163   _functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize;
164   
165   switch (_type) {
166   case Type_Normal:
167     // normal windows retain all of the possible decorations and
168     // functionality
169
170   case Type_Dialog:
171     // dialogs cannot be maximized
172     _decorations &= ~Decor_Maximize;
173     _functions &= ~Func_Maximize;
174     break;
175
176   case Type_Menu:
177   case Type_Toolbar:
178   case Type_Utility:
179     // these windows get less functionality
180     _decorations &= ~(Decor_Iconify | Decor_Handle);
181     _functions &= ~(Func_Iconify | Func_Resize);
182     break;
183
184   case Type_Desktop:
185   case Type_Dock:
186   case Type_Splash:
187     // none of these windows are manipulated by the window manager
188     _decorations = 0;
189     _functions = 0;
190     break;
191   }
192
193   // Mwm Hints are applied subtractively to what has already been chosen for
194   // decor and functionality
195   if (_mwmhints.flags & MwmFlag_Decorations) {
196     if (! (_mwmhints.decorations & MwmDecor_All)) {
197       if (! (_mwmhints.decorations & MwmDecor_Border))
198         _decorations &= ~Decor_Border;
199       if (! (_mwmhints.decorations & MwmDecor_Handle))
200         _decorations &= ~Decor_Handle;
201       if (! (_mwmhints.decorations & MwmDecor_Title))
202         _decorations &= ~Decor_Titlebar;
203       if (! (_mwmhints.decorations & MwmDecor_Iconify))
204         _decorations &= ~Decor_Iconify;
205       if (! (_mwmhints.decorations & MwmDecor_Maximize))
206         _decorations &= ~Decor_Maximize;
207     }
208   }
209
210   if (_mwmhints.flags & MwmFlag_Functions) {
211     if (! (_mwmhints.functions & MwmFunc_All)) {
212       if (! (_mwmhints.functions & MwmFunc_Resize))
213         _functions &= ~Func_Resize;
214       if (! (_mwmhints.functions & MwmFunc_Move))
215         _functions &= ~Func_Move;
216       if (! (_mwmhints.functions & MwmFunc_Iconify))
217         _functions &= ~Func_Iconify;
218       if (! (_mwmhints.functions & MwmFunc_Maximize))
219         _functions &= ~Func_Maximize;
220       // dont let mwm hints kill the close button
221       //if (! (_mwmhints.functions & MwmFunc_Close))
222       //  _functions &= ~Func_Close;
223     }
224   }
225
226   // XXX: changeAllowedActions();
227 }
228
229
230 void OBClient::getMwmHints()
231 {
232   const otk::OBProperty *property = Openbox::instance->property();
233
234   unsigned long num = MwmHints::elements;
235   unsigned long *hints;
236
237   _mwmhints.flags = 0; // default to none
238   
239   if (!property->get(_window, otk::OBProperty::motif_wm_hints,
240                      otk::OBProperty::motif_wm_hints, &num,
241                      (unsigned long **)&hints))
242     return;
243   
244   if (num >= MwmHints::elements) {
245     // retrieved the hints
246     _mwmhints.flags = hints[0];
247     _mwmhints.functions = hints[1];
248     _mwmhints.decorations = hints[2];
249   }
250
251   delete [] hints;
252 }
253
254
255 void OBClient::getArea()
256 {
257   XWindowAttributes wattrib;
258   Status ret;
259   
260   ret = XGetWindowAttributes(otk::OBDisplay::display, _window, &wattrib);
261   assert(ret != BadWindow);
262
263   _area.setRect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
264   _border_width = wattrib.border_width;
265 }
266
267
268 void OBClient::getState()
269 {
270   const otk::OBProperty *property = Openbox::instance->property();
271
272   _modal = _shaded = _max_horz = _max_vert = _fullscreen = _above = _below =
273     _skip_taskbar = _skip_pager = false;
274   
275   unsigned long *state;
276   unsigned long num = (unsigned) -1;
277   
278   if (property->get(_window, otk::OBProperty::net_wm_state,
279                     otk::OBProperty::Atom_Atom, &num, &state)) {
280     for (unsigned long i = 0; i < num; ++i) {
281       if (state[i] == property->atom(otk::OBProperty::net_wm_state_modal))
282         _modal = true;
283       else if (state[i] ==
284                property->atom(otk::OBProperty::net_wm_state_shaded)) {
285         _shaded = true;
286         _wmstate = IconicState;
287       } else if (state[i] ==
288                property->atom(otk::OBProperty::net_wm_state_skip_taskbar))
289         _skip_taskbar = true;
290       else if (state[i] ==
291                property->atom(otk::OBProperty::net_wm_state_skip_pager))
292         _skip_pager = true;
293       else if (state[i] ==
294                property->atom(otk::OBProperty::net_wm_state_fullscreen))
295         _fullscreen = true;
296       else if (state[i] ==
297                property->atom(otk::OBProperty::net_wm_state_maximized_vert))
298         _max_vert = true;
299       else if (state[i] ==
300                property->atom(otk::OBProperty::net_wm_state_maximized_horz))
301         _max_horz = true;
302       else if (state[i] ==
303                property->atom(otk::OBProperty::net_wm_state_above))
304         _above = true;
305       else if (state[i] ==
306                property->atom(otk::OBProperty::net_wm_state_below))
307         _below = true;
308     }
309
310     delete [] state;
311   }
312 }
313
314
315 void OBClient::getShaped()
316 {
317   _shaped = false;
318 #ifdef   SHAPE
319   if (otk::OBDisplay::shape()) {
320     int foo;
321     unsigned int ufoo;
322     int s;
323
324     XShapeSelectInput(otk::OBDisplay::display, _window, ShapeNotifyMask);
325
326     XShapeQueryExtents(otk::OBDisplay::display, _window, &s, &foo,
327                        &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo);
328     _shaped = (s != 0);
329   }
330 #endif // SHAPE
331 }
332
333
334 void OBClient::calcLayer() {
335   StackLayer l;
336
337   if (_iconic) l = Layer_Icon;
338   else if (_fullscreen) l = Layer_Fullscreen;
339   else if (_type == Type_Desktop) l = Layer_Desktop;
340   else if (_type == Type_Dock) {
341     if (!_below) l = Layer_Top;
342     else l = Layer_Normal;
343   }
344   else if (_above) l = Layer_Above;
345   else if (_below) l = Layer_Below;
346   else l = Layer_Normal;
347
348   if (l != _layer) {
349     _layer = l;
350     if (frame) {
351       /*
352         if we don't have a frame, then we aren't mapped yet (and this would
353         SIGSEGV :)
354       */
355       Openbox::instance->screen(_screen)->restack(true, this); // raise
356     }
357   }
358 }
359
360
361 void OBClient::updateProtocols()
362 {
363   const otk::OBProperty *property = Openbox::instance->property();
364
365   Atom *proto;
366   int num_return = 0;
367
368   _focus_notify = false;
369   _decorations &= ~Decor_Close;
370   _functions &= ~Func_Close;
371
372   if (XGetWMProtocols(otk::OBDisplay::display, _window, &proto, &num_return)) {
373     for (int i = 0; i < num_return; ++i) {
374       if (proto[i] == property->atom(otk::OBProperty::wm_delete_window)) {
375         _decorations |= Decor_Close;
376         _functions |= Func_Close;
377         if (frame)
378           frame->adjustSize(); // update the decorations
379       } else if (proto[i] == property->atom(otk::OBProperty::wm_take_focus))
380         // if this protocol is requested, then the window will be notified
381         // by the window manager whenever it receives focus
382         _focus_notify = true;
383     }
384     XFree(proto);
385   }
386 }
387
388
389 void OBClient::updateNormalHints()
390 {
391   XSizeHints size;
392   long ret;
393   int oldgravity = _gravity;
394
395   // defaults
396   _gravity = NorthWestGravity;
397   _size_inc.setPoint(1, 1);
398   _base_size.setPoint(0, 0);
399   _min_size.setPoint(0, 0);
400   _max_size.setPoint(INT_MAX, INT_MAX);
401
402   // XXX: might want to cancel any interactive resizing of the window at this
403   // point..
404
405   // get the hints from the window
406   if (XGetWMNormalHints(otk::OBDisplay::display, _window, &size, &ret)) {
407     _positioned = (size.flags & (PPosition|USPosition));
408
409     if (size.flags & PWinGravity)
410       _gravity = size.win_gravity;
411
412     if (size.flags & PMinSize)
413       _min_size.setPoint(size.min_width, size.min_height);
414     
415     if (size.flags & PMaxSize)
416       _max_size.setPoint(size.max_width, size.max_height);
417     
418     if (size.flags & PBaseSize)
419       _base_size.setPoint(size.base_width, size.base_height);
420     
421     if (size.flags & PResizeInc)
422       _size_inc.setPoint(size.width_inc, size.height_inc);
423   }
424
425   // if the client has a frame, i.e. has already been mapped and is
426   // changing its gravity
427   if (frame && _gravity != oldgravity) {
428     // move our idea of the client's position based on its new gravity
429     int x, y;
430     frame->frameGravity(x, y);
431     _area.setPos(x, y);
432   }
433 }
434
435
436 void OBClient::updateWMHints()
437 {
438   XWMHints *hints;
439
440   // assume a window takes input if it doesnt specify
441   _can_focus = true;
442   _urgent = false;
443   
444   if ((hints = XGetWMHints(otk::OBDisplay::display, _window)) != NULL) {
445     if (hints->flags & InputHint)
446       _can_focus = hints->input;
447
448     if (hints->flags & XUrgencyHint)
449       _urgent = true;
450
451     if (hints->flags & WindowGroupHint) {
452       if (hints->window_group != _group) {
453         // XXX: remove from the old group if there was one
454         _group = hints->window_group;
455         // XXX: do stuff with the group
456       }
457     } else // no group!
458       _group = None;
459
460     XFree(hints);
461   }
462 }
463
464
465 void OBClient::updateTitle()
466 {
467   const otk::OBProperty *property = Openbox::instance->property();
468
469   _title = "";
470   
471   // try netwm
472   if (! property->get(_window, otk::OBProperty::net_wm_name,
473                       otk::OBProperty::utf8, &_title)) {
474     // try old x stuff
475     property->get(_window, otk::OBProperty::wm_name,
476                   otk::OBProperty::ascii, &_title);
477   }
478
479   if (_title.empty())
480     _title = _("Unnamed Window");
481
482   if (frame)
483     frame->setTitle(_title);
484 }
485
486
487 void OBClient::updateIconTitle()
488 {
489   const otk::OBProperty *property = Openbox::instance->property();
490
491   _icon_title = "";
492   
493   // try netwm
494   if (! property->get(_window, otk::OBProperty::net_wm_icon_name,
495                       otk::OBProperty::utf8, &_icon_title)) {
496     // try old x stuff
497     property->get(_window, otk::OBProperty::wm_icon_name,
498                   otk::OBProperty::ascii, &_icon_title);
499   }
500
501   if (_title.empty())
502     _icon_title = _("Unnamed Window");
503 }
504
505
506 void OBClient::updateClass()
507 {
508   const otk::OBProperty *property = Openbox::instance->property();
509
510   // set the defaults
511   _app_name = _app_class = _role = "";
512
513   otk::OBProperty::StringVect v;
514   unsigned long num = 2;
515
516   if (property->get(_window, otk::OBProperty::wm_class,
517                     otk::OBProperty::ascii, &num, &v)) {
518     if (num > 0) _app_name = v[0];
519     if (num > 1) _app_class = v[1];
520   }
521
522   v.clear();
523   num = 1;
524   if (property->get(_window, otk::OBProperty::wm_window_role,
525                     otk::OBProperty::ascii, &num, &v)) {
526     if (num > 0) _role = v[0];
527   }
528 }
529
530
531 void OBClient::updateStrut()
532 {
533   unsigned long num = 4;
534   unsigned long *data;
535   if (!Openbox::instance->property()->get(_window,
536                                           otk::OBProperty::net_wm_strut,
537                                           otk::OBProperty::Atom_Cardinal,
538                                           &num, &data))
539     return;
540
541   if (num == 4) {
542     _strut.left = data[0];
543     _strut.right = data[1];
544     _strut.top = data[2];
545     _strut.bottom = data[3];
546     
547     Openbox::instance->screen(_screen)->updateStrut();
548   }
549
550   delete [] data;
551 }
552
553
554 void OBClient::updateTransientFor()
555 {
556   Window t = 0;
557   OBClient *c = 0;
558
559   if (XGetTransientForHint(otk::OBDisplay::display, _window, &t) &&
560       t != _window) { // cant be transient to itself!
561     c = Openbox::instance->findClient(t);
562     assert(c != this); // if this happens then we need to check for it
563
564     if (!c /*XXX: && _group*/) {
565       // not transient to a client, see if it is transient for a group
566       if (//t == _group->leader() ||
567         t == None ||
568         t == otk::OBDisplay::screenInfo(_screen)->rootWindow()) {
569         // window is a transient for its group!
570         // XXX: for now this is treated as non-transient.
571         //      this needs to be fixed!
572       }
573     }
574   }
575
576   // if anything has changed...
577   if (c != _transient_for) {
578     if (_transient_for)
579       _transient_for->_transients.remove(this); // remove from old parent
580     _transient_for = c;
581     if (_transient_for)
582       _transient_for->_transients.push_back(this); // add to new parent
583
584     // XXX: change decor status?
585   }
586 }
587
588
589 void OBClient::propertyHandler(const XPropertyEvent &e)
590 {
591   otk::OtkEventHandler::propertyHandler(e);
592   
593   const otk::OBProperty *property = Openbox::instance->property();
594
595   // compress changes to a single property into a single change
596   XEvent ce;
597   while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) {
598     // XXX: it would be nice to compress ALL changes to a property, not just
599     //      changes in a row without other props between.
600     if (ce.xproperty.atom != e.atom) {
601       XPutBackEvent(otk::OBDisplay::display, &ce);
602       break;
603     }
604   }
605
606   if (e.atom == XA_WM_NORMAL_HINTS)
607     updateNormalHints();
608   else if (e.atom == XA_WM_HINTS)
609     updateWMHints();
610   else if (e.atom == XA_WM_TRANSIENT_FOR) {
611     updateTransientFor();
612     getType();
613     calcLayer(); // type may have changed, so update the layer
614     setupDecorAndFunctions();
615     frame->adjustSize(); // this updates the frame for any new decor settings
616   }
617   else if (e.atom == property->atom(otk::OBProperty::net_wm_name) ||
618            e.atom == property->atom(otk::OBProperty::wm_name))
619     updateTitle();
620   else if (e.atom == property->atom(otk::OBProperty::net_wm_icon_name) ||
621            e.atom == property->atom(otk::OBProperty::wm_icon_name))
622     updateIconTitle();
623   else if (e.atom == property->atom(otk::OBProperty::wm_class))
624     updateClass();
625   else if (e.atom == property->atom(otk::OBProperty::wm_protocols))
626     updateProtocols();
627   else if (e.atom == property->atom(otk::OBProperty::net_wm_strut))
628     updateStrut();
629 }
630
631
632 void OBClient::setWMState(long state)
633 {
634   if (state == _wmstate) return; // no change
635   
636   _wmstate = state;
637   switch (_wmstate) {
638   case IconicState:
639     // XXX: cause it to iconify
640     break;
641   case NormalState:
642     // XXX: cause it to uniconify
643     break;
644   }
645 }
646
647
648 void OBClient::setDesktop(long target)
649 {
650   printf("Setting desktop %ld\n", target);
651   assert(target >= 0 || target == (signed)0xffffffff);
652   //assert(target == 0xffffffff || target < MAX);
653
654   // XXX: move the window to the new desktop (and set root property)
655   _desktop = target;
656 }
657
658
659 void OBClient::setState(StateAction action, long data1, long data2)
660 {
661   const otk::OBProperty *property = Openbox::instance->property();
662   bool shadestate = _shaded;
663
664   if (!(action == State_Add || action == State_Remove ||
665         action == State_Toggle))
666     return; // an invalid action was passed to the client message, ignore it
667
668   for (int i = 0; i < 2; ++i) {
669     Atom state = i == 0 ? data1 : data2;
670     
671     if (! state) continue;
672
673     // if toggling, then pick whether we're adding or removing
674     if (action == State_Toggle) {
675       if (state == property->atom(otk::OBProperty::net_wm_state_modal))
676         action = _modal ? State_Remove : State_Add;
677       else if (state ==
678                property->atom(otk::OBProperty::net_wm_state_maximized_vert))
679         action = _max_vert ? State_Remove : State_Add;
680       else if (state ==
681                property->atom(otk::OBProperty::net_wm_state_maximized_horz))
682         action = _max_horz ? State_Remove : State_Add;
683       else if (state == property->atom(otk::OBProperty::net_wm_state_shaded))
684         action = _shaded ? State_Remove : State_Add;
685       else if (state ==
686                property->atom(otk::OBProperty::net_wm_state_skip_taskbar))
687         action = _skip_taskbar ? State_Remove : State_Add;
688       else if (state ==
689                property->atom(otk::OBProperty::net_wm_state_skip_pager))
690         action = _skip_pager ? State_Remove : State_Add;
691       else if (state ==
692                property->atom(otk::OBProperty::net_wm_state_fullscreen))
693         action = _fullscreen ? State_Remove : State_Add;
694       else if (state == property->atom(otk::OBProperty::net_wm_state_above))
695         action = _above ? State_Remove : State_Add;
696       else if (state == property->atom(otk::OBProperty::net_wm_state_below))
697         action = _below ? State_Remove : State_Add;
698     }
699     
700     if (action == State_Add) {
701       if (state == property->atom(otk::OBProperty::net_wm_state_modal)) {
702         if (_modal) continue;
703         _modal = true;
704         // XXX: give it focus if another window has focus that shouldnt now
705       } else if (state ==
706                  property->atom(otk::OBProperty::net_wm_state_maximized_vert)){
707         if (_max_vert) continue;
708         _max_vert = true;
709         // XXX: resize the window etc
710       } else if (state ==
711                  property->atom(otk::OBProperty::net_wm_state_maximized_horz)){
712         if (_max_horz) continue;
713         _max_horz = true;
714         // XXX: resize the window etc
715       } else if (state ==
716                  property->atom(otk::OBProperty::net_wm_state_shaded)) {
717         if (_shaded) continue;
718         // shade when we're all thru here
719         shadestate = true;
720       } else if (state ==
721                  property->atom(otk::OBProperty::net_wm_state_skip_taskbar)) {
722         _skip_taskbar = true;
723       } else if (state ==
724                  property->atom(otk::OBProperty::net_wm_state_skip_pager)) {
725         _skip_pager = true;
726       } else if (state ==
727                  property->atom(otk::OBProperty::net_wm_state_fullscreen)) {
728         if (_fullscreen) continue;
729         _fullscreen = true;
730       } else if (state ==
731                  property->atom(otk::OBProperty::net_wm_state_above)) {
732         if (_above) continue;
733         _above = true;
734       } else if (state ==
735                  property->atom(otk::OBProperty::net_wm_state_below)) {
736         if (_below) continue;
737         _below = true;
738       }
739
740     } else { // action == State_Remove
741       if (state == property->atom(otk::OBProperty::net_wm_state_modal)) {
742         if (!_modal) continue;
743         _modal = false;
744       } else if (state ==
745                  property->atom(otk::OBProperty::net_wm_state_maximized_vert)){
746         if (!_max_vert) continue;
747         _max_vert = false;
748         // XXX: resize the window etc
749       } else if (state ==
750                  property->atom(otk::OBProperty::net_wm_state_maximized_horz)){
751         if (!_max_horz) continue;
752         _max_horz = false;
753         // XXX: resize the window etc
754       } else if (state ==
755                  property->atom(otk::OBProperty::net_wm_state_shaded)) {
756         if (!_shaded) continue;
757         // unshade when we're all thru here
758         shadestate = false;
759       } else if (state ==
760                  property->atom(otk::OBProperty::net_wm_state_skip_taskbar)) {
761         _skip_taskbar = false;
762       } else if (state ==
763                  property->atom(otk::OBProperty::net_wm_state_skip_pager)) {
764         _skip_pager = false;
765       } else if (state ==
766                  property->atom(otk::OBProperty::net_wm_state_fullscreen)) {
767         if (!_fullscreen) continue;
768         _fullscreen = false;
769       } else if (state ==
770                  property->atom(otk::OBProperty::net_wm_state_above)) {
771         if (!_above) continue;
772         _above = false;
773       } else if (state ==
774                  property->atom(otk::OBProperty::net_wm_state_below)) {
775         if (!_below) continue;
776         _below = false;
777       }
778     }
779   }
780   if (shadestate != _shaded)
781     shade(shadestate);
782   calcLayer();
783 }
784
785
786 void OBClient::toggleClientBorder(bool addborder)
787 {
788   // adjust our idea of where the client is, based on its border. When the
789   // border is removed, the client should now be considered to be in a
790   // different position.
791   // when re-adding the border to the client, the same operation needs to be
792   // reversed.
793   int x = _area.x(), y = _area.y();
794   switch(_gravity) {
795   case NorthWestGravity:
796   case WestGravity:
797   case SouthWestGravity:
798     break;
799   case NorthEastGravity:
800   case EastGravity:
801   case SouthEastGravity:
802     if (addborder) x -= _border_width * 2;
803     else           x += _border_width * 2;
804     break;
805   }
806   switch(_gravity) {
807   case NorthWestGravity:
808   case NorthGravity:
809   case NorthEastGravity:
810     break;
811   case SouthWestGravity:
812   case SouthGravity:
813   case SouthEastGravity:
814     if (addborder) y -= _border_width * 2;
815     else           y += _border_width * 2;
816     break;
817   default:
818     // no change for StaticGravity etc.
819     break;
820   }
821   _area.setPos(x, y);
822
823   if (addborder) {
824     XSetWindowBorderWidth(otk::OBDisplay::display, _window, _border_width);
825
826     // move the client so it is back it the right spot _with_ its border!
827     XMoveWindow(otk::OBDisplay::display, _window, x, y);
828   } else
829     XSetWindowBorderWidth(otk::OBDisplay::display, _window, 0);
830 }
831
832
833 void OBClient::clientMessageHandler(const XClientMessageEvent &e)
834 {
835   otk::OtkEventHandler::clientMessageHandler(e);
836   
837   if (e.format != 32) return;
838
839   const otk::OBProperty *property = Openbox::instance->property();
840   
841   if (e.message_type == property->atom(otk::OBProperty::wm_change_state)) {
842     // compress changes into a single change
843     bool compress = false;
844     XEvent ce;
845     while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) {
846       // XXX: it would be nice to compress ALL messages of a type, not just
847       //      messages in a row without other message types between.
848       if (ce.xclient.message_type != e.message_type) {
849         XPutBackEvent(otk::OBDisplay::display, &ce);
850         break;
851       }
852       compress = true;
853     }
854     if (compress)
855       setWMState(ce.xclient.data.l[0]); // use the found event
856     else
857       setWMState(e.data.l[0]); // use the original event
858   } else if (e.message_type ==
859              property->atom(otk::OBProperty::net_wm_desktop)) {
860     // compress changes into a single change 
861     bool compress = false;
862     XEvent ce;
863     while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) {
864       // XXX: it would be nice to compress ALL messages of a type, not just
865       //      messages in a row without other message types between.
866       if (ce.xclient.message_type != e.message_type) {
867         XPutBackEvent(otk::OBDisplay::display, &ce);
868         break;
869       }
870       compress = true;
871     }
872     if (compress)
873       setDesktop(e.data.l[0]); // use the found event
874     else
875       setDesktop(e.data.l[0]); // use the original event
876   } else if (e.message_type == property->atom(otk::OBProperty::net_wm_state)) {
877     // can't compress these
878     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
879   } else if (e.message_type ==
880              property->atom(otk::OBProperty::net_close_window)) {
881     close();
882   } else if (e.message_type ==
883              property->atom(otk::OBProperty::net_active_window)) {
884     focus();
885     Openbox::instance->screen(_screen)->restack(true, this); // raise
886   } else {
887   }
888 }
889
890
891 #if defined(SHAPE)
892 void OBClient::shapeHandler(const XShapeEvent &e)
893 {
894   otk::OtkEventHandler::shapeHandler(e);
895   
896   _shaped = e.shaped;
897   frame->adjustShape();
898 }
899 #endif
900
901
902 void OBClient::resize(Corner anchor, int w, int h, int x, int y)
903 {
904   w -= _base_size.x(); 
905   h -= _base_size.y();
906
907   // for interactive resizing. have to move half an increment in each
908   // direction.
909   w += _size_inc.x() / 2;
910   h += _size_inc.y() / 2;
911
912   // is the window resizable? if it is not, then don't check its sizes, the
913   // client can do what it wants and the user can't change it anyhow
914   if (_min_size.x() <= _max_size.x() && _min_size.y() <= _max_size.y()) {
915     // smaller than min size or bigger than max size?
916     if (w < _min_size.x()) w = _min_size.x();
917     else if (w > _max_size.x()) w = _max_size.x();
918     if (h < _min_size.y()) h = _min_size.y();
919     else if (h > _max_size.y()) h = _max_size.y();
920   }
921
922   // keep to the increments
923   w /= _size_inc.x();
924   h /= _size_inc.y();
925
926   // store the logical size
927   _logical_size.setPoint(w, h);
928
929   w *= _size_inc.x();
930   h *= _size_inc.y();
931
932   w += _base_size.x();
933   h += _base_size.y();
934
935   if (x == INT_MIN || y == INT_MIN) {
936     x = _area.x();
937     y = _area.y();
938     switch (anchor) {
939     case TopLeft:
940       break;
941     case TopRight:
942       x -= w - _area.width();
943       break;
944     case BottomLeft:
945       y -= h - _area.height();
946       break;
947     case BottomRight:
948       x -= w - _area.width();
949       y -= h - _area.height();
950       break;
951     }
952   }
953
954   _area.setSize(w, h);
955
956   XResizeWindow(otk::OBDisplay::display, _window, w, h);
957
958   // resize the frame to match the request
959   frame->adjustSize();
960   move(x, y);
961 }
962
963
964 void OBClient::move(int x, int y)
965 {
966   _area.setPos(x, y);
967
968   // move the frame to be in the requested position
969   frame->adjustPosition();
970 }
971
972
973 void OBClient::close()
974 {
975   XEvent ce;
976   const otk::OBProperty *property = Openbox::instance->property();
977
978   if (!(_functions & Func_Close)) return;
979
980   // XXX: itd be cool to do timeouts and shit here for killing the client's
981   //      process off
982   // like... if the window is around after 5 seconds, then the close button
983   // turns a nice red, and if this function is called again, the client is
984   // explicitly killed.
985
986   ce.xclient.type = ClientMessage;
987   ce.xclient.message_type =  property->atom(otk::OBProperty::wm_protocols);
988   ce.xclient.display = otk::OBDisplay::display;
989   ce.xclient.window = _window;
990   ce.xclient.format = 32;
991   ce.xclient.data.l[0] = property->atom(otk::OBProperty::wm_delete_window);
992   ce.xclient.data.l[1] = CurrentTime;
993   ce.xclient.data.l[2] = 0l;
994   ce.xclient.data.l[3] = 0l;
995   ce.xclient.data.l[4] = 0l;
996   XSendEvent(otk::OBDisplay::display, _window, False, NoEventMask, &ce);
997 }
998
999
1000 void OBClient::changeState()
1001 {
1002   const otk::OBProperty *property = Openbox::instance->property();
1003
1004   unsigned long state[2];
1005   state[0] = _wmstate;
1006   state[1] = None;
1007   property->set(_window, otk::OBProperty::wm_state, otk::OBProperty::wm_state,
1008                 state, 2);
1009   
1010   Atom netstate[10];
1011   int num = 0;
1012   if (_modal)
1013     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_modal);
1014   if (_shaded)
1015     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_shaded);
1016   if (_iconic)
1017     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_hidden);
1018   if (_skip_taskbar)
1019     netstate[num++] =
1020       property->atom(otk::OBProperty::net_wm_state_skip_taskbar);
1021   if (_skip_pager)
1022     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_skip_pager);
1023   if (_fullscreen)
1024     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_fullscreen);
1025   if (_max_vert)
1026     netstate[num++] =
1027       property->atom(otk::OBProperty::net_wm_state_maximized_vert);
1028   if (_max_horz)
1029     netstate[num++] =
1030       property->atom(otk::OBProperty::net_wm_state_maximized_horz);
1031   if (_above)
1032     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_above);
1033   if (_below)
1034     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_below);
1035   property->set(_window, otk::OBProperty::net_wm_state,
1036                 otk::OBProperty::Atom_Atom, netstate, num);
1037
1038   calcLayer();
1039 }
1040
1041
1042 void OBClient::setStackLayer(int l)
1043 {
1044   if (l == 0)
1045     _above = _below = false;  // normal
1046   else if (l > 0) {
1047     _above = true;
1048     _below = false; // above
1049   } else {
1050     _above = false;
1051     _below = true;  // below
1052   }
1053   changeState();
1054 }
1055
1056
1057 void OBClient::shade(bool shade)
1058 {
1059   if (shade == _shaded) return; // already done
1060
1061   _wmstate = shade ? IconicState : NormalState;
1062   _shaded = shade;
1063   changeState();
1064   frame->adjustSize();
1065 }
1066
1067
1068 bool OBClient::focus()
1069 {
1070   if (!(_can_focus || _focus_notify) || _focused) return false;
1071
1072   if (_can_focus)
1073     XSetInputFocus(otk::OBDisplay::display, _window, RevertToNone, CurrentTime);
1074
1075   if (_focus_notify) {
1076     XEvent ce;
1077     const otk::OBProperty *property = Openbox::instance->property();
1078     
1079     ce.xclient.type = ClientMessage;
1080     ce.xclient.message_type =  property->atom(otk::OBProperty::wm_protocols);
1081     ce.xclient.display = otk::OBDisplay::display;
1082     ce.xclient.window = _window;
1083     ce.xclient.format = 32;
1084     ce.xclient.data.l[0] = property->atom(otk::OBProperty::wm_take_focus);
1085     ce.xclient.data.l[1] = Openbox::instance->lastTime();
1086     ce.xclient.data.l[2] = 0l;
1087     ce.xclient.data.l[3] = 0l;
1088     ce.xclient.data.l[4] = 0l;
1089     XSendEvent(otk::OBDisplay::display, _window, False, NoEventMask, &ce);
1090   }
1091
1092   return true;
1093 }
1094
1095
1096 void OBClient::unfocus()
1097 {
1098   if (!_focused) return;
1099
1100   assert(Openbox::instance->focusedClient() == this);
1101   Openbox::instance->setFocusedClient(0);
1102 }
1103
1104
1105 void OBClient::focusHandler(const XFocusChangeEvent &e)
1106 {
1107 #ifdef    DEBUG
1108   printf("FocusIn for 0x%lx\n", e.window);
1109 #endif // DEBUG
1110   
1111   OtkEventHandler::focusHandler(e);
1112
1113   frame->focus();
1114   _focused = true;
1115
1116   Openbox::instance->setFocusedClient(this);
1117 }
1118
1119
1120 void OBClient::unfocusHandler(const XFocusChangeEvent &e)
1121 {
1122 #ifdef    DEBUG
1123   printf("FocusOut for 0x%lx\n", e.window);
1124 #endif // DEBUG
1125   
1126   OtkEventHandler::unfocusHandler(e);
1127
1128   frame->unfocus();
1129   _focused = false;
1130
1131   if (Openbox::instance->focusedClient() == this) {
1132     printf("UNFOCUSED!\n");
1133     Openbox::instance->setFocusedClient(this);
1134   }
1135 }
1136
1137
1138 void OBClient::configureRequestHandler(const XConfigureRequestEvent &e)
1139 {
1140 #ifdef    DEBUG
1141   printf("ConfigureRequest for 0x%lx\n", e.window);
1142 #endif // DEBUG
1143   
1144   OtkEventHandler::configureRequestHandler(e);
1145
1146   // XXX: if we are iconic (or shaded? (fvwm does that)) ignore the event
1147
1148   if (e.value_mask & CWBorderWidth)
1149     _border_width = e.border_width;
1150
1151   // resize, then move, as specified in the EWMH section 7.7
1152   if (e.value_mask & (CWWidth | CWHeight)) {
1153     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1154     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1155
1156     Corner corner;
1157     switch (_gravity) {
1158     case NorthEastGravity:
1159     case EastGravity:
1160       corner = TopRight;
1161       break;
1162     case SouthWestGravity:
1163     case SouthGravity:
1164       corner = BottomLeft;
1165       break;
1166     case SouthEastGravity:
1167       corner = BottomRight;
1168       break;
1169     default:     // NorthWest, Static, etc
1170       corner = TopLeft;
1171     }
1172
1173     // if moving AND resizing ...
1174     if (e.value_mask & (CWX | CWY)) {
1175       int x = (e.value_mask & CWX) ? e.x : _area.x();
1176       int y = (e.value_mask & CWY) ? e.y : _area.y();
1177       resize(corner, w, h, x, y);
1178     } else // if JUST resizing...
1179       resize(corner, w, h);
1180   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1181     int x = (e.value_mask & CWX) ? e.x : _area.x();
1182     int y = (e.value_mask & CWY) ? e.y : _area.y();
1183     move(x, y);
1184   }
1185
1186   if (e.value_mask & CWStackMode) {
1187     switch (e.detail) {
1188     case Below:
1189     case BottomIf:
1190       Openbox::instance->screen(_screen)->restack(false, this); // lower
1191       break;
1192
1193     case Above:
1194     case TopIf:
1195     default:
1196       Openbox::instance->screen(_screen)->restack(true, this); // raise
1197       break;
1198     }
1199   }
1200 }
1201
1202
1203 void OBClient::unmapHandler(const XUnmapEvent &e)
1204 {
1205 #ifdef    DEBUG
1206   printf("UnmapNotify for 0x%lx\n", e.window);
1207 #endif // DEBUG
1208
1209   if (ignore_unmaps) {
1210     ignore_unmaps--;
1211     return;
1212   }
1213   
1214   OtkEventHandler::unmapHandler(e);
1215
1216   // this deletes us etc
1217   Openbox::instance->screen(_screen)->unmanageWindow(this);
1218 }
1219
1220
1221 void OBClient::destroyHandler(const XDestroyWindowEvent &e)
1222 {
1223 #ifdef    DEBUG
1224   printf("DestroyNotify for 0x%lx\n", e.window);
1225 #endif // DEBUG
1226
1227   OtkEventHandler::destroyHandler(e);
1228
1229   // this deletes us etc
1230   Openbox::instance->screen(_screen)->unmanageWindow(this);
1231 }
1232
1233
1234 void OBClient::reparentHandler(const XReparentEvent &e)
1235 {
1236   // this is when the client is first taken captive in the frame
1237   if (e.parent == frame->plate()) return;
1238
1239 #ifdef    DEBUG
1240   printf("ReparentNotify for 0x%lx\n", e.window);
1241 #endif // DEBUG
1242
1243   OtkEventHandler::reparentHandler(e);
1244
1245   /*
1246     This event is quite rare and is usually handled in unmapHandler.
1247     However, if the window is unmapped when the reparent event occurs,
1248     the window manager never sees it because an unmap event is not sent
1249     to an already unmapped window.
1250   */
1251
1252   // this deletes us etc
1253   Openbox::instance->screen(_screen)->unmanageWindow(this);
1254 }
1255
1256
1257 void OBClient::mapRequestHandler(const XMapRequestEvent &e)
1258 {
1259   printf("\nMAP REQUEST\n\n");
1260   
1261   otk::OtkEventHandler::mapRequestHandler(e);
1262
1263   if (_shaded)
1264     shade(false);
1265   // XXX: uniconify the window
1266   focus();
1267 }
1268
1269 }