]> icculus.org git repositories - dana/openbox.git/blob - src/client.cc
can tell where events are coming from!
[dana/openbox.git] / src / client.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #ifdef HAVE_CONFIG_H
4 # include "../config.h"
5 #endif
6
7 #include "client.hh"
8 #include "frame.hh"
9 #include "screen.hh"
10 #include "bbscreen.hh"
11 #include "openbox.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
19 #include <assert.h>
20
21 #include "gettext.h"
22 #define _(str) gettext(str)
23 }
24
25 namespace ob {
26
27 OBClient::OBClient(int screen, Window window)
28   : otk::OtkEventHandler(),
29     OBWidget(OBWidget::Type_Client),
30     frame(0), _screen(screen), _window(window)
31 {
32   assert(screen >= 0);
33   assert(window);
34
35   ignore_unmaps = 0;
36   
37   // update EVERYTHING the first time!!
38
39   // the state is kinda assumed to be normal. is this right? XXX
40   _wmstate = NormalState;
41   // no default decors or functions, each has to be enabled
42   _decorations = _functions = 0;
43   
44   getArea();
45   getDesktop();
46   getType();
47
48   // set the decorations and functions
49   switch (_type) {
50   case Type_Normal:
51     // normal windows retain all of the possible decorations and
52     // functionality
53     _decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
54                    Decor_Iconify | Decor_Maximize;
55     _functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize;
56
57   case Type_Dialog:
58     // dialogs cannot be maximized
59     _decorations &= ~Decor_Maximize;
60     _functions &= ~Func_Maximize;
61     break;
62
63   case Type_Menu:
64   case Type_Toolbar:
65   case Type_Utility:
66     // these windows get less functionality
67     _decorations &= ~(Decor_Iconify | Decor_Handle);
68     _functions &= ~(Func_Iconify | Func_Resize);
69     break;
70
71   case Type_Desktop:
72   case Type_Dock:
73   case Type_Splash:
74     // none of these windows are manipulated by the window manager
75     _decorations = 0;
76     _functions = 0;
77     break;
78   }
79   
80   getMwmHints(); // this fucks (in good ways) with the decors and functions
81   getState();
82   getShaped();
83
84   updateProtocols();
85   updateNormalHints();
86   updateWMHints();
87   // XXX: updateTransientFor();
88   updateTitle();
89   updateIconTitle();
90   updateClass();
91
92 /*
93 #ifdef DEBUG
94   printf("Mapped window: 0x%lx\n"
95          "  title:         \t%s\t  icon title:    \t%s\n"
96          "  app name:      \t%s\t\t  class:         \t%s\n"
97          "  position:      \t%d, %d\t\t  size:          \t%d, %d\n"
98          "  desktop:       \t%lu\t\t  group:         \t0x%lx\n"
99          "  type:          \t%d\t\t  min size       \t%d, %d\n"
100          "  base size      \t%d, %d\t\t  max size       \t%d, %d\n"
101          "  size incr      \t%d, %d\t\t  gravity        \t%d\n"
102          "  wm state       \t%ld\t\t  can be focused:\t%s\n"
103          "  notify focus:  \t%s\t\t  urgent:        \t%s\n"
104          "  shaped:        \t%s\t\t  modal:         \t%s\n"
105          "  shaded:        \t%s\t\t  iconic:        \t%s\n"
106          "  vert maximized:\t%s\t\t  horz maximized:\t%s\n"
107          "  fullscreen:    \t%s\t\t  floating:      \t%s\n"
108          "  requested pos: \t%s\n",
109          _window,
110          _title.c_str(),
111          _icon_title.c_str(),
112          _app_name.c_str(),
113          _app_class.c_str(),
114          _area.x(), _area.y(),
115          _area.width(), _area.height(),
116          _desktop,
117          _group,
118          _type,
119          _min_x, _min_y,
120          _base_x, _base_y,
121          _max_x, _max_y,
122          _inc_x, _inc_y,
123          _gravity,
124          _wmstate,
125          _can_focus ? "yes" : "no",
126          _focus_notify ? "yes" : "no",
127          _urgent ? "yes" : "no",
128          _shaped ? "yes" : "no",
129          _modal ? "yes" : "no",
130          _shaded ? "yes" : "no",
131          _iconic ? "yes" : "no",
132          _max_vert ? "yes" : "no",
133          _max_horz ? "yes" : "no",
134          _fullscreen ? "yes" : "no",
135          _floating ? "yes" : "no",
136          _positioned ? "yes" : "no");
137 #endif
138 */
139 }
140
141
142 OBClient::~OBClient()
143 {
144   const otk::OBProperty *property = Openbox::instance->property();
145
146   // these values should not be persisted across a window unmapping/mapping
147   property->erase(_window, otk::OBProperty::net_wm_desktop);
148   property->erase(_window, otk::OBProperty::net_wm_state);
149 }
150
151
152 void OBClient::getDesktop()
153 {
154   const otk::OBProperty *property = Openbox::instance->property();
155
156   // defaults to the current desktop
157   _desktop = 0; // XXX: change this to the current desktop!
158
159   property->get(_window, otk::OBProperty::net_wm_desktop,
160                 otk::OBProperty::Atom_Cardinal,
161                 &_desktop);
162 }
163
164
165 void OBClient::getType()
166 {
167   const otk::OBProperty *property = Openbox::instance->property();
168
169   _type = (WindowType) -1;
170   
171   unsigned long *val;
172   unsigned long num = (unsigned) -1;
173   if (property->get(_window, otk::OBProperty::net_wm_window_type,
174                     otk::OBProperty::Atom_Atom,
175                     &num, &val)) {
176     // use the first value that we know about in the array
177     for (unsigned long i = 0; i < num; ++i) {
178       if (val[i] ==
179           property->atom(otk::OBProperty::net_wm_window_type_desktop))
180         _type = Type_Desktop;
181       else if (val[i] ==
182                property->atom(otk::OBProperty::net_wm_window_type_dock))
183         _type = Type_Dock;
184       else if (val[i] ==
185                property->atom(otk::OBProperty::net_wm_window_type_toolbar))
186         _type = Type_Toolbar;
187       else if (val[i] ==
188                property->atom(otk::OBProperty::net_wm_window_type_menu))
189         _type = Type_Menu;
190       else if (val[i] ==
191                property->atom(otk::OBProperty::net_wm_window_type_utility))
192         _type = Type_Utility;
193       else if (val[i] ==
194                property->atom(otk::OBProperty::net_wm_window_type_splash))
195         _type = Type_Splash;
196       else if (val[i] ==
197                property->atom(otk::OBProperty::net_wm_window_type_dialog))
198         _type = Type_Dialog;
199       else if (val[i] ==
200                property->atom(otk::OBProperty::net_wm_window_type_normal))
201         _type = Type_Normal;
202 //      else if (val[i] ==
203 //               property->atom(otk::OBProperty::kde_net_wm_window_type_override))
204 //        mwm_decorations = 0; // prevent this window from getting any decor
205       // XXX: make this work again
206     }
207     delete val;
208   }
209     
210   if (_type == (WindowType) -1) {
211     /*
212      * the window type hint was not set, which means we either classify ourself
213      * as a normal window or a dialog, depending on if we are a transient.
214      */
215     // XXX: make this code work!
216     //if (isTransient())
217     //  _type = Type_Dialog;
218     //else
219       _type = Type_Normal;
220   }
221 }
222
223
224 void OBClient::getMwmHints()
225 {
226   const otk::OBProperty *property = Openbox::instance->property();
227
228   unsigned long num;
229   MwmHints *hints;
230
231   num = MwmHints::elements;
232   if (!property->get(_window, otk::OBProperty::motif_wm_hints,
233                      otk::OBProperty::motif_wm_hints, &num,
234                      (unsigned long **)&hints))
235     return;
236   
237   if (num < MwmHints::elements) {
238     delete [] hints;
239     return;
240   }
241
242   // retrieved the hints
243   // Mwm Hints are applied subtractively to what has already been chosen for
244   // decor and functionality
245
246   if (hints->flags & MwmFlag_Decorations) {
247     if (! (hints->decorations & MwmDecor_All)) {
248       if (! (hints->decorations & MwmDecor_Border))
249         _decorations &= ~Decor_Border;
250       if (! (hints->decorations & MwmDecor_Handle))
251         _decorations &= ~Decor_Handle;
252       if (! (hints->decorations & MwmDecor_Title))
253         _decorations &= ~Decor_Titlebar;
254       if (! (hints->decorations & MwmDecor_Iconify))
255         _decorations &= ~Decor_Iconify;
256       if (! (hints->decorations & MwmDecor_Maximize))
257         _decorations &= ~Decor_Maximize;
258     }
259   }
260
261   if (hints->flags & MwmFlag_Functions) {
262     if (! (hints->functions & MwmFunc_All)) {
263       if (! (hints->functions & MwmFunc_Resize))
264         _functions &= ~Func_Resize;
265       if (! (hints->functions & MwmFunc_Move))
266         _functions &= ~Func_Move;
267       if (! (hints->functions & MwmFunc_Iconify))
268         _functions &= ~Func_Iconify;
269       if (! (hints->functions & MwmFunc_Maximize))
270         _functions &= ~Func_Maximize;
271       //if (! (hints->functions & MwmFunc_Close))
272       //  _functions &= ~Func_Close;
273     }
274   }
275   delete [] hints;
276 }
277
278
279 void OBClient::getArea()
280 {
281   XWindowAttributes wattrib;
282   Status ret;
283   
284   ret = XGetWindowAttributes(otk::OBDisplay::display, _window, &wattrib);
285   assert(ret != BadWindow);
286
287   _area.setRect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
288   _border_width = wattrib.border_width;
289 }
290
291
292 void OBClient::getState()
293 {
294   const otk::OBProperty *property = Openbox::instance->property();
295
296   _modal = _shaded = _max_horz = _max_vert = _fullscreen = _floating = false;
297   
298   unsigned long *state;
299   unsigned long num = (unsigned) -1;
300   
301   if (property->get(_window, otk::OBProperty::net_wm_state,
302                     otk::OBProperty::Atom_Atom, &num, &state)) {
303     for (unsigned long i = 0; i < num; ++i) {
304       if (state[i] == property->atom(otk::OBProperty::net_wm_state_modal))
305         _modal = true;
306       else if (state[i] ==
307                property->atom(otk::OBProperty::net_wm_state_shaded))
308         _shaded = true;
309       else if (state[i] ==
310                property->atom(otk::OBProperty::net_wm_state_fullscreen))
311         _fullscreen = true;
312       else if (state[i] ==
313                property->atom(otk::OBProperty::net_wm_state_maximized_vert))
314         _max_vert = true;
315       else if (state[i] ==
316                property->atom(otk::OBProperty::net_wm_state_maximized_horz))
317         _max_horz = true;
318     }
319
320     delete [] state;
321   }
322 }
323
324
325 void OBClient::getShaped()
326 {
327   _shaped = false;
328 #ifdef   SHAPE
329   if (otk::OBDisplay::shape()) {
330     int foo;
331     unsigned int ufoo;
332     int s;
333
334     XShapeSelectInput(otk::OBDisplay::display, _window, ShapeNotifyMask);
335
336     XShapeQueryExtents(otk::OBDisplay::display, _window, &s, &foo,
337                        &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo);
338     _shaped = (s != 0);
339   }
340 #endif // SHAPE
341 }
342
343
344 void OBClient::updateProtocols()
345 {
346   const otk::OBProperty *property = Openbox::instance->property();
347
348   Atom *proto;
349   int num_return = 0;
350
351   _focus_notify = false;
352   _decorations &= ~Decor_Close;
353   _functions &= ~Func_Close;
354
355   if (XGetWMProtocols(otk::OBDisplay::display, _window, &proto, &num_return)) {
356     for (int i = 0; i < num_return; ++i) {
357       if (proto[i] == property->atom(otk::OBProperty::wm_delete_window)) {
358         _decorations |= Decor_Close;
359         _functions |= Func_Close;
360         // XXX: update the decor?
361       } else if (proto[i] == property->atom(otk::OBProperty::wm_take_focus))
362         // if this protocol is requested, then the window will be notified
363         // by the window manager whenever it receives focus
364         _focus_notify = true;
365     }
366     XFree(proto);
367   }
368 }
369
370
371 void OBClient::updateNormalHints()
372 {
373   XSizeHints size;
374   long ret;
375   int oldgravity = _gravity;
376
377   // defaults
378   _gravity = NorthWestGravity;
379   _size_inc.setPoint(1, 1);
380   _base_size.setPoint(0, 0);
381   _min_size.setPoint(0, 0);
382   _max_size.setPoint(INT_MAX, INT_MAX);
383
384   // XXX: might want to cancel any interactive resizing of the window at this
385   // point..
386
387   // get the hints from the window
388   if (XGetWMNormalHints(otk::OBDisplay::display, _window, &size, &ret)) {
389     _positioned = (size.flags & (PPosition|USPosition));
390
391     if (size.flags & PWinGravity)
392       _gravity = size.win_gravity;
393
394     if (size.flags & PMinSize)
395       _min_size.setPoint(size.min_width, size.min_height);
396     
397     if (size.flags & PMaxSize)
398       _max_size.setPoint(size.max_width, size.max_height);
399     
400     if (size.flags & PBaseSize)
401       _base_size.setPoint(size.base_width, size.base_height);
402     
403     if (size.flags & PResizeInc)
404       _size_inc.setPoint(size.width_inc, size.height_inc);
405   }
406
407   // if the client has a frame, i.e. has already been mapped and is
408   // changing its gravity
409   if (frame && _gravity != oldgravity) {
410     // move our idea of the client's position based on its new gravity
411     int x, y;
412     frame->frameGravity(x, y);
413     _area.setPos(x, y);
414   }
415 }
416
417
418 void OBClient::updateWMHints()
419 {
420   XWMHints *hints;
421
422   // assume a window takes input if it doesnt specify
423   _can_focus = true;
424   _urgent = false;
425   
426   if ((hints = XGetWMHints(otk::OBDisplay::display, _window)) != NULL) {
427     if (hints->flags & InputHint)
428       _can_focus = hints->input;
429
430     if (hints->flags & XUrgencyHint)
431       _urgent = true;
432
433     if (hints->flags & WindowGroupHint) {
434       if (hints->window_group != _group) {
435         // XXX: remove from the old group if there was one
436         _group = hints->window_group;
437         // XXX: do stuff with the group
438       }
439     } else // no group!
440       _group = None;
441
442     XFree(hints);
443   }
444 }
445
446
447 void OBClient::updateTitle()
448 {
449   const otk::OBProperty *property = Openbox::instance->property();
450
451   _title = "";
452   
453   // try netwm
454   if (! property->get(_window, otk::OBProperty::net_wm_name,
455                       otk::OBProperty::utf8, &_title)) {
456     // try old x stuff
457     property->get(_window, otk::OBProperty::wm_name,
458                   otk::OBProperty::ascii, &_title);
459   }
460
461   if (_title.empty())
462     _title = _("Unnamed Window");
463 }
464
465
466 void OBClient::updateIconTitle()
467 {
468   const otk::OBProperty *property = Openbox::instance->property();
469
470   _icon_title = "";
471   
472   // try netwm
473   if (! property->get(_window, otk::OBProperty::net_wm_icon_name,
474                       otk::OBProperty::utf8, &_icon_title)) {
475     // try old x stuff
476     property->get(_window, otk::OBProperty::wm_icon_name,
477                   otk::OBProperty::ascii, &_icon_title);
478   }
479
480   if (_title.empty())
481     _icon_title = _("Unnamed Window");
482 }
483
484
485 void OBClient::updateClass()
486 {
487   const otk::OBProperty *property = Openbox::instance->property();
488
489   // set the defaults
490   _app_name = _app_class = "";
491
492   otk::OBProperty::StringVect v;
493   unsigned long num = 2;
494
495   if (! property->get(_window, otk::OBProperty::wm_class,
496                       otk::OBProperty::ascii, &num, &v))
497     return;
498
499   if (num > 0) _app_name = v[0];
500   if (num > 1) _app_class = v[1];
501 }
502
503
504 void OBClient::propertyHandler(const XPropertyEvent &e)
505 {
506   otk::OtkEventHandler::propertyHandler(e);
507   
508   const otk::OBProperty *property = Openbox::instance->property();
509
510   // compress changes to a single property into a single change
511   XEvent ce;
512   while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) {
513     // XXX: it would be nice to compress ALL changes to a property, not just
514     //      changes in a row without other props between.
515     if (ce.xproperty.atom != e.atom) {
516       XPutBackEvent(otk::OBDisplay::display, &ce);
517       break;
518     }
519   }
520
521   if (e.atom == XA_WM_NORMAL_HINTS)
522     updateNormalHints();
523   else if (e.atom == XA_WM_HINTS)
524     updateWMHints();
525   else if (e.atom == property->atom(otk::OBProperty::net_wm_name) ||
526            e.atom == property->atom(otk::OBProperty::wm_name))
527     updateTitle();
528   else if (e.atom == property->atom(otk::OBProperty::net_wm_icon_name) ||
529            e.atom == property->atom(otk::OBProperty::wm_icon_name))
530     updateIconTitle();
531   else if (e.atom == property->atom(otk::OBProperty::wm_class))
532     updateClass();
533   else if (e.atom == property->atom(otk::OBProperty::wm_protocols))
534     updateProtocols();
535   // XXX: transient for hint
536   // XXX: strut hint
537 }
538
539
540 void OBClient::setWMState(long state)
541 {
542   if (state == _wmstate) return; // no change
543   
544   switch (state) {
545   case IconicState:
546     // XXX: cause it to iconify
547     break;
548   case NormalState:
549     // XXX: cause it to uniconify
550     break;
551   }
552   _wmstate = state;
553 }
554
555
556 void OBClient::setDesktop(long target)
557 {
558   assert(target >= 0);
559   //assert(target == 0xffffffff || target < MAX);
560   
561   // XXX: move the window to the new desktop
562   _desktop = target;
563 }
564
565
566 void OBClient::setState(StateAction action, long data1, long data2)
567 {
568   const otk::OBProperty *property = Openbox::instance->property();
569
570   if (!(action == State_Add || action == State_Remove ||
571         action == State_Toggle))
572     return; // an invalid action was passed to the client message, ignore it
573
574   for (int i = 0; i < 2; ++i) {
575     Atom state = i == 0 ? data1 : data2;
576     
577     if (! state) continue;
578
579     // if toggling, then pick whether we're adding or removing
580     if (action == State_Toggle) {
581       if (state == property->atom(otk::OBProperty::net_wm_state_modal))
582         action = _modal ? State_Remove : State_Add;
583       else if (state ==
584                property->atom(otk::OBProperty::net_wm_state_maximized_vert))
585         action = _max_vert ? State_Remove : State_Add;
586       else if (state ==
587                property->atom(otk::OBProperty::net_wm_state_maximized_horz))
588         action = _max_horz ? State_Remove : State_Add;
589       else if (state == property->atom(otk::OBProperty::net_wm_state_shaded))
590         action = _shaded ? State_Remove : State_Add;
591       else if (state ==
592                property->atom(otk::OBProperty::net_wm_state_fullscreen))
593         action = _fullscreen ? State_Remove : State_Add;
594       else if (state == property->atom(otk::OBProperty::net_wm_state_floating))
595         action = _floating ? State_Remove : State_Add;
596     }
597     
598     if (action == State_Add) {
599       if (state == property->atom(otk::OBProperty::net_wm_state_modal)) {
600         if (_modal) continue;
601         _modal = true;
602         // XXX: give it focus if another window has focus that shouldnt now
603       } else if (state ==
604                  property->atom(otk::OBProperty::net_wm_state_maximized_vert)){
605         if (_max_vert) continue;
606         _max_vert = true;
607         // XXX: resize the window etc
608       } else if (state ==
609                  property->atom(otk::OBProperty::net_wm_state_maximized_horz)){
610         if (_max_horz) continue;
611         _max_horz = true;
612         // XXX: resize the window etc
613       } else if (state ==
614                  property->atom(otk::OBProperty::net_wm_state_shaded)) {
615         if (_shaded) continue;
616         _shaded = true;
617         // XXX: hide the client window
618       } else if (state ==
619                  property->atom(otk::OBProperty::net_wm_state_fullscreen)) {
620         if (_fullscreen) continue;
621         _fullscreen = true;
622         // XXX: raise the window n shit
623       } else if (state ==
624                  property->atom(otk::OBProperty::net_wm_state_floating)) {
625         if (_floating) continue;
626         _floating = true;
627         // XXX: raise the window n shit
628       }
629
630     } else { // action == State_Remove
631       if (state == property->atom(otk::OBProperty::net_wm_state_modal)) {
632         if (!_modal) continue;
633         _modal = false;
634       } else if (state ==
635                  property->atom(otk::OBProperty::net_wm_state_maximized_vert)){
636         if (!_max_vert) continue;
637         _max_vert = false;
638         // XXX: resize the window etc
639       } else if (state ==
640                  property->atom(otk::OBProperty::net_wm_state_maximized_horz)){
641         if (!_max_horz) continue;
642         _max_horz = false;
643         // XXX: resize the window etc
644       } else if (state ==
645                  property->atom(otk::OBProperty::net_wm_state_shaded)) {
646         if (!_shaded) continue;
647         _shaded = false;
648         // XXX: show the client window
649       } else if (state ==
650                  property->atom(otk::OBProperty::net_wm_state_fullscreen)) {
651         if (!_fullscreen) continue;
652         _fullscreen = false;
653         // XXX: lower the window to its proper layer
654       } else if (state ==
655                  property->atom(otk::OBProperty::net_wm_state_floating)) {
656         if (!_floating) continue;
657         _floating = false;
658         // XXX: lower the window to its proper layer
659       }
660     }
661   }
662 }
663
664
665 void OBClient::toggleClientBorder(bool addborder)
666 {
667   // adjust our idea of where the client is, based on its border. When the
668   // border is removed, the client should now be considered to be in a
669   // different position.
670   // when re-adding the border to the client, the same operation needs to be
671   // reversed.
672   int x = _area.x(), y = _area.y();
673   switch(_gravity) {
674   case NorthWestGravity:
675   case WestGravity:
676   case SouthWestGravity:
677     if (addborder) x += _border_width;
678     else           x -= _border_width;
679     break;
680   case NorthEastGravity:
681   case EastGravity:
682   case SouthEastGravity:
683     if (addborder) x -= _border_width * 2;
684     else           x += _border_width * 2;
685     break;
686   }
687   switch(_gravity) {
688   case NorthWestGravity:
689   case NorthGravity:
690   case NorthEastGravity:
691     if (addborder) y += _border_width;
692     else           y -= _border_width;
693     break;
694   case SouthWestGravity:
695   case SouthGravity:
696   case SouthEastGravity:
697     if (addborder) y -= _border_width * 2;
698     else           y += _border_width * 2;
699     break;
700   default:
701     // no change for StaticGravity etc.
702     break;
703   }
704   _area.setPos(x, y);
705
706   if (addborder) {
707     XSetWindowBorderWidth(otk::OBDisplay::display, _window, _border_width);
708
709     // move the client so it is back it the right spot _with_ its border!
710     XMoveWindow(otk::OBDisplay::display, _window, x, y);
711   } else
712     XSetWindowBorderWidth(otk::OBDisplay::display, _window, 0);
713 }
714
715
716 void OBClient::clientMessageHandler(const XClientMessageEvent &e)
717 {
718   otk::OtkEventHandler::clientMessageHandler(e);
719   
720   if (e.format != 32) return;
721
722   const otk::OBProperty *property = Openbox::instance->property();
723   
724   if (e.message_type == property->atom(otk::OBProperty::wm_change_state)) {
725     // compress changes into a single change
726     bool compress = false;
727     XEvent ce;
728     while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) {
729       // XXX: it would be nice to compress ALL messages of a type, not just
730       //      messages in a row without other message types between.
731       if (ce.xclient.message_type != e.message_type) {
732         XPutBackEvent(otk::OBDisplay::display, &ce);
733         break;
734       }
735       compress = true;
736     }
737     if (compress)
738       setWMState(ce.xclient.data.l[0]); // use the found event
739     else
740       setWMState(e.data.l[0]); // use the original event
741   } else if (e.message_type ==
742              property->atom(otk::OBProperty::net_wm_desktop)) {
743     // compress changes into a single change 
744     bool compress = false;
745     XEvent ce;
746     while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) {
747       // XXX: it would be nice to compress ALL messages of a type, not just
748       //      messages in a row without other message types between.
749       if (ce.xclient.message_type != e.message_type) {
750         XPutBackEvent(otk::OBDisplay::display, &ce);
751         break;
752       }
753       compress = true;
754     }
755     if (compress)
756       setDesktop(e.data.l[0]); // use the found event
757     else
758       setDesktop(e.data.l[0]); // use the original event
759   }
760   else if (e.message_type == property->atom(otk::OBProperty::net_wm_state))
761     // can't compress these
762     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
763 }
764
765
766 #if defined(SHAPE) || defined(DOXYGEN_IGNORE)
767 void OBClient::shapeHandler(const XShapeEvent &e)
768 {
769   otk::OtkEventHandler::shapeHandler(e);
770   
771   _shaped = e.shaped;
772 }
773 #endif
774
775
776 void OBClient::resize(Corner anchor, int w, int h)
777 {
778   w -= _base_size.x(); 
779   h -= _base_size.y();
780
781   // is the window resizable? if it is not, then don't check its sizes, the
782   // client can do what it wants and the user can't change it anyhow
783   if (_min_size.x() <= _max_size.x() && _min_size.y() <= _max_size.y()) {
784     // smaller than min size or bigger than max size?
785     if (w < _min_size.x()) w = _min_size.x();
786     else if (w > _max_size.x()) w = _max_size.x();
787     if (h < _min_size.y()) h = _min_size.y();
788     else if (h > _max_size.y()) h = _max_size.y();
789   }
790
791   // keep to the increments
792   w /= _size_inc.x();
793   h /= _size_inc.y();
794
795   // store the logical size
796   _logical_size.setPoint(w, h);
797
798   w *= _size_inc.x();
799   h *= _size_inc.y();
800
801   w += _base_size.x();
802   h += _base_size.y();
803
804   int x = _area.x(), y = _area.y();  
805   switch (anchor) {
806   case TopLeft:
807     break;
808   case TopRight:
809     x -= w - _area.width();
810     break;
811   case BottomLeft:
812     y -= h - _area.height();
813     break;
814   case BottomRight:
815     x -= w - _area.width();
816     y -= h - _area.height();
817     break;
818   }
819
820   _area.setSize(w, h);
821   XResizeWindow(otk::OBDisplay::display, _window, w, h);
822
823   // resize the frame to match the request
824   frame->adjustSize();
825   move(x, y);
826 }
827
828
829 void OBClient::move(int x, int y)
830 {
831   _area.setPos(x, y);
832   // move the frame to be in the requested position
833   frame->adjustPosition();
834 }
835
836
837 void OBClient::configureRequestHandler(const XConfigureRequestEvent &e)
838 {
839   OtkEventHandler::configureRequestHandler(e);
840
841   // XXX: if we are iconic (or shaded? (fvwm does that)) ignore the event
842
843   if (e.value_mask & CWBorderWidth)
844     _border_width = e.border_width;
845
846     // resize, then move, as specified in the EWMH section 7.7
847   if (e.value_mask & (CWWidth | CWHeight)) {
848     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
849     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
850
851     Corner corner;
852     switch (_gravity) {
853     case NorthEastGravity:
854     case EastGravity:
855       corner = TopRight;
856       break;
857     case SouthWestGravity:
858     case SouthGravity:
859       corner = BottomLeft;
860       break;
861     case SouthEastGravity:
862       corner = BottomRight;
863       break;
864     default:     // NorthWest, Static, etc
865       corner = TopLeft;
866     }
867
868     resize(corner, w, h);
869   }
870
871   if (e.value_mask & (CWX | CWY)) {
872     int x = (e.value_mask & CWX) ? e.x : _area.x();
873     int y = (e.value_mask & CWY) ? e.y : _area.y();
874     move(x, y);
875   }
876
877   if (e.value_mask & CWStackMode) {
878     switch (e.detail) {
879     case Below:
880     case BottomIf:
881       // XXX: lower the window
882       break;
883
884     case Above:
885     case TopIf:
886     default:
887       // XXX: raise the window
888       break;
889     }
890   }
891 }
892
893
894 void OBClient::unmapHandler(const XUnmapEvent &e)
895 {
896 #ifdef    DEBUG
897   printf("UnmapNotify for 0x%lx\n", e.window);
898 #endif // DEBUG
899
900   if (ignore_unmaps) {
901     ignore_unmaps--;
902     return;
903   }
904   
905   OtkEventHandler::unmapHandler(e);
906
907   // this deletes us etc
908   Openbox::instance->screen(_screen)->unmanageWindow(this);
909 }
910
911
912 void OBClient::destroyHandler(const XDestroyWindowEvent &e)
913 {
914 #ifdef    DEBUG
915   printf("DestroyNotify for 0x%lx\n", e.window);
916 #endif // DEBUG
917
918   OtkEventHandler::destroyHandler(e);
919
920   // this deletes us etc
921   Openbox::instance->screen(_screen)->unmanageWindow(this);
922 }
923
924
925 }