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