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