]> icculus.org git repositories - mikachu/openbox.git/blob - src/client.cc
blef
[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   // 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_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
660
661 void Client::setState(StateAction action, long data1, long data2)
662 {
663   bool shadestate = _shaded;
664   bool fsstate = _fullscreen;
665
666   if (!(action == State_Add || action == State_Remove ||
667         action == State_Toggle))
668     return; // an invalid action was passed to the client message, ignore it
669
670   for (int i = 0; i < 2; ++i) {
671     Atom state = i == 0 ? data1 : data2;
672     
673     if (! state) continue;
674
675     // if toggling, then pick whether we're adding or removing
676     if (action == State_Toggle) {
677       if (state == otk::Property::atoms.net_wm_state_modal)
678         action = _modal ? State_Remove : State_Add;
679       else if (state == otk::Property::atoms.net_wm_state_maximized_vert)
680         action = _max_vert ? State_Remove : State_Add;
681       else if (state == otk::Property::atoms.net_wm_state_maximized_horz)
682         action = _max_horz ? State_Remove : State_Add;
683       else if (state == otk::Property::atoms.net_wm_state_shaded)
684         action = _shaded ? State_Remove : State_Add;
685       else if (state == otk::Property::atoms.net_wm_state_skip_taskbar)
686         action = _skip_taskbar ? State_Remove : State_Add;
687       else if (state == otk::Property::atoms.net_wm_state_skip_pager)
688         action = _skip_pager ? State_Remove : State_Add;
689       else if (state == otk::Property::atoms.net_wm_state_fullscreen)
690         action = _fullscreen ? State_Remove : State_Add;
691       else if (state == otk::Property::atoms.net_wm_state_above)
692         action = _above ? State_Remove : State_Add;
693       else if (state == otk::Property::atoms.net_wm_state_below)
694         action = _below ? State_Remove : State_Add;
695     }
696     
697     if (action == State_Add) {
698       if (state == otk::Property::atoms.net_wm_state_modal) {
699         if (_modal) continue;
700         _modal = true;
701         // XXX: give it focus if another window has focus that shouldnt now
702       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
703         if (_max_vert) continue;
704         _max_vert = true;
705         // XXX: resize the window etc
706       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
707         if (_max_horz) continue;
708         _max_horz = true;
709         // XXX: resize the window etc
710       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
711         shadestate = true;
712       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
713         _skip_taskbar = true;
714       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
715         _skip_pager = true;
716       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
717         fsstate = true;
718       } else if (state == otk::Property::atoms.net_wm_state_above) {
719         if (_above) continue;
720         _above = true;
721       } else if (state == otk::Property::atoms.net_wm_state_below) {
722         if (_below) continue;
723         _below = true;
724       }
725
726     } else { // action == State_Remove
727       if (state == otk::Property::atoms.net_wm_state_modal) {
728         if (!_modal) continue;
729         _modal = false;
730       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
731         if (!_max_vert) continue;
732         _max_vert = false;
733         // XXX: resize the window etc
734       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
735         if (!_max_horz) continue;
736         _max_horz = false;
737         // XXX: resize the window etc
738       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
739         shadestate = false;
740       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
741         _skip_taskbar = false;
742       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
743         _skip_pager = false;
744       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
745         fsstate = false;
746       } else if (state == otk::Property::atoms.net_wm_state_above) {
747         if (!_above) continue;
748         _above = false;
749       } else if (state == otk::Property::atoms.net_wm_state_below) {
750         if (!_below) continue;
751         _below = false;
752       }
753     }
754   }
755   // change fullscreen state before shading, as it will affect if the window
756   // can shade or not
757   if (fsstate != _fullscreen)
758     fullscreen(fsstate);
759   if (shadestate != _shaded)
760     shade(shadestate);
761   calcLayer();
762 }
763
764
765 void Client::toggleClientBorder(bool addborder)
766 {
767   // adjust our idea of where the client is, based on its border. When the
768   // border is removed, the client should now be considered to be in a
769   // different position.
770   // when re-adding the border to the client, the same operation needs to be
771   // reversed.
772   int x = _area.x(), y = _area.y();
773   switch(_gravity) {
774   default:
775   case NorthWestGravity:
776   case WestGravity:
777   case SouthWestGravity:
778     break;
779   case NorthEastGravity:
780   case EastGravity:
781   case SouthEastGravity:
782     if (addborder) x -= _border_width * 2;
783     else           x += _border_width * 2;
784     break;
785   case NorthGravity:
786   case SouthGravity:
787   case CenterGravity:
788   case ForgetGravity:
789   case StaticGravity:
790     if (addborder) x -= _border_width;
791     else           x += _border_width;
792     break;
793   }
794   switch(_gravity) {
795   default:
796   case NorthWestGravity:
797   case NorthGravity:
798   case NorthEastGravity:
799     break;
800   case SouthWestGravity:
801   case SouthGravity:
802   case SouthEastGravity:
803     if (addborder) y -= _border_width * 2;
804     else           y += _border_width * 2;
805     break;
806   case WestGravity:
807   case EastGravity:
808   case CenterGravity:
809   case ForgetGravity:
810   case StaticGravity:
811     if (addborder) y -= _border_width;
812     else           y += _border_width;
813     break;
814   }
815   _area.setPos(x, y);
816
817   if (addborder) {
818     XSetWindowBorderWidth(**otk::display, _window, _border_width);
819
820     // move the client so it is back it the right spot _with_ its border!
821     XMoveWindow(**otk::display, _window, x, y);
822   } else
823     XSetWindowBorderWidth(**otk::display, _window, 0);
824 }
825
826
827 void Client::clientMessageHandler(const XClientMessageEvent &e)
828 {
829   otk::EventHandler::clientMessageHandler(e);
830   
831   if (e.format != 32) return;
832
833   if (e.message_type == otk::Property::atoms.wm_change_state) {
834     // compress changes into a single change
835     bool compress = false;
836     XEvent ce;
837     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
838       // XXX: it would be nice to compress ALL messages of a type, not just
839       //      messages in a row without other message types between.
840       if (ce.xclient.message_type != e.message_type) {
841         XPutBackEvent(**otk::display, &ce);
842         break;
843       }
844       compress = true;
845     }
846     if (compress)
847       setWMState(ce.xclient.data.l[0]); // use the found event
848     else
849       setWMState(e.data.l[0]); // use the original event
850   } else if (e.message_type == otk::Property::atoms.net_wm_desktop) {
851     // compress changes into a single change 
852     bool compress = false;
853     XEvent ce;
854     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
855       // XXX: it would be nice to compress ALL messages of a type, not just
856       //      messages in a row without other message types between.
857       if (ce.xclient.message_type != e.message_type) {
858         XPutBackEvent(**otk::display, &ce);
859         break;
860       }
861       compress = true;
862     }
863     if (compress)
864       setDesktop(e.data.l[0]); // use the found event
865     else
866       setDesktop(e.data.l[0]); // use the original event
867   } else if (e.message_type == otk::Property::atoms.net_wm_state) {
868     // can't compress these
869 #ifdef DEBUG
870     printf("net_wm_state %s %ld %ld for 0x%lx\n",
871            (e.data.l[0] == 0 ? "Remove" : e.data.l[0] == 1 ? "Add" :
872             e.data.l[0] == 2 ? "Toggle" : "INVALID"),
873            e.data.l[1], e.data.l[2], _window);
874 #endif
875     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
876   } else if (e.message_type == otk::Property::atoms.net_close_window) {
877 #ifdef DEBUG
878     printf("net_close_window for 0x%lx\n", _window);
879 #endif
880     close();
881   } else if (e.message_type == otk::Property::atoms.net_active_window) {
882 #ifdef DEBUG
883     printf("net_active_window for 0x%lx\n", _window);
884 #endif
885     if (_shaded)
886       shade(false);
887     // XXX: deiconify
888     focus();
889     openbox->screen(_screen)->raiseWindow(this);
890   }
891 }
892
893
894 #if defined(SHAPE)
895 void Client::shapeHandler(const XShapeEvent &e)
896 {
897   otk::EventHandler::shapeHandler(e);
898
899   if (e.kind == ShapeBounding) {
900     _shaped = e.shaped;
901     frame->adjustShape();
902   }
903 }
904 #endif
905
906
907 void Client::resize(Corner anchor, int w, int h)
908 {
909   if (!(_functions & Func_Resize)) return;
910   internal_resize(anchor, w, h);
911 }
912
913
914 void Client::internal_resize(Corner anchor, int w, int h, int x, int y)
915 {
916   w -= _base_size.x(); 
917   h -= _base_size.y();
918
919   // for interactive resizing. have to move half an increment in each
920   // direction.
921   w += _size_inc.x() / 2;
922   h += _size_inc.y() / 2;
923
924   // is the window resizable? if it is not, then don't check its sizes, the
925   // client can do what it wants and the user can't change it anyhow
926   if (_min_size.x() <= _max_size.x() && _min_size.y() <= _max_size.y()) {
927     // smaller than min size or bigger than max size?
928     if (w < _min_size.x()) w = _min_size.x();
929     else if (w > _max_size.x()) w = _max_size.x();
930     if (h < _min_size.y()) h = _min_size.y();
931     else if (h > _max_size.y()) h = _max_size.y();
932   }
933
934   // keep to the increments
935   w /= _size_inc.x();
936   h /= _size_inc.y();
937
938   // you cannot resize to nothing
939   if (w < 1) w = 1;
940   if (h < 1) h = 1;
941   
942   // store the logical size
943   _logical_size.setPoint(w, h);
944
945   w *= _size_inc.x();
946   h *= _size_inc.y();
947
948   w += _base_size.x();
949   h += _base_size.y();
950
951   if (x == INT_MIN || y == INT_MIN) {
952     x = _area.x();
953     y = _area.y();
954     switch (anchor) {
955     case TopLeft:
956       break;
957     case TopRight:
958       x -= w - _area.width();
959       break;
960     case BottomLeft:
961       y -= h - _area.height();
962       break;
963     case BottomRight:
964       x -= w - _area.width();
965       y -= h - _area.height();
966       break;
967     }
968   }
969
970   _area.setSize(w, h);
971
972   XResizeWindow(**otk::display, _window, w, h);
973
974   // resize the frame to match the request
975   frame->adjustSize();
976   internal_move(x, y);
977 }
978
979
980 void Client::move(int x, int y)
981 {
982   if (!(_functions & Func_Move)) return;
983   internal_move(x, y);
984 }
985
986
987 void Client::internal_move(int x, int y)
988 {
989   _area.setPos(x, y);
990
991   // move the frame to be in the requested position
992   if (frame) { // this can be called while mapping, before frame exists
993     frame->adjustPosition();
994
995     // send synthetic configure notify (we don't need to if we aren't mapped
996     // yet)
997     XEvent event;
998     event.type = ConfigureNotify;
999     event.xconfigure.display = **otk::display;
1000     event.xconfigure.event = _window;
1001     event.xconfigure.window = _window;
1002     event.xconfigure.x = x;
1003     event.xconfigure.y = y;
1004     event.xconfigure.width = _area.width();
1005     event.xconfigure.height = _area.height();
1006     event.xconfigure.border_width = _border_width;
1007     event.xconfigure.above = frame->window();
1008     event.xconfigure.override_redirect = False;
1009     XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1010                StructureNotifyMask, &event);
1011   }
1012 }
1013
1014
1015 void Client::close()
1016 {
1017   XEvent ce;
1018
1019   if (!(_functions & Func_Close)) return;
1020
1021   // XXX: itd be cool to do timeouts and shit here for killing the client's
1022   //      process off
1023   // like... if the window is around after 5 seconds, then the close button
1024   // turns a nice red, and if this function is called again, the client is
1025   // explicitly killed.
1026
1027   ce.xclient.type = ClientMessage;
1028   ce.xclient.message_type =  otk::Property::atoms.wm_protocols;
1029   ce.xclient.display = **otk::display;
1030   ce.xclient.window = _window;
1031   ce.xclient.format = 32;
1032   ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1033   ce.xclient.data.l[1] = CurrentTime;
1034   ce.xclient.data.l[2] = 0l;
1035   ce.xclient.data.l[3] = 0l;
1036   ce.xclient.data.l[4] = 0l;
1037   XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1038 }
1039
1040
1041 void Client::changeState()
1042 {
1043   unsigned long state[2];
1044   state[0] = _wmstate;
1045   state[1] = None;
1046   otk::Property::set(_window, otk::Property::atoms.wm_state,
1047                      otk::Property::atoms.wm_state, state, 2);
1048   
1049   Atom netstate[10];
1050   int num = 0;
1051   if (_modal)
1052     netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1053   if (_shaded)
1054     netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1055   if (_iconic)
1056     netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1057   if (_skip_taskbar)
1058     netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1059   if (_skip_pager)
1060     netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1061   if (_fullscreen)
1062     netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1063   if (_max_vert)
1064     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1065   if (_max_horz)
1066     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1067   if (_above)
1068     netstate[num++] = otk::Property::atoms.net_wm_state_above;
1069   if (_below)
1070     netstate[num++] = otk::Property::atoms.net_wm_state_below;
1071   otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1072                      otk::Property::atoms.atom, netstate, num);
1073
1074   calcLayer();
1075 }
1076
1077
1078 void Client::changeAllowedActions(void)
1079 {
1080   Atom actions[9];
1081   int num = 0;
1082
1083   actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1084
1085   if (_functions & Func_Shade)
1086     actions[num++] = otk::Property::atoms.net_wm_action_shade;
1087   if (_functions & Func_Close)
1088     actions[num++] = otk::Property::atoms.net_wm_action_close;
1089   if (_functions & Func_Move)
1090     actions[num++] = otk::Property::atoms.net_wm_action_move;
1091   if (_functions & Func_Iconify)
1092     actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1093   if (_functions & Func_Resize)
1094     actions[num++] = otk::Property::atoms.net_wm_action_resize;
1095   if (_functions & Func_Fullscreen)
1096     actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1097   if (_functions & Func_Maximize) {
1098     actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1099     actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1100   }
1101
1102   otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1103                      otk::Property::atoms.atom, actions, num);
1104 }
1105
1106
1107 void Client::applyStartupState()
1108 {
1109   // these are in a carefully crafted order..
1110   
1111   if (_fullscreen) {
1112     _fullscreen = false;
1113     fullscreen(true);
1114   }
1115   if (_shaded) {
1116     _shaded = false;
1117     shade(true);
1118   }
1119   
1120   if (_max_vert); // XXX: incomplete
1121   if (_max_horz); // XXX: incomplete
1122
1123   if (_skip_taskbar); // nothing to do for this
1124   if (_skip_pager);   // nothing to do for this
1125   if (_modal);        // nothing to do for this
1126   if (_above);        // nothing to do for this
1127   if (_below);        // nothing to do for this
1128 }
1129
1130
1131 void Client::shade(bool shade)
1132 {
1133   if (!(_functions & Func_Shade) || // can't
1134       _shaded == shade) return;     // already done
1135
1136   _wmstate = shade ? IconicState : NormalState;
1137   _shaded = shade;
1138   changeState();
1139   frame->adjustSize();
1140 }
1141
1142
1143 void Client::fullscreen(bool fs)
1144 {
1145   static FunctionFlags saved_func;
1146   static DecorationFlags saved_decor;
1147   static otk::Rect saved_area;
1148   static otk::Point saved_logical_size;
1149
1150   if (!(_functions & Func_Fullscreen) || // can't
1151       _fullscreen == fs) return;         // already done
1152
1153   _fullscreen = fs;
1154   changeState(); // change the state hints on the client
1155
1156   if (fs) {
1157     // save the functions and remove them
1158     saved_func = _functions;
1159     _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1160     // save the decorations and remove them
1161     saved_decor = _decorations;
1162     _decorations = 0;
1163     // save the area and adjust it (we don't call internal resize here for
1164     // constraints on the size, etc, we just make it fullscreen).
1165     saved_area = _area;
1166     const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1167     _area.setRect(0, 0, info->width(), info->height());
1168     saved_logical_size = _logical_size;
1169     _logical_size.setPoint((info->width() - _base_size.x()) / _size_inc.x(),
1170                            (info->height() - _base_size.y()) / _size_inc.y());
1171   } else {
1172     _functions = saved_func;
1173     _decorations = saved_decor;
1174     _area = saved_area;
1175     _logical_size = saved_logical_size;
1176   }
1177   
1178   changeAllowedActions();  // based on the new _functions
1179   
1180   frame->adjustSize();     // drop/replace the decor's and resize
1181   frame->adjustPosition(); // get (back) in position!
1182
1183   // raise (back) into our stacking layer
1184   openbox->screen(_screen)->raiseWindow(this);
1185
1186   // try focus us when we go into fullscreen mode
1187   if (fs) focus();
1188 }
1189
1190
1191 bool Client::focus() const
1192 {
1193   // won't try focus if the client doesn't want it, or if the window isn't
1194   // visible on the screen
1195   if (!(frame->isVisible() && (_can_focus || _focus_notify))) return false;
1196
1197   if (_focused) return true;
1198
1199   if (_can_focus)
1200     XSetInputFocus(**otk::display, _window,
1201                    RevertToNone, CurrentTime);
1202
1203   if (_focus_notify) {
1204     XEvent ce;
1205     ce.xclient.type = ClientMessage;
1206     ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1207     ce.xclient.display = **otk::display;
1208     ce.xclient.window = _window;
1209     ce.xclient.format = 32;
1210     ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1211     ce.xclient.data.l[1] = openbox->lastTime();
1212     ce.xclient.data.l[2] = 0l;
1213     ce.xclient.data.l[3] = 0l;
1214     ce.xclient.data.l[4] = 0l;
1215     XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1216   }
1217
1218   return true;
1219 }
1220
1221
1222 void Client::unfocus() const
1223 {
1224   if (!_focused) return;
1225
1226   assert(openbox->focusedClient() == this);
1227   openbox->setFocusedClient(0);
1228 }
1229
1230
1231 void Client::focusHandler(const XFocusChangeEvent &e)
1232 {
1233 #ifdef    DEBUG
1234 //  printf("FocusIn for 0x%lx\n", e.window);
1235 #endif // DEBUG
1236   
1237   otk::EventHandler::focusHandler(e);
1238
1239   frame->focus();
1240   _focused = true;
1241
1242   openbox->setFocusedClient(this);
1243 }
1244
1245
1246 void Client::unfocusHandler(const XFocusChangeEvent &e)
1247 {
1248 #ifdef    DEBUG
1249 //  printf("FocusOut for 0x%lx\n", e.window);
1250 #endif // DEBUG
1251   
1252   otk::EventHandler::unfocusHandler(e);
1253
1254   frame->unfocus();
1255   _focused = false;
1256
1257   if (openbox->focusedClient() == this)
1258     openbox->setFocusedClient(0);
1259 }
1260
1261
1262 void Client::configureRequestHandler(const XConfigureRequestEvent &e)
1263 {
1264 #ifdef    DEBUG
1265   printf("ConfigureRequest for 0x%lx\n", e.window);
1266 #endif // DEBUG
1267   
1268   otk::EventHandler::configureRequestHandler(e);
1269
1270   // XXX: if we are iconic (or shaded? (fvwm does that)) ignore the event
1271
1272   if (e.value_mask & CWBorderWidth)
1273     _border_width = e.border_width;
1274
1275   // resize, then move, as specified in the EWMH section 7.7
1276   if (e.value_mask & (CWWidth | CWHeight)) {
1277     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1278     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1279
1280     Corner corner;
1281     switch (_gravity) {
1282     case NorthEastGravity:
1283     case EastGravity:
1284       corner = TopRight;
1285       break;
1286     case SouthWestGravity:
1287     case SouthGravity:
1288       corner = BottomLeft;
1289       break;
1290     case SouthEastGravity:
1291       corner = BottomRight;
1292       break;
1293     default:     // NorthWest, Static, etc
1294       corner = TopLeft;
1295     }
1296
1297     // if moving AND resizing ...
1298     if (e.value_mask & (CWX | CWY)) {
1299       int x = (e.value_mask & CWX) ? e.x : _area.x();
1300       int y = (e.value_mask & CWY) ? e.y : _area.y();
1301       internal_resize(corner, w, h, x, y);
1302     } else // if JUST resizing...
1303       internal_resize(corner, w, h);
1304   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1305     int x = (e.value_mask & CWX) ? e.x : _area.x();
1306     int y = (e.value_mask & CWY) ? e.y : _area.y();
1307     internal_move(x, y);
1308   }
1309
1310   if (e.value_mask & CWStackMode) {
1311     switch (e.detail) {
1312     case Below:
1313     case BottomIf:
1314       openbox->screen(_screen)->lowerWindow(this);
1315       break;
1316
1317     case Above:
1318     case TopIf:
1319     default:
1320       openbox->screen(_screen)->raiseWindow(this);
1321       break;
1322     }
1323   }
1324 }
1325
1326
1327 void Client::unmapHandler(const XUnmapEvent &e)
1328 {
1329   if (ignore_unmaps) {
1330 #ifdef    DEBUG
1331     printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1332 #endif // DEBUG
1333     ignore_unmaps--;
1334     return;
1335   }
1336   
1337 #ifdef    DEBUG
1338   printf("UnmapNotify for 0x%lx\n", e.window);
1339 #endif // DEBUG
1340
1341   otk::EventHandler::unmapHandler(e);
1342
1343   // this deletes us etc
1344   openbox->screen(_screen)->unmanageWindow(this);
1345 }
1346
1347
1348 void Client::destroyHandler(const XDestroyWindowEvent &e)
1349 {
1350 #ifdef    DEBUG
1351   printf("DestroyNotify for 0x%lx\n", e.window);
1352 #endif // DEBUG
1353
1354   otk::EventHandler::destroyHandler(e);
1355
1356   // this deletes us etc
1357   openbox->screen(_screen)->unmanageWindow(this);
1358 }
1359
1360
1361 void Client::reparentHandler(const XReparentEvent &e)
1362 {
1363   // this is when the client is first taken captive in the frame
1364   if (e.parent == frame->plate()) return;
1365
1366 #ifdef    DEBUG
1367   printf("ReparentNotify for 0x%lx\n", e.window);
1368 #endif // DEBUG
1369
1370   otk::EventHandler::reparentHandler(e);
1371
1372   /*
1373     This event is quite rare and is usually handled in unmapHandler.
1374     However, if the window is unmapped when the reparent event occurs,
1375     the window manager never sees it because an unmap event is not sent
1376     to an already unmapped window.
1377   */
1378
1379   // we don't want the reparent event, put it back on the stack for the X
1380   // server to deal with after we unmanage the window
1381   XEvent ev;
1382   ev.xreparent = e;
1383   XPutBackEvent(**otk::display, &ev);
1384   
1385   // this deletes us etc
1386   openbox->screen(_screen)->unmanageWindow(this);
1387 }
1388
1389 }