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