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