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