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