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