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