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