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