add the Frame class
[mikachu/openbox.git] / src / client.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #ifdef HAVE_CONFIG_H
4 # include "../config.h"
5 #endif
6
7 #include "client.hh"
8 #include "frame.hh"
9 #include "screen.hh"
10 #include "openbox.hh"
11 #include "otk/display.hh"
12 #include "otk/property.hh"
13
14 extern "C" {
15 #include <X11/Xlib.h>
16 #include <X11/Xutil.h>
17 #include <X11/Xatom.h>
18
19 #include <assert.h>
20
21 #include "gettext.h"
22 #define _(str) gettext(str)
23 }
24
25 namespace ob {
26
27 Client::Client(int screen, Window window)
28   : otk::EventHandler(),
29     WidgetBase(WidgetBase::Type_Client),
30     frame(0), _screen(screen), _window(window)
31 {
32   assert(screen >= 0);
33   assert(window);
34
35   ignore_unmaps = 0;
36   
37   // update EVERYTHING the first time!!
38
39   // the state is kinda assumed to be normal. is this right? XXX
40   _wmstate = NormalState; _iconic = false;
41   // no default decors or functions, each has to be enabled
42   _decorations = _functions = 0;
43   // start unfocused
44   _focused = false;
45   // not a transient by default of course
46   _transient_for = 0;
47   // pick a layer to start from
48   _layer = Layer_Normal;
49   
50   getArea();
51   getDesktop();
52
53   updateTransientFor();
54   getType();
55   getMwmHints();
56
57   setupDecorAndFunctions();
58   
59   getState();
60   getShaped();
61
62   updateProtocols();
63   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   // XXX: 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, int x, int y)
905 {
906   w -= _base_size.x(); 
907   h -= _base_size.y();
908
909   // for interactive resizing. have to move half an increment in each
910   // direction.
911   w += _size_inc.x() / 2;
912   h += _size_inc.y() / 2;
913
914   // is the window resizable? if it is not, then don't check its sizes, the
915   // client can do what it wants and the user can't change it anyhow
916   if (_min_size.x() <= _max_size.x() && _min_size.y() <= _max_size.y()) {
917     // smaller than min size or bigger than max size?
918     if (w < _min_size.x()) w = _min_size.x();
919     else if (w > _max_size.x()) w = _max_size.x();
920     if (h < _min_size.y()) h = _min_size.y();
921     else if (h > _max_size.y()) h = _max_size.y();
922   }
923
924   // keep to the increments
925   w /= _size_inc.x();
926   h /= _size_inc.y();
927
928   // you cannot resize to nothing
929   if (w < 1) w = 1;
930   if (h < 1) h = 1;
931   
932   // store the logical size
933   _logical_size.setPoint(w, h);
934
935   w *= _size_inc.x();
936   h *= _size_inc.y();
937
938   w += _base_size.x();
939   h += _base_size.y();
940
941   if (x == INT_MIN || y == INT_MIN) {
942     x = _area.x();
943     y = _area.y();
944     switch (anchor) {
945     case TopLeft:
946       break;
947     case TopRight:
948       x -= w - _area.width();
949       break;
950     case BottomLeft:
951       y -= h - _area.height();
952       break;
953     case BottomRight:
954       x -= w - _area.width();
955       y -= h - _area.height();
956       break;
957     }
958   }
959
960   _area.setSize(w, h);
961
962   XResizeWindow(**otk::display, _window, w, h);
963
964   // resize the frame to match the request
965   frame->adjustSize();
966   move(x, y);
967 }
968
969
970 void Client::move(int x, int y)
971 {
972   _area.setPos(x, y);
973
974   // move the frame to be in the requested position
975   if (frame) { // this can be called while mapping, before frame exists
976     frame->adjustPosition();
977
978     // send synthetic configure notify (we don't need to if we aren't mapped
979     // yet)
980     XEvent event;
981     event.type = ConfigureNotify;
982     event.xconfigure.display = **otk::display;
983     event.xconfigure.event = _window;
984     event.xconfigure.window = _window;
985     event.xconfigure.x = x;
986     event.xconfigure.y = y;
987     event.xconfigure.width = _area.width();
988     event.xconfigure.height = _area.height();
989     event.xconfigure.border_width = _border_width;
990     event.xconfigure.above = frame->window();
991     event.xconfigure.override_redirect = False;
992     XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
993                StructureNotifyMask, &event);
994   }
995 }
996
997
998 void Client::close()
999 {
1000   XEvent ce;
1001
1002   if (!(_functions & Func_Close)) return;
1003
1004   // XXX: itd be cool to do timeouts and shit here for killing the client's
1005   //      process off
1006   // like... if the window is around after 5 seconds, then the close button
1007   // turns a nice red, and if this function is called again, the client is
1008   // explicitly killed.
1009
1010   ce.xclient.type = ClientMessage;
1011   ce.xclient.message_type =  otk::Property::atoms.wm_protocols;
1012   ce.xclient.display = **otk::display;
1013   ce.xclient.window = _window;
1014   ce.xclient.format = 32;
1015   ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1016   ce.xclient.data.l[1] = CurrentTime;
1017   ce.xclient.data.l[2] = 0l;
1018   ce.xclient.data.l[3] = 0l;
1019   ce.xclient.data.l[4] = 0l;
1020   XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1021 }
1022
1023
1024 void Client::changeState()
1025 {
1026   unsigned long state[2];
1027   state[0] = _wmstate;
1028   state[1] = None;
1029   otk::Property::set(_window, otk::Property::atoms.wm_state,
1030                      otk::Property::atoms.wm_state, state, 2);
1031   
1032   Atom netstate[10];
1033   int num = 0;
1034   if (_modal)
1035     netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1036   if (_shaded)
1037     netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1038   if (_iconic)
1039     netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1040   if (_skip_taskbar)
1041     netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1042   if (_skip_pager)
1043     netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1044   if (_fullscreen)
1045     netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1046   if (_max_vert)
1047     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1048   if (_max_horz)
1049     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1050   if (_above)
1051     netstate[num++] = otk::Property::atoms.net_wm_state_above;
1052   if (_below)
1053     netstate[num++] = otk::Property::atoms.net_wm_state_below;
1054   otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1055                      otk::Property::atoms.atom, netstate, num);
1056
1057   calcLayer();
1058 }
1059
1060
1061 void Client::shade(bool shade)
1062 {
1063   if (shade == _shaded) return; // already done
1064
1065   _wmstate = shade ? IconicState : NormalState;
1066   _shaded = shade;
1067   changeState();
1068   frame->adjustSize();
1069 }
1070
1071
1072 bool Client::focus() const
1073 {
1074   // won't try focus if the client doesn't want it, or if the window isn't
1075   // visible on the screen
1076   if (!(frame->isVisible() && (_can_focus || _focus_notify))) return false;
1077
1078   if (_focused) return true;
1079
1080   if (_can_focus)
1081     XSetInputFocus(**otk::display, _window,
1082                    RevertToNone, CurrentTime);
1083
1084   if (_focus_notify) {
1085     XEvent ce;
1086     ce.xclient.type = ClientMessage;
1087     ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1088     ce.xclient.display = **otk::display;
1089     ce.xclient.window = _window;
1090     ce.xclient.format = 32;
1091     ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1092     ce.xclient.data.l[1] = openbox->lastTime();
1093     ce.xclient.data.l[2] = 0l;
1094     ce.xclient.data.l[3] = 0l;
1095     ce.xclient.data.l[4] = 0l;
1096     XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1097   }
1098
1099   return true;
1100 }
1101
1102
1103 void Client::unfocus() const
1104 {
1105   if (!_focused) return;
1106
1107   assert(openbox->focusedClient() == this);
1108   openbox->setFocusedClient(0);
1109 }
1110
1111
1112 void Client::focusHandler(const XFocusChangeEvent &e)
1113 {
1114 #ifdef    DEBUG
1115 //  printf("FocusIn for 0x%lx\n", e.window);
1116 #endif // DEBUG
1117   
1118   otk::EventHandler::focusHandler(e);
1119
1120   frame->focus();
1121   _focused = true;
1122
1123   openbox->setFocusedClient(this);
1124 }
1125
1126
1127 void Client::unfocusHandler(const XFocusChangeEvent &e)
1128 {
1129 #ifdef    DEBUG
1130 //  printf("FocusOut for 0x%lx\n", e.window);
1131 #endif // DEBUG
1132   
1133   otk::EventHandler::unfocusHandler(e);
1134
1135   frame->unfocus();
1136   _focused = false;
1137
1138   if (openbox->focusedClient() == this)
1139     openbox->setFocusedClient(0);
1140 }
1141
1142
1143 void Client::configureRequestHandler(const XConfigureRequestEvent &e)
1144 {
1145 #ifdef    DEBUG
1146   printf("ConfigureRequest for 0x%lx\n", e.window);
1147 #endif // DEBUG
1148   
1149   otk::EventHandler::configureRequestHandler(e);
1150
1151   // XXX: if we are iconic (or shaded? (fvwm does that)) ignore the event
1152
1153   if (e.value_mask & CWBorderWidth)
1154     _border_width = e.border_width;
1155
1156   // resize, then move, as specified in the EWMH section 7.7
1157   if (e.value_mask & (CWWidth | CWHeight)) {
1158     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1159     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1160
1161     Corner corner;
1162     switch (_gravity) {
1163     case NorthEastGravity:
1164     case EastGravity:
1165       corner = TopRight;
1166       break;
1167     case SouthWestGravity:
1168     case SouthGravity:
1169       corner = BottomLeft;
1170       break;
1171     case SouthEastGravity:
1172       corner = BottomRight;
1173       break;
1174     default:     // NorthWest, Static, etc
1175       corner = TopLeft;
1176     }
1177
1178     // if moving AND resizing ...
1179     if (e.value_mask & (CWX | CWY)) {
1180       int x = (e.value_mask & CWX) ? e.x : _area.x();
1181       int y = (e.value_mask & CWY) ? e.y : _area.y();
1182       resize(corner, w, h, x, y);
1183     } else // if JUST resizing...
1184       resize(corner, w, h);
1185   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1186     int x = (e.value_mask & CWX) ? e.x : _area.x();
1187     int y = (e.value_mask & CWY) ? e.y : _area.y();
1188     move(x, y);
1189   }
1190
1191   if (e.value_mask & CWStackMode) {
1192     switch (e.detail) {
1193     case Below:
1194     case BottomIf:
1195       openbox->screen(_screen)->lowerWindow(this);
1196       break;
1197
1198     case Above:
1199     case TopIf:
1200     default:
1201       openbox->screen(_screen)->raiseWindow(this);
1202       break;
1203     }
1204   }
1205 }
1206
1207
1208 void Client::unmapHandler(const XUnmapEvent &e)
1209 {
1210   if (ignore_unmaps) {
1211 #ifdef    DEBUG
1212     printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1213 #endif // DEBUG
1214     ignore_unmaps--;
1215     return;
1216   }
1217   
1218 #ifdef    DEBUG
1219   printf("UnmapNotify for 0x%lx\n", e.window);
1220 #endif // DEBUG
1221
1222   otk::EventHandler::unmapHandler(e);
1223
1224   // this deletes us etc
1225   openbox->screen(_screen)->unmanageWindow(this);
1226 }
1227
1228
1229 void Client::destroyHandler(const XDestroyWindowEvent &e)
1230 {
1231 #ifdef    DEBUG
1232   printf("DestroyNotify for 0x%lx\n", e.window);
1233 #endif // DEBUG
1234
1235   otk::EventHandler::destroyHandler(e);
1236
1237   // this deletes us etc
1238   openbox->screen(_screen)->unmanageWindow(this);
1239 }
1240
1241
1242 void Client::reparentHandler(const XReparentEvent &e)
1243 {
1244   // this is when the client is first taken captive in the frame
1245   if (e.parent == frame->plate()) return;
1246
1247 #ifdef    DEBUG
1248   printf("ReparentNotify for 0x%lx\n", e.window);
1249 #endif // DEBUG
1250
1251   otk::EventHandler::reparentHandler(e);
1252
1253   /*
1254     This event is quite rare and is usually handled in unmapHandler.
1255     However, if the window is unmapped when the reparent event occurs,
1256     the window manager never sees it because an unmap event is not sent
1257     to an already unmapped window.
1258   */
1259
1260   // we don't want the reparent event, put it back on the stack for the X
1261   // server to deal with after we unmanage the window
1262   XEvent ev;
1263   ev.xreparent = e;
1264   XPutBackEvent(**otk::display, &ev);
1265   
1266   // this deletes us etc
1267   openbox->screen(_screen)->unmanageWindow(this);
1268 }
1269
1270 }