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