]> icculus.org git repositories - mikachu/openbox.git/blob - src/client.cc
keep track of transients. however not group transients yet
[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   }
612   else if (e.atom == property->atom(otk::OBProperty::net_wm_name) ||
613            e.atom == property->atom(otk::OBProperty::wm_name))
614     updateTitle();
615   else if (e.atom == property->atom(otk::OBProperty::net_wm_icon_name) ||
616            e.atom == property->atom(otk::OBProperty::wm_icon_name))
617     updateIconTitle();
618   else if (e.atom == property->atom(otk::OBProperty::wm_class))
619     updateClass();
620   else if (e.atom == property->atom(otk::OBProperty::wm_protocols))
621     updateProtocols();
622   else if (e.atom == property->atom(otk::OBProperty::net_wm_strut))
623     updateStrut();
624 }
625
626
627 void OBClient::setWMState(long state)
628 {
629   if (state == _wmstate) return; // no change
630   
631   _wmstate = state;
632   switch (_wmstate) {
633   case IconicState:
634     // XXX: cause it to iconify
635     break;
636   case NormalState:
637     // XXX: cause it to uniconify
638     break;
639   }
640 }
641
642
643 void OBClient::setDesktop(long target)
644 {
645   printf("Setting desktop %ld\n", target);
646   assert(target >= 0 || target == (signed)0xffffffff);
647   //assert(target == 0xffffffff || target < MAX);
648
649   // XXX: move the window to the new desktop (and set root property)
650   _desktop = target;
651 }
652
653
654 void OBClient::setState(StateAction action, long data1, long data2)
655 {
656   const otk::OBProperty *property = Openbox::instance->property();
657   bool shadestate = _shaded;
658
659   if (!(action == State_Add || action == State_Remove ||
660         action == State_Toggle))
661     return; // an invalid action was passed to the client message, ignore it
662
663   for (int i = 0; i < 2; ++i) {
664     Atom state = i == 0 ? data1 : data2;
665     
666     if (! state) continue;
667
668     // if toggling, then pick whether we're adding or removing
669     if (action == State_Toggle) {
670       if (state == property->atom(otk::OBProperty::net_wm_state_modal))
671         action = _modal ? State_Remove : State_Add;
672       else if (state ==
673                property->atom(otk::OBProperty::net_wm_state_maximized_vert))
674         action = _max_vert ? State_Remove : State_Add;
675       else if (state ==
676                property->atom(otk::OBProperty::net_wm_state_maximized_horz))
677         action = _max_horz ? State_Remove : State_Add;
678       else if (state == property->atom(otk::OBProperty::net_wm_state_shaded))
679         action = _shaded ? State_Remove : State_Add;
680       else if (state ==
681                property->atom(otk::OBProperty::net_wm_state_skip_taskbar))
682         action = _skip_taskbar ? State_Remove : State_Add;
683       else if (state ==
684                property->atom(otk::OBProperty::net_wm_state_skip_pager))
685         action = _skip_pager ? State_Remove : State_Add;
686       else if (state ==
687                property->atom(otk::OBProperty::net_wm_state_fullscreen))
688         action = _fullscreen ? State_Remove : State_Add;
689       else if (state == property->atom(otk::OBProperty::net_wm_state_above))
690         action = _above ? State_Remove : State_Add;
691       else if (state == property->atom(otk::OBProperty::net_wm_state_below))
692         action = _below ? State_Remove : State_Add;
693     }
694     
695     if (action == State_Add) {
696       if (state == property->atom(otk::OBProperty::net_wm_state_modal)) {
697         if (_modal) continue;
698         _modal = true;
699         // XXX: give it focus if another window has focus that shouldnt now
700       } else if (state ==
701                  property->atom(otk::OBProperty::net_wm_state_maximized_vert)){
702         if (_max_vert) continue;
703         _max_vert = true;
704         // XXX: resize the window etc
705       } else if (state ==
706                  property->atom(otk::OBProperty::net_wm_state_maximized_horz)){
707         if (_max_horz) continue;
708         _max_horz = true;
709         // XXX: resize the window etc
710       } else if (state ==
711                  property->atom(otk::OBProperty::net_wm_state_shaded)) {
712         if (_shaded) continue;
713         // shade when we're all thru here
714         shadestate = true;
715       } else if (state ==
716                  property->atom(otk::OBProperty::net_wm_state_skip_taskbar)) {
717         _skip_taskbar = true;
718       } else if (state ==
719                  property->atom(otk::OBProperty::net_wm_state_skip_pager)) {
720         _skip_pager = true;
721       } else if (state ==
722                  property->atom(otk::OBProperty::net_wm_state_fullscreen)) {
723         if (_fullscreen) continue;
724         _fullscreen = true;
725       } else if (state ==
726                  property->atom(otk::OBProperty::net_wm_state_above)) {
727         if (_above) continue;
728         _above = true;
729       } else if (state ==
730                  property->atom(otk::OBProperty::net_wm_state_below)) {
731         if (_below) continue;
732         _below = true;
733       }
734
735     } else { // action == State_Remove
736       if (state == property->atom(otk::OBProperty::net_wm_state_modal)) {
737         if (!_modal) continue;
738         _modal = false;
739       } else if (state ==
740                  property->atom(otk::OBProperty::net_wm_state_maximized_vert)){
741         if (!_max_vert) continue;
742         _max_vert = false;
743         // XXX: resize the window etc
744       } else if (state ==
745                  property->atom(otk::OBProperty::net_wm_state_maximized_horz)){
746         if (!_max_horz) continue;
747         _max_horz = false;
748         // XXX: resize the window etc
749       } else if (state ==
750                  property->atom(otk::OBProperty::net_wm_state_shaded)) {
751         if (!_shaded) continue;
752         // unshade when we're all thru here
753         shadestate = false;
754       } else if (state ==
755                  property->atom(otk::OBProperty::net_wm_state_skip_taskbar)) {
756         _skip_taskbar = false;
757       } else if (state ==
758                  property->atom(otk::OBProperty::net_wm_state_skip_pager)) {
759         _skip_pager = false;
760       } else if (state ==
761                  property->atom(otk::OBProperty::net_wm_state_fullscreen)) {
762         if (!_fullscreen) continue;
763         _fullscreen = false;
764       } else if (state ==
765                  property->atom(otk::OBProperty::net_wm_state_above)) {
766         if (!_above) continue;
767         _above = false;
768       } else if (state ==
769                  property->atom(otk::OBProperty::net_wm_state_below)) {
770         if (!_below) continue;
771         _below = false;
772       }
773     }
774   }
775   if (shadestate != _shaded)
776     shade(shadestate);
777   calcLayer();
778 }
779
780
781 void OBClient::toggleClientBorder(bool addborder)
782 {
783   // adjust our idea of where the client is, based on its border. When the
784   // border is removed, the client should now be considered to be in a
785   // different position.
786   // when re-adding the border to the client, the same operation needs to be
787   // reversed.
788   int x = _area.x(), y = _area.y();
789   switch(_gravity) {
790   case NorthWestGravity:
791   case WestGravity:
792   case SouthWestGravity:
793     break;
794   case NorthEastGravity:
795   case EastGravity:
796   case SouthEastGravity:
797     if (addborder) x -= _border_width * 2;
798     else           x += _border_width * 2;
799     break;
800   }
801   switch(_gravity) {
802   case NorthWestGravity:
803   case NorthGravity:
804   case NorthEastGravity:
805     break;
806   case SouthWestGravity:
807   case SouthGravity:
808   case SouthEastGravity:
809     if (addborder) y -= _border_width * 2;
810     else           y += _border_width * 2;
811     break;
812   default:
813     // no change for StaticGravity etc.
814     break;
815   }
816   _area.setPos(x, y);
817
818   if (addborder) {
819     XSetWindowBorderWidth(otk::OBDisplay::display, _window, _border_width);
820
821     // move the client so it is back it the right spot _with_ its border!
822     XMoveWindow(otk::OBDisplay::display, _window, x, y);
823   } else
824     XSetWindowBorderWidth(otk::OBDisplay::display, _window, 0);
825 }
826
827
828 void OBClient::clientMessageHandler(const XClientMessageEvent &e)
829 {
830   otk::OtkEventHandler::clientMessageHandler(e);
831   
832   if (e.format != 32) return;
833
834   const otk::OBProperty *property = Openbox::instance->property();
835   
836   if (e.message_type == property->atom(otk::OBProperty::wm_change_state)) {
837     // compress changes into a single change
838     bool compress = false;
839     XEvent ce;
840     while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) {
841       // XXX: it would be nice to compress ALL messages of a type, not just
842       //      messages in a row without other message types between.
843       if (ce.xclient.message_type != e.message_type) {
844         XPutBackEvent(otk::OBDisplay::display, &ce);
845         break;
846       }
847       compress = true;
848     }
849     if (compress)
850       setWMState(ce.xclient.data.l[0]); // use the found event
851     else
852       setWMState(e.data.l[0]); // use the original event
853   } else if (e.message_type ==
854              property->atom(otk::OBProperty::net_wm_desktop)) {
855     // compress changes into a single change 
856     bool compress = false;
857     XEvent ce;
858     while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) {
859       // XXX: it would be nice to compress ALL messages of a type, not just
860       //      messages in a row without other message types between.
861       if (ce.xclient.message_type != e.message_type) {
862         XPutBackEvent(otk::OBDisplay::display, &ce);
863         break;
864       }
865       compress = true;
866     }
867     if (compress)
868       setDesktop(e.data.l[0]); // use the found event
869     else
870       setDesktop(e.data.l[0]); // use the original event
871   } else if (e.message_type == property->atom(otk::OBProperty::net_wm_state)) {
872     // can't compress these
873     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
874   } else if (e.message_type ==
875              property->atom(otk::OBProperty::net_close_window)) {
876     close();
877   } else if (e.message_type ==
878              property->atom(otk::OBProperty::net_active_window)) {
879     focus();
880     Openbox::instance->screen(_screen)->restack(true, this); // raise
881   } else {
882   }
883 }
884
885
886 #if defined(SHAPE)
887 void OBClient::shapeHandler(const XShapeEvent &e)
888 {
889   otk::OtkEventHandler::shapeHandler(e);
890   
891   _shaped = e.shaped;
892   frame->adjustShape();
893 }
894 #endif
895
896
897 void OBClient::resize(Corner anchor, int w, int h, int x, int y)
898 {
899   w -= _base_size.x(); 
900   h -= _base_size.y();
901
902   // for interactive resizing. have to move half an increment in each
903   // direction.
904   w += _size_inc.x() / 2;
905   h += _size_inc.y() / 2;
906
907   // is the window resizable? if it is not, then don't check its sizes, the
908   // client can do what it wants and the user can't change it anyhow
909   if (_min_size.x() <= _max_size.x() && _min_size.y() <= _max_size.y()) {
910     // smaller than min size or bigger than max size?
911     if (w < _min_size.x()) w = _min_size.x();
912     else if (w > _max_size.x()) w = _max_size.x();
913     if (h < _min_size.y()) h = _min_size.y();
914     else if (h > _max_size.y()) h = _max_size.y();
915   }
916
917   // keep to the increments
918   w /= _size_inc.x();
919   h /= _size_inc.y();
920
921   // store the logical size
922   _logical_size.setPoint(w, h);
923
924   w *= _size_inc.x();
925   h *= _size_inc.y();
926
927   w += _base_size.x();
928   h += _base_size.y();
929
930   if (x == INT_MIN || y == INT_MIN) {
931     x = _area.x();
932     y = _area.y();
933     switch (anchor) {
934     case TopLeft:
935       break;
936     case TopRight:
937       x -= w - _area.width();
938       break;
939     case BottomLeft:
940       y -= h - _area.height();
941       break;
942     case BottomRight:
943       x -= w - _area.width();
944       y -= h - _area.height();
945       break;
946     }
947   }
948
949   _area.setSize(w, h);
950
951   XResizeWindow(otk::OBDisplay::display, _window, w, h);
952
953   // resize the frame to match the request
954   frame->adjustSize();
955   move(x, y);
956 }
957
958
959 void OBClient::move(int x, int y)
960 {
961   _area.setPos(x, y);
962
963   // move the frame to be in the requested position
964   frame->adjustPosition();
965 }
966
967
968 void OBClient::close()
969 {
970   XEvent ce;
971   const otk::OBProperty *property = Openbox::instance->property();
972
973   if (!(_functions & Func_Close)) return;
974
975   // XXX: itd be cool to do timeouts and shit here for killing the client's
976   //      process off
977   // like... if the window is around after 5 seconds, then the close button
978   // turns a nice red, and if this function is called again, the client is
979   // explicitly killed.
980
981   ce.xclient.type = ClientMessage;
982   ce.xclient.message_type =  property->atom(otk::OBProperty::wm_protocols);
983   ce.xclient.display = otk::OBDisplay::display;
984   ce.xclient.window = _window;
985   ce.xclient.format = 32;
986   ce.xclient.data.l[0] = property->atom(otk::OBProperty::wm_delete_window);
987   ce.xclient.data.l[1] = CurrentTime;
988   ce.xclient.data.l[2] = 0l;
989   ce.xclient.data.l[3] = 0l;
990   ce.xclient.data.l[4] = 0l;
991   XSendEvent(otk::OBDisplay::display, _window, False, NoEventMask, &ce);
992 }
993
994
995 void OBClient::changeState()
996 {
997   const otk::OBProperty *property = Openbox::instance->property();
998
999   unsigned long state[2];
1000   state[0] = _wmstate;
1001   state[1] = None;
1002   property->set(_window, otk::OBProperty::wm_state, otk::OBProperty::wm_state,
1003                 state, 2);
1004   
1005   Atom netstate[10];
1006   int num = 0;
1007   if (_modal)
1008     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_modal);
1009   if (_shaded)
1010     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_shaded);
1011   if (_iconic)
1012     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_hidden);
1013   if (_skip_taskbar)
1014     netstate[num++] =
1015       property->atom(otk::OBProperty::net_wm_state_skip_taskbar);
1016   if (_skip_pager)
1017     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_skip_pager);
1018   if (_fullscreen)
1019     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_fullscreen);
1020   if (_max_vert)
1021     netstate[num++] =
1022       property->atom(otk::OBProperty::net_wm_state_maximized_vert);
1023   if (_max_horz)
1024     netstate[num++] =
1025       property->atom(otk::OBProperty::net_wm_state_maximized_horz);
1026   if (_above)
1027     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_above);
1028   if (_below)
1029     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_below);
1030   property->set(_window, otk::OBProperty::net_wm_state,
1031                 otk::OBProperty::Atom_Atom, netstate, num);
1032
1033   calcLayer();
1034 }
1035
1036
1037 void OBClient::setStackLayer(int l)
1038 {
1039   if (l == 0)
1040     _above = _below = false;  // normal
1041   else if (l > 0) {
1042     _above = true;
1043     _below = false; // above
1044   } else {
1045     _above = false;
1046     _below = true;  // below
1047   }
1048   changeState();
1049 }
1050
1051
1052 void OBClient::shade(bool shade)
1053 {
1054   if (shade == _shaded) return; // already done
1055
1056   _wmstate = shade ? IconicState : NormalState;
1057   _shaded = shade;
1058   changeState();
1059   frame->adjustSize();
1060 }
1061
1062
1063 bool OBClient::focus()
1064 {
1065   if (!(_can_focus || _focus_notify) || _focused) return false;
1066
1067   if (_can_focus)
1068     XSetInputFocus(otk::OBDisplay::display, _window, RevertToNone, CurrentTime);
1069
1070   if (_focus_notify) {
1071     XEvent ce;
1072     const otk::OBProperty *property = Openbox::instance->property();
1073     
1074     ce.xclient.type = ClientMessage;
1075     ce.xclient.message_type =  property->atom(otk::OBProperty::wm_protocols);
1076     ce.xclient.display = otk::OBDisplay::display;
1077     ce.xclient.window = _window;
1078     ce.xclient.format = 32;
1079     ce.xclient.data.l[0] = property->atom(otk::OBProperty::wm_take_focus);
1080     ce.xclient.data.l[1] = Openbox::instance->lastTime();
1081     ce.xclient.data.l[2] = 0l;
1082     ce.xclient.data.l[3] = 0l;
1083     ce.xclient.data.l[4] = 0l;
1084     XSendEvent(otk::OBDisplay::display, _window, False, NoEventMask, &ce);
1085   }
1086
1087   return true;
1088 }
1089
1090
1091 void OBClient::unfocus()
1092 {
1093   if (!_focused) return;
1094
1095   assert(Openbox::instance->focusedClient() == this);
1096   Openbox::instance->setFocusedClient(0);
1097 }
1098
1099
1100 void OBClient::focusHandler(const XFocusChangeEvent &e)
1101 {
1102 #ifdef    DEBUG
1103   printf("FocusIn for 0x%lx\n", e.window);
1104 #endif // DEBUG
1105   
1106   OtkEventHandler::focusHandler(e);
1107
1108   frame->focus();
1109   _focused = true;
1110
1111   Openbox::instance->setFocusedClient(this);
1112 }
1113
1114
1115 void OBClient::unfocusHandler(const XFocusChangeEvent &e)
1116 {
1117 #ifdef    DEBUG
1118   printf("FocusOut for 0x%lx\n", e.window);
1119 #endif // DEBUG
1120   
1121   OtkEventHandler::unfocusHandler(e);
1122
1123   frame->unfocus();
1124   _focused = false;
1125
1126   if (Openbox::instance->focusedClient() == this) {
1127     printf("UNFOCUSED!\n");
1128     Openbox::instance->setFocusedClient(this);
1129   }
1130 }
1131
1132
1133 void OBClient::configureRequestHandler(const XConfigureRequestEvent &e)
1134 {
1135 #ifdef    DEBUG
1136   printf("ConfigureRequest for 0x%lx\n", e.window);
1137 #endif // DEBUG
1138   
1139   OtkEventHandler::configureRequestHandler(e);
1140
1141   // XXX: if we are iconic (or shaded? (fvwm does that)) ignore the event
1142
1143   if (e.value_mask & CWBorderWidth)
1144     _border_width = e.border_width;
1145
1146   // resize, then move, as specified in the EWMH section 7.7
1147   if (e.value_mask & (CWWidth | CWHeight)) {
1148     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1149     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1150
1151     Corner corner;
1152     switch (_gravity) {
1153     case NorthEastGravity:
1154     case EastGravity:
1155       corner = TopRight;
1156       break;
1157     case SouthWestGravity:
1158     case SouthGravity:
1159       corner = BottomLeft;
1160       break;
1161     case SouthEastGravity:
1162       corner = BottomRight;
1163       break;
1164     default:     // NorthWest, Static, etc
1165       corner = TopLeft;
1166     }
1167
1168     // if moving AND resizing ...
1169     if (e.value_mask & (CWX | CWY)) {
1170       int x = (e.value_mask & CWX) ? e.x : _area.x();
1171       int y = (e.value_mask & CWY) ? e.y : _area.y();
1172       resize(corner, w, h, x, y);
1173     } else // if JUST resizing...
1174       resize(corner, w, h);
1175   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1176     int x = (e.value_mask & CWX) ? e.x : _area.x();
1177     int y = (e.value_mask & CWY) ? e.y : _area.y();
1178     move(x, y);
1179   }
1180
1181   if (e.value_mask & CWStackMode) {
1182     switch (e.detail) {
1183     case Below:
1184     case BottomIf:
1185       Openbox::instance->screen(_screen)->restack(false, this); // lower
1186       break;
1187
1188     case Above:
1189     case TopIf:
1190     default:
1191       Openbox::instance->screen(_screen)->restack(true, this); // raise
1192       break;
1193     }
1194   }
1195 }
1196
1197
1198 void OBClient::unmapHandler(const XUnmapEvent &e)
1199 {
1200 #ifdef    DEBUG
1201   printf("UnmapNotify for 0x%lx\n", e.window);
1202 #endif // DEBUG
1203
1204   if (ignore_unmaps) {
1205     ignore_unmaps--;
1206     return;
1207   }
1208   
1209   OtkEventHandler::unmapHandler(e);
1210
1211   // this deletes us etc
1212   Openbox::instance->screen(_screen)->unmanageWindow(this);
1213 }
1214
1215
1216 void OBClient::destroyHandler(const XDestroyWindowEvent &e)
1217 {
1218 #ifdef    DEBUG
1219   printf("DestroyNotify for 0x%lx\n", e.window);
1220 #endif // DEBUG
1221
1222   OtkEventHandler::destroyHandler(e);
1223
1224   // this deletes us etc
1225   Openbox::instance->screen(_screen)->unmanageWindow(this);
1226 }
1227
1228
1229 void OBClient::reparentHandler(const XReparentEvent &e)
1230 {
1231   // this is when the client is first taken captive in the frame
1232   if (e.parent == frame->plate()) return;
1233
1234 #ifdef    DEBUG
1235   printf("ReparentNotify for 0x%lx\n", e.window);
1236 #endif // DEBUG
1237
1238   OtkEventHandler::reparentHandler(e);
1239
1240   /*
1241     This event is quite rare and is usually handled in unmapHandler.
1242     However, if the window is unmapped when the reparent event occurs,
1243     the window manager never sees it because an unmap event is not sent
1244     to an already unmapped window.
1245   */
1246
1247   // this deletes us etc
1248   Openbox::instance->screen(_screen)->unmanageWindow(this);
1249 }
1250
1251
1252 void OBClient::mapRequestHandler(const XMapRequestEvent &e)
1253 {
1254   printf("\nMAP REQUEST\n\n");
1255   
1256   otk::OtkEventHandler::mapRequestHandler(e);
1257
1258   if (_shaded)
1259     shade(false);
1260   // XXX: uniconify the window
1261   focus();
1262 }
1263
1264 }