set the client's desired decoration and function flags
[mikachu/openbox.git] / src / client.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #include "client.hh"
4 #include "screen.hh"
5 #include "openbox.hh"
6 #include "otk/display.hh"
7 #include "otk/property.hh"
8
9 extern "C" {
10 #include <X11/Xlib.h>
11 #include <X11/Xutil.h>
12
13 #include <assert.h>
14
15 #include "gettext.h"
16 #define _(str) gettext(str)
17 }
18
19 namespace ob {
20
21 OBClient::OBClient(Window window)
22   : _window(window)
23 {
24   assert(window);
25
26   // update EVERYTHING the first time!!
27
28   // the state is kinda assumed to be normal. is this right? XXX
29   _wmstate = NormalState;
30   // no default decors or functions, each has to be enabled
31   _decorations = _functions = 0;
32   
33   getArea();
34   getDesktop();
35   getType();
36
37   // set the decorations and functions
38   switch (_type) {
39   case Type_Normal:
40     // normal windows retain all of the possible decorations and
41     // functionality
42     _decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
43                    Decor_Iconify | Decor_Maximize;
44     _functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize;
45
46   case Type_Dialog:
47     // dialogs cannot be maximized
48     _decorations &= ~Decor_Maximize;
49     _functions &= ~Func_Maximize;
50     break;
51
52   case Type_Menu:
53   case Type_Toolbar:
54   case Type_Utility:
55     // these windows get less functionality
56     _decorations &= ~(Decor_Iconify | Decor_Handle);
57     _functions &= ~(Func_Iconify | Func_Resize);
58     break;
59
60   case Type_Desktop:
61   case Type_Dock:
62   case Type_Splash:
63     // none of these windows are manipulated by the window manager
64     _decorations = 0;
65     _functions = 0;
66     break;
67   }
68   
69   getMwmHints(); // this fucks (in good ways) with the decors and functions
70   getState();
71   getShaped();
72
73   updateProtocols();
74   updateNormalHints();
75   updateWMHints();
76   // XXX: updateTransientFor();
77   updateTitle();
78   updateClass();
79
80 #ifdef DEBUG
81   printf("Mapped window: 0x%lx\n"
82          "  title:         \t%s\t  icon title:    \t%s\n"
83          "  app name:      \t%s\t\t  class:         \t%s\n"
84          "  position:      \t%d, %d\t\t  size:          \t%d, %d\n"
85          "  desktop:       \t%lu\t\t  group:         \t0x%lx\n"
86          "  type:          \t%d\t\t  min size       \t%d, %d\n"
87          "  base size      \t%d, %d\t\t  max size       \t%d, %d\n"
88          "  size incr      \t%d, %d\t\t  gravity        \t%d\n"
89          "  wm state       \t%ld\t\t  can be focused:\t%s\n"
90          "  notify focus:  \t%s\t\t  urgent:        \t%s\n"
91          "  shaped:        \t%s\t\t  modal:         \t%s\n"
92          "  shaded:        \t%s\t\t  iconic:        \t%s\n"
93          "  vert maximized:\t%s\t\t  horz maximized:\t%s\n"
94          "  fullscreen:    \t%s\t\t  floating:      \t%s\n"
95          "  requested pos: \t%s\n",
96          _window,
97          _title.c_str(),
98          _icon_title.c_str(),
99          _app_name.c_str(),
100          _app_class.c_str(),
101          _area.x(), _area.y(),
102          _area.width(), _area.height(),
103          _desktop,
104          _group,
105          _type,
106          _min_x, _min_y,
107          _base_x, _base_y,
108          _max_x, _max_y,
109          _inc_x, _inc_y,
110          _gravity,
111          _wmstate,
112          _can_focus ? "yes" : "no",
113          _focus_notify ? "yes" : "no",
114          _urgent ? "yes" : "no",
115          _shaped ? "yes" : "no",
116          _modal ? "yes" : "no",
117          _shaded ? "yes" : "no",
118          _iconic ? "yes" : "no",
119          _max_vert ? "yes" : "no",
120          _max_horz ? "yes" : "no",
121          _fullscreen ? "yes" : "no",
122          _floating ? "yes" : "no",
123          _positioned ? "yes" : "no");
124 #endif
125 }
126
127
128 OBClient::~OBClient()
129 {
130   const otk::OBProperty *property = Openbox::instance->property();
131
132   // these values should not be persisted across a window unmapping/mapping
133   property->erase(_window, otk::OBProperty::net_wm_desktop);
134   property->erase(_window, otk::OBProperty::net_wm_state);
135 }
136
137
138 void OBClient::getDesktop()
139 {
140   const otk::OBProperty *property = Openbox::instance->property();
141
142   // defaults to the current desktop
143   _desktop = 0; // XXX: change this to the current desktop!
144
145   property->get(_window, otk::OBProperty::net_wm_desktop,
146                 otk::OBProperty::Atom_Cardinal,
147                 &_desktop);
148 }
149
150
151 void OBClient::getType()
152 {
153   const otk::OBProperty *property = Openbox::instance->property();
154
155   _type = (WindowType) -1;
156   
157   unsigned long *val;
158   unsigned long num = (unsigned) -1;
159   if (property->get(_window, otk::OBProperty::net_wm_window_type,
160                     otk::OBProperty::Atom_Atom,
161                     &num, &val)) {
162     // use the first value that we know about in the array
163     for (unsigned long i = 0; i < num; ++i) {
164       if (val[i] ==
165           property->atom(otk::OBProperty::net_wm_window_type_desktop))
166         _type = Type_Desktop;
167       else if (val[i] ==
168                property->atom(otk::OBProperty::net_wm_window_type_dock))
169         _type = Type_Dock;
170       else if (val[i] ==
171                property->atom(otk::OBProperty::net_wm_window_type_toolbar))
172         _type = Type_Toolbar;
173       else if (val[i] ==
174                property->atom(otk::OBProperty::net_wm_window_type_menu))
175         _type = Type_Menu;
176       else if (val[i] ==
177                property->atom(otk::OBProperty::net_wm_window_type_utility))
178         _type = Type_Utility;
179       else if (val[i] ==
180                property->atom(otk::OBProperty::net_wm_window_type_splash))
181         _type = Type_Splash;
182       else if (val[i] ==
183                property->atom(otk::OBProperty::net_wm_window_type_dialog))
184         _type = Type_Dialog;
185       else if (val[i] ==
186                property->atom(otk::OBProperty::net_wm_window_type_normal))
187         _type = Type_Normal;
188 //      else if (val[i] ==
189 //               property->atom(otk::OBProperty::kde_net_wm_window_type_override))
190 //        mwm_decorations = 0; // prevent this window from getting any decor
191       // XXX: make this work again
192     }
193     delete val;
194   }
195     
196   if (_type == (WindowType) -1) {
197     /*
198      * the window type hint was not set, which means we either classify ourself
199      * as a normal window or a dialog, depending on if we are a transient.
200      */
201     // XXX: make this code work!
202     //if (isTransient())
203     //  _type = Type_Dialog;
204     //else
205       _type = Type_Normal;
206   }
207 }
208
209
210 void OBClient::getMwmHints()
211 {
212   const otk::OBProperty *property = Openbox::instance->property();
213
214   unsigned long num;
215   MwmHints *hints;
216
217   num = MwmHints::elements;
218   if (!property->get(_window, otk::OBProperty::motif_wm_hints,
219                      otk::OBProperty::motif_wm_hints, &num,
220                      (unsigned long **)&hints))
221     return;
222   
223   if (num < MwmHints::elements) {
224     delete [] hints;
225     return;
226   }
227
228   // retrieved the hints
229   // Mwm Hints are applied subtractively to what has already been chosen for
230   // decor and functionality
231
232   if (hints->flags & MwmFlag_Decorations) {
233     if (! (hints->decorations & MwmDecor_All)) {
234       if (! (hints->decorations & MwmDecor_Border))
235         _decorations &= ~Decor_Border;
236       if (! (hints->decorations & MwmDecor_Handle))
237         _decorations &= ~Decor_Handle;
238       if (! (hints->decorations & MwmDecor_Title))
239         _decorations &= ~Decor_Titlebar;
240       if (! (hints->decorations & MwmDecor_Iconify))
241         _decorations &= ~Decor_Iconify;
242       if (! (hints->decorations & MwmDecor_Maximize))
243         _decorations &= ~Decor_Maximize;
244     }
245   }
246
247   if (hints->flags & MwmFlag_Functions) {
248     if (! (hints->functions & MwmFunc_All)) {
249       if (! (hints->functions & MwmFunc_Resize))
250         _functions &= ~Func_Resize;
251       if (! (hints->functions & MwmFunc_Move))
252         _functions &= ~Func_Move;
253       if (! (hints->functions & MwmFunc_Iconify))
254         _functions &= ~Func_Iconify;
255       if (! (hints->functions & MwmFunc_Maximize))
256         _functions &= ~Func_Maximize;
257       //if (! (hints->functions & MwmFunc_Close))
258       //  _functions &= ~Func_Close;
259     }
260   }
261   delete [] hints;
262 }
263
264
265 void OBClient::getArea()
266 {
267   XWindowAttributes wattrib;
268   assert(XGetWindowAttributes(otk::OBDisplay::display, _window, &wattrib));
269
270   _area.setRect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
271   _border_width = wattrib.border_width;
272 }
273
274
275 void OBClient::getState()
276 {
277   const otk::OBProperty *property = Openbox::instance->property();
278
279   _modal = _shaded = _max_horz = _max_vert = _fullscreen = _floating = false;
280   
281   unsigned long *state;
282   unsigned long num = (unsigned) -1;
283   
284   if (property->get(_window, otk::OBProperty::net_wm_state,
285                     otk::OBProperty::Atom_Atom, &num, &state)) {
286     for (unsigned long i = 0; i < num; ++i) {
287       if (state[i] == property->atom(otk::OBProperty::net_wm_state_modal))
288         _modal = true;
289       else if (state[i] ==
290                property->atom(otk::OBProperty::net_wm_state_shaded))
291         _shaded = true;
292       else if (state[i] ==
293                property->atom(otk::OBProperty::net_wm_state_fullscreen))
294         _fullscreen = true;
295       else if (state[i] ==
296                property->atom(otk::OBProperty::net_wm_state_maximized_vert))
297         _max_vert = true;
298       else if (state[i] ==
299                property->atom(otk::OBProperty::net_wm_state_maximized_horz))
300         _max_horz = true;
301     }
302
303     delete [] state;
304   }
305 }
306
307
308 void OBClient::getShaped()
309 {
310   _shaped = false;
311 #ifdef   SHAPE
312   if (otk::OBDisplay::shape()) {
313     int foo;
314     unsigned int ufoo;
315
316     XShapeQueryExtents(otk::OBDisplay::display, client.window, &_shaped, &foo,
317                        &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo);
318   }
319 #endif // SHAPE
320 }
321
322
323 void OBClient::updateProtocols()
324 {
325   const otk::OBProperty *property = Openbox::instance->property();
326
327   Atom *proto;
328   int num_return = 0;
329
330   _focus_notify = false;
331
332   if (XGetWMProtocols(otk::OBDisplay::display, _window, &proto, &num_return)) {
333     for (int i = 0; i < num_return; ++i) {
334       if (proto[i] == property->atom(otk::OBProperty::wm_delete_window)) {
335         _decorations |= Decor_Close;
336         _functions |= Func_Close;
337         // XXX: update the decor?
338       } else if (proto[i] == property->atom(otk::OBProperty::wm_take_focus))
339         // if this protocol is requested, then the window will be notified
340         // by the window manager whenever it receives focus
341         _focus_notify = true;
342     }
343     XFree(proto);
344   }
345 }
346
347
348 void OBClient::updateNormalHints()
349 {
350   XSizeHints size;
351   long ret;
352
353   // defaults
354   _gravity = NorthWestGravity;
355   _inc_x = _inc_y = 1;
356   _base_x = _base_y = 0;
357   _min_x = _min_y = 0;
358   _max_x = _max_y = INT_MAX;
359
360   // XXX: might want to cancel any interactive resizing of the window at this
361   // point..
362
363   // get the hints from the window
364   if (XGetWMNormalHints(otk::OBDisplay::display, _window, &size, &ret)) {
365     _positioned = (size.flags & (PPosition|USPosition));
366
367     if (size.flags & PWinGravity)
368       _gravity = size.win_gravity;
369     
370     if (size.flags & PMinSize) {
371       _min_x = size.min_width;
372       _min_y = size.min_height;
373     }
374     
375     if (size.flags & PMaxSize) {
376       _max_x = size.max_width;
377       _max_y = size.max_height;
378     }
379     
380     if (size.flags & PBaseSize) {
381       _base_x = size.base_width;
382       _base_y = size.base_height;
383     }
384     
385     if (size.flags & PResizeInc) {
386       _inc_x = size.width_inc;
387       _inc_y = size.height_inc;
388     }
389   }
390 }
391
392
393 void OBClient::updateWMHints()
394 {
395   XWMHints *hints;
396
397   // assume a window takes input if it doesnt specify
398   _can_focus = true;
399   _urgent = false;
400   
401   if ((hints = XGetWMHints(otk::OBDisplay::display, _window)) != NULL) {
402     if (hints->flags & InputHint)
403       _can_focus = hints->input;
404
405     if (hints->flags & XUrgencyHint)
406       _urgent = true;
407
408     if (hints->flags & WindowGroupHint) {
409       if (hints->window_group != _group) {
410         // XXX: remove from the old group if there was one
411         _group = hints->window_group;
412         // XXX: do stuff with the group
413       }
414     } else // no group!
415       _group = None;
416
417     XFree(hints);
418   }
419 }
420
421
422 void OBClient::updateTitle()
423 {
424   const otk::OBProperty *property = Openbox::instance->property();
425
426   _title = "";
427   
428   // try netwm
429   if (! property->get(_window, otk::OBProperty::net_wm_name,
430                       otk::OBProperty::utf8, &_title)) {
431     // try old x stuff
432     property->get(_window, otk::OBProperty::wm_name,
433                   otk::OBProperty::ascii, &_title);
434   }
435
436   if (_title.empty())
437     _title = _("Unnamed Window");
438 }
439
440
441 void OBClient::updateClass()
442 {
443   const otk::OBProperty *property = Openbox::instance->property();
444
445   // set the defaults
446   _app_name = _app_class = "";
447
448   otk::OBProperty::StringVect v;
449   unsigned long num = 2;
450
451   if (! property->get(_window, otk::OBProperty::wm_class,
452                       otk::OBProperty::ascii, &num, &v))
453     return;
454
455   if (num > 0) _app_name = v[0];
456   if (num > 1) _app_class = v[1];
457 }
458
459
460 void OBClient::update(const XPropertyEvent &e)
461 {
462   const otk::OBProperty *property = Openbox::instance->property();
463
464   if (e.atom == XA_WM_NORMAL_HINTS)
465     updateNormalHints();
466   else if (e.atom == XA_WM_HINTS)
467     updateWMHints();
468   else if (e.atom == property->atom(otk::OBProperty::net_wm_name) ||
469            e.atom == property->atom(otk::OBProperty::wm_name) ||
470            e.atom == property->atom(otk::OBProperty::net_wm_icon_name) ||
471            e.atom == property->atom(otk::OBProperty::wm_icon_name))
472     updateTitle();
473   else if (e.atom == property->atom(otk::OBProperty::wm_class))
474     updateClass();
475   else if (e.atom == property->atom(otk::OBProperty::wm_protocols))
476     updateProtocols();
477   // XXX: transient for hint
478   // XXX: strut hint
479 }
480
481
482 void OBClient::setWMState(long state)
483 {
484   if (state == _wmstate) return; // no change
485   
486   switch (state) {
487   case IconicState:
488     // XXX: cause it to iconify
489     break;
490   case NormalState:
491     // XXX: cause it to uniconify
492     break;
493   }
494   _wmstate = state;
495 }
496
497
498 void OBClient::setDesktop(long target)
499 {
500   assert(target >= 0);
501   //assert(target == 0xffffffff || target < MAX);
502   
503   // XXX: move the window to the new desktop
504   _desktop = target;
505 }
506
507
508 void OBClient::setState(StateAction action, long data1, long data2)
509 {
510   const otk::OBProperty *property = Openbox::instance->property();
511
512   if (!(action == State_Add || action == State_Remove ||
513         action == State_Toggle))
514     return; // an invalid action was passed to the client message, ignore it
515
516   for (int i = 0; i < 2; ++i) {
517     Atom state = i == 0 ? data1 : data2;
518     
519     if (! state) continue;
520
521     // if toggling, then pick whether we're adding or removing
522     if (action == State_Toggle) {
523       if (state == property->atom(otk::OBProperty::net_wm_state_modal))
524         action = _modal ? State_Remove : State_Add;
525       else if (state ==
526                property->atom(otk::OBProperty::net_wm_state_maximized_vert))
527         action = _max_vert ? State_Remove : State_Add;
528       else if (state ==
529                property->atom(otk::OBProperty::net_wm_state_maximized_horz))
530         action = _max_horz ? State_Remove : State_Add;
531       else if (state == property->atom(otk::OBProperty::net_wm_state_shaded))
532         action = _shaded ? State_Remove : State_Add;
533       else if (state ==
534                property->atom(otk::OBProperty::net_wm_state_fullscreen))
535         action = _fullscreen ? State_Remove : State_Add;
536       else if (state == property->atom(otk::OBProperty::net_wm_state_floating))
537         action = _floating ? State_Remove : State_Add;
538     }
539     
540     if (action == State_Add) {
541       if (state == property->atom(otk::OBProperty::net_wm_state_modal)) {
542         if (_modal) continue;
543         _modal = true;
544         // XXX: give it focus if another window has focus that shouldnt now
545       } else if (state ==
546                  property->atom(otk::OBProperty::net_wm_state_maximized_vert)){
547         if (_max_vert) continue;
548         _max_vert = true;
549         // XXX: resize the window etc
550       } else if (state ==
551                  property->atom(otk::OBProperty::net_wm_state_maximized_horz)){
552         if (_max_horz) continue;
553         _max_horz = true;
554         // XXX: resize the window etc
555       } else if (state ==
556                  property->atom(otk::OBProperty::net_wm_state_shaded)) {
557         if (_shaded) continue;
558         _shaded = true;
559         // XXX: hide the client window
560       } else if (state ==
561                  property->atom(otk::OBProperty::net_wm_state_fullscreen)) {
562         if (_fullscreen) continue;
563         _fullscreen = true;
564         // XXX: raise the window n shit
565       } else if (state ==
566                  property->atom(otk::OBProperty::net_wm_state_floating)) {
567         if (_floating) continue;
568         _floating = true;
569         // XXX: raise the window n shit
570       }
571
572     } else { // action == State_Remove
573       if (state == property->atom(otk::OBProperty::net_wm_state_modal)) {
574         if (!_modal) continue;
575         _modal = false;
576       } else if (state ==
577                  property->atom(otk::OBProperty::net_wm_state_maximized_vert)){
578         if (!_max_vert) continue;
579         _max_vert = false;
580         // XXX: resize the window etc
581       } else if (state ==
582                  property->atom(otk::OBProperty::net_wm_state_maximized_horz)){
583         if (!_max_horz) continue;
584         _max_horz = false;
585         // XXX: resize the window etc
586       } else if (state ==
587                  property->atom(otk::OBProperty::net_wm_state_shaded)) {
588         if (!_shaded) continue;
589         _shaded = false;
590         // XXX: show the client window
591       } else if (state ==
592                  property->atom(otk::OBProperty::net_wm_state_fullscreen)) {
593         if (!_fullscreen) continue;
594         _fullscreen = false;
595         // XXX: lower the window to its proper layer
596       } else if (state ==
597                  property->atom(otk::OBProperty::net_wm_state_floating)) {
598         if (!_floating) continue;
599         _floating = false;
600         // XXX: lower the window to its proper layer
601       }
602     }
603   }
604 }
605
606
607 void OBClient::update(const XClientMessageEvent &e)
608 {
609   if (e.format != 32) return;
610
611   const otk::OBProperty *property = Openbox::instance->property();
612   
613   if (e.message_type == property->atom(otk::OBProperty::wm_change_state))
614     setWMState(e.data.l[0]);
615   else if (e.message_type ==
616              property->atom(otk::OBProperty::net_wm_desktop))
617     setDesktop(e.data.l[0]);
618   else if (e.message_type == property->atom(otk::OBProperty::net_wm_state))
619     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
620 }
621
622
623 void OBClient::setArea(const otk::Rect &area)
624 {
625   _area = area;
626 }
627
628 }