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