rm a debug print
[dana/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   if (!_iconic &&
768       (_desktop == openbox->screen(_screen)->desktop() ||
769        _desktop == 0xffffffff))
770     frame->show();
771   else
772     frame->hide();
773 }
774
775
776 void Client::setState(StateAction action, long data1, long data2)
777 {
778   bool shadestate = _shaded;
779   bool fsstate = _fullscreen;
780   bool maxh = _max_horz;
781   bool maxv = _max_vert;
782
783   if (!(action == State_Add || action == State_Remove ||
784         action == State_Toggle))
785     return; // an invalid action was passed to the client message, ignore it
786
787   for (int i = 0; i < 2; ++i) {
788     Atom state = i == 0 ? data1 : data2;
789     
790     if (! state) continue;
791
792     // if toggling, then pick whether we're adding or removing
793     if (action == State_Toggle) {
794       if (state == otk::Property::atoms.net_wm_state_modal)
795         action = _modal ? State_Remove : State_Add;
796       else if (state == otk::Property::atoms.net_wm_state_maximized_vert)
797         action = _max_vert ? State_Remove : State_Add;
798       else if (state == otk::Property::atoms.net_wm_state_maximized_horz)
799         action = _max_horz ? State_Remove : State_Add;
800       else if (state == otk::Property::atoms.net_wm_state_shaded)
801         action = _shaded ? State_Remove : State_Add;
802       else if (state == otk::Property::atoms.net_wm_state_skip_taskbar)
803         action = _skip_taskbar ? State_Remove : State_Add;
804       else if (state == otk::Property::atoms.net_wm_state_skip_pager)
805         action = _skip_pager ? State_Remove : State_Add;
806       else if (state == otk::Property::atoms.net_wm_state_fullscreen)
807         action = _fullscreen ? State_Remove : State_Add;
808       else if (state == otk::Property::atoms.net_wm_state_above)
809         action = _above ? State_Remove : State_Add;
810       else if (state == otk::Property::atoms.net_wm_state_below)
811         action = _below ? State_Remove : State_Add;
812     }
813     
814     if (action == State_Add) {
815       if (state == otk::Property::atoms.net_wm_state_modal) {
816         if (_modal) continue;
817         _modal = true;
818       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
819         maxv = true;
820       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
821         if (_max_horz) continue;
822         maxh = true;
823       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
824         shadestate = true;
825       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
826         _skip_taskbar = true;
827       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
828         _skip_pager = true;
829       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
830         fsstate = true;
831       } else if (state == otk::Property::atoms.net_wm_state_above) {
832         if (_above) continue;
833         _above = true;
834       } else if (state == otk::Property::atoms.net_wm_state_below) {
835         if (_below) continue;
836         _below = true;
837       }
838
839     } else { // action == State_Remove
840       if (state == otk::Property::atoms.net_wm_state_modal) {
841         if (!_modal) continue;
842         _modal = false;
843       } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
844         maxv = false;
845       } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
846         maxh = false;
847       } else if (state == otk::Property::atoms.net_wm_state_shaded) {
848         shadestate = false;
849       } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
850         _skip_taskbar = false;
851       } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
852         _skip_pager = false;
853       } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
854         fsstate = false;
855       } else if (state == otk::Property::atoms.net_wm_state_above) {
856         if (!_above) continue;
857         _above = false;
858       } else if (state == otk::Property::atoms.net_wm_state_below) {
859         if (!_below) continue;
860         _below = false;
861       }
862     }
863   }
864   if (maxh != _max_horz || maxv != _max_vert) {
865     if (maxh != _max_horz && maxv != _max_vert) { // toggling both
866       if (maxh == maxv) { // both going the same way
867         maximize(maxh, 0, true);
868       } else {
869         maximize(maxh, 1, true);
870         maximize(maxv, 2, true);
871       }
872     } else { // toggling one
873       if (maxh != _max_horz)
874         maximize(maxh, 1, true);
875       else
876         maximize(maxv, 2, true);
877     }
878   }
879   // change fullscreen state before shading, as it will affect if the window
880   // can shade or not
881   if (fsstate != _fullscreen)
882     fullscreen(fsstate, true);
883   if (shadestate != _shaded)
884     shade(shadestate);
885   calcLayer();
886   changeState(); // change the hint to relect these changes
887 }
888
889
890 void Client::toggleClientBorder(bool addborder)
891 {
892   // adjust our idea of where the client is, based on its border. When the
893   // border is removed, the client should now be considered to be in a
894   // different position.
895   // when re-adding the border to the client, the same operation needs to be
896   // reversed.
897   int oldx = _area.x(), oldy = _area.y();
898   int x = oldx, y = oldy;
899   switch(_gravity) {
900   default:
901   case NorthWestGravity:
902   case WestGravity:
903   case SouthWestGravity:
904     break;
905   case NorthEastGravity:
906   case EastGravity:
907   case SouthEastGravity:
908     if (addborder) x -= _border_width * 2;
909     else           x += _border_width * 2;
910     break;
911   case NorthGravity:
912   case SouthGravity:
913   case CenterGravity:
914   case ForgetGravity:
915   case StaticGravity:
916     if (addborder) x -= _border_width;
917     else           x += _border_width;
918     break;
919   }
920   switch(_gravity) {
921   default:
922   case NorthWestGravity:
923   case NorthGravity:
924   case NorthEastGravity:
925     break;
926   case SouthWestGravity:
927   case SouthGravity:
928   case SouthEastGravity:
929     if (addborder) y -= _border_width * 2;
930     else           y += _border_width * 2;
931     break;
932   case WestGravity:
933   case EastGravity:
934   case CenterGravity:
935   case ForgetGravity:
936   case StaticGravity:
937     if (addborder) y -= _border_width;
938     else           y += _border_width;
939     break;
940   }
941   _area = otk::Rect(otk::Point(x, y), _area.size());
942
943   if (addborder) {
944     XSetWindowBorderWidth(**otk::display, _window, _border_width);
945
946     // move the client so it is back it the right spot _with_ its border!
947     if (x != oldx || y != oldy)
948       XMoveWindow(**otk::display, _window, x, y);
949   } else
950     XSetWindowBorderWidth(**otk::display, _window, 0);
951 }
952
953
954 void Client::clientMessageHandler(const XClientMessageEvent &e)
955 {
956   otk::EventHandler::clientMessageHandler(e);
957   
958   // validate cuz we query stuff off the client here
959   if (!validate()) return;
960   
961   if (e.format != 32) return;
962
963   if (e.message_type == otk::Property::atoms.wm_change_state) {
964     // compress changes into a single change
965     bool compress = false;
966     XEvent ce;
967     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
968       // XXX: it would be nice to compress ALL messages of a type, not just
969       //      messages in a row without other message types between.
970       if (ce.xclient.message_type != e.message_type) {
971         XPutBackEvent(**otk::display, &ce);
972         break;
973       }
974       compress = true;
975     }
976     if (compress)
977       setWMState(ce.xclient.data.l[0]); // use the found event
978     else
979       setWMState(e.data.l[0]); // use the original event
980   } else if (e.message_type == otk::Property::atoms.net_wm_desktop) {
981     // compress changes into a single change 
982     bool compress = false;
983     XEvent ce;
984     while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
985       // XXX: it would be nice to compress ALL messages of a type, not just
986       //      messages in a row without other message types between.
987       if (ce.xclient.message_type != e.message_type) {
988         XPutBackEvent(**otk::display, &ce);
989         break;
990       }
991       compress = true;
992     }
993     if (compress)
994       setDesktop(e.data.l[0]); // use the found event
995     else
996       setDesktop(e.data.l[0]); // use the original event
997   } else if (e.message_type == otk::Property::atoms.net_wm_state) {
998     // can't compress these
999 #ifdef DEBUG
1000     printf("net_wm_state %s %ld %ld for 0x%lx\n",
1001            (e.data.l[0] == 0 ? "Remove" : e.data.l[0] == 1 ? "Add" :
1002             e.data.l[0] == 2 ? "Toggle" : "INVALID"),
1003            e.data.l[1], e.data.l[2], _window);
1004 #endif
1005     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
1006   } else if (e.message_type == otk::Property::atoms.net_close_window) {
1007 #ifdef DEBUG
1008     printf("net_close_window for 0x%lx\n", _window);
1009 #endif
1010     close();
1011   } else if (e.message_type == otk::Property::atoms.net_active_window) {
1012 #ifdef DEBUG
1013     printf("net_active_window for 0x%lx\n", _window);
1014 #endif
1015     if (_iconic)
1016       iconify(false);
1017     if (_shaded)
1018       shade(false);
1019     focus();
1020     openbox->screen(_screen)->raiseWindow(this);
1021   } else if (e.message_type == otk::Property::atoms.openbox_active_window) {
1022     if (_iconic)
1023       iconify(false);
1024     if (e.data.l[0] && _shaded)
1025       shade(false);
1026     focus();
1027     if (e.data.l[1])
1028       openbox->screen(_screen)->raiseWindow(this);
1029   }
1030 }
1031
1032
1033 #if defined(SHAPE)
1034 void Client::shapeHandler(const XShapeEvent &e)
1035 {
1036   otk::EventHandler::shapeHandler(e);
1037
1038   if (e.kind == ShapeBounding) {
1039     _shaped = e.shaped;
1040     frame->adjustShape();
1041   }
1042 }
1043 #endif
1044
1045
1046 void Client::resize(Corner anchor, int w, int h)
1047 {
1048   if (!(_functions & Func_Resize)) return;
1049   internal_resize(anchor, w, h);
1050 }
1051
1052
1053 void Client::internal_resize(Corner anchor, int w, int h,
1054                              bool user, int x, int y)
1055 {
1056   w -= _base_size.width();
1057   h -= _base_size.height();
1058
1059   if (user) {
1060     // for interactive resizing. have to move half an increment in each
1061     // direction.
1062     int mw = w % _size_inc.width(); // how far we are towards the next size inc
1063     int mh = h % _size_inc.height();
1064     int aw = _size_inc.width() / 2; // amount to add
1065     int ah = _size_inc.height() / 2;
1066     // don't let us move into a new size increment
1067     if (mw + aw >= _size_inc.width()) aw = _size_inc.width() - mw - 1;
1068     if (mh + ah >= _size_inc.height()) ah = _size_inc.height() - mh - 1;
1069     w += aw;
1070     h += ah;
1071     
1072     // if this is a user-requested resize, then check against min/max sizes
1073     // and aspect ratios
1074
1075     // smaller than min size or bigger than max size?
1076     if (w > _max_size.width()) w = _max_size.width();
1077     if (w < _min_size.width()) w = _min_size.width();
1078     if (h > _max_size.height()) h = _max_size.height();
1079     if (h < _min_size.height()) h = _min_size.height();
1080
1081     // adjust the height ot match the width for the aspect ratios
1082     if (_min_ratio)
1083       if (h * _min_ratio > w) h = static_cast<int>(w / _min_ratio);
1084     if (_max_ratio)
1085       if (h * _max_ratio < w) h = static_cast<int>(w / _max_ratio);
1086   }
1087
1088   // keep to the increments
1089   w /= _size_inc.width();
1090   h /= _size_inc.height();
1091
1092   // you cannot resize to nothing
1093   if (w < 1) w = 1;
1094   if (h < 1) h = 1;
1095   
1096   // store the logical size
1097   _logical_size = otk::Size(w, h);
1098
1099   w *= _size_inc.width();
1100   h *= _size_inc.height();
1101
1102   w += _base_size.width();
1103   h += _base_size.height();
1104
1105   if (x == INT_MIN || y == INT_MIN) {
1106     x = _area.x();
1107     y = _area.y();
1108     switch (anchor) {
1109     case TopLeft:
1110       break;
1111     case TopRight:
1112       x -= w - _area.width();
1113       break;
1114     case BottomLeft:
1115       y -= h - _area.height();
1116       break;
1117     case BottomRight:
1118       x -= w - _area.width();
1119       y -= h - _area.height();
1120       break;
1121     }
1122   }
1123
1124   _area = otk::Rect(_area.position(), otk::Size(w, h));
1125
1126   XResizeWindow(**otk::display, _window, w, h);
1127
1128   // resize the frame to match the request
1129   frame->adjustSize();
1130   internal_move(x, y);
1131 }
1132
1133
1134 void Client::move(int x, int y)
1135 {
1136   if (!(_functions & Func_Move)) return;
1137   frame->frameGravity(x, y); // get the client's position based on x,y for the
1138                              // frame
1139   internal_move(x, y);
1140 }
1141
1142
1143 void Client::internal_move(int x, int y)
1144 {
1145   _area = otk::Rect(otk::Point(x, y), _area.size());
1146
1147   // move the frame to be in the requested position
1148   if (frame) { // this can be called while mapping, before frame exists
1149     frame->adjustPosition();
1150
1151     // send synthetic configure notify (we don't need to if we aren't mapped
1152     // yet)
1153     XEvent event;
1154     event.type = ConfigureNotify;
1155     event.xconfigure.display = **otk::display;
1156     event.xconfigure.event = _window;
1157     event.xconfigure.window = _window;
1158     
1159     // root window coords with border in mind
1160     event.xconfigure.x = x - _border_width + frame->size().left;
1161     event.xconfigure.y = y - _border_width + frame->size().top;
1162     
1163     event.xconfigure.width = _area.width();
1164     event.xconfigure.height = _area.height();
1165     event.xconfigure.border_width = _border_width;
1166     event.xconfigure.above = frame->plate();
1167     event.xconfigure.override_redirect = False;
1168     XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1169                StructureNotifyMask, &event);
1170 #if 0//def DEBUG
1171     printf("Sent synthetic ConfigureNotify %d,%d %d,%d to 0x%lx\n",
1172            event.xconfigure.x, event.xconfigure.y, event.xconfigure.width,
1173            event.xconfigure.height, event.xconfigure.window);
1174 #endif
1175   }
1176 }
1177
1178
1179 void Client::close()
1180 {
1181   XEvent ce;
1182
1183   if (!(_functions & Func_Close)) return;
1184
1185   // XXX: itd be cool to do timeouts and shit here for killing the client's
1186   //      process off
1187   // like... if the window is around after 5 seconds, then the close button
1188   // turns a nice red, and if this function is called again, the client is
1189   // explicitly killed.
1190
1191   ce.xclient.type = ClientMessage;
1192   ce.xclient.message_type =  otk::Property::atoms.wm_protocols;
1193   ce.xclient.display = **otk::display;
1194   ce.xclient.window = _window;
1195   ce.xclient.format = 32;
1196   ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1197   ce.xclient.data.l[1] = CurrentTime;
1198   ce.xclient.data.l[2] = 0l;
1199   ce.xclient.data.l[3] = 0l;
1200   ce.xclient.data.l[4] = 0l;
1201   XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1202 }
1203
1204
1205 void Client::changeState()
1206 {
1207   unsigned long state[2];
1208   state[0] = _wmstate;
1209   state[1] = None;
1210   otk::Property::set(_window, otk::Property::atoms.wm_state,
1211                      otk::Property::atoms.wm_state, state, 2);
1212
1213   Atom netstate[10];
1214   int num = 0;
1215   if (_modal)
1216     netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1217   if (_shaded)
1218     netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1219   if (_iconic)
1220     netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1221   if (_skip_taskbar)
1222     netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1223   if (_skip_pager)
1224     netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1225   if (_fullscreen)
1226     netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1227   if (_max_vert)
1228     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1229   if (_max_horz)
1230     netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1231   if (_above)
1232     netstate[num++] = otk::Property::atoms.net_wm_state_above;
1233   if (_below)
1234     netstate[num++] = otk::Property::atoms.net_wm_state_below;
1235   otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1236                      otk::Property::atoms.atom, netstate, num);
1237
1238   calcLayer();
1239
1240   if (frame)
1241     frame->adjustState();
1242 }
1243
1244
1245 void Client::changeAllowedActions(void)
1246 {
1247   Atom actions[9];
1248   int num = 0;
1249
1250   actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1251
1252   if (_functions & Func_Shade)
1253     actions[num++] = otk::Property::atoms.net_wm_action_shade;
1254   if (_functions & Func_Close)
1255     actions[num++] = otk::Property::atoms.net_wm_action_close;
1256   if (_functions & Func_Move)
1257     actions[num++] = otk::Property::atoms.net_wm_action_move;
1258   if (_functions & Func_Iconify)
1259     actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1260   if (_functions & Func_Resize)
1261     actions[num++] = otk::Property::atoms.net_wm_action_resize;
1262   if (_functions & Func_Fullscreen)
1263     actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1264   if (_functions & Func_Maximize) {
1265     actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1266     actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1267   }
1268
1269   otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1270                      otk::Property::atoms.atom, actions, num);
1271
1272   // make sure the window isn't breaking any rules now
1273   
1274   if (!(_functions & Func_Shade) && _shaded)
1275     if (frame) shade(false);
1276     else _shaded = false;
1277   if (!(_functions & Func_Iconify) && _iconic)
1278     if (frame) setDesktop(openbox->screen(_screen)->desktop());
1279     else _iconic = false;
1280   if (!(_functions & Func_Fullscreen) && _fullscreen)
1281     if (frame) fullscreen(false);
1282     else _fullscreen = false;
1283   if (!(_functions & Func_Maximize) && (_max_horz || _max_vert))
1284     if (frame) maximize(false, 0);
1285     else _max_vert = _max_horz = false;
1286 }
1287
1288
1289 void Client::remaximize()
1290 {
1291   int dir;
1292   if (_max_horz && _max_vert)
1293     dir = 0;
1294   else if (_max_horz)
1295     dir = 1;
1296   else if (_max_vert)
1297     dir = 2;
1298   else
1299     return; // not maximized
1300   _max_horz = _max_vert = false;
1301   maximize(true, dir, false);
1302 }
1303
1304
1305 void Client::applyStartupState()
1306 {
1307   // these are in a carefully crafted order..
1308
1309   if (_iconic) {
1310     _iconic = false;
1311     iconify(true);
1312   }
1313   if (_fullscreen) {
1314     _fullscreen = false;
1315     fullscreen(true, false);
1316   }
1317   if (_shaded) {
1318     _shaded = false;
1319     shade(true);
1320   }
1321   if (_urgent)
1322     fireUrgent();
1323   
1324   if (_max_vert && _max_horz) {
1325     _max_vert = _max_horz = false;
1326     maximize(true, 0, false);
1327   } else if (_max_vert) {
1328     _max_vert = false;
1329     maximize(true, 2, false);
1330   } else if (_max_horz) {
1331     _max_horz = false;
1332     maximize(true, 1, false);
1333   }
1334
1335   if (_skip_taskbar); // nothing to do for this
1336   if (_skip_pager);   // nothing to do for this
1337   if (_modal);        // nothing to do for this
1338   if (_above);        // nothing to do for this
1339   if (_below);        // nothing to do for this
1340 }
1341
1342
1343 void Client::fireUrgent()
1344 {
1345   // call the python UrgentWindow callbacks
1346   EventData data(_screen, this, EventAction::UrgentWindow, 0);
1347   openbox->bindings()->fireEvent(&data);
1348 }
1349
1350
1351 void Client::shade(bool shade)
1352 {
1353   if (!(_functions & Func_Shade) || // can't
1354       _shaded == shade) return;     // already done
1355
1356   // when we're iconic, don't change the wmstate
1357   if (!_iconic)
1358     _wmstate = shade ? IconicState : NormalState;
1359   _shaded = shade;
1360   changeState();
1361   frame->adjustSize();
1362 }
1363
1364
1365 void Client::maximize(bool max, int dir, bool savearea)
1366 {
1367   assert(dir == 0 || dir == 1 || dir == 2);
1368   if (!(_functions & Func_Maximize)) return; // can't
1369
1370   // check if already done
1371   if (max) {
1372     if (dir == 0 && _max_horz && _max_vert) return;
1373     if (dir == 1 && _max_horz) return;
1374     if (dir == 2 && _max_vert) return;
1375   } else {
1376     if (dir == 0 && !_max_horz && !_max_vert) return;
1377     if (dir == 1 && !_max_horz) return;
1378     if (dir == 2 && !_max_vert) return;
1379   }
1380
1381   const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1382   int x = frame->area().x(), y = frame->area().y(),
1383     w = _area.width(), h = _area.height();
1384   
1385   if (max) {
1386     if (savearea) {
1387       long dimensions[4];
1388       long *readdim;
1389       unsigned long n = 4;
1390
1391       dimensions[0] = x;
1392       dimensions[1] = y;
1393       dimensions[2] = w;
1394       dimensions[3] = h;
1395
1396       // get the property off the window and use it for the dimentions we are
1397       // already maxed on
1398       if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1399                              otk::Property::atoms.cardinal, &n,
1400                              (long unsigned**) &readdim)) {
1401         if (n >= 4) {
1402           if (_max_horz) {
1403             dimensions[0] = readdim[0];
1404             dimensions[2] = readdim[2];
1405           }
1406           if (_max_vert) {
1407             dimensions[1] = readdim[1];
1408             dimensions[3] = readdim[3];
1409           }
1410         }
1411         delete readdim;
1412       }
1413       
1414       otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1415                          otk::Property::atoms.cardinal,
1416                          (long unsigned*)dimensions, 4);
1417     }
1418     if (dir == 0 || dir == 1) { // horz
1419       x = a.x();
1420       w = a.width();
1421     }
1422     if (dir == 0 || dir == 2) { // vert
1423       y = a.y();
1424       h = a.height() - frame->size().top - frame->size().bottom;
1425     }
1426   } else {
1427     long *dimensions;
1428     long unsigned n = 4;
1429       
1430     if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1431                            otk::Property::atoms.cardinal, &n,
1432                            (long unsigned**) &dimensions)) {
1433       if (n >= 4) {
1434         if (dir == 0 || dir == 1) { // horz
1435           x = (signed int)dimensions[0];
1436           w = (signed int)dimensions[2];
1437         }
1438         if (dir == 0 || dir == 2) { // vert
1439           y = (signed int)dimensions[1];
1440           h = (signed int)dimensions[3];
1441         }
1442       }
1443       delete dimensions;
1444     } else {
1445       // pick some fallbacks...
1446       if (dir == 0 || dir == 1) { // horz
1447         x = a.x() + a.width() / 4;
1448         w = a.width() / 2;
1449       }
1450       if (dir == 0 || dir == 2) { // vert
1451         y = a.y() + a.height() / 4;
1452         h = a.height() / 2;
1453       }
1454     }
1455   }
1456
1457   if (dir == 0 || dir == 1) // horz
1458     _max_horz = max;
1459   if (dir == 0 || dir == 2) // vert
1460     _max_vert = max;
1461
1462   if (!_max_horz && !_max_vert)
1463     otk::Property::erase(_window, otk::Property::atoms.openbox_premax);
1464
1465   changeState(); // change the state hints on the client
1466
1467   frame->frameGravity(x, y); // figure out where the client should be going
1468   internal_resize(TopLeft, w, h, true, x, y);
1469 }
1470
1471
1472 void Client::fullscreen(bool fs, bool savearea)
1473 {
1474   static FunctionFlags saved_func;
1475   static DecorationFlags saved_decor;
1476
1477   if (!(_functions & Func_Fullscreen) || // can't
1478       _fullscreen == fs) return;         // already done
1479
1480   _fullscreen = fs;
1481   changeState(); // change the state hints on the client
1482
1483   int x = _area.x(), y = _area.y(), w = _area.width(), h = _area.height();
1484   
1485   if (fs) {
1486     // save the functions and remove them
1487     saved_func = _functions;
1488     _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1489     // save the decorations and remove them
1490     saved_decor = _decorations;
1491     _decorations = 0;
1492     if (savearea) {
1493       long dimensions[4];
1494       dimensions[0] = _area.x();
1495       dimensions[1] = _area.y();
1496       dimensions[2] = _area.width();
1497       dimensions[3] = _area.height();
1498       otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1499                          otk::Property::atoms.cardinal,
1500                          (long unsigned*)dimensions, 4);
1501     }
1502     const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1503     x = 0;
1504     y = 0;
1505     w = info->size().width();
1506     h = info->size().height();
1507   } else {
1508     _functions = saved_func;
1509     _decorations = saved_decor;
1510
1511     long *dimensions;
1512     long unsigned n = 4;
1513       
1514     if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1515                            otk::Property::atoms.cardinal, &n,
1516                            (long unsigned**) &dimensions)) {
1517       if (n >= 4) {
1518         x = dimensions[0];
1519         y = dimensions[1];
1520         w = dimensions[2];
1521         h = dimensions[3];
1522       }
1523       delete dimensions;
1524     } else {
1525       // pick some fallbacks...
1526       const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1527       x = a.x() + a.width() / 4;
1528       y = a.y() + a.height() / 4;
1529       w = a.width() / 2;
1530         h = a.height() / 2;
1531     }    
1532   }
1533   
1534   changeAllowedActions();  // based on the new _functions
1535
1536   // when fullscreening, don't obey things like increments, fill the screen
1537   internal_resize(TopLeft, w, h, !fs, x, y);
1538
1539   // raise (back) into our stacking layer
1540   openbox->screen(_screen)->raiseWindow(this);
1541
1542   // try focus us when we go into fullscreen mode
1543   if (fs) focus();
1544 }
1545
1546
1547 void Client::iconify(bool iconic, bool curdesk)
1548 {
1549   if (_iconic == iconic) return; // nothing to do
1550
1551   _iconic = iconic;
1552
1553   if (_iconic) {
1554     _wmstate = IconicState;
1555     ignore_unmaps++;
1556     // we unmap the client itself so that we can get MapRequest events, and
1557     // because the ICCCM tells us to!
1558     XUnmapWindow(**otk::display, _window);
1559   } else {
1560     if (curdesk)
1561       setDesktop(openbox->screen(_screen)->desktop());
1562     _wmstate = NormalState;
1563     XMapWindow(**otk::display, _window);
1564   }
1565   changeState();
1566
1567   showhide();
1568   
1569   openbox->screen(_screen)->updateStruts();
1570 }
1571
1572
1573 void Client::disableDecorations(DecorationFlags flags)
1574 {
1575   _disabled_decorations = flags;
1576   setupDecorAndFunctions();
1577 }
1578
1579
1580 void Client::installColormap(bool install) const
1581 {
1582   XWindowAttributes wa;
1583   if (XGetWindowAttributes(**otk::display, _window, &wa)) {
1584     if (install)
1585       XInstallColormap(**otk::display, wa.colormap);
1586     else
1587       XUninstallColormap(**otk::display, wa.colormap);
1588   }
1589 }
1590
1591
1592 // recursively searches the client 'tree' for a modal client, always skips the
1593 // topmost node (the window you're starting with)
1594 Client *Client::searchModalTree(Client *node, Client *skip)
1595 {
1596   List::const_iterator it, end = node->_transients.end();
1597   Client *ret;
1598   
1599   for (it = node->_transients.begin(); it != end; ++it) {
1600     if (*it == skip) continue; // circular?
1601     if ((ret = searchModalTree(*it, skip))) return ret; // got one
1602     if ((*it)->_modal) return *it; // got one
1603   }
1604   return 0;
1605 }
1606
1607 Client *Client::findModalChild()
1608 {
1609   return searchModalTree(this, this);
1610 }
1611
1612
1613 bool Client::focus()
1614 {
1615   // if we have a modal child, then focus it, not us
1616   Client *c = findModalChild();
1617   if (c) return c->focus();
1618
1619   // won't try focus if the client doesn't want it, or if the window isn't
1620   // visible on the screen
1621   if (!(frame->visible() && (_can_focus || _focus_notify))) return false;
1622
1623   if (_focused) return true;
1624
1625   // do a check to see if the window has already been unmapped or destroyed
1626   // do this intelligently while watching out for unmaps we've generated
1627   // (ignore_unmaps > 0)
1628   XEvent ev;
1629   if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1630     XPutBackEvent(**otk::display, &ev);
1631     return false;
1632   }
1633   while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1634     if (ignore_unmaps) {
1635       unmapHandler(ev.xunmap);
1636     } else {
1637       XPutBackEvent(**otk::display, &ev);
1638       return false;
1639     }
1640   }
1641
1642   if (_can_focus)
1643     XSetInputFocus(**otk::display, _window,
1644                    RevertToNone, CurrentTime);
1645
1646   if (_focus_notify) {
1647     XEvent ce;
1648     ce.xclient.type = ClientMessage;
1649     ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1650     ce.xclient.display = **otk::display;
1651     ce.xclient.window = _window;
1652     ce.xclient.format = 32;
1653     ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1654     ce.xclient.data.l[1] = openbox->lastTime();
1655     ce.xclient.data.l[2] = 0l;
1656     ce.xclient.data.l[3] = 0l;
1657     ce.xclient.data.l[4] = 0l;
1658     XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1659   }
1660
1661   XSync(**otk::display, False);
1662   return true;
1663 }
1664
1665
1666 void Client::unfocus() const
1667 {
1668   if (!_focused) return;
1669
1670   assert(openbox->focusedClient() == this);
1671   openbox->setFocusedClient(0);
1672 }
1673
1674
1675 void Client::focusHandler(const XFocusChangeEvent &e)
1676 {
1677 #ifdef    DEBUG
1678 //  printf("FocusIn for 0x%lx\n", e.window);
1679 #endif // DEBUG
1680   
1681   otk::EventHandler::focusHandler(e);
1682
1683   _focused = true;
1684   frame->adjustFocus();
1685
1686   openbox->setFocusedClient(this);
1687 }
1688
1689
1690 void Client::unfocusHandler(const XFocusChangeEvent &e)
1691 {
1692 #ifdef    DEBUG
1693 //  printf("FocusOut for 0x%lx\n", e.window);
1694 #endif // DEBUG
1695   
1696   otk::EventHandler::unfocusHandler(e);
1697
1698   _focused = false;
1699   frame->adjustFocus();
1700
1701   if (openbox->focusedClient() == this)
1702     openbox->setFocusedClient(0);
1703 }
1704
1705
1706 void Client::configureRequestHandler(const XConfigureRequestEvent &ec)
1707 {
1708 #ifdef    DEBUG
1709   printf("ConfigureRequest for 0x%lx\n", ec.window);
1710 #endif // DEBUG
1711   
1712   otk::EventHandler::configureRequestHandler(ec);
1713
1714   // compress these
1715   XConfigureRequestEvent e = ec;
1716   XEvent ev;
1717   while (XCheckTypedWindowEvent(**otk::display, window(), ConfigureRequest,
1718                                 &ev)) {
1719     // XXX if this causes bad things.. we can compress config req's with the
1720     //     same mask.
1721     e.value_mask |= ev.xconfigurerequest.value_mask;
1722     if (ev.xconfigurerequest.value_mask & CWX)
1723       e.x = ev.xconfigurerequest.x;
1724     if (ev.xconfigurerequest.value_mask & CWY)
1725       e.y = ev.xconfigurerequest.y;
1726     if (ev.xconfigurerequest.value_mask & CWWidth)
1727       e.width = ev.xconfigurerequest.width;
1728     if (ev.xconfigurerequest.value_mask & CWHeight)
1729       e.height = ev.xconfigurerequest.height;
1730     if (ev.xconfigurerequest.value_mask & CWBorderWidth)
1731       e.border_width = ev.xconfigurerequest.border_width;
1732     if (ev.xconfigurerequest.value_mask & CWStackMode)
1733       e.detail = ev.xconfigurerequest.detail;
1734   }
1735
1736   // if we are iconic (or shaded (fvwm does this)) ignore the event
1737   if (_iconic || _shaded) return;
1738
1739   if (e.value_mask & CWBorderWidth)
1740     _border_width = e.border_width;
1741
1742   // resize, then move, as specified in the EWMH section 7.7
1743   if (e.value_mask & (CWWidth | CWHeight)) {
1744     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1745     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1746
1747     Corner corner;
1748     switch (_gravity) {
1749     case NorthEastGravity:
1750     case EastGravity:
1751       corner = TopRight;
1752       break;
1753     case SouthWestGravity:
1754     case SouthGravity:
1755       corner = BottomLeft;
1756       break;
1757     case SouthEastGravity:
1758       corner = BottomRight;
1759       break;
1760     default:     // NorthWest, Static, etc
1761       corner = TopLeft;
1762     }
1763
1764     // if moving AND resizing ...
1765     if (e.value_mask & (CWX | CWY)) {
1766       int x = (e.value_mask & CWX) ? e.x : _area.x();
1767       int y = (e.value_mask & CWY) ? e.y : _area.y();
1768       internal_resize(corner, w, h, false, x, y);
1769     } else // if JUST resizing...
1770       internal_resize(corner, w, h, false);
1771   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1772     int x = (e.value_mask & CWX) ? e.x : _area.x();
1773     int y = (e.value_mask & CWY) ? e.y : _area.y();
1774     internal_move(x, y);
1775   }
1776
1777   if (e.value_mask & CWStackMode) {
1778     switch (e.detail) {
1779     case Below:
1780     case BottomIf:
1781       openbox->screen(_screen)->lowerWindow(this);
1782       break;
1783
1784     case Above:
1785     case TopIf:
1786     default:
1787       openbox->screen(_screen)->raiseWindow(this);
1788       break;
1789     }
1790   }
1791 }
1792
1793
1794 void Client::unmapHandler(const XUnmapEvent &e)
1795 {
1796   if (ignore_unmaps) {
1797 #ifdef    DEBUG
1798 //  printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1799 #endif // DEBUG
1800     ignore_unmaps--;
1801     return;
1802   }
1803   
1804 #ifdef    DEBUG
1805   printf("UnmapNotify for 0x%lx\n", e.window);
1806 #endif // DEBUG
1807
1808   otk::EventHandler::unmapHandler(e);
1809
1810   // this deletes us etc
1811   openbox->screen(_screen)->unmanageWindow(this);
1812 }
1813
1814
1815 void Client::destroyHandler(const XDestroyWindowEvent &e)
1816 {
1817 #ifdef    DEBUG
1818   printf("DestroyNotify for 0x%lx\n", e.window);
1819 #endif // DEBUG
1820
1821   otk::EventHandler::destroyHandler(e);
1822
1823   // this deletes us etc
1824   openbox->screen(_screen)->unmanageWindow(this);
1825 }
1826
1827
1828 void Client::reparentHandler(const XReparentEvent &e)
1829 {
1830   // this is when the client is first taken captive in the frame
1831   if (e.parent == frame->plate()) return;
1832
1833 #ifdef    DEBUG
1834   printf("ReparentNotify for 0x%lx\n", e.window);
1835 #endif // DEBUG
1836
1837   otk::EventHandler::reparentHandler(e);
1838
1839   /*
1840     This event is quite rare and is usually handled in unmapHandler.
1841     However, if the window is unmapped when the reparent event occurs,
1842     the window manager never sees it because an unmap event is not sent
1843     to an already unmapped window.
1844   */
1845
1846   // we don't want the reparent event, put it back on the stack for the X
1847   // server to deal with after we unmanage the window
1848   XEvent ev;
1849   ev.xreparent = e;
1850   XPutBackEvent(**otk::display, &ev);
1851   
1852   // this deletes us etc
1853   openbox->screen(_screen)->unmanageWindow(this);
1854 }
1855
1856 void Client::mapRequestHandler(const XMapRequestEvent &e)
1857 {
1858 #ifdef    DEBUG
1859   printf("MapRequest for already managed 0x%lx\n", e.window);
1860 #endif // DEBUG
1861
1862   assert(_iconic); // we shouldn't be able to get this unless we're iconic
1863
1864   // move to the current desktop (uniconify)
1865   iconify(false);
1866   // XXX: should we focus/raise the window? (basically a net_wm_active_window)
1867 }
1868
1869 }