]> icculus.org git repositories - dana/openbox.git/blob - src/client.cc
window states persist across restarts
[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   assert(target >= 0);
543   //assert(target == 0xffffffff || target < MAX);
544   
545   // XXX: move the window to the new desktop
546   _desktop = target;
547 }
548
549
550 void OBClient::setState(StateAction action, long data1, long data2)
551 {
552   const otk::OBProperty *property = Openbox::instance->property();
553
554   if (!(action == State_Add || action == State_Remove ||
555         action == State_Toggle))
556     return; // an invalid action was passed to the client message, ignore it
557
558   for (int i = 0; i < 2; ++i) {
559     Atom state = i == 0 ? data1 : data2;
560     
561     if (! state) continue;
562
563     // if toggling, then pick whether we're adding or removing
564     if (action == State_Toggle) {
565       if (state == property->atom(otk::OBProperty::net_wm_state_modal))
566         action = _modal ? State_Remove : State_Add;
567       else if (state ==
568                property->atom(otk::OBProperty::net_wm_state_maximized_vert))
569         action = _max_vert ? State_Remove : State_Add;
570       else if (state ==
571                property->atom(otk::OBProperty::net_wm_state_maximized_horz))
572         action = _max_horz ? State_Remove : State_Add;
573       else if (state == property->atom(otk::OBProperty::net_wm_state_shaded))
574         action = _shaded ? State_Remove : State_Add;
575       else if (state ==
576                property->atom(otk::OBProperty::net_wm_state_skip_taskbar))
577         action = _skip_taskbar ? State_Remove : State_Add;
578       else if (state ==
579                property->atom(otk::OBProperty::net_wm_state_skip_pager))
580         action = _skip_pager ? State_Remove : State_Add;
581       else if (state ==
582                property->atom(otk::OBProperty::net_wm_state_fullscreen))
583         action = _fullscreen ? State_Remove : State_Add;
584       else if (state == property->atom(otk::OBProperty::net_wm_state_above))
585         action = _above ? State_Remove : State_Add;
586       else if (state == property->atom(otk::OBProperty::net_wm_state_below))
587         action = _below ? State_Remove : State_Add;
588     }
589     
590     if (action == State_Add) {
591       if (state == property->atom(otk::OBProperty::net_wm_state_modal)) {
592         if (_modal) continue;
593         _modal = true;
594         // XXX: give it focus if another window has focus that shouldnt now
595       } else if (state ==
596                  property->atom(otk::OBProperty::net_wm_state_maximized_vert)){
597         if (_max_vert) continue;
598         _max_vert = true;
599         // XXX: resize the window etc
600       } else if (state ==
601                  property->atom(otk::OBProperty::net_wm_state_maximized_horz)){
602         if (_max_horz) continue;
603         _max_horz = true;
604         // XXX: resize the window etc
605       } else if (state ==
606                  property->atom(otk::OBProperty::net_wm_state_shaded)) {
607         if (_shaded) continue;
608         _shaded = true;
609         // XXX: hide the client window
610       } else if (state ==
611                  property->atom(otk::OBProperty::net_wm_state_skip_taskbar)) {
612         _skip_taskbar = true;
613       } else if (state ==
614                  property->atom(otk::OBProperty::net_wm_state_skip_pager)) {
615         _skip_pager = true;
616       } else if (state ==
617                  property->atom(otk::OBProperty::net_wm_state_fullscreen)) {
618         if (_fullscreen) continue;
619         _fullscreen = true;
620         // XXX: raise the window n shit
621       } else if (state ==
622                  property->atom(otk::OBProperty::net_wm_state_above)) {
623         if (_above) continue;
624         _above = true;
625         // XXX: raise the window n shit
626       } else if (state ==
627                  property->atom(otk::OBProperty::net_wm_state_below)) {
628         if (_below) continue;
629         _below = true;
630         // XXX: lower the window n shit
631       }
632
633     } else { // action == State_Remove
634       if (state == property->atom(otk::OBProperty::net_wm_state_modal)) {
635         if (!_modal) continue;
636         _modal = false;
637       } else if (state ==
638                  property->atom(otk::OBProperty::net_wm_state_maximized_vert)){
639         if (!_max_vert) continue;
640         _max_vert = false;
641         // XXX: resize the window etc
642       } else if (state ==
643                  property->atom(otk::OBProperty::net_wm_state_maximized_horz)){
644         if (!_max_horz) continue;
645         _max_horz = false;
646         // XXX: resize the window etc
647       } else if (state ==
648                  property->atom(otk::OBProperty::net_wm_state_shaded)) {
649         if (!_shaded) continue;
650         _shaded = false;
651         // XXX: show the client window
652       } else if (state ==
653                  property->atom(otk::OBProperty::net_wm_state_skip_taskbar)) {
654         _skip_taskbar = false;
655       } else if (state ==
656                  property->atom(otk::OBProperty::net_wm_state_skip_pager)) {
657         _skip_pager = false;
658       } else if (state ==
659                  property->atom(otk::OBProperty::net_wm_state_fullscreen)) {
660         if (!_fullscreen) continue;
661         _fullscreen = false;
662         // XXX: lower the window to its proper layer
663       } else if (state ==
664                  property->atom(otk::OBProperty::net_wm_state_above)) {
665         if (!_above) continue;
666         _above = false;
667         // XXX: lower the window to its proper layer
668       } else if (state ==
669                  property->atom(otk::OBProperty::net_wm_state_below)) {
670         if (!_below) continue;
671         _below = false;
672         // XXX: raise the window to its proper layer
673       }
674     }
675   }
676   calcLayer();
677   Openbox::instance->screen(_screen)->restack(true, this); // raise
678 }
679
680
681 void OBClient::toggleClientBorder(bool addborder)
682 {
683   // adjust our idea of where the client is, based on its border. When the
684   // border is removed, the client should now be considered to be in a
685   // different position.
686   // when re-adding the border to the client, the same operation needs to be
687   // reversed.
688   int x = _area.x(), y = _area.y();
689   switch(_gravity) {
690   case NorthWestGravity:
691   case WestGravity:
692   case SouthWestGravity:
693     break;
694   case NorthEastGravity:
695   case EastGravity:
696   case SouthEastGravity:
697     if (addborder) x -= _border_width * 2;
698     else           x += _border_width * 2;
699     break;
700   }
701   switch(_gravity) {
702   case NorthWestGravity:
703   case NorthGravity:
704   case NorthEastGravity:
705     break;
706   case SouthWestGravity:
707   case SouthGravity:
708   case SouthEastGravity:
709     if (addborder) y -= _border_width * 2;
710     else           y += _border_width * 2;
711     break;
712   default:
713     // no change for StaticGravity etc.
714     break;
715   }
716   _area.setPos(x, y);
717
718   if (addborder) {
719     XSetWindowBorderWidth(otk::OBDisplay::display, _window, _border_width);
720
721     // move the client so it is back it the right spot _with_ its border!
722     XMoveWindow(otk::OBDisplay::display, _window, x, y);
723   } else
724     XSetWindowBorderWidth(otk::OBDisplay::display, _window, 0);
725 }
726
727
728 void OBClient::clientMessageHandler(const XClientMessageEvent &e)
729 {
730   otk::OtkEventHandler::clientMessageHandler(e);
731   
732   if (e.format != 32) return;
733
734   const otk::OBProperty *property = Openbox::instance->property();
735   
736   if (e.message_type == property->atom(otk::OBProperty::wm_change_state)) {
737     // compress changes into a single change
738     bool compress = false;
739     XEvent ce;
740     while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) {
741       // XXX: it would be nice to compress ALL messages of a type, not just
742       //      messages in a row without other message types between.
743       if (ce.xclient.message_type != e.message_type) {
744         XPutBackEvent(otk::OBDisplay::display, &ce);
745         break;
746       }
747       compress = true;
748     }
749     if (compress)
750       setWMState(ce.xclient.data.l[0]); // use the found event
751     else
752       setWMState(e.data.l[0]); // use the original event
753   } else if (e.message_type ==
754              property->atom(otk::OBProperty::net_wm_desktop)) {
755     // compress changes into a single change 
756     bool compress = false;
757     XEvent ce;
758     while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) {
759       // XXX: it would be nice to compress ALL messages of a type, not just
760       //      messages in a row without other message types between.
761       if (ce.xclient.message_type != e.message_type) {
762         XPutBackEvent(otk::OBDisplay::display, &ce);
763         break;
764       }
765       compress = true;
766     }
767     if (compress)
768       setDesktop(e.data.l[0]); // use the found event
769     else
770       setDesktop(e.data.l[0]); // use the original event
771   }
772   else if (e.message_type == property->atom(otk::OBProperty::net_wm_state))
773     // can't compress these
774     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
775 }
776
777
778 #if defined(SHAPE) || defined(DOXYGEN_IGNORE)
779 void OBClient::shapeHandler(const XShapeEvent &e)
780 {
781   otk::OtkEventHandler::shapeHandler(e);
782   
783   _shaped = e.shaped;
784 }
785 #endif
786
787
788 void OBClient::resize(Corner anchor, int w, int h)
789 {
790   w -= _base_size.x(); 
791   h -= _base_size.y();
792
793   // for interactive resizing. have to move half an increment in each
794   // direction.
795   w += _size_inc.x() / 2;
796   h += _size_inc.y() / 2;
797
798   // is the window resizable? if it is not, then don't check its sizes, the
799   // client can do what it wants and the user can't change it anyhow
800   if (_min_size.x() <= _max_size.x() && _min_size.y() <= _max_size.y()) {
801     // smaller than min size or bigger than max size?
802     if (w < _min_size.x()) w = _min_size.x();
803     else if (w > _max_size.x()) w = _max_size.x();
804     if (h < _min_size.y()) h = _min_size.y();
805     else if (h > _max_size.y()) h = _max_size.y();
806   }
807
808   // keep to the increments
809   w /= _size_inc.x();
810   h /= _size_inc.y();
811
812   // store the logical size
813   _logical_size.setPoint(w, h);
814
815   w *= _size_inc.x();
816   h *= _size_inc.y();
817
818   w += _base_size.x();
819   h += _base_size.y();
820
821   int x = _area.x(), y = _area.y();  
822   switch (anchor) {
823   case TopLeft:
824     break;
825   case TopRight:
826     x -= w - _area.width();
827     break;
828   case BottomLeft:
829     y -= h - _area.height();
830     break;
831   case BottomRight:
832     x -= w - _area.width();
833     y -= h - _area.height();
834     break;
835   }
836
837   _area.setSize(w, h);
838   XResizeWindow(otk::OBDisplay::display, _window, w, h);
839
840   // resize the frame to match the request
841   frame->adjustSize();
842   move(x, y);
843 }
844
845
846 void OBClient::move(int x, int y)
847 {
848   _area.setPos(x, y);
849   // move the frame to be in the requested position
850   frame->adjustPosition();
851 }
852
853
854 void OBClient::close()
855 {
856   XEvent ce;
857   const otk::OBProperty *property = Openbox::instance->property();
858
859   if (!(_functions & Func_Close)) return;
860
861   // XXX: itd be cool to do timeouts and shit here for killing the client's
862   //      process off
863
864   ce.xclient.type = ClientMessage;
865   ce.xclient.message_type =  property->atom(otk::OBProperty::wm_protocols);
866   ce.xclient.display = otk::OBDisplay::display;
867   ce.xclient.window = _window;
868   ce.xclient.format = 32;
869   ce.xclient.data.l[0] = property->atom(otk::OBProperty::wm_delete_window);
870   ce.xclient.data.l[1] = CurrentTime;
871   ce.xclient.data.l[2] = 0l;
872   ce.xclient.data.l[3] = 0l;
873   ce.xclient.data.l[4] = 0l;
874   XSendEvent(otk::OBDisplay::display, _window, False, NoEventMask, &ce);
875 }
876
877
878 void OBClient::changeState()
879 {
880   const otk::OBProperty *property = Openbox::instance->property();
881
882   unsigned long state[2];
883   state[0] = _wmstate;
884   state[1] = None;
885   property->set(_window, otk::OBProperty::wm_state, otk::OBProperty::wm_state,
886                 state, 2);
887   
888   Atom netstate[10];
889   int num = 0;
890   if (_modal)
891     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_modal);
892   if (_shaded)
893     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_shaded);
894   if (_iconic)
895     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_hidden);
896   if (_skip_taskbar)
897     netstate[num++] =
898       property->atom(otk::OBProperty::net_wm_state_skip_taskbar);
899   if (_skip_pager)
900     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_skip_pager);
901   if (_fullscreen)
902     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_fullscreen);
903   if (_max_vert)
904     netstate[num++] =
905       property->atom(otk::OBProperty::net_wm_state_maximized_vert);
906   if (_max_horz)
907     netstate[num++] =
908       property->atom(otk::OBProperty::net_wm_state_maximized_horz);
909   if (_above)
910     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_above);
911   if (_below)
912     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_below);
913   property->set(_window, otk::OBProperty::net_wm_state,
914                 otk::OBProperty::Atom_Atom, netstate, num);
915   
916 }
917
918 void OBClient::shade(bool shade)
919 {
920   if (shade == _shaded) return; // already done
921
922   _wmstate = shade ? IconicState : NormalState;
923   _shaded = shade;
924   changeState();
925   frame->adjustSize();
926 }
927
928
929 bool OBClient::focus()
930 {
931   if (!_can_focus || _focused) return false;
932
933   XSetInputFocus(otk::OBDisplay::display, _window, RevertToNone, CurrentTime);
934   return true;
935 }
936
937
938 void OBClient::unfocus()
939 {
940   if (!_focused) return;
941
942   assert(Openbox::instance->focusedClient() == this);
943   Openbox::instance->setFocusedClient(0);
944 }
945
946
947 void OBClient::focusHandler(const XFocusChangeEvent &e)
948 {
949 #ifdef    DEBUG
950   printf("FocusIn for 0x%lx\n", e.window);
951 #endif // DEBUG
952   
953   OtkEventHandler::focusHandler(e);
954
955   frame->focus();
956   _focused = true;
957
958   Openbox::instance->setFocusedClient(this);
959 }
960
961
962 void OBClient::unfocusHandler(const XFocusChangeEvent &e)
963 {
964 #ifdef    DEBUG
965   printf("FocusOut for 0x%lx\n", e.window);
966 #endif // DEBUG
967   
968   OtkEventHandler::unfocusHandler(e);
969
970   frame->unfocus();
971   _focused = false;
972
973   if (Openbox::instance->focusedClient() == this) {
974     printf("UNFOCUSED!\n");
975     Openbox::instance->setFocusedClient(this);
976   }
977 }
978
979
980 void OBClient::configureRequestHandler(const XConfigureRequestEvent &e)
981 {
982 #ifdef    DEBUG
983   printf("ConfigureRequest for 0x%lx\n", e.window);
984 #endif // DEBUG
985   
986   OtkEventHandler::configureRequestHandler(e);
987
988   // XXX: if we are iconic (or shaded? (fvwm does that)) ignore the event
989
990   if (e.value_mask & CWBorderWidth)
991     _border_width = e.border_width;
992
993   // resize, then move, as specified in the EWMH section 7.7
994   if (e.value_mask & (CWWidth | CWHeight)) {
995     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
996     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
997
998     Corner corner;
999     switch (_gravity) {
1000     case NorthEastGravity:
1001     case EastGravity:
1002       corner = TopRight;
1003       break;
1004     case SouthWestGravity:
1005     case SouthGravity:
1006       corner = BottomLeft;
1007       break;
1008     case SouthEastGravity:
1009       corner = BottomRight;
1010       break;
1011     default:     // NorthWest, Static, etc
1012       corner = TopLeft;
1013     }
1014
1015     resize(corner, w, h);
1016   }
1017
1018   if (e.value_mask & (CWX | CWY)) {
1019     int x = (e.value_mask & CWX) ? e.x : _area.x();
1020     int y = (e.value_mask & CWY) ? e.y : _area.y();
1021     move(x, y);
1022   }
1023
1024   if (e.value_mask & CWStackMode) {
1025     switch (e.detail) {
1026     case Below:
1027     case BottomIf:
1028       // XXX: lower the window
1029       break;
1030
1031     case Above:
1032     case TopIf:
1033     default:
1034       // XXX: raise the window
1035       break;
1036     }
1037   }
1038 }
1039
1040
1041 void OBClient::unmapHandler(const XUnmapEvent &e)
1042 {
1043 #ifdef    DEBUG
1044   printf("UnmapNotify for 0x%lx\n", e.window);
1045 #endif // DEBUG
1046
1047   if (ignore_unmaps) {
1048     ignore_unmaps--;
1049     return;
1050   }
1051   
1052   OtkEventHandler::unmapHandler(e);
1053
1054   // this deletes us etc
1055   Openbox::instance->screen(_screen)->unmanageWindow(this);
1056 }
1057
1058
1059 void OBClient::destroyHandler(const XDestroyWindowEvent &e)
1060 {
1061 #ifdef    DEBUG
1062   printf("DestroyNotify for 0x%lx\n", e.window);
1063 #endif // DEBUG
1064
1065   OtkEventHandler::destroyHandler(e);
1066
1067   // this deletes us etc
1068   Openbox::instance->screen(_screen)->unmanageWindow(this);
1069 }
1070
1071
1072 void OBClient::reparentHandler(const XReparentEvent &e)
1073 {
1074   // this is when the client is first taken captive in the frame
1075   if (e.parent == frame->plate()) return;
1076
1077 #ifdef    DEBUG
1078   printf("ReparentNotify for 0x%lx\n", e.window);
1079 #endif // DEBUG
1080
1081   OtkEventHandler::reparentHandler(e);
1082
1083   /*
1084     This event is quite rare and is usually handled in unmapHandler.
1085     However, if the window is unmapped when the reparent event occurs,
1086     the window manager never sees it because an unmap event is not sent
1087     to an already unmapped window.
1088   */
1089
1090   // this deletes us etc
1091   Openbox::instance->screen(_screen)->unmanageWindow(this);
1092 }
1093
1094 }