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