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