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