]> icculus.org git repositories - mikachu/openbox.git/blob - src/client.cc
show desktop mode works!
[mikachu/openbox.git] / src / client.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #include "config.h"
4
5 #include "client.hh"
6 #include "frame.hh"
7 #include "screen.hh"
8 #include "openbox.hh"
9 #include "bindings.hh"
10 #include "otk/display.hh"
11 #include "otk/property.hh"
12
13 extern "C" {
14 #include <X11/Xlib.h>
15 #include <X11/Xutil.h>
16 #include <X11/Xatom.h>
17
18 #include "gettext.h"
19 #define _(str) gettext(str)
20 }
21
22 #include <climits>
23 #include <cassert>
24 #include <algorithm>
25
26 namespace ob {
27
28 Client::Client(int screen, Window window)
29   : otk::EventHandler(),
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   // defaults
40   _wmstate = NormalState;
41   _focused = false;
42   _transient_for = 0;
43   _layer = Layer_Normal;
44   _urgent = false;
45   _positioned = false;
46   _disabled_decorations = 0;
47   _group = None;
48   _desktop = 0;
49   
50   getArea();
51   getDesktop();
52   getState();  // do this before updateTransientFor! (for _modal)
53   getShaped();
54
55   updateTransientFor();
56   getMwmHints();
57   getType(); // this can change the mwmhints for special cases
58
59   updateProtocols();
60
61   getGravity();        // get the attribute gravity
62   updateNormalHints(); // this may override the attribute gravity
63
64   // got the type, the mwmhints, the protocols, and the normal hints (min/max
65   // sizes), so we're ready to set up
66   // the decorations/functions
67   setupDecorAndFunctions();
68   
69   // also get the initial_state and set _iconic if we aren't "starting"
70   // when we're "starting" that means we should use whatever state was already
71   // on the window over the initial map state, because it was already mapped
72   updateWMHints(openbox->state() != Openbox::State_Starting);
73   updateTitle();
74   updateIconTitle();
75   updateClass();
76   updateStrut();
77
78   // this makes sure that these windows appear on all desktops
79   if (/*_type == Type_Dock ||*/ _type == Type_Desktop)
80     _desktop = 0xffffffff;
81   
82   // set the desktop hint, to make sure that it always exists, and to reflect
83   // any changes we've made here
84   otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
85                      otk::Property::atoms.cardinal, (unsigned)_desktop);
86   
87   changeState();
88 }
89
90
91 Client::~Client()
92 {
93   // clean up childrens' references
94   while (!_transients.empty()) {
95     _transients.front()->_transient_for = 0;
96     _transients.pop_front();
97   }
98
99   // clean up parents reference to this
100   if (_transient_for)
101     _transient_for->_transients.remove(this); // remove from old parent
102   
103   if (openbox->state() != Openbox::State_Exiting) {
104     // these values should not be persisted across a window unmapping/mapping
105     otk::Property::erase(_window, otk::Property::atoms.net_wm_desktop);
106     otk::Property::erase(_window, otk::Property::atoms.net_wm_state);
107   } else {
108     // if we're left in an iconic state, the client wont be mapped. this is
109     // bad, since we will no longer be managing the window on restart
110     if (_iconic)
111       XMapWindow(**otk::display, _window);
112   }
113 }
114
115
116 bool Client::validate() const
117 {
118   XSync(**otk::display, false); // get all events on the server
119
120   XEvent e;
121   if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &e) ||
122       XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &e)) {
123     XPutBackEvent(**otk::display, &e);
124     return false;
125   }
126
127   return true;
128 }
129
130
131 void Client::getGravity()
132 {
133   XWindowAttributes wattrib;
134   Status ret;
135
136   ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
137   assert(ret != BadWindow);
138   _gravity = wattrib.win_gravity;
139 }
140
141
142 void Client::getDesktop()
143 {
144   // defaults to the current desktop
145   _desktop = openbox->screen(_screen)->desktop();
146
147   if (otk::Property::get(_window, otk::Property::atoms.net_wm_desktop,
148                          otk::Property::atoms.cardinal,
149                          (long unsigned*)&_desktop)) {
150 #ifdef DEBUG
151 //    printf("Window requested desktop: %ld\n", _desktop);
152 #endif
153   }
154 }
155
156
157 void Client::getType()
158 {
159   _type = (WindowType) -1;
160   
161   unsigned long *val;
162   unsigned long num = (unsigned) -1;
163   if (otk::Property::get(_window, otk::Property::atoms.net_wm_window_type,
164                          otk::Property::atoms.atom, &num, &val)) {
165     // use the first value that we know about in the array
166     for (unsigned long i = 0; i < num; ++i) {
167       if (val[i] == otk::Property::atoms.net_wm_window_type_desktop)
168         _type = Type_Desktop;
169       else if (val[i] == otk::Property::atoms.net_wm_window_type_dock)
170         _type = Type_Dock;
171       else if (val[i] == otk::Property::atoms.net_wm_window_type_toolbar)
172         _type = Type_Toolbar;
173       else if (val[i] == otk::Property::atoms.net_wm_window_type_menu)
174         _type = Type_Menu;
175       else if (val[i] == otk::Property::atoms.net_wm_window_type_utility)
176         _type = Type_Utility;
177       else if (val[i] == otk::Property::atoms.net_wm_window_type_splash)
178         _type = Type_Splash;
179       else if (val[i] == otk::Property::atoms.net_wm_window_type_dialog)
180         _type = Type_Dialog;
181       else if (val[i] == otk::Property::atoms.net_wm_window_type_normal)
182         _type = Type_Normal;
183       else if (val[i] == otk::Property::atoms.kde_net_wm_window_type_override){
184         // prevent this window from getting any decor or functionality
185         _mwmhints.flags &= MwmFlag_Functions | MwmFlag_Decorations;
186         _mwmhints.decorations = 0;
187         _mwmhints.functions = 0;
188       }
189       if (_type != (WindowType) -1)
190         break; // grab the first known type
191     }
192     delete val;
193   }
194     
195   if (_type == (WindowType) -1) {
196     /*
197      * the window type hint was not set, which means we either classify ourself
198      * as a normal window or a dialog, depending on if we are a transient.
199      */
200     if (_transient_for)
201       _type = Type_Dialog;
202     else
203       _type = Type_Normal;
204   }
205 }
206
207
208 void Client::setupDecorAndFunctions()
209 {
210   // start with everything (cept fullscreen)
211   _decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
212     Decor_AllDesktops | Decor_Iconify | Decor_Maximize;
213   _functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize |
214     Func_Shade;
215   if (_delete_window) {
216     _decorations |= Decor_Close;
217     _functions |= Func_Close;
218   }
219
220   if (!(_min_size.width() < _max_size.width() ||
221         _min_size.height() < _max_size.height())) {
222     _decorations &= ~(Decor_Maximize | Decor_Handle);
223     _functions &= ~(Func_Resize | Func_Maximize);
224   }
225   
226   switch (_type) {
227   case Type_Normal:
228     // normal windows retain all of the possible decorations and
229     // functionality, and are the only windows that you can fullscreen
230     _functions |= Func_Fullscreen;
231     break;
232
233   case Type_Dialog:
234     // dialogs cannot be maximized
235     _decorations &= ~Decor_Maximize;
236     _functions &= ~Func_Maximize;
237     break;
238
239   case Type_Menu:
240   case Type_Toolbar:
241   case Type_Utility:
242     // these windows get less functionality
243     _decorations &= ~(Decor_Iconify | Decor_Handle);
244     _functions &= ~(Func_Iconify | Func_Resize);
245     break;
246
247   case Type_Desktop:
248   case Type_Dock:
249   case Type_Splash:
250     // none of these windows are manipulated by the window manager
251     _decorations = 0;
252     _functions = 0;
253     break;
254   }
255
256   // Mwm Hints are applied subtractively to what has already been chosen for
257   // decor and functionality
258   if (_mwmhints.flags & MwmFlag_Decorations) {
259     if (! (_mwmhints.decorations & MwmDecor_All)) {
260       if (! (_mwmhints.decorations & MwmDecor_Border))
261         _decorations &= ~Decor_Border;
262       if (! (_mwmhints.decorations & MwmDecor_Handle))
263         _decorations &= ~Decor_Handle;
264       if (! (_mwmhints.decorations & MwmDecor_Title))
265         _decorations &= ~Decor_Titlebar;
266       if (! (_mwmhints.decorations & MwmDecor_Iconify))
267         _decorations &= ~Decor_Iconify;
268       if (! (_mwmhints.decorations & MwmDecor_Maximize))
269         _decorations &= ~Decor_Maximize;
270     }
271   }
272
273   if (_mwmhints.flags & MwmFlag_Functions) {
274     if (! (_mwmhints.functions & MwmFunc_All)) {
275       if (! (_mwmhints.functions & MwmFunc_Resize))
276         _functions &= ~Func_Resize;
277       if (! (_mwmhints.functions & MwmFunc_Move))
278         _functions &= ~Func_Move;
279       if (! (_mwmhints.functions & MwmFunc_Iconify))
280         _functions &= ~Func_Iconify;
281       if (! (_mwmhints.functions & MwmFunc_Maximize))
282         _functions &= ~Func_Maximize;
283       // dont let mwm hints kill the close button
284       //if (! (_mwmhints.functions & MwmFunc_Close))
285       //  _functions &= ~Func_Close;
286     }
287   }
288
289   // can't maximize without moving/resizing
290   if (!((_functions & Func_Move) && (_functions & Func_Resize)))
291     _functions &= ~Func_Maximize;
292
293   // finally, user specified disabled decorations are applied to subtract
294   // decorations
295   if (_disabled_decorations & Decor_Titlebar)
296     _decorations &= ~Decor_Titlebar;
297   if (_disabled_decorations & Decor_Handle)
298     _decorations &= ~Decor_Handle;
299   if (_disabled_decorations & Decor_Border)
300     _decorations &= ~Decor_Border;
301   if (_disabled_decorations & Decor_Iconify)
302     _decorations &= ~Decor_Iconify;
303   if (_disabled_decorations & Decor_Maximize)
304     _decorations &= ~Decor_Maximize;
305   if (_disabled_decorations & Decor_AllDesktops)
306     _decorations &= ~Decor_AllDesktops;
307   if (_disabled_decorations & Decor_Close)
308     _decorations &= ~Decor_Close;
309
310   // if we don't have a titlebar, then we cannot shade!
311   if (!(_decorations & Decor_Titlebar))
312     _functions &= ~Func_Shade;
313
314   changeAllowedActions();
315
316   if (frame) {
317     frame->adjustSize(); // change the decors on the frame
318     frame->adjustPosition(); // with more/less decorations, we may need to be
319                              // moved
320     remaximize(); // with new decor, the window's maximized size may change
321   }
322 }
323
324
325 void Client::getMwmHints()
326 {
327   unsigned long num = MwmHints::elements;
328   unsigned long *hints;
329
330   _mwmhints.flags = 0; // default to none
331   
332   if (!otk::Property::get(_window, otk::Property::atoms.motif_wm_hints,
333                           otk::Property::atoms.motif_wm_hints, &num,
334                           (unsigned long **)&hints))
335     return;
336   
337   if (num >= MwmHints::elements) {
338     // retrieved the hints
339     _mwmhints.flags = hints[0];
340     _mwmhints.functions = hints[1];
341     _mwmhints.decorations = hints[2];
342   }
343
344   delete [] hints;
345 }
346
347
348 void Client::getArea()
349 {
350   XWindowAttributes wattrib;
351   Status ret;
352   
353   ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
354   assert(ret != BadWindow);
355
356   _area = otk::Rect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
357   _border_width = wattrib.border_width;
358 }
359
360
361 void Client::getState()
362 {
363   _modal = _shaded = _max_horz = _max_vert = _fullscreen = _above = _below =
364     _iconic = _skip_taskbar = _skip_pager = false;
365   
366   unsigned long *state;
367   unsigned long num = (unsigned) -1;
368   
369   if (otk::Property::get(_window, otk::Property::atoms.net_wm_state,
370                          otk::Property::atoms.atom, &num, &state)) {
371     for (unsigned long i = 0; i < num; ++i) {
372       if (state[i] == otk::Property::atoms.net_wm_state_modal)
373         _modal = true;
374       else if (state[i] == otk::Property::atoms.net_wm_state_shaded)
375         _shaded = true;
376       else if (state[i] == otk::Property::atoms.net_wm_state_hidden)
377         _iconic = true;
378       else if (state[i] == otk::Property::atoms.net_wm_state_skip_taskbar)
379         _skip_taskbar = true;
380       else if (state[i] == otk::Property::atoms.net_wm_state_skip_pager)
381         _skip_pager = true;
382       else if (state[i] == otk::Property::atoms.net_wm_state_fullscreen)
383         _fullscreen = true;
384       else if (state[i] == otk::Property::atoms.net_wm_state_maximized_vert)
385         _max_vert = true;
386       else if (state[i] == otk::Property::atoms.net_wm_state_maximized_horz)
387         _max_horz = true;
388       else if (state[i] == otk::Property::atoms.net_wm_state_above)
389         _above = true;
390       else if (state[i] == otk::Property::atoms.net_wm_state_below)
391         _below = true;
392     }
393
394     delete [] state;
395   }
396 }
397
398
399 void Client::getShaped()
400 {
401   _shaped = false;
402 #ifdef   SHAPE
403   if (otk::display->shape()) {
404     int foo;
405     unsigned int ufoo;
406     int s;
407
408     XShapeSelectInput(**otk::display, _window, ShapeNotifyMask);
409
410     XShapeQueryExtents(**otk::display, _window, &s, &foo,
411                        &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo);
412     _shaped = (s != 0);
413   }
414 #endif // SHAPE
415 }
416
417
418 void Client::calcLayer() {
419   StackLayer l;
420
421   if (_iconic) l = Layer_Icon;
422   else if (_fullscreen) l = Layer_Fullscreen;
423   else if (_type == Type_Desktop) l = Layer_Desktop;
424   else if (_type == Type_Dock) {
425     if (!_below) l = Layer_Top;
426     else l = Layer_Normal;
427   }
428   else if (_above) l = Layer_Above;
429   else if (_below) l = Layer_Below;
430   else l = Layer_Normal;
431
432   if (l != _layer) {
433     _layer = l;
434     if (frame) {
435       /*
436         if we don't have a frame, then we aren't mapped yet (and this would
437         SIGSEGV :)
438       */
439       openbox->screen(_screen)->raiseWindow(this);
440     }
441   }
442 }
443
444
445 void Client::updateProtocols()
446 {
447   Atom *proto;
448   int num_return = 0;
449
450   _focus_notify = false;
451   _delete_window = false;
452
453   if (XGetWMProtocols(**otk::display, _window, &proto, &num_return)) {
454     for (int i = 0; i < num_return; ++i) {
455       if (proto[i] == otk::Property::atoms.wm_delete_window) {
456         // this means we can request the window to close
457         _delete_window = true;
458       } else if (proto[i] == otk::Property::atoms.wm_take_focus)
459         // if this protocol is requested, then the window will be notified
460         // by the window manager whenever it receives focus
461         _focus_notify = true;
462     }
463     XFree(proto);
464   }
465 }
466
467
468 void Client::updateNormalHints()
469 {
470   XSizeHints size;
471   long ret;
472   int oldgravity = _gravity;
473
474   // defaults
475   _min_ratio = 0.0;
476   _max_ratio = 0.0;
477   _size_inc = otk::Size(1, 1);
478   _base_size = otk::Size(0, 0);
479   _min_size = otk::Size(0, 0);
480   _max_size = otk::Size(INT_MAX, INT_MAX);
481
482   // get the hints from the window
483   if (XGetWMNormalHints(**otk::display, _window, &size, &ret)) {
484     _positioned = (size.flags & (PPosition|USPosition));
485
486     if (size.flags & PWinGravity) {
487       _gravity = size.win_gravity;
488       
489       // if the client has a frame, i.e. has already been mapped and is
490       // changing its gravity
491       if (frame && _gravity != oldgravity) {
492         // move our idea of the client's position based on its new gravity
493         int x = frame->area().x(), y = frame->area().y();
494         frame->frameGravity(x, y);
495         _area = otk::Rect(otk::Point(x, y), _area.size());
496       }
497     }
498
499     if (size.flags & PAspect) {
500       if (size.min_aspect.y) _min_ratio = size.min_aspect.x/size.min_aspect.y;
501       if (size.max_aspect.y) _max_ratio = size.max_aspect.x/size.max_aspect.y;
502     }
503
504     if (size.flags & PMinSize)
505       _min_size = otk::Size(size.min_width, size.min_height);
506     
507     if (size.flags & PMaxSize)
508       _max_size = otk::Size(size.max_width, size.max_height);
509     
510     if (size.flags & PBaseSize)
511       _base_size = otk::Size(size.base_width, size.base_height);
512     
513     if (size.flags & PResizeInc)
514       _size_inc = otk::Size(size.width_inc, size.height_inc);
515   }
516 }
517
518
519 void Client::updateWMHints(bool initstate)
520 {
521   XWMHints *hints;
522
523   // assume a window takes input if it doesnt specify
524   _can_focus = true;
525   bool ur = false;
526   
527   if ((hints = XGetWMHints(**otk::display, _window)) != NULL) {
528     if (hints->flags & InputHint)
529       _can_focus = hints->input;
530
531     // only do this when initstate is true!
532     if (initstate && (hints->flags & StateHint))
533       _iconic = hints->initial_state == IconicState;
534
535     if (hints->flags & XUrgencyHint)
536       ur = true;
537
538     if (hints->flags & WindowGroupHint) {
539       if (hints->window_group != _group) {
540         // XXX: remove from the old group if there was one
541         _group = hints->window_group;
542         // XXX: do stuff with the group
543       }
544     } else // no group!
545       _group = None;
546
547     XFree(hints);
548   }
549
550   if (ur != _urgent) {
551     _urgent = ur;
552 #ifdef DEBUG
553     printf("DEBUG: Urgent Hint for 0x%lx: %s\n",
554            (long)_window, _urgent ? "ON" : "OFF");
555 #endif
556     // fire the urgent callback if we're mapped, otherwise, wait until after
557     // we're mapped
558     if (frame)
559       fireUrgent();
560   }
561 }
562
563
564 void Client::updateTitle()
565 {
566   _title = "";
567   
568   // try netwm
569   if (!otk::Property::get(_window, otk::Property::atoms.net_wm_name,
570                           otk::Property::utf8, &_title)) {
571     // try old x stuff
572     otk::Property::get(_window, otk::Property::atoms.wm_name,
573                        otk::Property::ascii, &_title);
574   }
575
576   if (_title.empty())
577     _title = _("Unnamed Window");
578
579   if (frame)
580     frame->adjustTitle();
581 }
582
583
584 void Client::updateIconTitle()
585 {
586   _icon_title = "";
587   
588   // try netwm
589   if (!otk::Property::get(_window, otk::Property::atoms.net_wm_icon_name,
590                           otk::Property::utf8, &_icon_title)) {
591     // try old x stuff
592     otk::Property::get(_window, otk::Property::atoms.wm_icon_name,
593                        otk::Property::ascii, &_icon_title);
594   }
595
596   if (_title.empty())
597     _icon_title = _("Unnamed Window");
598 }
599
600
601 void Client::updateClass()
602 {
603   // set the defaults
604   _app_name = _app_class = _role = "";
605
606   otk::Property::StringVect v;
607   unsigned long num = 2;
608
609   if (otk::Property::get(_window, otk::Property::atoms.wm_class,
610                          otk::Property::ascii, &num, &v)) {
611     if (num > 0) _app_name = v[0].c_str();
612     if (num > 1) _app_class = v[1].c_str();
613   }
614
615   v.clear();
616   num = 1;
617   if (otk::Property::get(_window, otk::Property::atoms.wm_window_role,
618                          otk::Property::ascii, &num, &v)) {
619     if (num > 0) _role = v[0].c_str();
620   }
621 }
622
623
624 void Client::updateStrut()
625 {
626   unsigned long num = 4;
627   unsigned long *data;
628   if (!otk::Property::get(_window, otk::Property::atoms.net_wm_strut,
629                           otk::Property::atoms.cardinal, &num, &data))
630     return;
631
632   if (num == 4) {
633     _strut.left = data[0];
634     _strut.right = data[1];
635     _strut.top = data[2];
636     _strut.bottom = data[3]; 
637
638     // updating here is pointless while we're being mapped cuz we're not in
639     // the screen's client list yet
640     if (frame)
641       openbox->screen(_screen)->updateStruts();
642   }
643
644   delete [] data;
645 }
646
647
648 void Client::updateTransientFor()
649 {
650   Window t = 0;
651   Client *c = 0;
652
653   if (XGetTransientForHint(**otk::display, _window, &t) &&
654       t != _window) { // cant be transient to itself!
655     c = openbox->findClient(t);
656     assert(c != this); // if this happens then we need to check for it
657
658     if (!c /*XXX: && _group*/) {
659       // not transient to a client, see if it is transient for a group
660       if (//t == _group->leader() ||
661         t == None ||
662         t == otk::display->screenInfo(_screen)->rootWindow()) {
663         // window is a transient for its group!
664         // XXX: for now this is treated as non-transient.
665         //      this needs to be fixed!
666       }
667     }
668   }
669
670   // if anything has changed...
671   if (c != _transient_for) {
672     if (_transient_for)
673       _transient_for->_transients.remove(this); // remove from old parent
674     _transient_for = c;
675     if (_transient_for)
676       _transient_for->_transients.push_back(this); // add to new parent
677   }
678 }
679
680
681 void Client::propertyHandler(const XPropertyEvent &e)
682 {
683   otk::EventHandler::propertyHandler(e);
684
685   // validate cuz we query stuff off the client here
686   if (!validate()) return;
687   
688   // compress changes to a single property into a single change
689   XEvent ce;
690   while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
691     // XXX: it would be nice to compress ALL changes to a property, not just
692     //      changes in a row without other props between.
693     if (ce.xproperty.atom != e.atom) {
694       XPutBackEvent(**otk::display, &ce);
695       break;
696     }
697   }
698
699   if (e.atom == XA_WM_NORMAL_HINTS) {
700     updateNormalHints();
701     setupDecorAndFunctions(); // normal hints can make a window non-resizable
702   } else if (e.atom == XA_WM_HINTS)
703     updateWMHints();
704   else if (e.atom == XA_WM_TRANSIENT_FOR) {
705     updateTransientFor();
706     getType();
707     calcLayer(); // type may have changed, so update the layer
708     setupDecorAndFunctions();
709   }
710   else if (e.atom == otk::Property::atoms.net_wm_name ||
711            e.atom == otk::Property::atoms.wm_name)
712     updateTitle();
713   else if (e.atom == otk::Property::atoms.net_wm_icon_name ||
714            e.atom == otk::Property::atoms.wm_icon_name)
715     updateIconTitle();
716   else if (e.atom == otk::Property::atoms.wm_class)
717     updateClass();
718   else if (e.atom == otk::Property::atoms.wm_protocols) {
719     updateProtocols();
720     setupDecorAndFunctions();
721   }
722   else if (e.atom == otk::Property::atoms.net_wm_strut)
723     updateStrut();
724 }
725
726
727 void Client::setWMState(long state)
728 {
729   if (state == _wmstate) return; // no change
730   
731   switch (state) {
732   case IconicState:
733     iconify(true);
734     break;
735   case NormalState:
736     iconify(false);
737     break;
738   }
739 }
740
741
742 void Client::setDesktop(unsigned int target)
743 {
744   if (target == _desktop) return;
745   
746   printf("Setting desktop %u\n", target);
747
748   if (!(target < openbox->screen(_screen)->numDesktops() ||
749         target == 0xffffffff))
750     return;
751
752   _desktop = target;
753
754   // set the desktop hint
755   otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
756                      otk::Property::atoms.cardinal, _desktop);
757   
758   // 'move' the window to the new desktop
759   showhide();
760
761   openbox->screen(_screen)->updateStruts();
762 }
763
764
765 void Client::showhide()
766 {
767   bool show;
768   Screen *s = openbox->screen(_screen);
769
770   if (_iconic) show = false;
771   else if (!(_desktop == s->desktop() ||
772              _desktop == 0xffffffff)) show = false;
773   else if (normal() && s->showingDesktop()) show = false;
774   else show = true;
775
776   if (show) frame->show();
777   else      frame->hide();
778 }
779
780
781 void Client::setState(StateAction action, long data1, long data2)
782 {
783   bool shadestate = _shaded;
784   bool fsstate = _fullscreen;
785   bool maxh = _max_horz;
786   bool maxv = _max_vert;
787
788   if (!(action == State_Add || action == State_Remove ||
789         action == State_Toggle))
790     return; // an invalid action was passed to the client message, ignore it
791
792   for (int i = 0; i < 2; ++i) {
793     Atom state = i == 0 ? data1 : data2;
794     
795     if (! state) continue;
796
797     // if toggling, then pick whether we're adding or removing
798     if (action == State_Toggle) {
799       if (state == otk::Property::atoms.net_wm_state_modal)
800         action = _modal ? State_Remove : State_Add;
801       else if (state == otk::Property::atoms.net_wm_state_maximized_vert)
802         action = _max_vert ? State_Remove : State_Add;
803       else if (state == otk::Property::atoms.net_wm_state_maximized_horz)
804         action = _max_horz ? State_Remove : State_Add;
805       else if (state == otk::Property::atoms.net_wm_state_shaded)
806         action = _shaded ? State_Remove : State_Add;
807       else if (state == otk::Property::atoms.net_wm_state_skip_taskbar)
808         action = _skip_taskbar ? State_Remove : State_Add;
809       else if (state == otk::Property::atoms.net_wm_state_skip_pager)
810         action = _skip_pager ? State_Remove : State_Add;
811       else if (state == otk::Property::atoms.net_wm_state_fullscreen)
812         action = _fullscreen ? State_Remove : State_Add;
813       else if (state == otk::Property::atoms.net_wm_state_above)
814         action = _above ? State_Remove : State_Add;
815       else if (state == otk::Property::atoms.net_wm_state_below)
816         action = _below ? State_Remove : State_Add;
817     }
818     
819     if (action == State_Add) {
820       if (state == otk::Property::atoms.net_wm_state_modal) {
821         if (_modal) continue;
822         _modal = true;
823       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
824         maxv = true;
825       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
826         if (_max_horz) continue;
827         maxh = true;
828       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
829         shadestate = true;
830       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
831         _skip_taskbar = true;
832       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
833         _skip_pager = true;
834       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
835         fsstate = true;
836       } else if (state == otk::Property::atoms.net_wm_state_above) {
837         if (_above) continue;
838         _above = true;
839       } else if (state == otk::Property::atoms.net_wm_state_below) {
840         if (_below) continue;
841         _below = true;
842       }
843
844     } else { // action == State_Remove
845       if (state == otk::Property::atoms.net_wm_state_modal) {
846         if (!_modal) continue;
847         _modal = false;
848       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
849         maxv = false;
850       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
851         maxh = false;
852       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
853         shadestate = false;
854       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
855         _skip_taskbar = false;
856       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
857         _skip_pager = false;
858       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
859         fsstate = false;
860       } else if (state == otk::Property::atoms.net_wm_state_above) {
861         if (!_above) continue;
862         _above = false;
863       } else if (state == otk::Property::atoms.net_wm_state_below) {
864         if (!_below) continue;
865         _below = false;
866       }
867     }
868   }
869   if (maxh != _max_horz || maxv != _max_vert) {
870     if (maxh != _max_horz && maxv != _max_vert) { // toggling both
871       if (maxh == maxv) { // both going the same way
872         maximize(maxh, 0, true);
873       } else {
874         maximize(maxh, 1, true);
875         maximize(maxv, 2, true);
876       }
877     } else { // toggling one
878       if (maxh != _max_horz)
879         maximize(maxh, 1, true);
880       else
881         maximize(maxv, 2, true);
882     }
883   }
884   // change fullscreen state before shading, as it will affect if the window
885   // can shade or not
886   if (fsstate != _fullscreen)
887     fullscreen(fsstate, true);
888   if (shadestate != _shaded)
889     shade(shadestate);
890   calcLayer();
891   changeState(); // change the hint to relect these changes
892 }
893
894
895 void Client::toggleClientBorder(bool addborder)
896 {
897   // adjust our idea of where the client is, based on its border. When the
898   // border is removed, the client should now be considered to be in a
899   // different position.
900   // when re-adding the border to the client, the same operation needs to be
901   // reversed.
902   int oldx = _area.x(), oldy = _area.y();
903   int x = oldx, y = oldy;
904   switch(_gravity) {
905   default:
906   case NorthWestGravity:
907   case WestGravity:
908   case SouthWestGravity:
909     break;
910   case NorthEastGravity:
911   case EastGravity:
912   case SouthEastGravity:
913     if (addborder) x -= _border_width * 2;
914     else           x += _border_width * 2;
915     break;
916   case NorthGravity:
917   case SouthGravity:
918   case CenterGravity:
919   case ForgetGravity:
920   case StaticGravity:
921     if (addborder) x -= _border_width;
922     else           x += _border_width;
923     break;
924   }
925   switch(_gravity) {
926   default:
927   case NorthWestGravity:
928   case NorthGravity:
929   case NorthEastGravity:
930     break;
931   case SouthWestGravity:
932   case SouthGravity:
933   case SouthEastGravity:
934     if (addborder) y -= _border_width * 2;
935     else           y += _border_width * 2;
936     break;
937   case WestGravity:
938   case EastGravity:
939   case CenterGravity:
940   case ForgetGravity:
941   case StaticGravity:
942     if (addborder) y -= _border_width;
943     else           y += _border_width;
944     break;
945   }
946   _area = otk::Rect(otk::Point(x, y), _area.size());
947
948   if (addborder) {
949     XSetWindowBorderWidth(**otk::display, _window, _border_width);
950
951     // move the client so it is back it the right spot _with_ its border!
952     if (x != oldx || y != oldy)
953       XMoveWindow(**otk::display, _window, x, y);
954   } else
955     XSetWindowBorderWidth(**otk::display, _window, 0);
956 }
957
958
959 void Client::clientMessageHandler(const XClientMessageEvent &e)
960 {
961   otk::EventHandler::clientMessageHandler(e);
962   
963   // validate cuz we query stuff off the client here
964   if (!validate()) return;
965   
966   if (e.format != 32) return;
967
968   if (e.message_type == otk::Property::atoms.wm_change_state) {
969     // compress changes into a single change
970     bool compress = false;
971     XEvent ce;
972     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
973       // XXX: it would be nice to compress ALL messages of a type, not just
974       //      messages in a row without other message types between.
975       if (ce.xclient.message_type != e.message_type) {
976         XPutBackEvent(**otk::display, &ce);
977         break;
978       }
979       compress = true;
980     }
981     if (compress)
982       setWMState(ce.xclient.data.l[0]); // use the found event
983     else
984       setWMState(e.data.l[0]); // use the original event
985   } else if (e.message_type == otk::Property::atoms.net_wm_desktop) {
986     // compress changes into a single change 
987     bool compress = false;
988     XEvent ce;
989     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
990       // XXX: it would be nice to compress ALL messages of a type, not just
991       //      messages in a row without other message types between.
992       if (ce.xclient.message_type != e.message_type) {
993         XPutBackEvent(**otk::display, &ce);
994         break;
995       }
996       compress = true;
997     }
998     if (compress)
999       setDesktop(e.data.l[0]); // use the found event
1000     else
1001       setDesktop(e.data.l[0]); // use the original event
1002   } else if (e.message_type == otk::Property::atoms.net_wm_state) {
1003     // can't compress these
1004 #ifdef DEBUG
1005     printf("net_wm_state %s %ld %ld for 0x%lx\n",
1006            (e.data.l[0] == 0 ? "Remove" : e.data.l[0] == 1 ? "Add" :
1007             e.data.l[0] == 2 ? "Toggle" : "INVALID"),
1008            e.data.l[1], e.data.l[2], _window);
1009 #endif
1010     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
1011   } else if (e.message_type == otk::Property::atoms.net_close_window) {
1012 #ifdef DEBUG
1013     printf("net_close_window for 0x%lx\n", _window);
1014 #endif
1015     close();
1016   } else if (e.message_type == otk::Property::atoms.net_active_window) {
1017 #ifdef DEBUG
1018     printf("net_active_window for 0x%lx\n", _window);
1019 #endif
1020     if (_iconic)
1021       iconify(false);
1022     if (_shaded)
1023       shade(false);
1024     focus();
1025     openbox->screen(_screen)->raiseWindow(this);
1026   } else if (e.message_type == otk::Property::atoms.openbox_active_window) {
1027     if (_iconic)
1028       iconify(false);
1029     if (e.data.l[0] && _shaded)
1030       shade(false);
1031     focus();
1032     if (e.data.l[1])
1033       openbox->screen(_screen)->raiseWindow(this);
1034   }
1035 }
1036
1037
1038 #if defined(SHAPE)
1039 void Client::shapeHandler(const XShapeEvent &e)
1040 {
1041   otk::EventHandler::shapeHandler(e);
1042
1043   if (e.kind == ShapeBounding) {
1044     _shaped = e.shaped;
1045     frame->adjustShape();
1046   }
1047 }
1048 #endif
1049
1050
1051 void Client::resize(Corner anchor, int w, int h)
1052 {
1053   if (!(_functions & Func_Resize)) return;
1054   internal_resize(anchor, w, h);
1055 }
1056
1057
1058 void Client::internal_resize(Corner anchor, int w, int h,
1059                              bool user, int x, int y)
1060 {
1061   w -= _base_size.width();
1062   h -= _base_size.height();
1063
1064   if (user) {
1065     // for interactive resizing. have to move half an increment in each
1066     // direction.
1067     int mw = w % _size_inc.width(); // how far we are towards the next size inc
1068     int mh = h % _size_inc.height();
1069     int aw = _size_inc.width() / 2; // amount to add
1070     int ah = _size_inc.height() / 2;
1071     // don't let us move into a new size increment
1072     if (mw + aw >= _size_inc.width()) aw = _size_inc.width() - mw - 1;
1073     if (mh + ah >= _size_inc.height()) ah = _size_inc.height() - mh - 1;
1074     w += aw;
1075     h += ah;
1076     
1077     // if this is a user-requested resize, then check against min/max sizes
1078     // and aspect ratios
1079
1080     // smaller than min size or bigger than max size?
1081     if (w > _max_size.width()) w = _max_size.width();
1082     if (w < _min_size.width()) w = _min_size.width();
1083     if (h > _max_size.height()) h = _max_size.height();
1084     if (h < _min_size.height()) h = _min_size.height();
1085
1086     // adjust the height ot match the width for the aspect ratios
1087     if (_min_ratio)
1088       if (h * _min_ratio > w) h = static_cast<int>(w / _min_ratio);
1089     if (_max_ratio)
1090       if (h * _max_ratio < w) h = static_cast<int>(w / _max_ratio);
1091   }
1092
1093   // keep to the increments
1094   w /= _size_inc.width();
1095   h /= _size_inc.height();
1096
1097   // you cannot resize to nothing
1098   if (w < 1) w = 1;
1099   if (h < 1) h = 1;
1100   
1101   // store the logical size
1102   _logical_size = otk::Size(w, h);
1103
1104   w *= _size_inc.width();
1105   h *= _size_inc.height();
1106
1107   w += _base_size.width();
1108   h += _base_size.height();
1109
1110   if (x == INT_MIN || y == INT_MIN) {
1111     x = _area.x();
1112     y = _area.y();
1113     switch (anchor) {
1114     case TopLeft:
1115       break;
1116     case TopRight:
1117       x -= w - _area.width();
1118       break;
1119     case BottomLeft:
1120       y -= h - _area.height();
1121       break;
1122     case BottomRight:
1123       x -= w - _area.width();
1124       y -= h - _area.height();
1125       break;
1126     }
1127   }
1128
1129   _area = otk::Rect(_area.position(), otk::Size(w, h));
1130
1131   XResizeWindow(**otk::display, _window, w, h);
1132
1133   // resize the frame to match the request
1134   frame->adjustSize();
1135   internal_move(x, y);
1136 }
1137
1138
1139 void Client::move(int x, int y)
1140 {
1141   if (!(_functions & Func_Move)) return;
1142   frame->frameGravity(x, y); // get the client's position based on x,y for the
1143                              // frame
1144   internal_move(x, y);
1145 }
1146
1147
1148 void Client::internal_move(int x, int y)
1149 {
1150   _area = otk::Rect(otk::Point(x, y), _area.size());
1151
1152   // move the frame to be in the requested position
1153   if (frame) { // this can be called while mapping, before frame exists
1154     frame->adjustPosition();
1155
1156     // send synthetic configure notify (we don't need to if we aren't mapped
1157     // yet)
1158     XEvent event;
1159     event.type = ConfigureNotify;
1160     event.xconfigure.display = **otk::display;
1161     event.xconfigure.event = _window;
1162     event.xconfigure.window = _window;
1163     
1164     // root window coords with border in mind
1165     event.xconfigure.x = x - _border_width + frame->size().left;
1166     event.xconfigure.y = y - _border_width + frame->size().top;
1167     
1168     event.xconfigure.width = _area.width();
1169     event.xconfigure.height = _area.height();
1170     event.xconfigure.border_width = _border_width;
1171     event.xconfigure.above = frame->plate();
1172     event.xconfigure.override_redirect = False;
1173     XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1174                StructureNotifyMask, &event);
1175 #if 0//def DEBUG
1176     printf("Sent synthetic ConfigureNotify %d,%d %d,%d to 0x%lx\n",
1177            event.xconfigure.x, event.xconfigure.y, event.xconfigure.width,
1178            event.xconfigure.height, event.xconfigure.window);
1179 #endif
1180   }
1181 }
1182
1183
1184 void Client::close()
1185 {
1186   XEvent ce;
1187
1188   if (!(_functions & Func_Close)) return;
1189
1190   // XXX: itd be cool to do timeouts and shit here for killing the client's
1191   //      process off
1192   // like... if the window is around after 5 seconds, then the close button
1193   // turns a nice red, and if this function is called again, the client is
1194   // explicitly killed.
1195
1196   ce.xclient.type = ClientMessage;
1197   ce.xclient.message_type =  otk::Property::atoms.wm_protocols;
1198   ce.xclient.display = **otk::display;
1199   ce.xclient.window = _window;
1200   ce.xclient.format = 32;
1201   ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1202   ce.xclient.data.l[1] = CurrentTime;
1203   ce.xclient.data.l[2] = 0l;
1204   ce.xclient.data.l[3] = 0l;
1205   ce.xclient.data.l[4] = 0l;
1206   XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1207 }
1208
1209
1210 void Client::changeState()
1211 {
1212   unsigned long state[2];
1213   state[0] = _wmstate;
1214   state[1] = None;
1215   otk::Property::set(_window, otk::Property::atoms.wm_state,
1216                      otk::Property::atoms.wm_state, state, 2);
1217
1218   Atom netstate[10];
1219   int num = 0;
1220   if (_modal)
1221     netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1222   if (_shaded)
1223     netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1224   if (_iconic)
1225     netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1226   if (_skip_taskbar)
1227     netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1228   if (_skip_pager)
1229     netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1230   if (_fullscreen)
1231     netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1232   if (_max_vert)
1233     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1234   if (_max_horz)
1235     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1236   if (_above)
1237     netstate[num++] = otk::Property::atoms.net_wm_state_above;
1238   if (_below)
1239     netstate[num++] = otk::Property::atoms.net_wm_state_below;
1240   otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1241                      otk::Property::atoms.atom, netstate, num);
1242
1243   calcLayer();
1244
1245   if (frame)
1246     frame->adjustState();
1247 }
1248
1249
1250 void Client::changeAllowedActions(void)
1251 {
1252   Atom actions[9];
1253   int num = 0;
1254
1255   actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1256
1257   if (_functions & Func_Shade)
1258     actions[num++] = otk::Property::atoms.net_wm_action_shade;
1259   if (_functions & Func_Close)
1260     actions[num++] = otk::Property::atoms.net_wm_action_close;
1261   if (_functions & Func_Move)
1262     actions[num++] = otk::Property::atoms.net_wm_action_move;
1263   if (_functions & Func_Iconify)
1264     actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1265   if (_functions & Func_Resize)
1266     actions[num++] = otk::Property::atoms.net_wm_action_resize;
1267   if (_functions & Func_Fullscreen)
1268     actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1269   if (_functions & Func_Maximize) {
1270     actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1271     actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1272   }
1273
1274   otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1275                      otk::Property::atoms.atom, actions, num);
1276
1277   // make sure the window isn't breaking any rules now
1278   
1279   if (!(_functions & Func_Shade) && _shaded)
1280     if (frame) shade(false);
1281     else _shaded = false;
1282   if (!(_functions & Func_Iconify) && _iconic)
1283     if (frame) setDesktop(openbox->screen(_screen)->desktop());
1284     else _iconic = false;
1285   if (!(_functions & Func_Fullscreen) && _fullscreen)
1286     if (frame) fullscreen(false);
1287     else _fullscreen = false;
1288   if (!(_functions & Func_Maximize) && (_max_horz || _max_vert))
1289     if (frame) maximize(false, 0);
1290     else _max_vert = _max_horz = false;
1291 }
1292
1293
1294 void Client::remaximize()
1295 {
1296   int dir;
1297   if (_max_horz && _max_vert)
1298     dir = 0;
1299   else if (_max_horz)
1300     dir = 1;
1301   else if (_max_vert)
1302     dir = 2;
1303   else
1304     return; // not maximized
1305   _max_horz = _max_vert = false;
1306   maximize(true, dir, false);
1307 }
1308
1309
1310 void Client::applyStartupState()
1311 {
1312   // these are in a carefully crafted order..
1313
1314   if (_iconic) {
1315     _iconic = false;
1316     iconify(true);
1317   }
1318   if (_fullscreen) {
1319     _fullscreen = false;
1320     fullscreen(true, false);
1321   }
1322   if (_shaded) {
1323     _shaded = false;
1324     shade(true);
1325   }
1326   if (_urgent)
1327     fireUrgent();
1328   
1329   if (_max_vert && _max_horz) {
1330     _max_vert = _max_horz = false;
1331     maximize(true, 0, false);
1332   } else if (_max_vert) {
1333     _max_vert = false;
1334     maximize(true, 2, false);
1335   } else if (_max_horz) {
1336     _max_horz = false;
1337     maximize(true, 1, false);
1338   }
1339
1340   if (_skip_taskbar); // nothing to do for this
1341   if (_skip_pager);   // nothing to do for this
1342   if (_modal);        // nothing to do for this
1343   if (_above);        // nothing to do for this
1344   if (_below);        // nothing to do for this
1345 }
1346
1347
1348 void Client::fireUrgent()
1349 {
1350   // call the python UrgentWindow callbacks
1351   EventData data(_screen, this, EventAction::UrgentWindow, 0);
1352   openbox->bindings()->fireEvent(&data);
1353 }
1354
1355
1356 void Client::shade(bool shade)
1357 {
1358   if (!(_functions & Func_Shade) || // can't
1359       _shaded == shade) return;     // already done
1360
1361   // when we're iconic, don't change the wmstate
1362   if (!_iconic)
1363     _wmstate = shade ? IconicState : NormalState;
1364   _shaded = shade;
1365   changeState();
1366   frame->adjustSize();
1367 }
1368
1369
1370 void Client::maximize(bool max, int dir, bool savearea)
1371 {
1372   assert(dir == 0 || dir == 1 || dir == 2);
1373   if (!(_functions & Func_Maximize)) return; // can't
1374
1375   // check if already done
1376   if (max) {
1377     if (dir == 0 && _max_horz && _max_vert) return;
1378     if (dir == 1 && _max_horz) return;
1379     if (dir == 2 && _max_vert) return;
1380   } else {
1381     if (dir == 0 && !_max_horz && !_max_vert) return;
1382     if (dir == 1 && !_max_horz) return;
1383     if (dir == 2 && !_max_vert) return;
1384   }
1385
1386   const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1387   int x = frame->area().x(), y = frame->area().y(),
1388     w = _area.width(), h = _area.height();
1389   
1390   if (max) {
1391     if (savearea) {
1392       long dimensions[4];
1393       long *readdim;
1394       unsigned long n = 4;
1395
1396       dimensions[0] = x;
1397       dimensions[1] = y;
1398       dimensions[2] = w;
1399       dimensions[3] = h;
1400
1401       // get the property off the window and use it for the dimentions we are
1402       // already maxed on
1403       if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1404                              otk::Property::atoms.cardinal, &n,
1405                              (long unsigned**) &readdim)) {
1406         if (n >= 4) {
1407           if (_max_horz) {
1408             dimensions[0] = readdim[0];
1409             dimensions[2] = readdim[2];
1410           }
1411           if (_max_vert) {
1412             dimensions[1] = readdim[1];
1413             dimensions[3] = readdim[3];
1414           }
1415         }
1416         delete readdim;
1417       }
1418       
1419       otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1420                          otk::Property::atoms.cardinal,
1421                          (long unsigned*)dimensions, 4);
1422     }
1423     if (dir == 0 || dir == 1) { // horz
1424       x = a.x();
1425       w = a.width();
1426     }
1427     if (dir == 0 || dir == 2) { // vert
1428       y = a.y();
1429       h = a.height() - frame->size().top - frame->size().bottom;
1430     }
1431   } else {
1432     long *dimensions;
1433     long unsigned n = 4;
1434       
1435     if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1436                            otk::Property::atoms.cardinal, &n,
1437                            (long unsigned**) &dimensions)) {
1438       if (n >= 4) {
1439         if (dir == 0 || dir == 1) { // horz
1440           x = (signed int)dimensions[0];
1441           w = (signed int)dimensions[2];
1442         }
1443         if (dir == 0 || dir == 2) { // vert
1444           y = (signed int)dimensions[1];
1445           h = (signed int)dimensions[3];
1446         }
1447       }
1448       delete dimensions;
1449     } else {
1450       // pick some fallbacks...
1451       if (dir == 0 || dir == 1) { // horz
1452         x = a.x() + a.width() / 4;
1453         w = a.width() / 2;
1454       }
1455       if (dir == 0 || dir == 2) { // vert
1456         y = a.y() + a.height() / 4;
1457         h = a.height() / 2;
1458       }
1459     }
1460   }
1461
1462   if (dir == 0 || dir == 1) // horz
1463     _max_horz = max;
1464   if (dir == 0 || dir == 2) // vert
1465     _max_vert = max;
1466
1467   if (!_max_horz && !_max_vert)
1468     otk::Property::erase(_window, otk::Property::atoms.openbox_premax);
1469
1470   changeState(); // change the state hints on the client
1471
1472   frame->frameGravity(x, y); // figure out where the client should be going
1473   internal_resize(TopLeft, w, h, true, x, y);
1474 }
1475
1476
1477 void Client::fullscreen(bool fs, bool savearea)
1478 {
1479   static FunctionFlags saved_func;
1480   static DecorationFlags saved_decor;
1481
1482   if (!(_functions & Func_Fullscreen) || // can't
1483       _fullscreen == fs) return;         // already done
1484
1485   _fullscreen = fs;
1486   changeState(); // change the state hints on the client
1487
1488   int x = _area.x(), y = _area.y(), w = _area.width(), h = _area.height();
1489   
1490   if (fs) {
1491     // save the functions and remove them
1492     saved_func = _functions;
1493     _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1494     // save the decorations and remove them
1495     saved_decor = _decorations;
1496     _decorations = 0;
1497     if (savearea) {
1498       long dimensions[4];
1499       dimensions[0] = _area.x();
1500       dimensions[1] = _area.y();
1501       dimensions[2] = _area.width();
1502       dimensions[3] = _area.height();
1503       otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1504                          otk::Property::atoms.cardinal,
1505                          (long unsigned*)dimensions, 4);
1506     }
1507     const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1508     x = 0;
1509     y = 0;
1510     w = info->size().width();
1511     h = info->size().height();
1512   } else {
1513     _functions = saved_func;
1514     _decorations = saved_decor;
1515
1516     long *dimensions;
1517     long unsigned n = 4;
1518       
1519     if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1520                            otk::Property::atoms.cardinal, &n,
1521                            (long unsigned**) &dimensions)) {
1522       if (n >= 4) {
1523         x = dimensions[0];
1524         y = dimensions[1];
1525         w = dimensions[2];
1526         h = dimensions[3];
1527       }
1528       delete dimensions;
1529     } else {
1530       // pick some fallbacks...
1531       const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1532       x = a.x() + a.width() / 4;
1533       y = a.y() + a.height() / 4;
1534       w = a.width() / 2;
1535         h = a.height() / 2;
1536     }    
1537   }
1538   
1539   changeAllowedActions();  // based on the new _functions
1540
1541   // when fullscreening, don't obey things like increments, fill the screen
1542   internal_resize(TopLeft, w, h, !fs, x, y);
1543
1544   // raise (back) into our stacking layer
1545   openbox->screen(_screen)->raiseWindow(this);
1546
1547   // try focus us when we go into fullscreen mode
1548   if (fs) focus();
1549 }
1550
1551
1552 void Client::iconify(bool iconic, bool curdesk)
1553 {
1554   if (_iconic == iconic) return; // nothing to do
1555
1556   _iconic = iconic;
1557
1558   if (_iconic) {
1559     _wmstate = IconicState;
1560     ignore_unmaps++;
1561     // we unmap the client itself so that we can get MapRequest events, and
1562     // because the ICCCM tells us to!
1563     XUnmapWindow(**otk::display, _window);
1564   } else {
1565     if (curdesk)
1566       setDesktop(openbox->screen(_screen)->desktop());
1567     _wmstate = NormalState;
1568     XMapWindow(**otk::display, _window);
1569   }
1570   changeState();
1571
1572   showhide();
1573   
1574   openbox->screen(_screen)->updateStruts();
1575 }
1576
1577
1578 void Client::disableDecorations(DecorationFlags flags)
1579 {
1580   _disabled_decorations = flags;
1581   setupDecorAndFunctions();
1582 }
1583
1584
1585 void Client::installColormap(bool install) const
1586 {
1587   XWindowAttributes wa;
1588   if (XGetWindowAttributes(**otk::display, _window, &wa)) {
1589     if (install)
1590       XInstallColormap(**otk::display, wa.colormap);
1591     else
1592       XUninstallColormap(**otk::display, wa.colormap);
1593   }
1594 }
1595
1596
1597 // recursively searches the client 'tree' for a modal client, always skips the
1598 // topmost node (the window you're starting with)
1599 Client *Client::searchModalTree(Client *node, Client *skip)
1600 {
1601   List::const_iterator it, end = node->_transients.end();
1602   Client *ret;
1603   
1604   for (it = node->_transients.begin(); it != end; ++it) {
1605     if (*it == skip) continue; // circular?
1606     if ((ret = searchModalTree(*it, skip))) return ret; // got one
1607     if ((*it)->_modal) return *it; // got one
1608   }
1609   return 0;
1610 }
1611
1612 Client *Client::findModalChild()
1613 {
1614   return searchModalTree(this, this);
1615 }
1616
1617
1618 bool Client::focus()
1619 {
1620   // if we have a modal child, then focus it, not us
1621   Client *c = findModalChild();
1622   if (c) return c->focus();
1623
1624   // won't try focus if the client doesn't want it, or if the window isn't
1625   // visible on the screen
1626   if (!(frame->visible() && (_can_focus || _focus_notify))) return false;
1627
1628   if (_focused) return true;
1629
1630   // do a check to see if the window has already been unmapped or destroyed
1631   // do this intelligently while watching out for unmaps we've generated
1632   // (ignore_unmaps > 0)
1633   XEvent ev;
1634   if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1635     XPutBackEvent(**otk::display, &ev);
1636     return false;
1637   }
1638   while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1639     if (ignore_unmaps) {
1640       unmapHandler(ev.xunmap);
1641     } else {
1642       XPutBackEvent(**otk::display, &ev);
1643       return false;
1644     }
1645   }
1646
1647   if (_can_focus)
1648     XSetInputFocus(**otk::display, _window,
1649                    RevertToNone, CurrentTime);
1650
1651   if (_focus_notify) {
1652     XEvent ce;
1653     ce.xclient.type = ClientMessage;
1654     ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1655     ce.xclient.display = **otk::display;
1656     ce.xclient.window = _window;
1657     ce.xclient.format = 32;
1658     ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1659     ce.xclient.data.l[1] = openbox->lastTime();
1660     ce.xclient.data.l[2] = 0l;
1661     ce.xclient.data.l[3] = 0l;
1662     ce.xclient.data.l[4] = 0l;
1663     XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1664   }
1665
1666   XSync(**otk::display, False);
1667   return true;
1668 }
1669
1670
1671 void Client::unfocus() const
1672 {
1673   if (!_focused) return;
1674
1675   assert(openbox->focusedClient() == this);
1676   openbox->setFocusedClient(0);
1677 }
1678
1679
1680 void Client::focusHandler(const XFocusChangeEvent &e)
1681 {
1682 #ifdef    DEBUG
1683 //  printf("FocusIn for 0x%lx\n", e.window);
1684 #endif // DEBUG
1685   
1686   otk::EventHandler::focusHandler(e);
1687
1688   _focused = true;
1689   frame->adjustFocus();
1690
1691   openbox->setFocusedClient(this);
1692 }
1693
1694
1695 void Client::unfocusHandler(const XFocusChangeEvent &e)
1696 {
1697 #ifdef    DEBUG
1698 //  printf("FocusOut for 0x%lx\n", e.window);
1699 #endif // DEBUG
1700   
1701   otk::EventHandler::unfocusHandler(e);
1702
1703   _focused = false;
1704   frame->adjustFocus();
1705
1706   if (openbox->focusedClient() == this)
1707     openbox->setFocusedClient(0);
1708 }
1709
1710
1711 void Client::configureRequestHandler(const XConfigureRequestEvent &ec)
1712 {
1713 #ifdef    DEBUG
1714   printf("ConfigureRequest for 0x%lx\n", ec.window);
1715 #endif // DEBUG
1716   
1717   otk::EventHandler::configureRequestHandler(ec);
1718
1719   // compress these
1720   XConfigureRequestEvent e = ec;
1721   XEvent ev;
1722   while (XCheckTypedWindowEvent(**otk::display, window(), ConfigureRequest,
1723                                 &ev)) {
1724     // XXX if this causes bad things.. we can compress config req's with the
1725     //     same mask.
1726     e.value_mask |= ev.xconfigurerequest.value_mask;
1727     if (ev.xconfigurerequest.value_mask & CWX)
1728       e.x = ev.xconfigurerequest.x;
1729     if (ev.xconfigurerequest.value_mask & CWY)
1730       e.y = ev.xconfigurerequest.y;
1731     if (ev.xconfigurerequest.value_mask & CWWidth)
1732       e.width = ev.xconfigurerequest.width;
1733     if (ev.xconfigurerequest.value_mask & CWHeight)
1734       e.height = ev.xconfigurerequest.height;
1735     if (ev.xconfigurerequest.value_mask & CWBorderWidth)
1736       e.border_width = ev.xconfigurerequest.border_width;
1737     if (ev.xconfigurerequest.value_mask & CWStackMode)
1738       e.detail = ev.xconfigurerequest.detail;
1739   }
1740
1741   // if we are iconic (or shaded (fvwm does this)) ignore the event
1742   if (_iconic || _shaded) return;
1743
1744   if (e.value_mask & CWBorderWidth)
1745     _border_width = e.border_width;
1746
1747   // resize, then move, as specified in the EWMH section 7.7
1748   if (e.value_mask & (CWWidth | CWHeight)) {
1749     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1750     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1751
1752     Corner corner;
1753     switch (_gravity) {
1754     case NorthEastGravity:
1755     case EastGravity:
1756       corner = TopRight;
1757       break;
1758     case SouthWestGravity:
1759     case SouthGravity:
1760       corner = BottomLeft;
1761       break;
1762     case SouthEastGravity:
1763       corner = BottomRight;
1764       break;
1765     default:     // NorthWest, Static, etc
1766       corner = TopLeft;
1767     }
1768
1769     // if moving AND resizing ...
1770     if (e.value_mask & (CWX | CWY)) {
1771       int x = (e.value_mask & CWX) ? e.x : _area.x();
1772       int y = (e.value_mask & CWY) ? e.y : _area.y();
1773       internal_resize(corner, w, h, false, x, y);
1774     } else // if JUST resizing...
1775       internal_resize(corner, w, h, false);
1776   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1777     int x = (e.value_mask & CWX) ? e.x : _area.x();
1778     int y = (e.value_mask & CWY) ? e.y : _area.y();
1779     internal_move(x, y);
1780   }
1781
1782   if (e.value_mask & CWStackMode) {
1783     switch (e.detail) {
1784     case Below:
1785     case BottomIf:
1786       openbox->screen(_screen)->lowerWindow(this);
1787       break;
1788
1789     case Above:
1790     case TopIf:
1791     default:
1792       openbox->screen(_screen)->raiseWindow(this);
1793       break;
1794     }
1795   }
1796 }
1797
1798
1799 void Client::unmapHandler(const XUnmapEvent &e)
1800 {
1801   if (ignore_unmaps) {
1802 #ifdef    DEBUG
1803 //  printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1804 #endif // DEBUG
1805     ignore_unmaps--;
1806     return;
1807   }
1808   
1809 #ifdef    DEBUG
1810   printf("UnmapNotify for 0x%lx\n", e.window);
1811 #endif // DEBUG
1812
1813   otk::EventHandler::unmapHandler(e);
1814
1815   // this deletes us etc
1816   openbox->screen(_screen)->unmanageWindow(this);
1817 }
1818
1819
1820 void Client::destroyHandler(const XDestroyWindowEvent &e)
1821 {
1822 #ifdef    DEBUG
1823   printf("DestroyNotify for 0x%lx\n", e.window);
1824 #endif // DEBUG
1825
1826   otk::EventHandler::destroyHandler(e);
1827
1828   // this deletes us etc
1829   openbox->screen(_screen)->unmanageWindow(this);
1830 }
1831
1832
1833 void Client::reparentHandler(const XReparentEvent &e)
1834 {
1835   // this is when the client is first taken captive in the frame
1836   if (e.parent == frame->plate()) return;
1837
1838 #ifdef    DEBUG
1839   printf("ReparentNotify for 0x%lx\n", e.window);
1840 #endif // DEBUG
1841
1842   otk::EventHandler::reparentHandler(e);
1843
1844   /*
1845     This event is quite rare and is usually handled in unmapHandler.
1846     However, if the window is unmapped when the reparent event occurs,
1847     the window manager never sees it because an unmap event is not sent
1848     to an already unmapped window.
1849   */
1850
1851   // we don't want the reparent event, put it back on the stack for the X
1852   // server to deal with after we unmanage the window
1853   XEvent ev;
1854   ev.xreparent = e;
1855   XPutBackEvent(**otk::display, &ev);
1856   
1857   // this deletes us etc
1858   openbox->screen(_screen)->unmanageWindow(this);
1859 }
1860
1861 void Client::mapRequestHandler(const XMapRequestEvent &e)
1862 {
1863 #ifdef    DEBUG
1864   printf("MapRequest for already managed 0x%lx\n", e.window);
1865 #endif // DEBUG
1866
1867   assert(_iconic); // we shouldn't be able to get this unless we're iconic
1868
1869   // move to the current desktop (uniconify)
1870   iconify(false);
1871   // XXX: should we focus/raise the window? (basically a net_wm_active_window)
1872 }
1873
1874 }