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