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