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