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