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