]> icculus.org git repositories - mikachu/openbox.git/blob - src/client.cc
if a dock has the 'below' state set, then it goes in the normal window layer
[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       else if (state[i] ==
270                property->atom(otk::OBProperty::net_wm_state_skip_taskbar))
271         _skip_taskbar = true;
272       else if (state[i] ==
273                property->atom(otk::OBProperty::net_wm_state_skip_pager))
274         _skip_pager = true;
275       else if (state[i] ==
276                property->atom(otk::OBProperty::net_wm_state_fullscreen))
277         _fullscreen = true;
278       else if (state[i] ==
279                property->atom(otk::OBProperty::net_wm_state_maximized_vert))
280         _max_vert = true;
281       else if (state[i] ==
282                property->atom(otk::OBProperty::net_wm_state_maximized_horz))
283         _max_horz = true;
284       else if (state[i] ==
285                property->atom(otk::OBProperty::net_wm_state_above))
286         _above = true;
287       else if (state[i] ==
288                property->atom(otk::OBProperty::net_wm_state_below))
289         _below = true;
290     }
291
292     delete [] state;
293   }
294 }
295
296
297 void OBClient::getShaped()
298 {
299   _shaped = false;
300 #ifdef   SHAPE
301   if (otk::OBDisplay::shape()) {
302     int foo;
303     unsigned int ufoo;
304     int s;
305
306     XShapeSelectInput(otk::OBDisplay::display, _window, ShapeNotifyMask);
307
308     XShapeQueryExtents(otk::OBDisplay::display, _window, &s, &foo,
309                        &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo);
310     _shaped = (s != 0);
311   }
312 #endif // SHAPE
313 }
314
315
316 void OBClient::calcLayer() {
317   if (_iconic) _layer = OBScreen::Layer_Icon;
318   else if (_fullscreen) _layer = OBScreen::Layer_Fullscreen;
319   else if (_type == Type_Desktop) _layer = OBScreen::Layer_Desktop;
320   else if (_type == Type_Dock) {
321     if (!_below) _layer = OBScreen::Layer_Top;
322     else _layer = OBScreen::Layer_Normal;
323   }
324   else if (_above) _layer = OBScreen::Layer_Above;
325   else if (_below) _layer = OBScreen::Layer_Below;
326   else _layer = OBScreen::Layer_Normal;
327 }
328
329
330 void OBClient::updateProtocols()
331 {
332   const otk::OBProperty *property = Openbox::instance->property();
333
334   Atom *proto;
335   int num_return = 0;
336
337   _focus_notify = false;
338   _decorations &= ~Decor_Close;
339   _functions &= ~Func_Close;
340
341   if (XGetWMProtocols(otk::OBDisplay::display, _window, &proto, &num_return)) {
342     for (int i = 0; i < num_return; ++i) {
343       if (proto[i] == property->atom(otk::OBProperty::wm_delete_window)) {
344         _decorations |= Decor_Close;
345         _functions |= Func_Close;
346         if (frame)
347           frame->adjustSize(); // update the decorations
348       } else if (proto[i] == property->atom(otk::OBProperty::wm_take_focus))
349         // if this protocol is requested, then the window will be notified
350         // by the window manager whenever it receives focus
351         _focus_notify = true;
352     }
353     XFree(proto);
354   }
355 }
356
357
358 void OBClient::updateNormalHints()
359 {
360   XSizeHints size;
361   long ret;
362   int oldgravity = _gravity;
363
364   // defaults
365   _gravity = NorthWestGravity;
366   _size_inc.setPoint(1, 1);
367   _base_size.setPoint(0, 0);
368   _min_size.setPoint(0, 0);
369   _max_size.setPoint(INT_MAX, INT_MAX);
370
371   // XXX: might want to cancel any interactive resizing of the window at this
372   // point..
373
374   // get the hints from the window
375   if (XGetWMNormalHints(otk::OBDisplay::display, _window, &size, &ret)) {
376     _positioned = (size.flags & (PPosition|USPosition));
377
378     if (size.flags & PWinGravity)
379       _gravity = size.win_gravity;
380
381     if (size.flags & PMinSize)
382       _min_size.setPoint(size.min_width, size.min_height);
383     
384     if (size.flags & PMaxSize)
385       _max_size.setPoint(size.max_width, size.max_height);
386     
387     if (size.flags & PBaseSize)
388       _base_size.setPoint(size.base_width, size.base_height);
389     
390     if (size.flags & PResizeInc)
391       _size_inc.setPoint(size.width_inc, size.height_inc);
392   }
393
394   // if the client has a frame, i.e. has already been mapped and is
395   // changing its gravity
396   if (frame && _gravity != oldgravity) {
397     // move our idea of the client's position based on its new gravity
398     int x, y;
399     frame->frameGravity(x, y);
400     _area.setPos(x, y);
401   }
402 }
403
404
405 void OBClient::updateWMHints()
406 {
407   XWMHints *hints;
408
409   // assume a window takes input if it doesnt specify
410   _can_focus = true;
411   _urgent = false;
412   
413   if ((hints = XGetWMHints(otk::OBDisplay::display, _window)) != NULL) {
414     if (hints->flags & InputHint)
415       _can_focus = hints->input;
416
417     if (hints->flags & XUrgencyHint)
418       _urgent = true;
419
420     if (hints->flags & WindowGroupHint) {
421       if (hints->window_group != _group) {
422         // XXX: remove from the old group if there was one
423         _group = hints->window_group;
424         // XXX: do stuff with the group
425       }
426     } else // no group!
427       _group = None;
428
429     XFree(hints);
430   }
431 }
432
433
434 void OBClient::updateTitle()
435 {
436   const otk::OBProperty *property = Openbox::instance->property();
437
438   _title = "";
439   
440   // try netwm
441   if (! property->get(_window, otk::OBProperty::net_wm_name,
442                       otk::OBProperty::utf8, &_title)) {
443     // try old x stuff
444     property->get(_window, otk::OBProperty::wm_name,
445                   otk::OBProperty::ascii, &_title);
446   }
447
448   if (_title.empty())
449     _title = _("Unnamed Window");
450
451   if (frame)
452     frame->setTitle(_title);
453 }
454
455
456 void OBClient::updateIconTitle()
457 {
458   const otk::OBProperty *property = Openbox::instance->property();
459
460   _icon_title = "";
461   
462   // try netwm
463   if (! property->get(_window, otk::OBProperty::net_wm_icon_name,
464                       otk::OBProperty::utf8, &_icon_title)) {
465     // try old x stuff
466     property->get(_window, otk::OBProperty::wm_icon_name,
467                   otk::OBProperty::ascii, &_icon_title);
468   }
469
470   if (_title.empty())
471     _icon_title = _("Unnamed Window");
472 }
473
474
475 void OBClient::updateClass()
476 {
477   const otk::OBProperty *property = Openbox::instance->property();
478
479   // set the defaults
480   _app_name = _app_class = "";
481
482   otk::OBProperty::StringVect v;
483   unsigned long num = 2;
484
485   if (! property->get(_window, otk::OBProperty::wm_class,
486                       otk::OBProperty::ascii, &num, &v))
487     return;
488
489   if (num > 0) _app_name = v[0];
490   if (num > 1) _app_class = v[1];
491 }
492
493
494 void OBClient::updateStrut()
495 {
496   unsigned long num = 4;
497   unsigned long *data;
498   if (!Openbox::instance->property()->get(_window,
499                                           otk::OBProperty::net_wm_strut,
500                                           otk::OBProperty::Atom_Cardinal,
501                                           &num, &data))
502     return;
503
504   if (num == 4) {
505     _strut.left = data[0];
506     _strut.right = data[1];
507     _strut.top = data[2];
508     _strut.bottom = data[3];
509     
510     Openbox::instance->screen(_screen)->updateStrut();
511   }
512
513   delete [] data;
514 }
515
516
517 void OBClient::propertyHandler(const XPropertyEvent &e)
518 {
519   otk::OtkEventHandler::propertyHandler(e);
520   
521   const otk::OBProperty *property = Openbox::instance->property();
522
523   // compress changes to a single property into a single change
524   XEvent ce;
525   while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) {
526     // XXX: it would be nice to compress ALL changes to a property, not just
527     //      changes in a row without other props between.
528     if (ce.xproperty.atom != e.atom) {
529       XPutBackEvent(otk::OBDisplay::display, &ce);
530       break;
531     }
532   }
533
534   if (e.atom == XA_WM_NORMAL_HINTS)
535     updateNormalHints();
536   else if (e.atom == XA_WM_HINTS)
537     updateWMHints();
538   else if (e.atom == property->atom(otk::OBProperty::net_wm_name) ||
539            e.atom == property->atom(otk::OBProperty::wm_name))
540     updateTitle();
541   else if (e.atom == property->atom(otk::OBProperty::net_wm_icon_name) ||
542            e.atom == property->atom(otk::OBProperty::wm_icon_name))
543     updateIconTitle();
544   else if (e.atom == property->atom(otk::OBProperty::wm_class))
545     updateClass();
546   else if (e.atom == property->atom(otk::OBProperty::wm_protocols))
547     updateProtocols();
548   // XXX: transient for hint
549   else if (e.atom == property->atom(otk::OBProperty::net_wm_strut))
550     updateStrut();
551   // XXX: strut hint
552 }
553
554
555 void OBClient::setWMState(long state)
556 {
557   if (state == _wmstate) return; // no change
558   
559   switch (state) {
560   case IconicState:
561     // XXX: cause it to iconify
562     break;
563   case NormalState:
564     // XXX: cause it to uniconify
565     break;
566   }
567   _wmstate = state;
568 }
569
570
571 void OBClient::setDesktop(long target)
572 {
573   printf("Setting desktop %ld\n", target);
574   assert(target >= 0 || target == (signed)0xffffffff);
575   //assert(target == 0xffffffff || target < MAX);
576
577   // XXX: move the window to the new desktop (and set root property)
578   _desktop = target;
579 }
580
581
582 void OBClient::setState(StateAction action, long data1, long data2)
583 {
584   const otk::OBProperty *property = Openbox::instance->property();
585   bool restack = false, shadestate = _shaded;
586
587   if (!(action == State_Add || action == State_Remove ||
588         action == State_Toggle))
589     return; // an invalid action was passed to the client message, ignore it
590
591   for (int i = 0; i < 2; ++i) {
592     Atom state = i == 0 ? data1 : data2;
593     
594     if (! state) continue;
595
596     // if toggling, then pick whether we're adding or removing
597     if (action == State_Toggle) {
598       if (state == property->atom(otk::OBProperty::net_wm_state_modal))
599         action = _modal ? State_Remove : State_Add;
600       else if (state ==
601                property->atom(otk::OBProperty::net_wm_state_maximized_vert))
602         action = _max_vert ? State_Remove : State_Add;
603       else if (state ==
604                property->atom(otk::OBProperty::net_wm_state_maximized_horz))
605         action = _max_horz ? State_Remove : State_Add;
606       else if (state == property->atom(otk::OBProperty::net_wm_state_shaded))
607         action = _shaded ? State_Remove : State_Add;
608       else if (state ==
609                property->atom(otk::OBProperty::net_wm_state_skip_taskbar))
610         action = _skip_taskbar ? State_Remove : State_Add;
611       else if (state ==
612                property->atom(otk::OBProperty::net_wm_state_skip_pager))
613         action = _skip_pager ? State_Remove : State_Add;
614       else if (state ==
615                property->atom(otk::OBProperty::net_wm_state_fullscreen))
616         action = _fullscreen ? State_Remove : State_Add;
617       else if (state == property->atom(otk::OBProperty::net_wm_state_above))
618         action = _above ? State_Remove : State_Add;
619       else if (state == property->atom(otk::OBProperty::net_wm_state_below))
620         action = _below ? State_Remove : State_Add;
621     }
622     
623     if (action == State_Add) {
624       if (state == property->atom(otk::OBProperty::net_wm_state_modal)) {
625         if (_modal) continue;
626         _modal = true;
627         // XXX: give it focus if another window has focus that shouldnt now
628       } else if (state ==
629                  property->atom(otk::OBProperty::net_wm_state_maximized_vert)){
630         if (_max_vert) continue;
631         _max_vert = true;
632         // XXX: resize the window etc
633       } else if (state ==
634                  property->atom(otk::OBProperty::net_wm_state_maximized_horz)){
635         if (_max_horz) continue;
636         _max_horz = true;
637         // XXX: resize the window etc
638       } else if (state ==
639                  property->atom(otk::OBProperty::net_wm_state_shaded)) {
640         if (_shaded) continue;
641         // shade when we're all thru here
642         shadestate = true;
643       } else if (state ==
644                  property->atom(otk::OBProperty::net_wm_state_skip_taskbar)) {
645         _skip_taskbar = true;
646       } else if (state ==
647                  property->atom(otk::OBProperty::net_wm_state_skip_pager)) {
648         _skip_pager = true;
649       } else if (state ==
650                  property->atom(otk::OBProperty::net_wm_state_fullscreen)) {
651         if (_fullscreen) continue;
652         _fullscreen = true;
653         restack = false;
654       } else if (state ==
655                  property->atom(otk::OBProperty::net_wm_state_above)) {
656         if (_above) continue;
657         _above = true;
658         restack = true;
659       } else if (state ==
660                  property->atom(otk::OBProperty::net_wm_state_below)) {
661         if (_below) continue;
662         _below = true;
663         restack = true;
664       }
665
666     } else { // action == State_Remove
667       if (state == property->atom(otk::OBProperty::net_wm_state_modal)) {
668         if (!_modal) continue;
669         _modal = false;
670       } else if (state ==
671                  property->atom(otk::OBProperty::net_wm_state_maximized_vert)){
672         if (!_max_vert) continue;
673         _max_vert = false;
674         // XXX: resize the window etc
675       } else if (state ==
676                  property->atom(otk::OBProperty::net_wm_state_maximized_horz)){
677         if (!_max_horz) continue;
678         _max_horz = false;
679         // XXX: resize the window etc
680       } else if (state ==
681                  property->atom(otk::OBProperty::net_wm_state_shaded)) {
682         if (!_shaded) continue;
683         // unshade when we're all thru here
684         shadestate = false;
685       } else if (state ==
686                  property->atom(otk::OBProperty::net_wm_state_skip_taskbar)) {
687         _skip_taskbar = false;
688       } else if (state ==
689                  property->atom(otk::OBProperty::net_wm_state_skip_pager)) {
690         _skip_pager = false;
691       } else if (state ==
692                  property->atom(otk::OBProperty::net_wm_state_fullscreen)) {
693         if (!_fullscreen) continue;
694         _fullscreen = false;
695         restack = true;
696       } else if (state ==
697                  property->atom(otk::OBProperty::net_wm_state_above)) {
698         if (!_above) continue;
699         _above = false;
700         restack = true;
701       } else if (state ==
702                  property->atom(otk::OBProperty::net_wm_state_below)) {
703         if (!_below) continue;
704         _below = false;
705         restack = true;
706       }
707     }
708   }
709   if (shadestate != _shaded)
710     shade(shadestate);
711   if (restack) {
712     calcLayer();
713     Openbox::instance->screen(_screen)->restack(true, this); // raise
714   }
715 }
716
717
718 void OBClient::toggleClientBorder(bool addborder)
719 {
720   // adjust our idea of where the client is, based on its border. When the
721   // border is removed, the client should now be considered to be in a
722   // different position.
723   // when re-adding the border to the client, the same operation needs to be
724   // reversed.
725   int x = _area.x(), y = _area.y();
726   switch(_gravity) {
727   case NorthWestGravity:
728   case WestGravity:
729   case SouthWestGravity:
730     break;
731   case NorthEastGravity:
732   case EastGravity:
733   case SouthEastGravity:
734     if (addborder) x -= _border_width * 2;
735     else           x += _border_width * 2;
736     break;
737   }
738   switch(_gravity) {
739   case NorthWestGravity:
740   case NorthGravity:
741   case NorthEastGravity:
742     break;
743   case SouthWestGravity:
744   case SouthGravity:
745   case SouthEastGravity:
746     if (addborder) y -= _border_width * 2;
747     else           y += _border_width * 2;
748     break;
749   default:
750     // no change for StaticGravity etc.
751     break;
752   }
753   _area.setPos(x, y);
754
755   if (addborder) {
756     XSetWindowBorderWidth(otk::OBDisplay::display, _window, _border_width);
757
758     // move the client so it is back it the right spot _with_ its border!
759     XMoveWindow(otk::OBDisplay::display, _window, x, y);
760   } else
761     XSetWindowBorderWidth(otk::OBDisplay::display, _window, 0);
762 }
763
764
765 void OBClient::clientMessageHandler(const XClientMessageEvent &e)
766 {
767   otk::OtkEventHandler::clientMessageHandler(e);
768   
769   if (e.format != 32) return;
770
771   const otk::OBProperty *property = Openbox::instance->property();
772   
773   if (e.message_type == property->atom(otk::OBProperty::wm_change_state)) {
774     // compress changes into a single change
775     bool compress = false;
776     XEvent ce;
777     while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) {
778       // XXX: it would be nice to compress ALL messages of a type, not just
779       //      messages in a row without other message types between.
780       if (ce.xclient.message_type != e.message_type) {
781         XPutBackEvent(otk::OBDisplay::display, &ce);
782         break;
783       }
784       compress = true;
785     }
786     if (compress)
787       setWMState(ce.xclient.data.l[0]); // use the found event
788     else
789       setWMState(e.data.l[0]); // use the original event
790   } else if (e.message_type ==
791              property->atom(otk::OBProperty::net_wm_desktop)) {
792     // compress changes into a single change 
793     bool compress = false;
794     XEvent ce;
795     while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) {
796       // XXX: it would be nice to compress ALL messages of a type, not just
797       //      messages in a row without other message types between.
798       if (ce.xclient.message_type != e.message_type) {
799         XPutBackEvent(otk::OBDisplay::display, &ce);
800         break;
801       }
802       compress = true;
803     }
804     if (compress)
805       setDesktop(e.data.l[0]); // use the found event
806     else
807       setDesktop(e.data.l[0]); // use the original event
808   } else if (e.message_type == property->atom(otk::OBProperty::net_wm_state)) {
809     // can't compress these
810     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
811   } else if (e.message_type ==
812              property->atom(otk::OBProperty::net_close_window)) {
813     close();
814   } else if (e.message_type ==
815              property->atom(otk::OBProperty::net_active_window)) {
816     focus();
817     Openbox::instance->screen(_screen)->restack(true, this); // raise
818   } else {
819   }
820 }
821
822
823 #if defined(SHAPE)
824 void OBClient::shapeHandler(const XShapeEvent &e)
825 {
826   otk::OtkEventHandler::shapeHandler(e);
827   
828   _shaped = e.shaped;
829   frame->adjustShape();
830 }
831 #endif
832
833
834 void OBClient::resize(Corner anchor, int w, int h, int x, int y)
835 {
836   w -= _base_size.x(); 
837   h -= _base_size.y();
838
839   // for interactive resizing. have to move half an increment in each
840   // direction.
841   w += _size_inc.x() / 2;
842   h += _size_inc.y() / 2;
843
844   // is the window resizable? if it is not, then don't check its sizes, the
845   // client can do what it wants and the user can't change it anyhow
846   if (_min_size.x() <= _max_size.x() && _min_size.y() <= _max_size.y()) {
847     // smaller than min size or bigger than max size?
848     if (w < _min_size.x()) w = _min_size.x();
849     else if (w > _max_size.x()) w = _max_size.x();
850     if (h < _min_size.y()) h = _min_size.y();
851     else if (h > _max_size.y()) h = _max_size.y();
852   }
853
854   // keep to the increments
855   w /= _size_inc.x();
856   h /= _size_inc.y();
857
858   // store the logical size
859   _logical_size.setPoint(w, h);
860
861   w *= _size_inc.x();
862   h *= _size_inc.y();
863
864   w += _base_size.x();
865   h += _base_size.y();
866
867   if (x == INT_MIN || y == INT_MIN) {
868     x = _area.x();
869     y = _area.y();
870     switch (anchor) {
871     case TopLeft:
872       break;
873     case TopRight:
874       x -= w - _area.width();
875       break;
876     case BottomLeft:
877       y -= h - _area.height();
878       break;
879     case BottomRight:
880       x -= w - _area.width();
881       y -= h - _area.height();
882       break;
883     }
884   }
885
886   _area.setSize(w, h);
887
888   XResizeWindow(otk::OBDisplay::display, _window, w, h);
889
890   // resize the frame to match the request
891   frame->adjustSize();
892   move(x, y);
893 }
894
895
896 void OBClient::move(int x, int y)
897 {
898   _area.setPos(x, y);
899
900   // move the frame to be in the requested position
901   frame->adjustPosition();
902 }
903
904
905 void OBClient::close()
906 {
907   XEvent ce;
908   const otk::OBProperty *property = Openbox::instance->property();
909
910   if (!(_functions & Func_Close)) return;
911
912   // XXX: itd be cool to do timeouts and shit here for killing the client's
913   //      process off
914   // like... if the window is around after 5 seconds, then the close button
915   // turns a nice red, and if this function is called again, the client is
916   // explicitly killed.
917
918   ce.xclient.type = ClientMessage;
919   ce.xclient.message_type =  property->atom(otk::OBProperty::wm_protocols);
920   ce.xclient.display = otk::OBDisplay::display;
921   ce.xclient.window = _window;
922   ce.xclient.format = 32;
923   ce.xclient.data.l[0] = property->atom(otk::OBProperty::wm_delete_window);
924   ce.xclient.data.l[1] = CurrentTime;
925   ce.xclient.data.l[2] = 0l;
926   ce.xclient.data.l[3] = 0l;
927   ce.xclient.data.l[4] = 0l;
928   XSendEvent(otk::OBDisplay::display, _window, False, NoEventMask, &ce);
929 }
930
931
932 void OBClient::changeState()
933 {
934   const otk::OBProperty *property = Openbox::instance->property();
935
936   unsigned long state[2];
937   state[0] = _wmstate;
938   state[1] = None;
939   property->set(_window, otk::OBProperty::wm_state, otk::OBProperty::wm_state,
940                 state, 2);
941   
942   Atom netstate[10];
943   int num = 0;
944   if (_modal)
945     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_modal);
946   if (_shaded)
947     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_shaded);
948   if (_iconic)
949     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_hidden);
950   if (_skip_taskbar)
951     netstate[num++] =
952       property->atom(otk::OBProperty::net_wm_state_skip_taskbar);
953   if (_skip_pager)
954     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_skip_pager);
955   if (_fullscreen)
956     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_fullscreen);
957   if (_max_vert)
958     netstate[num++] =
959       property->atom(otk::OBProperty::net_wm_state_maximized_vert);
960   if (_max_horz)
961     netstate[num++] =
962       property->atom(otk::OBProperty::net_wm_state_maximized_horz);
963   if (_above)
964     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_above);
965   if (_below)
966     netstate[num++] = property->atom(otk::OBProperty::net_wm_state_below);
967   property->set(_window, otk::OBProperty::net_wm_state,
968                 otk::OBProperty::Atom_Atom, netstate, num);
969   
970 }
971
972
973 void OBClient::setStackLayer(int l)
974 {
975   if (l == 0)
976     _above = _below = false;  // normal
977   else if (l > 0) {
978     _above = true;
979     _below = false; // above
980   } else {
981     _above = false;
982     _below = true;  // below
983   }
984   changeState();
985   calcLayer();
986   Openbox::instance->screen(_screen)->restack(true, this); // raise
987 }
988
989
990 void OBClient::shade(bool shade)
991 {
992   if (shade == _shaded) return; // already done
993
994   _wmstate = shade ? IconicState : NormalState;
995   _shaded = shade;
996   changeState();
997   frame->adjustSize();
998 }
999
1000
1001 bool OBClient::focus()
1002 {
1003   if (!(_can_focus || _focus_notify) || _focused) return false;
1004
1005   if (_can_focus)
1006     XSetInputFocus(otk::OBDisplay::display, _window, RevertToNone, CurrentTime);
1007
1008   if (_focus_notify) {
1009     XEvent ce;
1010     const otk::OBProperty *property = Openbox::instance->property();
1011     
1012     ce.xclient.type = ClientMessage;
1013     ce.xclient.message_type =  property->atom(otk::OBProperty::wm_protocols);
1014     ce.xclient.display = otk::OBDisplay::display;
1015     ce.xclient.window = _window;
1016     ce.xclient.format = 32;
1017     ce.xclient.data.l[0] = property->atom(otk::OBProperty::wm_take_focus);
1018     ce.xclient.data.l[1] = Openbox::instance->lastTime();
1019     ce.xclient.data.l[2] = 0l;
1020     ce.xclient.data.l[3] = 0l;
1021     ce.xclient.data.l[4] = 0l;
1022     XSendEvent(otk::OBDisplay::display, _window, False, NoEventMask, &ce);
1023   }
1024
1025   return true;
1026 }
1027
1028
1029 void OBClient::unfocus()
1030 {
1031   if (!_focused) return;
1032
1033   assert(Openbox::instance->focusedClient() == this);
1034   Openbox::instance->setFocusedClient(0);
1035 }
1036
1037
1038 void OBClient::focusHandler(const XFocusChangeEvent &e)
1039 {
1040 #ifdef    DEBUG
1041   printf("FocusIn for 0x%lx\n", e.window);
1042 #endif // DEBUG
1043   
1044   OtkEventHandler::focusHandler(e);
1045
1046   frame->focus();
1047   _focused = true;
1048
1049   Openbox::instance->setFocusedClient(this);
1050 }
1051
1052
1053 void OBClient::unfocusHandler(const XFocusChangeEvent &e)
1054 {
1055 #ifdef    DEBUG
1056   printf("FocusOut for 0x%lx\n", e.window);
1057 #endif // DEBUG
1058   
1059   OtkEventHandler::unfocusHandler(e);
1060
1061   frame->unfocus();
1062   _focused = false;
1063
1064   if (Openbox::instance->focusedClient() == this) {
1065     printf("UNFOCUSED!\n");
1066     Openbox::instance->setFocusedClient(this);
1067   }
1068 }
1069
1070
1071 void OBClient::configureRequestHandler(const XConfigureRequestEvent &e)
1072 {
1073 #ifdef    DEBUG
1074   printf("ConfigureRequest for 0x%lx\n", e.window);
1075 #endif // DEBUG
1076   
1077   OtkEventHandler::configureRequestHandler(e);
1078
1079   // XXX: if we are iconic (or shaded? (fvwm does that)) ignore the event
1080
1081   if (e.value_mask & CWBorderWidth)
1082     _border_width = e.border_width;
1083
1084   // resize, then move, as specified in the EWMH section 7.7
1085   if (e.value_mask & (CWWidth | CWHeight)) {
1086     int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1087     int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1088
1089     Corner corner;
1090     switch (_gravity) {
1091     case NorthEastGravity:
1092     case EastGravity:
1093       corner = TopRight;
1094       break;
1095     case SouthWestGravity:
1096     case SouthGravity:
1097       corner = BottomLeft;
1098       break;
1099     case SouthEastGravity:
1100       corner = BottomRight;
1101       break;
1102     default:     // NorthWest, Static, etc
1103       corner = TopLeft;
1104     }
1105
1106     // if moving AND resizing ...
1107     if (e.value_mask & (CWX | CWY)) {
1108       int x = (e.value_mask & CWX) ? e.x : _area.x();
1109       int y = (e.value_mask & CWY) ? e.y : _area.y();
1110       resize(corner, w, h, x, y);
1111     } else // if JUST resizing...
1112       resize(corner, w, h);
1113   } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1114     int x = (e.value_mask & CWX) ? e.x : _area.x();
1115     int y = (e.value_mask & CWY) ? e.y : _area.y();
1116     move(x, y);
1117   }
1118
1119   if (e.value_mask & CWStackMode) {
1120     switch (e.detail) {
1121     case Below:
1122     case BottomIf:
1123       // XXX: lower the window
1124       break;
1125
1126     case Above:
1127     case TopIf:
1128     default:
1129       // XXX: raise the window
1130       break;
1131     }
1132   }
1133 }
1134
1135
1136 void OBClient::unmapHandler(const XUnmapEvent &e)
1137 {
1138 #ifdef    DEBUG
1139   printf("UnmapNotify for 0x%lx\n", e.window);
1140 #endif // DEBUG
1141
1142   if (ignore_unmaps) {
1143     ignore_unmaps--;
1144     return;
1145   }
1146   
1147   OtkEventHandler::unmapHandler(e);
1148
1149   // this deletes us etc
1150   Openbox::instance->screen(_screen)->unmanageWindow(this);
1151 }
1152
1153
1154 void OBClient::destroyHandler(const XDestroyWindowEvent &e)
1155 {
1156 #ifdef    DEBUG
1157   printf("DestroyNotify for 0x%lx\n", e.window);
1158 #endif // DEBUG
1159
1160   OtkEventHandler::destroyHandler(e);
1161
1162   // this deletes us etc
1163   Openbox::instance->screen(_screen)->unmanageWindow(this);
1164 }
1165
1166
1167 void OBClient::reparentHandler(const XReparentEvent &e)
1168 {
1169   // this is when the client is first taken captive in the frame
1170   if (e.parent == frame->plate()) return;
1171
1172 #ifdef    DEBUG
1173   printf("ReparentNotify for 0x%lx\n", e.window);
1174 #endif // DEBUG
1175
1176   OtkEventHandler::reparentHandler(e);
1177
1178   /*
1179     This event is quite rare and is usually handled in unmapHandler.
1180     However, if the window is unmapped when the reparent event occurs,
1181     the window manager never sees it because an unmap event is not sent
1182     to an already unmapped window.
1183   */
1184
1185   // this deletes us etc
1186   Openbox::instance->screen(_screen)->unmanageWindow(this);
1187 }
1188
1189 }