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