]> icculus.org git repositories - mikachu/openbox.git/blob - src/client.cc
rm old bb shit that we dont use
[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->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->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->property();
98
99   // defaults to the current desktop
100   _desktop = openbox->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->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->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->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, _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->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, _window, ShapeNotifyMask);
341
342     XShapeQueryExtents(**otk::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->screen(_screen)->restack(true, this); // raise
372     }
373   }
374 }
375
376
377 void Client::updateProtocols()
378 {
379   const otk::Property *property = openbox->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, _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, _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, _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->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->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->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->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->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, _window, &t) &&
576       t != _window) { // cant be transient to itself!
577     c = openbox->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->property();
610
611   // compress changes to a single property into a single change
612   XEvent ce;
613   while (XCheckTypedEvent(**otk::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, &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->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->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->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, _window, _border_width);
854
855     // move the client so it is back it the right spot _with_ its border!
856     XMoveWindow(**otk::display, _window, x, y);
857   } else
858     XSetWindowBorderWidth(**otk::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->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, 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, &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, 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, &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->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   // you cannot resize to nothing
972   if (w < 1) w = 1;
973   if (h < 1) h = 1;
974   
975   // store the logical size
976   _logical_size.setPoint(w, h);
977
978   w *= _size_inc.x();
979   h *= _size_inc.y();
980
981   w += _base_size.x();
982   h += _base_size.y();
983
984   if (x == INT_MIN || y == INT_MIN) {
985     x = _area.x();
986     y = _area.y();
987     switch (anchor) {
988     case TopLeft:
989       break;
990     case TopRight:
991       x -= w - _area.width();
992       break;
993     case BottomLeft:
994       y -= h - _area.height();
995       break;
996     case BottomRight:
997       x -= w - _area.width();
998       y -= h - _area.height();
999       break;
1000     }
1001   }
1002
1003   _area.setSize(w, h);
1004
1005   XResizeWindow(**otk::display, _window, w, h);
1006
1007   // resize the frame to match the request
1008   frame->adjustSize();
1009   move(x, y);
1010 }
1011
1012
1013 void Client::move(int x, int y)
1014 {
1015   _area.setPos(x, y);
1016
1017   // move the frame to be in the requested position
1018   if (frame) { // this can be called while mapping, before frame exists
1019     frame->adjustPosition();
1020
1021     // send synthetic configure notify (we don't need to if we aren't mapped
1022     // yet)
1023     XEvent event;
1024     event.type = ConfigureNotify;
1025     event.xconfigure.display = **otk::display;
1026     event.xconfigure.event = _window;
1027     event.xconfigure.window = _window;
1028     event.xconfigure.x = x;
1029     event.xconfigure.y = y;
1030     event.xconfigure.width = _area.width();
1031     event.xconfigure.height = _area.height();
1032     event.xconfigure.border_width = _border_width;
1033     event.xconfigure.above = frame->window();
1034     event.xconfigure.override_redirect = False;
1035     XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1036                StructureNotifyMask, &event);
1037   }
1038 }
1039
1040
1041 void Client::close()
1042 {
1043   XEvent ce;
1044   const otk::Property *property = openbox->property();
1045
1046   if (!(_functions & Func_Close)) return;
1047
1048   // XXX: itd be cool to do timeouts and shit here for killing the client's
1049   //      process off
1050   // like... if the window is around after 5 seconds, then the close button
1051   // turns a nice red, and if this function is called again, the client is
1052   // explicitly killed.
1053
1054   ce.xclient.type = ClientMessage;
1055   ce.xclient.message_type =  property->atom(otk::Property::wm_protocols);
1056   ce.xclient.display = **otk::display;
1057   ce.xclient.window = _window;
1058   ce.xclient.format = 32;
1059   ce.xclient.data.l[0] = property->atom(otk::Property::wm_delete_window);
1060   ce.xclient.data.l[1] = CurrentTime;
1061   ce.xclient.data.l[2] = 0l;
1062   ce.xclient.data.l[3] = 0l;
1063   ce.xclient.data.l[4] = 0l;
1064   XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1065 }
1066
1067
1068 void Client::changeState()
1069 {
1070   const otk::Property *property = openbox->property();
1071
1072   unsigned long state[2];
1073   state[0] = _wmstate;
1074   state[1] = None;
1075   property->set(_window, otk::Property::wm_state, otk::Property::wm_state,
1076                 state, 2);
1077   
1078   Atom netstate[10];
1079   int num = 0;
1080   if (_modal)
1081     netstate[num++] = property->atom(otk::Property::net_wm_state_modal);
1082   if (_shaded)
1083     netstate[num++] = property->atom(otk::Property::net_wm_state_shaded);
1084   if (_iconic)
1085     netstate[num++] = property->atom(otk::Property::net_wm_state_hidden);
1086   if (_skip_taskbar)
1087     netstate[num++] =
1088       property->atom(otk::Property::net_wm_state_skip_taskbar);
1089   if (_skip_pager)
1090     netstate[num++] = property->atom(otk::Property::net_wm_state_skip_pager);
1091   if (_fullscreen)
1092     netstate[num++] = property->atom(otk::Property::net_wm_state_fullscreen);
1093   if (_max_vert)
1094     netstate[num++] =
1095       property->atom(otk::Property::net_wm_state_maximized_vert);
1096   if (_max_horz)
1097     netstate[num++] =
1098       property->atom(otk::Property::net_wm_state_maximized_horz);
1099   if (_above)
1100     netstate[num++] = property->atom(otk::Property::net_wm_state_above);
1101   if (_below)
1102     netstate[num++] = property->atom(otk::Property::net_wm_state_below);
1103   property->set(_window, otk::Property::net_wm_state,
1104                 otk::Property::Atom_Atom, netstate, num);
1105
1106   calcLayer();
1107 }
1108
1109
1110 void Client::shade(bool shade)
1111 {
1112   if (shade == _shaded) return; // already done
1113
1114   _wmstate = shade ? IconicState : NormalState;
1115   _shaded = shade;
1116   changeState();
1117   frame->adjustSize();
1118 }
1119
1120
1121 bool Client::focus() const
1122 {
1123   // won't try focus if the client doesn't want it, or if the window isn't
1124   // visible on the screen
1125   if (!(frame->isVisible() && (_can_focus || _focus_notify))) return false;
1126
1127   if (_focused) return true;
1128
1129   if (_can_focus)
1130     XSetInputFocus(**otk::display, _window,
1131                    RevertToNone, CurrentTime);
1132
1133   if (_focus_notify) {
1134     XEvent ce;
1135     const otk::Property *property = openbox->property();
1136     
1137     ce.xclient.type = ClientMessage;
1138     ce.xclient.message_type =  property->atom(otk::Property::wm_protocols);
1139     ce.xclient.display = **otk::display;
1140     ce.xclient.window = _window;
1141     ce.xclient.format = 32;
1142     ce.xclient.data.l[0] = property->atom(otk::Property::wm_take_focus);
1143     ce.xclient.data.l[1] = openbox->lastTime();
1144     ce.xclient.data.l[2] = 0l;
1145     ce.xclient.data.l[3] = 0l;
1146     ce.xclient.data.l[4] = 0l;
1147     XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1148   }
1149
1150   return true;
1151 }
1152
1153
1154 void Client::unfocus() const
1155 {
1156   if (!_focused) return;
1157
1158   assert(openbox->focusedClient() == this);
1159   openbox->setFocusedClient(0);
1160 }
1161
1162
1163 void Client::focusHandler(const XFocusChangeEvent &e)
1164 {
1165 #ifdef    DEBUG
1166 //  printf("FocusIn for 0x%lx\n", e.window);
1167 #endif // DEBUG
1168   
1169   otk::EventHandler::focusHandler(e);
1170
1171   frame->focus();
1172   _focused = true;
1173
1174   openbox->setFocusedClient(this);
1175 }
1176
1177
1178 void Client::unfocusHandler(const XFocusChangeEvent &e)
1179 {
1180 #ifdef    DEBUG
1181 //  printf("FocusOut for 0x%lx\n", e.window);
1182 #endif // DEBUG
1183   
1184   otk::EventHandler::unfocusHandler(e);
1185
1186   frame->unfocus();
1187   _focused = false;
1188
1189   if (openbox->focusedClient() == this)
1190     openbox->setFocusedClient(0);
1191 }
1192
1193
1194 void Client::configureRequestHandler(const XConfigureRequestEvent &e)
1195 {
1196 #ifdef    DEBUG
1197   printf("ConfigureRequest for 0x%lx\n", e.window);
1198 #endif // DEBUG
1199   
1200   otk::EventHandler::configureRequestHandler(e);
1201
1202   // XXX: if we are iconic (or shaded? (fvwm does that)) ignore the event
1203
1204   if (e.value_mask & CWBorderWidth)
1205     _border_width = e.border_width;
1206
1207   // resize, then move, as specified in the EWMH section 7.7
1208   if (e.value_mask & (CWWidth | CWHeight)) {
1209     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1210     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1211
1212     Corner corner;
1213     switch (_gravity) {
1214     case NorthEastGravity:
1215     case EastGravity:
1216       corner = TopRight;
1217       break;
1218     case SouthWestGravity:
1219     case SouthGravity:
1220       corner = BottomLeft;
1221       break;
1222     case SouthEastGravity:
1223       corner = BottomRight;
1224       break;
1225     default:     // NorthWest, Static, etc
1226       corner = TopLeft;
1227     }
1228
1229     // if moving AND resizing ...
1230     if (e.value_mask & (CWX | CWY)) {
1231       int x = (e.value_mask & CWX) ? e.x : _area.x();
1232       int y = (e.value_mask & CWY) ? e.y : _area.y();
1233       resize(corner, w, h, x, y);
1234     } else // if JUST resizing...
1235       resize(corner, w, h);
1236   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1237     int x = (e.value_mask & CWX) ? e.x : _area.x();
1238     int y = (e.value_mask & CWY) ? e.y : _area.y();
1239     move(x, y);
1240   }
1241
1242   if (e.value_mask & CWStackMode) {
1243     switch (e.detail) {
1244     case Below:
1245     case BottomIf:
1246       openbox->screen(_screen)->restack(false, this); // lower
1247       break;
1248
1249     case Above:
1250     case TopIf:
1251     default:
1252       openbox->screen(_screen)->restack(true, this); // raise
1253       break;
1254     }
1255   }
1256 }
1257
1258
1259 void Client::unmapHandler(const XUnmapEvent &e)
1260 {
1261   if (ignore_unmaps) {
1262 #ifdef    DEBUG
1263     printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1264 #endif // DEBUG
1265     ignore_unmaps--;
1266     return;
1267   }
1268   
1269 #ifdef    DEBUG
1270   printf("UnmapNotify for 0x%lx\n", e.window);
1271 #endif // DEBUG
1272
1273   otk::EventHandler::unmapHandler(e);
1274
1275   // this deletes us etc
1276   openbox->screen(_screen)->unmanageWindow(this);
1277 }
1278
1279
1280 void Client::destroyHandler(const XDestroyWindowEvent &e)
1281 {
1282 #ifdef    DEBUG
1283   printf("DestroyNotify for 0x%lx\n", e.window);
1284 #endif // DEBUG
1285
1286   otk::EventHandler::destroyHandler(e);
1287
1288   // this deletes us etc
1289   openbox->screen(_screen)->unmanageWindow(this);
1290 }
1291
1292
1293 void Client::reparentHandler(const XReparentEvent &e)
1294 {
1295   // this is when the client is first taken captive in the frame
1296   if (e.parent == frame->plate()) return;
1297
1298 #ifdef    DEBUG
1299   printf("ReparentNotify for 0x%lx\n", e.window);
1300 #endif // DEBUG
1301
1302   otk::EventHandler::reparentHandler(e);
1303
1304   /*
1305     This event is quite rare and is usually handled in unmapHandler.
1306     However, if the window is unmapped when the reparent event occurs,
1307     the window manager never sees it because an unmap event is not sent
1308     to an already unmapped window.
1309   */
1310
1311   // we don't want the reparent event, put it back on the stack for the X
1312   // server to deal with after we unmanage the window
1313   XEvent ev;
1314   ev.xreparent = e;
1315   XPutBackEvent(**otk::display, &ev);
1316   
1317   // this deletes us etc
1318   openbox->screen(_screen)->unmanageWindow(this);
1319 }
1320
1321 }