use INT_MAX for the default max sizes
[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   
31   getArea();
32   getDesktop();
33   getType();
34   getState();
35   getShaped();
36
37   updateNormalHints();
38   updateWMHints();
39   // XXX: updateTransientFor();
40   updateTitle();
41   updateClass();
42
43 #ifdef DEBUG
44   printf("Mapped window: 0x%lx\n"
45          "  title:         \t%s\t  icon title:    \t%s\n"
46          "  app name:      \t%s\t\t  class:         \t%s\n"
47          "  position:      \t%d, %d\t\t  size:          \t%d, %d\n"
48          "  desktop:       \t%lu\t\t  group:         \t0x%lx\n"
49          "  type:          \t%d\t\t  min size       \t%d, %d\n"
50          "  base size      \t%d, %d\t\t  max size       \t%d, %d\n"
51          "  size incr      \t%d, %d\t\t  gravity        \t%d\n"
52          "  wm state       \t%ld\t\t  can be focused:\t%s\n"
53          "  notify focus:  \t%s\t\t  urgent:        \t%s\n"
54          "  shaped:        \t%s\t\t  modal:         \t%s\n"
55          "  shaded:        \t%s\t\t  iconic:        \t%s\n"
56          "  vert maximized:\t%s\t\t  horz maximized:\t%s\n"
57          "  fullscreen:    \t%s\t\t  floating:      \t%s\n",
58          _window,
59          _title.c_str(),
60          _icon_title.c_str(),
61          _app_name.c_str(),
62          _app_class.c_str(),
63          _area.x(), _area.y(),
64          _area.width(), _area.height(),
65          _desktop,
66          _group,
67          _type,
68          _min_x, _min_y,
69          _base_x, _base_y,
70          _max_x, _max_y,
71          _inc_x, _inc_y,
72          _gravity,
73          _wmstate,
74          _can_focus ? "yes" : "no",
75          _focus_notify ? "yes" : "no",
76          _urgent ? "yes" : "no",
77          _shaped ? "yes" : "no",
78          _modal ? "yes" : "no",
79          _shaded ? "yes" : "no",
80          _iconic ? "yes" : "no",
81          _max_vert ? "yes" : "no",
82          _max_horz ? "yes" : "no",
83          _fullscreen ? "yes" : "no",
84          _floating ? "yes" : "no");
85 #endif
86 }
87
88
89 OBClient::~OBClient()
90 {
91   const otk::OBProperty *property = Openbox::instance->property();
92
93   // these values should not be persisted across a window unmapping/mapping
94   property->erase(_window, otk::OBProperty::net_wm_desktop);
95   property->erase(_window, otk::OBProperty::net_wm_state);
96 }
97
98
99 void OBClient::getDesktop()
100 {
101   const otk::OBProperty *property = Openbox::instance->property();
102
103   // defaults to the current desktop
104   _desktop = 0; // XXX: change this to the current desktop!
105
106   property->get(_window, otk::OBProperty::net_wm_desktop,
107                 otk::OBProperty::Atom_Cardinal,
108                 &_desktop);
109 }
110
111
112 void OBClient::getType()
113 {
114   const otk::OBProperty *property = Openbox::instance->property();
115
116   _type = (WindowType) -1;
117   
118   unsigned long *val;
119   unsigned long num = (unsigned) -1;
120   if (property->get(_window, otk::OBProperty::net_wm_window_type,
121                     otk::OBProperty::Atom_Atom,
122                     &num, &val)) {
123     // use the first value that we know about in the array
124     for (unsigned long i = 0; i < num; ++i) {
125       if (val[i] ==
126           property->atom(otk::OBProperty::net_wm_window_type_desktop))
127         _type = Type_Desktop;
128       else if (val[i] ==
129                property->atom(otk::OBProperty::net_wm_window_type_dock))
130         _type = Type_Dock;
131       else if (val[i] ==
132                property->atom(otk::OBProperty::net_wm_window_type_toolbar))
133         _type = Type_Toolbar;
134       else if (val[i] ==
135                property->atom(otk::OBProperty::net_wm_window_type_menu))
136         _type = Type_Menu;
137       else if (val[i] ==
138                property->atom(otk::OBProperty::net_wm_window_type_utility))
139         _type = Type_Utility;
140       else if (val[i] ==
141                property->atom(otk::OBProperty::net_wm_window_type_splash))
142         _type = Type_Splash;
143       else if (val[i] ==
144                property->atom(otk::OBProperty::net_wm_window_type_dialog))
145         _type = Type_Dialog;
146       else if (val[i] ==
147                property->atom(otk::OBProperty::net_wm_window_type_normal))
148         _type = Type_Normal;
149 //      else if (val[i] ==
150 //               property->atom(otk::OBProperty::kde_net_wm_window_type_override))
151 //        mwm_decorations = 0; // prevent this window from getting any decor
152       // XXX: make this work again
153     }
154     delete val;
155   }
156     
157   if (_type == (WindowType) -1) {
158     /*
159      * the window type hint was not set, which means we either classify ourself
160      * as a normal window or a dialog, depending on if we are a transient.
161      */
162     // XXX: make this code work!
163     //if (isTransient())
164     //  _type = Type_Dialog;
165     //else
166       _type = Type_Normal;
167   }
168 }
169
170
171 void OBClient::getArea()
172 {
173   XWindowAttributes wattrib;
174   assert(XGetWindowAttributes(otk::OBDisplay::display, _window, &wattrib));
175
176   _area.setRect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
177 }
178
179
180 void OBClient::getState()
181 {
182   const otk::OBProperty *property = Openbox::instance->property();
183
184   _modal = _shaded = _max_horz = _max_vert = _fullscreen = _floating = false;
185   
186   unsigned long *state;
187   unsigned long num = (unsigned) -1;
188   
189   if (property->get(_window, otk::OBProperty::net_wm_state,
190                     otk::OBProperty::Atom_Atom, &num, &state)) {
191     for (unsigned long i = 0; i < num; ++i) {
192       if (state[i] == property->atom(otk::OBProperty::net_wm_state_modal))
193         _modal = true;
194       else if (state[i] ==
195                property->atom(otk::OBProperty::net_wm_state_shaded))
196         _shaded = true;
197       else if (state[i] ==
198                property->atom(otk::OBProperty::net_wm_state_fullscreen))
199         _fullscreen = true;
200       else if (state[i] ==
201                property->atom(otk::OBProperty::net_wm_state_maximized_vert))
202         _max_vert = true;
203       else if (state[i] ==
204                property->atom(otk::OBProperty::net_wm_state_maximized_horz))
205         _max_horz = true;
206     }
207
208     delete [] state;
209   }
210 }
211
212
213 void OBClient::getShaped()
214 {
215   _shaped = false;
216 #ifdef   SHAPE
217   if (otk::OBDisplay::shape()) {
218     int foo;
219     unsigned int ufoo;
220
221     XShapeQueryExtents(otk::OBDisplay::display, client.window, &_shaped, &foo,
222                        &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo);
223   }
224 #endif // SHAPE
225 }
226
227
228 void OBClient::updateNormalHints()
229 {
230   XSizeHints size;
231   long ret;
232
233   // defaults
234   _gravity = NorthWestGravity;
235   _inc_x = _inc_y = 1;
236   _base_x = _base_y = 0;
237   _min_x = _min_y = 0;
238   _max_x = _max_y = INT_MAX;
239
240   // get the hints from the window
241   if (XGetWMNormalHints(otk::OBDisplay::display, _window, &size, &ret)) {
242     if (size.flags & PWinGravity)
243       _gravity = size.win_gravity;
244     if (size.flags & PMinSize) {
245       _min_x = size.min_width;
246       _min_y = size.min_height;
247     }
248     if (size.flags & PMaxSize) {
249       _max_x = size.max_width;
250       _max_y = size.max_height;
251     }
252     if (size.flags & PBaseSize) {
253       _base_x = size.base_width;
254       _base_y = size.base_height;
255     }
256     if (size.flags & PResizeInc) {
257       _inc_x = size.width_inc;
258       _inc_y = size.height_inc;
259     }
260   }
261 }
262
263
264 void OBClient::updateWMHints()
265 {
266   XWMHints *hints;
267
268   // assume a window takes input if it doesnt specify
269   _can_focus = true;
270   _urgent = false;
271   
272   if ((hints = XGetWMHints(otk::OBDisplay::display, _window)) != NULL) {
273     if (hints->flags & InputHint)
274       _can_focus = hints->input;
275
276     if (hints->flags & XUrgencyHint)
277       _urgent = true;
278
279     if (hints->flags & WindowGroupHint) {
280       if (hints->window_group != _group) {
281         // XXX: remove from the old group if there was one
282         _group = hints->window_group;
283         // XXX: do stuff with the group
284       }
285     } else // no group!
286       _group = None;
287
288     XFree(hints);
289   }
290 }
291
292
293 void OBClient::updateTitle()
294 {
295   const otk::OBProperty *property = Openbox::instance->property();
296
297   _title = "";
298   
299   // try netwm
300   if (! property->get(_window, otk::OBProperty::net_wm_name,
301                       otk::OBProperty::utf8, &_title)) {
302     // try old x stuff
303     property->get(_window, otk::OBProperty::wm_name,
304                   otk::OBProperty::ascii, &_title);
305   }
306
307   if (_title.empty())
308     _title = _("Unnamed Window");
309 }
310
311
312 void OBClient::updateClass()
313 {
314   const otk::OBProperty *property = Openbox::instance->property();
315
316   // set the defaults
317   _app_name = _app_class = "";
318
319   otk::OBProperty::StringVect v;
320   unsigned long num = 2;
321
322   if (! property->get(_window, otk::OBProperty::wm_class,
323                       otk::OBProperty::ascii, &num, &v))
324     return;
325
326   if (num > 0) _app_name = v[0];
327   if (num > 1) _app_class = v[1];
328 }
329
330
331 void OBClient::update(const XPropertyEvent &e)
332 {
333   const otk::OBProperty *property = Openbox::instance->property();
334
335   if (e.atom == XA_WM_NORMAL_HINTS)
336     updateNormalHints();
337   else if (e.atom == XA_WM_HINTS)
338     updateWMHints();
339   else if (e.atom == property->atom(otk::OBProperty::net_wm_name) ||
340            e.atom == property->atom(otk::OBProperty::wm_name) ||
341            e.atom == property->atom(otk::OBProperty::net_wm_icon_name) ||
342            e.atom == property->atom(otk::OBProperty::wm_icon_name))
343     updateTitle();
344   else if (e.atom == property->atom(otk::OBProperty::wm_class))
345     updateClass();
346   // XXX: transient for hint
347 }
348
349
350 void OBClient::setWMState(long state)
351 {
352   if (state == _wmstate) return; // no change
353   
354   switch (state) {
355   case IconicState:
356     // XXX: cause it to iconify
357     break;
358   case NormalState:
359     // XXX: cause it to uniconify
360     break;
361   }
362   _wmstate = state;
363 }
364
365
366 void OBClient::setDesktop(long target)
367 {
368   assert(target >= 0);
369   //assert(target == 0xffffffff || target < MAX);
370   
371   // XXX: move the window to the new desktop
372   _desktop = target;
373 }
374
375
376 void OBClient::setState(StateAction action, long data1, long data2)
377 {
378   const otk::OBProperty *property = Openbox::instance->property();
379
380   if (!(action == State_Add || action == State_Remove ||
381         action == State_Toggle))
382     return; // an invalid action was passed to the client message, ignore it
383
384   for (int i = 0; i < 2; ++i) {
385     Atom state = i == 0 ? data1 : data2;
386     
387     if (! state) continue;
388
389     // if toggling, then pick whether we're adding or removing
390     if (action == State_Toggle) {
391       if (state == property->atom(otk::OBProperty::net_wm_state_modal))
392         action = _modal ? State_Remove : State_Add;
393       else if (state ==
394                property->atom(otk::OBProperty::net_wm_state_maximized_vert))
395         action = _max_vert ? State_Remove : State_Add;
396       else if (state ==
397                property->atom(otk::OBProperty::net_wm_state_maximized_horz))
398         action = _max_horz ? State_Remove : State_Add;
399       else if (state == property->atom(otk::OBProperty::net_wm_state_shaded))
400         action = _shaded ? State_Remove : State_Add;
401       else if (state ==
402                property->atom(otk::OBProperty::net_wm_state_fullscreen))
403         action = _fullscreen ? State_Remove : State_Add;
404       else if (state == property->atom(otk::OBProperty::net_wm_state_floating))
405         action = _floating ? State_Remove : State_Add;
406     }
407     
408     if (action == State_Add) {
409       if (state == property->atom(otk::OBProperty::net_wm_state_modal)) {
410         if (_modal) continue;
411         _modal = true;
412         // XXX: give it focus if another window has focus that shouldnt now
413       } else if (state ==
414                  property->atom(otk::OBProperty::net_wm_state_maximized_vert)){
415         if (_max_vert) continue;
416         _max_vert = true;
417         // XXX: resize the window etc
418       } else if (state ==
419                  property->atom(otk::OBProperty::net_wm_state_maximized_horz)){
420         if (_max_horz) continue;
421         _max_horz = true;
422         // XXX: resize the window etc
423       } else if (state ==
424                  property->atom(otk::OBProperty::net_wm_state_shaded)) {
425         if (_shaded) continue;
426         _shaded = true;
427         // XXX: hide the client window
428       } else if (state ==
429                  property->atom(otk::OBProperty::net_wm_state_fullscreen)) {
430         if (_fullscreen) continue;
431         _fullscreen = true;
432         // XXX: raise the window n shit
433       } else if (state ==
434                  property->atom(otk::OBProperty::net_wm_state_floating)) {
435         if (_floating) continue;
436         _floating = true;
437         // XXX: raise the window n shit
438       }
439
440     } else { // action == State_Remove
441       if (state == property->atom(otk::OBProperty::net_wm_state_modal)) {
442         if (!_modal) continue;
443         _modal = false;
444       } else if (state ==
445                  property->atom(otk::OBProperty::net_wm_state_maximized_vert)){
446         if (!_max_vert) continue;
447         _max_vert = false;
448         // XXX: resize the window etc
449       } else if (state ==
450                  property->atom(otk::OBProperty::net_wm_state_maximized_horz)){
451         if (!_max_horz) continue;
452         _max_horz = false;
453         // XXX: resize the window etc
454       } else if (state ==
455                  property->atom(otk::OBProperty::net_wm_state_shaded)) {
456         if (!_shaded) continue;
457         _shaded = false;
458         // XXX: show the client window
459       } else if (state ==
460                  property->atom(otk::OBProperty::net_wm_state_fullscreen)) {
461         if (!_fullscreen) continue;
462         _fullscreen = false;
463         // XXX: lower the window to its proper layer
464       } else if (state ==
465                  property->atom(otk::OBProperty::net_wm_state_floating)) {
466         if (!_floating) continue;
467         _floating = false;
468         // XXX: lower the window to its proper layer
469       }
470     }
471   }
472 }
473
474
475 void OBClient::update(const XClientMessageEvent &e)
476 {
477   if (e.format != 32) return;
478
479   const otk::OBProperty *property = Openbox::instance->property();
480   
481   if (e.message_type == property->atom(otk::OBProperty::wm_change_state))
482     setWMState(e.data.l[0]);
483   else if (e.message_type ==
484              property->atom(otk::OBProperty::net_wm_desktop))
485     setDesktop(e.data.l[0]);
486   else if (e.message_type == property->atom(otk::OBProperty::net_wm_state))
487     setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
488 }
489
490
491 void OBClient::setArea(const otk::Rect &area)
492 {
493   _area = area;
494 }
495
496 }