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