]> icculus.org git repositories - mikachu/openbox.git/blob - src/client.cc
windows stay on their desktops
[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   printf("Setting desktop %ld\n", target);
659   assert(target >= 0 || target == (signed)0xffffffff);
660   //assert(target == 0xffffffff || target < MAX);
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   
672   // XXX: move the window to the new desktop
673 }
674
675
676 void OBClient::setState(StateAction action, long data1, long data2)
677 {
678   const otk::OBProperty *property = Openbox::instance->property();
679   bool shadestate = _shaded;
680
681   if (!(action == State_Add || action == State_Remove ||
682         action == State_Toggle))
683     return; // an invalid action was passed to the client message, ignore it
684
685   for (int i = 0; i < 2; ++i) {
686     Atom state = i == 0 ? data1 : data2;
687     
688     if (! state) continue;
689
690     // if toggling, then pick whether we're adding or removing
691     if (action == State_Toggle) {
692       if (state == property->atom(otk::OBProperty::net_wm_state_modal))
693         action = _modal ? State_Remove : State_Add;
694       else if (state ==
695                property->atom(otk::OBProperty::net_wm_state_maximized_vert))
696         action = _max_vert ? State_Remove : State_Add;
697       else if (state ==
698                property->atom(otk::OBProperty::net_wm_state_maximized_horz))
699         action = _max_horz ? State_Remove : State_Add;
700       else if (state == property->atom(otk::OBProperty::net_wm_state_shaded))
701         action = _shaded ? State_Remove : State_Add;
702       else if (state ==
703                property->atom(otk::OBProperty::net_wm_state_skip_taskbar))
704         action = _skip_taskbar ? State_Remove : State_Add;
705       else if (state ==
706                property->atom(otk::OBProperty::net_wm_state_skip_pager))
707         action = _skip_pager ? State_Remove : State_Add;
708       else if (state ==
709                property->atom(otk::OBProperty::net_wm_state_fullscreen))
710         action = _fullscreen ? State_Remove : State_Add;
711       else if (state == property->atom(otk::OBProperty::net_wm_state_above))
712         action = _above ? State_Remove : State_Add;
713       else if (state == property->atom(otk::OBProperty::net_wm_state_below))
714         action = _below ? State_Remove : State_Add;
715     }
716     
717     if (action == State_Add) {
718       if (state == property->atom(otk::OBProperty::net_wm_state_modal)) {
719         if (_modal) continue;
720         _modal = true;
721         // XXX: give it focus if another window has focus that shouldnt now
722       } else if (state ==
723                  property->atom(otk::OBProperty::net_wm_state_maximized_vert)){
724         if (_max_vert) continue;
725         _max_vert = true;
726         // XXX: resize the window etc
727       } else if (state ==
728                  property->atom(otk::OBProperty::net_wm_state_maximized_horz)){
729         if (_max_horz) continue;
730         _max_horz = true;
731         // XXX: resize the window etc
732       } else if (state ==
733                  property->atom(otk::OBProperty::net_wm_state_shaded)) {
734         if (_shaded) continue;
735         // shade when we're all thru here
736         shadestate = true;
737       } else if (state ==
738                  property->atom(otk::OBProperty::net_wm_state_skip_taskbar)) {
739         _skip_taskbar = true;
740       } else if (state ==
741                  property->atom(otk::OBProperty::net_wm_state_skip_pager)) {
742         _skip_pager = true;
743       } else if (state ==
744                  property->atom(otk::OBProperty::net_wm_state_fullscreen)) {
745         if (_fullscreen) continue;
746         _fullscreen = true;
747       } else if (state ==
748                  property->atom(otk::OBProperty::net_wm_state_above)) {
749         if (_above) continue;
750         _above = true;
751       } else if (state ==
752                  property->atom(otk::OBProperty::net_wm_state_below)) {
753         if (_below) continue;
754         _below = true;
755       }
756
757     } else { // action == State_Remove
758       if (state == property->atom(otk::OBProperty::net_wm_state_modal)) {
759         if (!_modal) continue;
760         _modal = false;
761       } else if (state ==
762                  property->atom(otk::OBProperty::net_wm_state_maximized_vert)){
763         if (!_max_vert) continue;
764         _max_vert = false;
765         // XXX: resize the window etc
766       } else if (state ==
767                  property->atom(otk::OBProperty::net_wm_state_maximized_horz)){
768         if (!_max_horz) continue;
769         _max_horz = false;
770         // XXX: resize the window etc
771       } else if (state ==
772                  property->atom(otk::OBProperty::net_wm_state_shaded)) {
773         if (!_shaded) continue;
774         // unshade when we're all thru here
775         shadestate = false;
776       } else if (state ==
777                  property->atom(otk::OBProperty::net_wm_state_skip_taskbar)) {
778         _skip_taskbar = false;
779       } else if (state ==
780                  property->atom(otk::OBProperty::net_wm_state_skip_pager)) {
781         _skip_pager = false;
782       } else if (state ==
783                  property->atom(otk::OBProperty::net_wm_state_fullscreen)) {
784         if (!_fullscreen) continue;
785         _fullscreen = false;
786       } else if (state ==
787                  property->atom(otk::OBProperty::net_wm_state_above)) {
788         if (!_above) continue;
789         _above = false;
790       } else if (state ==
791                  property->atom(otk::OBProperty::net_wm_state_below)) {
792         if (!_below) continue;
793         _below = false;
794       }
795     }
796   }
797   if (shadestate != _shaded)
798     shade(shadestate);
799   calcLayer();
800 }
801
802
803 void OBClient::toggleClientBorder(bool addborder)
804 {
805   // adjust our idea of where the client is, based on its border. When the
806   // border is removed, the client should now be considered to be in a
807   // different position.
808   // when re-adding the border to the client, the same operation needs to be
809   // reversed.
810   int x = _area.x(), y = _area.y();
811   switch(_gravity) {
812   case NorthWestGravity:
813   case WestGravity:
814   case SouthWestGravity:
815     break;
816   case NorthEastGravity:
817   case EastGravity:
818   case SouthEastGravity:
819     if (addborder) x -= _border_width * 2;
820     else           x += _border_width * 2;
821     break;
822   }
823   switch(_gravity) {
824   case NorthWestGravity:
825   case NorthGravity:
826   case NorthEastGravity:
827     break;
828   case SouthWestGravity:
829   case SouthGravity:
830   case SouthEastGravity:
831     if (addborder) y -= _border_width * 2;
832     else           y += _border_width * 2;
833     break;
834   default:
835     // no change for StaticGravity etc.
836     break;
837   }
838   _area.setPos(x, y);
839
840   if (addborder) {
841     XSetWindowBorderWidth(otk::OBDisplay::display, _window, _border_width);
842
843     // move the client so it is back it the right spot _with_ its border!
844     XMoveWindow(otk::OBDisplay::display, _window, x, y);
845   } else
846     XSetWindowBorderWidth(otk::OBDisplay::display, _window, 0);
847 }
848
849
850 void OBClient::clientMessageHandler(const XClientMessageEvent &e)
851 {
852   otk::OtkEventHandler::clientMessageHandler(e);
853   
854   if (e.format != 32) return;
855
856   const otk::OBProperty *property = Openbox::instance->property();
857   
858   if (e.message_type == property->atom(otk::OBProperty::wm_change_state)) {
859     // compress changes into a single change
860     bool compress = false;
861     XEvent ce;
862     while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) {
863       // XXX: it would be nice to compress ALL messages of a type, not just
864       //      messages in a row without other message types between.
865       if (ce.xclient.message_type != e.message_type) {
866         XPutBackEvent(otk::OBDisplay::display, &ce);
867         break;
868       }
869       compress = true;
870     }
871     if (compress)
872       setWMState(ce.xclient.data.l[0]); // use the found event
873     else
874       setWMState(e.data.l[0]); // use the original event
875   } else if (e.message_type ==
876              property->atom(otk::OBProperty::net_wm_desktop)) {
877     // compress changes into a single change 
878     bool compress = false;
879     XEvent ce;
880     while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) {
881       // XXX: it would be nice to compress ALL messages of a type, not just
882       //      messages in a row without other message types between.
883       if (ce.xclient.message_type != e.message_type) {
884         XPutBackEvent(otk::OBDisplay::display, &ce);
885         break;
886       }
887       compress = true;
888     }
889     if (compress)
890       setDesktop(e.data.l[0]); // use the found event
891     else
892       setDesktop(e.data.l[0]); // use the original event
893   } else if (e.message_type == property->atom(otk::OBProperty::net_wm_state)) {
894     // can't compress these
895 #ifdef DEBUG
896     printf("net_wm_state for 0x%lx\n", _window);
897 #endif
898     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
899   } else if (e.message_type ==
900              property->atom(otk::OBProperty::net_close_window)) {
901 #ifdef DEBUG
902     printf("net_close_window for 0x%lx\n", _window);
903 #endif
904     close();
905   } else if (e.message_type ==
906              property->atom(otk::OBProperty::net_active_window)) {
907 #ifdef DEBUG
908     printf("net_active_window for 0x%lx\n", _window);
909 #endif
910     focus();
911     Openbox::instance->screen(_screen)->restack(true, this); // raise
912   }
913 }
914
915
916 #if defined(SHAPE)
917 void OBClient::shapeHandler(const XShapeEvent &e)
918 {
919   otk::OtkEventHandler::shapeHandler(e);
920   
921   _shaped = e.shaped;
922   frame->adjustShape();
923 }
924 #endif
925
926
927 void OBClient::resize(Corner anchor, int w, int h, int x, int y)
928 {
929   w -= _base_size.x(); 
930   h -= _base_size.y();
931
932   // for interactive resizing. have to move half an increment in each
933   // direction.
934   w += _size_inc.x() / 2;
935   h += _size_inc.y() / 2;
936
937   // is the window resizable? if it is not, then don't check its sizes, the
938   // client can do what it wants and the user can't change it anyhow
939   if (_min_size.x() <= _max_size.x() && _min_size.y() <= _max_size.y()) {
940     // smaller than min size or bigger than max size?
941     if (w < _min_size.x()) w = _min_size.x();
942     else if (w > _max_size.x()) w = _max_size.x();
943     if (h < _min_size.y()) h = _min_size.y();
944     else if (h > _max_size.y()) h = _max_size.y();
945   }
946
947   // keep to the increments
948   w /= _size_inc.x();
949   h /= _size_inc.y();
950
951   // store the logical size
952   _logical_size.setPoint(w, h);
953
954   w *= _size_inc.x();
955   h *= _size_inc.y();
956
957   w += _base_size.x();
958   h += _base_size.y();
959
960   if (x == INT_MIN || y == INT_MIN) {
961     x = _area.x();
962     y = _area.y();
963     switch (anchor) {
964     case TopLeft:
965       break;
966     case TopRight:
967       x -= w - _area.width();
968       break;
969     case BottomLeft:
970       y -= h - _area.height();
971       break;
972     case BottomRight:
973       x -= w - _area.width();
974       y -= h - _area.height();
975       break;
976     }
977   }
978
979   _area.setSize(w, h);
980
981   XResizeWindow(otk::OBDisplay::display, _window, w, h);
982
983   // resize the frame to match the request
984   frame->adjustSize();
985   move(x, y);
986 }
987
988
989 void OBClient::move(int x, int y)
990 {
991   _area.setPos(x, y);
992
993   // move the frame to be in the requested position
994   frame->adjustPosition();
995 }
996
997
998 void OBClient::close()
999 {
1000   XEvent ce;
1001   const otk::OBProperty *property = Openbox::instance->property();
1002
1003   if (!(_functions & Func_Close)) return;
1004
1005   // XXX: itd be cool to do timeouts and shit here for killing the client's
1006   //      process off
1007   // like... if the window is around after 5 seconds, then the close button
1008   // turns a nice red, and if this function is called again, the client is
1009   // explicitly killed.
1010
1011   ce.xclient.type = ClientMessage;
1012   ce.xclient.message_type =  property->atom(otk::OBProperty::wm_protocols);
1013   ce.xclient.display = otk::OBDisplay::display;
1014   ce.xclient.window = _window;
1015   ce.xclient.format = 32;
1016   ce.xclient.data.l[0] = property->atom(otk::OBProperty::wm_delete_window);
1017   ce.xclient.data.l[1] = CurrentTime;
1018   ce.xclient.data.l[2] = 0l;
1019   ce.xclient.data.l[3] = 0l;
1020   ce.xclient.data.l[4] = 0l;
1021   XSendEvent(otk::OBDisplay::display, _window, False, NoEventMask, &ce);
1022 }
1023
1024
1025 void OBClient::changeState()
1026 {
1027   const otk::OBProperty *property = Openbox::instance->property();
1028
1029   unsigned long state[2];
1030   state[0] = _wmstate;
1031   state[1] = None;
1032   property->set(_window, otk::OBProperty::wm_state, otk::OBProperty::wm_state,
1033                 state, 2);
1034   
1035   Atom netstate[10];
1036   int num = 0;
1037   if (_modal)
1038     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_modal);
1039   if (_shaded)
1040     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_shaded);
1041   if (_iconic)
1042     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_hidden);
1043   if (_skip_taskbar)
1044     netstate[num++] =
1045       property->atom(otk::OBProperty::net_wm_state_skip_taskbar);
1046   if (_skip_pager)
1047     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_skip_pager);
1048   if (_fullscreen)
1049     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_fullscreen);
1050   if (_max_vert)
1051     netstate[num++] =
1052       property->atom(otk::OBProperty::net_wm_state_maximized_vert);
1053   if (_max_horz)
1054     netstate[num++] =
1055       property->atom(otk::OBProperty::net_wm_state_maximized_horz);
1056   if (_above)
1057     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_above);
1058   if (_below)
1059     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_below);
1060   property->set(_window, otk::OBProperty::net_wm_state,
1061                 otk::OBProperty::Atom_Atom, netstate, num);
1062
1063   calcLayer();
1064 }
1065
1066
1067 void OBClient::setStackLayer(int l)
1068 {
1069   if (l == 0)
1070     _above = _below = false;  // normal
1071   else if (l > 0) {
1072     _above = true;
1073     _below = false; // above
1074   } else {
1075     _above = false;
1076     _below = true;  // below
1077   }
1078   changeState();
1079 }
1080
1081
1082 void OBClient::shade(bool shade)
1083 {
1084   if (shade == _shaded) return; // already done
1085
1086   _wmstate = shade ? IconicState : NormalState;
1087   _shaded = shade;
1088   changeState();
1089   frame->adjustSize();
1090 }
1091
1092
1093 bool OBClient::focus()
1094 {
1095   if (!(_can_focus || _focus_notify) || _focused) return false;
1096
1097   if (_can_focus)
1098     XSetInputFocus(otk::OBDisplay::display, _window, RevertToNone, CurrentTime);
1099
1100   if (_focus_notify) {
1101     XEvent ce;
1102     const otk::OBProperty *property = Openbox::instance->property();
1103     
1104     ce.xclient.type = ClientMessage;
1105     ce.xclient.message_type =  property->atom(otk::OBProperty::wm_protocols);
1106     ce.xclient.display = otk::OBDisplay::display;
1107     ce.xclient.window = _window;
1108     ce.xclient.format = 32;
1109     ce.xclient.data.l[0] = property->atom(otk::OBProperty::wm_take_focus);
1110     ce.xclient.data.l[1] = Openbox::instance->lastTime();
1111     ce.xclient.data.l[2] = 0l;
1112     ce.xclient.data.l[3] = 0l;
1113     ce.xclient.data.l[4] = 0l;
1114     XSendEvent(otk::OBDisplay::display, _window, False, NoEventMask, &ce);
1115   }
1116
1117   return true;
1118 }
1119
1120
1121 void OBClient::unfocus()
1122 {
1123   if (!_focused) return;
1124
1125   assert(Openbox::instance->focusedClient() == this);
1126   Openbox::instance->setFocusedClient(0);
1127 }
1128
1129
1130 void OBClient::focusHandler(const XFocusChangeEvent &e)
1131 {
1132 #ifdef    DEBUG
1133 //  printf("FocusIn for 0x%lx\n", e.window);
1134 #endif // DEBUG
1135   
1136   OtkEventHandler::focusHandler(e);
1137
1138   frame->focus();
1139   _focused = true;
1140
1141   Openbox::instance->setFocusedClient(this);
1142 }
1143
1144
1145 void OBClient::unfocusHandler(const XFocusChangeEvent &e)
1146 {
1147 #ifdef    DEBUG
1148 //  printf("FocusOut for 0x%lx\n", e.window);
1149 #endif // DEBUG
1150   
1151   OtkEventHandler::unfocusHandler(e);
1152
1153   frame->unfocus();
1154   _focused = false;
1155
1156   if (Openbox::instance->focusedClient() == this)
1157     Openbox::instance->setFocusedClient(0);
1158 }
1159
1160
1161 void OBClient::configureRequestHandler(const XConfigureRequestEvent &e)
1162 {
1163 #ifdef    DEBUG
1164   printf("ConfigureRequest for 0x%lx\n", e.window);
1165 #endif // DEBUG
1166   
1167   OtkEventHandler::configureRequestHandler(e);
1168
1169   // XXX: if we are iconic (or shaded? (fvwm does that)) ignore the event
1170
1171   if (e.value_mask & CWBorderWidth)
1172     _border_width = e.border_width;
1173
1174   // resize, then move, as specified in the EWMH section 7.7
1175   if (e.value_mask & (CWWidth | CWHeight)) {
1176     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1177     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1178
1179     Corner corner;
1180     switch (_gravity) {
1181     case NorthEastGravity:
1182     case EastGravity:
1183       corner = TopRight;
1184       break;
1185     case SouthWestGravity:
1186     case SouthGravity:
1187       corner = BottomLeft;
1188       break;
1189     case SouthEastGravity:
1190       corner = BottomRight;
1191       break;
1192     default:     // NorthWest, Static, etc
1193       corner = TopLeft;
1194     }
1195
1196     // if moving AND resizing ...
1197     if (e.value_mask & (CWX | CWY)) {
1198       int x = (e.value_mask & CWX) ? e.x : _area.x();
1199       int y = (e.value_mask & CWY) ? e.y : _area.y();
1200       resize(corner, w, h, x, y);
1201     } else // if JUST resizing...
1202       resize(corner, w, h);
1203   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1204     int x = (e.value_mask & CWX) ? e.x : _area.x();
1205     int y = (e.value_mask & CWY) ? e.y : _area.y();
1206     move(x, y);
1207   }
1208
1209   if (e.value_mask & CWStackMode) {
1210     switch (e.detail) {
1211     case Below:
1212     case BottomIf:
1213       Openbox::instance->screen(_screen)->restack(false, this); // lower
1214       break;
1215
1216     case Above:
1217     case TopIf:
1218     default:
1219       Openbox::instance->screen(_screen)->restack(true, this); // raise
1220       break;
1221     }
1222   }
1223 }
1224
1225
1226 void OBClient::unmapHandler(const XUnmapEvent &e)
1227 {
1228 #ifdef    DEBUG
1229   printf("UnmapNotify for 0x%lx\n", e.window);
1230 #endif // DEBUG
1231
1232   if (ignore_unmaps) {
1233     ignore_unmaps--;
1234     return;
1235   }
1236   
1237   OtkEventHandler::unmapHandler(e);
1238
1239   // this deletes us etc
1240   Openbox::instance->screen(_screen)->unmanageWindow(this);
1241 }
1242
1243
1244 void OBClient::destroyHandler(const XDestroyWindowEvent &e)
1245 {
1246 #ifdef    DEBUG
1247   printf("DestroyNotify for 0x%lx\n", e.window);
1248 #endif // DEBUG
1249
1250   OtkEventHandler::destroyHandler(e);
1251
1252   // this deletes us etc
1253   Openbox::instance->screen(_screen)->unmanageWindow(this);
1254 }
1255
1256
1257 void OBClient::reparentHandler(const XReparentEvent &e)
1258 {
1259   // this is when the client is first taken captive in the frame
1260   if (e.parent == frame->plate()) return;
1261
1262 #ifdef    DEBUG
1263   printf("ReparentNotify for 0x%lx\n", e.window);
1264 #endif // DEBUG
1265
1266   OtkEventHandler::reparentHandler(e);
1267
1268   /*
1269     This event is quite rare and is usually handled in unmapHandler.
1270     However, if the window is unmapped when the reparent event occurs,
1271     the window manager never sees it because an unmap event is not sent
1272     to an already unmapped window.
1273   */
1274
1275   // this deletes us etc
1276   Openbox::instance->screen(_screen)->unmanageWindow(this);
1277 }
1278
1279 }