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