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