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