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