]> icculus.org git repositories - mikachu/openbox.git/blob - src/client.cc
might not compile... ob uses its own widgets now, which subclass only the base otk...
[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) return false;
870
871   XSetInputFocus(otk::OBDisplay::display, _window, RevertToNone, CurrentTime);
872   return true;
873 }
874
875
876 void OBClient::focusHandler(const XFocusChangeEvent &)
877 {
878   frame->focus();
879   _focused = true;
880
881   Openbox::instance->setFocusedClient(this);
882 }
883
884
885 void OBClient::unfocusHandler(const XFocusChangeEvent &)
886 {
887   frame->unfocus();
888   _focused = false;
889
890   if (Openbox::instance->focusedClient() == this) {
891     printf("UNFOCUSING\n");
892     Openbox::instance->setFocusedClient(0);
893   } else
894     printf("UNFOCUSED ALREADY COULDNT UNFOCUS\n");
895 }
896
897
898 void OBClient::configureRequestHandler(const XConfigureRequestEvent &e)
899 {
900   OtkEventHandler::configureRequestHandler(e);
901
902   // XXX: if we are iconic (or shaded? (fvwm does that)) ignore the event
903
904   if (e.value_mask & CWBorderWidth)
905     _border_width = e.border_width;
906
907     // resize, then move, as specified in the EWMH section 7.7
908   if (e.value_mask & (CWWidth | CWHeight)) {
909     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
910     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
911
912     Corner corner;
913     switch (_gravity) {
914     case NorthEastGravity:
915     case EastGravity:
916       corner = TopRight;
917       break;
918     case SouthWestGravity:
919     case SouthGravity:
920       corner = BottomLeft;
921       break;
922     case SouthEastGravity:
923       corner = BottomRight;
924       break;
925     default:     // NorthWest, Static, etc
926       corner = TopLeft;
927     }
928
929     resize(corner, w, h);
930   }
931
932   if (e.value_mask & (CWX | CWY)) {
933     int x = (e.value_mask & CWX) ? e.x : _area.x();
934     int y = (e.value_mask & CWY) ? e.y : _area.y();
935     move(x, y);
936   }
937
938   if (e.value_mask & CWStackMode) {
939     switch (e.detail) {
940     case Below:
941     case BottomIf:
942       // XXX: lower the window
943       break;
944
945     case Above:
946     case TopIf:
947     default:
948       // XXX: raise the window
949       break;
950     }
951   }
952 }
953
954
955 void OBClient::unmapHandler(const XUnmapEvent &e)
956 {
957 #ifdef    DEBUG
958   printf("UnmapNotify for 0x%lx\n", e.window);
959 #endif // DEBUG
960
961   if (ignore_unmaps) {
962     ignore_unmaps--;
963     return;
964   }
965   
966   OtkEventHandler::unmapHandler(e);
967
968   // this deletes us etc
969   Openbox::instance->screen(_screen)->unmanageWindow(this);
970 }
971
972
973 void OBClient::destroyHandler(const XDestroyWindowEvent &e)
974 {
975 #ifdef    DEBUG
976   printf("DestroyNotify for 0x%lx\n", e.window);
977 #endif // DEBUG
978
979   OtkEventHandler::destroyHandler(e);
980
981   // this deletes us etc
982   Openbox::instance->screen(_screen)->unmanageWindow(this);
983 }
984
985
986 }