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