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