]> Dogcows Code - chaz/openbox/blob - src/client.cc
out with the blackbox source
[chaz/openbox] / src / client.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #ifdef HAVE_CONFIG_H
4 # include "../config.h"
5 #endif
6
7 #include "client.hh"
8 #include "frame.hh"
9 #include "screen.hh"
10 #include "openbox.hh"
11 #include "otk/display.hh"
12 #include "otk/property.hh"
13
14 extern "C" {
15 #include <X11/Xlib.h>
16 #include <X11/Xutil.h>
17 #include <X11/Xatom.h>
18
19 #include <assert.h>
20
21 #include "gettext.h"
22 #define _(str) gettext(str)
23 }
24
25 namespace ob {
26
27 Client::Client(int screen, Window window)
28 : otk::EventHandler(),
29 WidgetBase(WidgetBase::Type_Client),
30 frame(0), _screen(screen), _window(window)
31 {
32 assert(screen >= 0);
33 assert(window);
34
35 ignore_unmaps = 0;
36
37 // update EVERYTHING the first time!!
38
39 // we default to NormalState, visible
40 _wmstate = NormalState;
41 // start unfocused
42 _focused = false;
43 // not a transient by default of course
44 _transient_for = 0;
45 // pick a layer to start from
46 _layer = Layer_Normal;
47
48 getArea();
49 getDesktop();
50
51 updateTransientFor();
52 getType();
53 getMwmHints();
54
55 getState();
56 getShaped();
57
58 updateProtocols();
59
60 // got the type, the mwmhints, and the protocols, so we're ready to set up
61 // the decorations/functions
62 setupDecorAndFunctions();
63
64 getGravity(); // get the attribute gravity
65 updateNormalHints(); // this may override the attribute gravity
66 // also get the initial_state and set _iconic if we aren't "starting"
67 // when we're "starting" that means we should use whatever state was already
68 // on the window over the initial map state, because it was already mapped
69 updateWMHints(openbox->state() != Openbox::State_Starting);
70 updateTitle();
71 updateIconTitle();
72 updateClass();
73 updateStrut();
74
75 // this makes sure that these windows appear on all desktops
76 if (_type == Type_Dock || _type == Type_Desktop)
77 _desktop = 0xffffffff;
78
79 // set the desktop hint, to make sure that it always exists, and to reflect
80 // any changes we've made here
81 otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
82 otk::Property::atoms.cardinal, (unsigned)_desktop);
83
84 changeState();
85 }
86
87
88 Client::~Client()
89 {
90 // clean up childrens' references
91 while (!_transients.empty()) {
92 _transients.front()->_transient_for = 0;
93 _transients.pop_front();
94 }
95
96 // clean up parents reference to this
97 if (_transient_for)
98 _transient_for->_transients.remove(this); // remove from old parent
99
100 if (openbox->state() != Openbox::State_Exiting) {
101 // these values should not be persisted across a window unmapping/mapping
102 otk::Property::erase(_window, otk::Property::atoms.net_wm_desktop);
103 otk::Property::erase(_window, otk::Property::atoms.net_wm_state);
104 } else {
105 // if we're left in an iconic state, the client wont be mapped. this is
106 // bad, since we will no longer be managing the window on restart
107 if (_iconic)
108 XMapWindow(**otk::display, _window);
109 }
110 }
111
112
113 void Client::getGravity()
114 {
115 XWindowAttributes wattrib;
116 Status ret;
117
118 ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
119 assert(ret != BadWindow);
120 _gravity = wattrib.win_gravity;
121 }
122
123
124 void Client::getDesktop()
125 {
126 // defaults to the current desktop
127 _desktop = openbox->screen(_screen)->desktop();
128
129 otk::Property::get(_window, otk::Property::atoms.net_wm_desktop,
130 otk::Property::atoms.cardinal,
131 (long unsigned*)&_desktop);
132 }
133
134
135 void Client::getType()
136 {
137 _type = (WindowType) -1;
138
139 unsigned long *val;
140 unsigned long num = (unsigned) -1;
141 if (otk::Property::get(_window, otk::Property::atoms.net_wm_window_type,
142 otk::Property::atoms.atom, &num, &val)) {
143 // use the first value that we know about in the array
144 for (unsigned long i = 0; i < num; ++i) {
145 if (val[i] == otk::Property::atoms.net_wm_window_type_desktop)
146 _type = Type_Desktop;
147 else if (val[i] == otk::Property::atoms.net_wm_window_type_dock)
148 _type = Type_Dock;
149 else if (val[i] == otk::Property::atoms.net_wm_window_type_toolbar)
150 _type = Type_Toolbar;
151 else if (val[i] == otk::Property::atoms.net_wm_window_type_menu)
152 _type = Type_Menu;
153 else if (val[i] == otk::Property::atoms.net_wm_window_type_utility)
154 _type = Type_Utility;
155 else if (val[i] == otk::Property::atoms.net_wm_window_type_splash)
156 _type = Type_Splash;
157 else if (val[i] == otk::Property::atoms.net_wm_window_type_dialog)
158 _type = Type_Dialog;
159 else if (val[i] == otk::Property::atoms.net_wm_window_type_normal)
160 _type = Type_Normal;
161 // XXX: make this work again
162 // else if (val[i] == otk::Property::atoms.kde_net_wm_window_type_override)
163 // mwm_decorations = 0; // prevent this window from getting any decor
164 if (_type != (WindowType) -1)
165 break; // grab the first known type
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 if (_transient_for)
176 _type = Type_Dialog;
177 else
178 _type = Type_Normal;
179 }
180 }
181
182
183 void Client::setupDecorAndFunctions()
184 {
185 // start with everything (cept fullscreen)
186 _decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
187 Decor_AllDesktops | Decor_Iconify | Decor_Maximize;
188 _functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize |
189 Func_Shade;
190 if (_delete_window) {
191 _decorations |= Decor_Close;
192 _functions |= Func_Close;
193 }
194
195 switch (_type) {
196 case Type_Normal:
197 // normal windows retain all of the possible decorations and
198 // functionality, and are the only windows that you can fullscreen
199 _functions |= Func_Fullscreen;
200
201 case Type_Dialog:
202 // dialogs cannot be maximized
203 _decorations &= ~Decor_Maximize;
204 _functions &= ~Func_Maximize;
205 break;
206
207 case Type_Menu:
208 case Type_Toolbar:
209 case Type_Utility:
210 // these windows get less functionality
211 _decorations &= ~(Decor_Iconify | Decor_Handle);
212 _functions &= ~(Func_Iconify | Func_Resize);
213 break;
214
215 case Type_Desktop:
216 case Type_Dock:
217 case Type_Splash:
218 // none of these windows are manipulated by the window manager
219 _decorations = 0;
220 _functions = 0;
221 break;
222 }
223
224 // Mwm Hints are applied subtractively to what has already been chosen for
225 // decor and functionality
226 if (_mwmhints.flags & MwmFlag_Decorations) {
227 if (! (_mwmhints.decorations & MwmDecor_All)) {
228 if (! (_mwmhints.decorations & MwmDecor_Border))
229 _decorations &= ~Decor_Border;
230 if (! (_mwmhints.decorations & MwmDecor_Handle))
231 _decorations &= ~Decor_Handle;
232 if (! (_mwmhints.decorations & MwmDecor_Title)) {
233 _decorations &= ~Decor_Titlebar;
234 // if we don't have a titlebar, then we cannot shade!
235 _functions &= ~Func_Shade;
236 }
237 if (! (_mwmhints.decorations & MwmDecor_Iconify))
238 _decorations &= ~Decor_Iconify;
239 if (! (_mwmhints.decorations & MwmDecor_Maximize))
240 _decorations &= ~Decor_Maximize;
241 }
242 }
243
244 if (_mwmhints.flags & MwmFlag_Functions) {
245 if (! (_mwmhints.functions & MwmFunc_All)) {
246 if (! (_mwmhints.functions & MwmFunc_Resize))
247 _functions &= ~Func_Resize;
248 if (! (_mwmhints.functions & MwmFunc_Move))
249 _functions &= ~Func_Move;
250 if (! (_mwmhints.functions & MwmFunc_Iconify))
251 _functions &= ~Func_Iconify;
252 if (! (_mwmhints.functions & MwmFunc_Maximize))
253 _functions &= ~Func_Maximize;
254 // dont let mwm hints kill the close button
255 //if (! (_mwmhints.functions & MwmFunc_Close))
256 // _functions &= ~Func_Close;
257 }
258 }
259
260 changeAllowedActions();
261 }
262
263
264 void Client::getMwmHints()
265 {
266 unsigned long num = MwmHints::elements;
267 unsigned long *hints;
268
269 _mwmhints.flags = 0; // default to none
270
271 if (!otk::Property::get(_window, otk::Property::atoms.motif_wm_hints,
272 otk::Property::atoms.motif_wm_hints, &num,
273 (unsigned long **)&hints))
274 return;
275
276 if (num >= MwmHints::elements) {
277 // retrieved the hints
278 _mwmhints.flags = hints[0];
279 _mwmhints.functions = hints[1];
280 _mwmhints.decorations = hints[2];
281 }
282
283 delete [] hints;
284 }
285
286
287 void Client::getArea()
288 {
289 XWindowAttributes wattrib;
290 Status ret;
291
292 ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
293 assert(ret != BadWindow);
294
295 _area.setRect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
296 _border_width = wattrib.border_width;
297 }
298
299
300 void Client::getState()
301 {
302 _modal = _shaded = _max_horz = _max_vert = _fullscreen = _above = _below =
303 _iconic = _skip_taskbar = _skip_pager = false;
304
305 unsigned long *state;
306 unsigned long num = (unsigned) -1;
307
308 if (otk::Property::get(_window, otk::Property::atoms.net_wm_state,
309 otk::Property::atoms.atom, &num, &state)) {
310 for (unsigned long i = 0; i < num; ++i) {
311 if (state[i] == otk::Property::atoms.net_wm_state_modal)
312 _modal = true;
313 else if (state[i] == otk::Property::atoms.net_wm_state_shaded)
314 _shaded = true;
315 else if (state[i] == otk::Property::atoms.net_wm_state_hidden)
316 _iconic = true;
317 else if (state[i] == otk::Property::atoms.net_wm_state_skip_taskbar)
318 _skip_taskbar = true;
319 else if (state[i] == otk::Property::atoms.net_wm_state_skip_pager)
320 _skip_pager = true;
321 else if (state[i] == otk::Property::atoms.net_wm_state_fullscreen)
322 _fullscreen = true;
323 else if (state[i] == otk::Property::atoms.net_wm_state_maximized_vert)
324 _max_vert = true;
325 else if (state[i] == otk::Property::atoms.net_wm_state_maximized_horz)
326 _max_horz = true;
327 else if (state[i] == otk::Property::atoms.net_wm_state_above)
328 _above = true;
329 else if (state[i] == otk::Property::atoms.net_wm_state_below)
330 _below = true;
331 }
332
333 delete [] state;
334 }
335 }
336
337
338 void Client::getShaped()
339 {
340 _shaped = false;
341 #ifdef SHAPE
342 if (otk::display->shape()) {
343 int foo;
344 unsigned int ufoo;
345 int s;
346
347 XShapeSelectInput(**otk::display, _window, ShapeNotifyMask);
348
349 XShapeQueryExtents(**otk::display, _window, &s, &foo,
350 &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo);
351 _shaped = (s != 0);
352 }
353 #endif // SHAPE
354 }
355
356
357 void Client::calcLayer() {
358 StackLayer l;
359
360 if (_iconic) l = Layer_Icon;
361 else if (_fullscreen) l = Layer_Fullscreen;
362 else if (_type == Type_Desktop) l = Layer_Desktop;
363 else if (_type == Type_Dock) {
364 if (!_below) l = Layer_Top;
365 else l = Layer_Normal;
366 }
367 else if (_above) l = Layer_Above;
368 else if (_below) l = Layer_Below;
369 else l = Layer_Normal;
370
371 if (l != _layer) {
372 _layer = l;
373 if (frame) {
374 /*
375 if we don't have a frame, then we aren't mapped yet (and this would
376 SIGSEGV :)
377 */
378 openbox->screen(_screen)->raiseWindow(this);
379 }
380 }
381 }
382
383
384 void Client::updateProtocols()
385 {
386 Atom *proto;
387 int num_return = 0;
388
389 _focus_notify = false;
390 _delete_window = false;
391
392 if (XGetWMProtocols(**otk::display, _window, &proto, &num_return)) {
393 for (int i = 0; i < num_return; ++i) {
394 if (proto[i] == otk::Property::atoms.wm_delete_window) {
395 // this means we can request the window to close
396 _delete_window = true;
397 } else if (proto[i] == otk::Property::atoms.wm_take_focus)
398 // if this protocol is requested, then the window will be notified
399 // by the window manager whenever it receives focus
400 _focus_notify = true;
401 }
402 XFree(proto);
403 }
404 }
405
406
407 void Client::updateNormalHints()
408 {
409 XSizeHints size;
410 long ret;
411 int oldgravity = _gravity;
412
413 // defaults
414 _size_inc.setPoint(1, 1);
415 _base_size.setPoint(0, 0);
416 _min_size.setPoint(0, 0);
417 _max_size.setPoint(INT_MAX, INT_MAX);
418
419 // XXX: might want to cancel any interactive resizing of the window at this
420 // point..
421
422 // get the hints from the window
423 if (XGetWMNormalHints(**otk::display, _window, &size, &ret)) {
424 _positioned = (size.flags & (PPosition|USPosition));
425
426 if (size.flags & PWinGravity) {
427 _gravity = size.win_gravity;
428
429 // if the client has a frame, i.e. has already been mapped and is
430 // changing its gravity
431 if (frame && _gravity != oldgravity) {
432 // move our idea of the client's position based on its new gravity
433 int x, y;
434 frame->frameGravity(x, y);
435 _area.setPos(x, y);
436 }
437 }
438
439 if (size.flags & PMinSize)
440 _min_size.setPoint(size.min_width, size.min_height);
441
442 if (size.flags & PMaxSize)
443 _max_size.setPoint(size.max_width, size.max_height);
444
445 if (size.flags & PBaseSize)
446 _base_size.setPoint(size.base_width, size.base_height);
447
448 if (size.flags & PResizeInc)
449 _size_inc.setPoint(size.width_inc, size.height_inc);
450 }
451 }
452
453
454 void Client::updateWMHints(bool initstate)
455 {
456 XWMHints *hints;
457
458 // assume a window takes input if it doesnt specify
459 _can_focus = true;
460 _urgent = false;
461
462 if ((hints = XGetWMHints(**otk::display, _window)) != NULL) {
463 if (hints->flags & InputHint)
464 _can_focus = hints->input;
465
466 // only do this when initstate is true!
467 if (initstate && (hints->flags & StateHint))
468 _iconic = hints->initial_state == IconicState;
469
470 if (hints->flags & XUrgencyHint)
471 _urgent = true;
472
473 if (hints->flags & WindowGroupHint) {
474 if (hints->window_group != _group) {
475 // XXX: remove from the old group if there was one
476 _group = hints->window_group;
477 // XXX: do stuff with the group
478 }
479 } else // no group!
480 _group = None;
481
482 XFree(hints);
483 }
484 }
485
486
487 void Client::updateTitle()
488 {
489 _title = "";
490
491 // try netwm
492 if (!otk::Property::get(_window, otk::Property::atoms.net_wm_name,
493 otk::Property::utf8, &_title)) {
494 // try old x stuff
495 otk::Property::get(_window, otk::Property::atoms.wm_name,
496 otk::Property::ascii, &_title);
497 }
498
499 if (_title.empty())
500 _title = _("Unnamed Window");
501
502 if (frame)
503 frame->setTitle(_title);
504 }
505
506
507 void Client::updateIconTitle()
508 {
509 _icon_title = "";
510
511 // try netwm
512 if (!otk::Property::get(_window, otk::Property::atoms.net_wm_icon_name,
513 otk::Property::utf8, &_icon_title)) {
514 // try old x stuff
515 otk::Property::get(_window, otk::Property::atoms.wm_icon_name,
516 otk::Property::ascii, &_icon_title);
517 }
518
519 if (_title.empty())
520 _icon_title = _("Unnamed Window");
521 }
522
523
524 void Client::updateClass()
525 {
526 // set the defaults
527 _app_name = _app_class = _role = "";
528
529 otk::Property::StringVect v;
530 unsigned long num = 2;
531
532 if (otk::Property::get(_window, otk::Property::atoms.wm_class,
533 otk::Property::ascii, &num, &v)) {
534 if (num > 0) _app_name = v[0].c_str();
535 if (num > 1) _app_class = v[1].c_str();
536 }
537
538 v.clear();
539 num = 1;
540 if (otk::Property::get(_window, otk::Property::atoms.wm_window_role,
541 otk::Property::ascii, &num, &v)) {
542 if (num > 0) _role = v[0].c_str();
543 }
544 }
545
546
547 void Client::updateStrut()
548 {
549 unsigned long num = 4;
550 unsigned long *data;
551 if (!otk::Property::get(_window, otk::Property::atoms.net_wm_strut,
552 otk::Property::atoms.cardinal, &num, &data))
553 return;
554
555 if (num == 4) {
556 _strut.left = data[0];
557 _strut.right = data[1];
558 _strut.top = data[2];
559 _strut.bottom = data[3];
560
561 openbox->screen(_screen)->updateStrut();
562 }
563
564 delete [] data;
565 }
566
567
568 void Client::updateTransientFor()
569 {
570 Window t = 0;
571 Client *c = 0;
572
573 if (XGetTransientForHint(**otk::display, _window, &t) &&
574 t != _window) { // cant be transient to itself!
575 c = openbox->findClient(t);
576 assert(c != this); // if this happens then we need to check for it
577
578 if (!c /*XXX: && _group*/) {
579 // not transient to a client, see if it is transient for a group
580 if (//t == _group->leader() ||
581 t == None ||
582 t == otk::display->screenInfo(_screen)->rootWindow()) {
583 // window is a transient for its group!
584 // XXX: for now this is treated as non-transient.
585 // this needs to be fixed!
586 }
587 }
588 }
589
590 // if anything has changed...
591 if (c != _transient_for) {
592 if (_transient_for)
593 _transient_for->_transients.remove(this); // remove from old parent
594 _transient_for = c;
595 if (_transient_for)
596 _transient_for->_transients.push_back(this); // add to new parent
597
598 // XXX: change decor status?
599 }
600 }
601
602
603 void Client::propertyHandler(const XPropertyEvent &e)
604 {
605 otk::EventHandler::propertyHandler(e);
606
607 // compress changes to a single property into a single change
608 XEvent ce;
609 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
610 // XXX: it would be nice to compress ALL changes to a property, not just
611 // changes in a row without other props between.
612 if (ce.xproperty.atom != e.atom) {
613 XPutBackEvent(**otk::display, &ce);
614 break;
615 }
616 }
617
618 if (e.atom == XA_WM_NORMAL_HINTS)
619 updateNormalHints();
620 else if (e.atom == XA_WM_HINTS)
621 updateWMHints();
622 else if (e.atom == XA_WM_TRANSIENT_FOR) {
623 updateTransientFor();
624 getType();
625 calcLayer(); // type may have changed, so update the layer
626 setupDecorAndFunctions();
627 frame->adjustSize(); // this updates the frame for any new decor settings
628 }
629 else if (e.atom == otk::Property::atoms.net_wm_name ||
630 e.atom == otk::Property::atoms.wm_name)
631 updateTitle();
632 else if (e.atom == otk::Property::atoms.net_wm_icon_name ||
633 e.atom == otk::Property::atoms.wm_icon_name)
634 updateIconTitle();
635 else if (e.atom == otk::Property::atoms.wm_class)
636 updateClass();
637 else if (e.atom == otk::Property::atoms.wm_protocols) {
638 updateProtocols();
639 setupDecorAndFunctions();
640 frame->adjustSize(); // update the decorations
641 }
642 else if (e.atom == otk::Property::atoms.net_wm_strut)
643 updateStrut();
644 }
645
646
647 void Client::setWMState(long state)
648 {
649 if (state == _wmstate) return; // no change
650
651 switch (state) {
652 case IconicState:
653 setDesktop(ICONIC_DESKTOP);
654 break;
655 case NormalState:
656 setDesktop(openbox->screen(_screen)->desktop());
657 break;
658 }
659 }
660
661
662 void Client::setDesktop(long target)
663 {
664 if (target == _desktop) return;
665
666 printf("Setting desktop %ld\n", target);
667
668 if (!(target >= 0 || target == (signed)0xffffffff ||
669 target == ICONIC_DESKTOP))
670 return;
671
672 _desktop = target;
673
674 // set the desktop hint, but not if we're iconifying
675 if (_desktop != ICONIC_DESKTOP)
676 otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
677 otk::Property::atoms.cardinal, (unsigned)_desktop);
678
679 // 'move' the window to the new desktop
680 if (_desktop == openbox->screen(_screen)->desktop() ||
681 _desktop == (signed)0xffffffff)
682 frame->show();
683 else
684 frame->hide();
685
686 // Handle Iconic state. Iconic state is maintained by the client being a
687 // member of the ICONIC_DESKTOP, so this is where we make iconifying and
688 // uniconifying happen.
689 bool i = _desktop == ICONIC_DESKTOP;
690 if (i != _iconic) { // has the state changed?
691 _iconic = i;
692 if (_iconic) {
693 _wmstate = IconicState;
694 ignore_unmaps++;
695 // we unmap the client itself so that we can get MapRequest events, and
696 // because the ICCCM tells us to!
697 XUnmapWindow(**otk::display, _window);
698 } else {
699 _wmstate = NormalState;
700 XMapWindow(**otk::display, _window);
701 }
702 changeState();
703 }
704
705 frame->adjustState();
706 }
707
708
709 void Client::setState(StateAction action, long data1, long data2)
710 {
711 bool shadestate = _shaded;
712 bool fsstate = _fullscreen;
713
714 if (!(action == State_Add || action == State_Remove ||
715 action == State_Toggle))
716 return; // an invalid action was passed to the client message, ignore it
717
718 for (int i = 0; i < 2; ++i) {
719 Atom state = i == 0 ? data1 : data2;
720
721 if (! state) continue;
722
723 // if toggling, then pick whether we're adding or removing
724 if (action == State_Toggle) {
725 if (state == otk::Property::atoms.net_wm_state_modal)
726 action = _modal ? State_Remove : State_Add;
727 else if (state == otk::Property::atoms.net_wm_state_maximized_vert)
728 action = _max_vert ? State_Remove : State_Add;
729 else if (state == otk::Property::atoms.net_wm_state_maximized_horz)
730 action = _max_horz ? State_Remove : State_Add;
731 else if (state == otk::Property::atoms.net_wm_state_shaded)
732 action = _shaded ? State_Remove : State_Add;
733 else if (state == otk::Property::atoms.net_wm_state_skip_taskbar)
734 action = _skip_taskbar ? State_Remove : State_Add;
735 else if (state == otk::Property::atoms.net_wm_state_skip_pager)
736 action = _skip_pager ? State_Remove : State_Add;
737 else if (state == otk::Property::atoms.net_wm_state_fullscreen)
738 action = _fullscreen ? State_Remove : State_Add;
739 else if (state == otk::Property::atoms.net_wm_state_above)
740 action = _above ? State_Remove : State_Add;
741 else if (state == otk::Property::atoms.net_wm_state_below)
742 action = _below ? State_Remove : State_Add;
743 }
744
745 if (action == State_Add) {
746 if (state == otk::Property::atoms.net_wm_state_modal) {
747 if (_modal) continue;
748 _modal = true;
749 // XXX: give it focus if another window has focus that shouldnt now
750 } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
751 if (_max_vert) continue;
752 _max_vert = true;
753 // XXX: resize the window etc
754 } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
755 if (_max_horz) continue;
756 _max_horz = true;
757 // XXX: resize the window etc
758 } else if (state == otk::Property::atoms.net_wm_state_shaded) {
759 shadestate = true;
760 } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
761 _skip_taskbar = true;
762 } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
763 _skip_pager = true;
764 } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
765 fsstate = true;
766 } else if (state == otk::Property::atoms.net_wm_state_above) {
767 if (_above) continue;
768 _above = true;
769 } else if (state == otk::Property::atoms.net_wm_state_below) {
770 if (_below) continue;
771 _below = true;
772 }
773
774 } else { // action == State_Remove
775 if (state == otk::Property::atoms.net_wm_state_modal) {
776 if (!_modal) continue;
777 _modal = false;
778 } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
779 if (!_max_vert) continue;
780 _max_vert = false;
781 // XXX: resize the window etc
782 } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
783 if (!_max_horz) continue;
784 _max_horz = false;
785 // XXX: resize the window etc
786 } else if (state == otk::Property::atoms.net_wm_state_shaded) {
787 shadestate = false;
788 } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
789 _skip_taskbar = false;
790 } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
791 _skip_pager = false;
792 } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
793 fsstate = false;
794 } else if (state == otk::Property::atoms.net_wm_state_above) {
795 if (!_above) continue;
796 _above = false;
797 } else if (state == otk::Property::atoms.net_wm_state_below) {
798 if (!_below) continue;
799 _below = false;
800 }
801 }
802 }
803 // change fullscreen state before shading, as it will affect if the window
804 // can shade or not
805 if (fsstate != _fullscreen)
806 fullscreen(fsstate);
807 if (shadestate != _shaded)
808 shade(shadestate);
809 calcLayer();
810 }
811
812
813 void Client::toggleClientBorder(bool addborder)
814 {
815 // adjust our idea of where the client is, based on its border. When the
816 // border is removed, the client should now be considered to be in a
817 // different position.
818 // when re-adding the border to the client, the same operation needs to be
819 // reversed.
820 int x = _area.x(), y = _area.y();
821 switch(_gravity) {
822 default:
823 case NorthWestGravity:
824 case WestGravity:
825 case SouthWestGravity:
826 break;
827 case NorthEastGravity:
828 case EastGravity:
829 case SouthEastGravity:
830 if (addborder) x -= _border_width * 2;
831 else x += _border_width * 2;
832 break;
833 case NorthGravity:
834 case SouthGravity:
835 case CenterGravity:
836 case ForgetGravity:
837 case StaticGravity:
838 if (addborder) x -= _border_width;
839 else x += _border_width;
840 break;
841 }
842 switch(_gravity) {
843 default:
844 case NorthWestGravity:
845 case NorthGravity:
846 case NorthEastGravity:
847 break;
848 case SouthWestGravity:
849 case SouthGravity:
850 case SouthEastGravity:
851 if (addborder) y -= _border_width * 2;
852 else y += _border_width * 2;
853 break;
854 case WestGravity:
855 case EastGravity:
856 case CenterGravity:
857 case ForgetGravity:
858 case StaticGravity:
859 if (addborder) y -= _border_width;
860 else y += _border_width;
861 break;
862 }
863 _area.setPos(x, y);
864
865 if (addborder) {
866 XSetWindowBorderWidth(**otk::display, _window, _border_width);
867
868 // move the client so it is back it the right spot _with_ its border!
869 XMoveWindow(**otk::display, _window, x, y);
870 } else
871 XSetWindowBorderWidth(**otk::display, _window, 0);
872 }
873
874
875 void Client::clientMessageHandler(const XClientMessageEvent &e)
876 {
877 otk::EventHandler::clientMessageHandler(e);
878
879 if (e.format != 32) return;
880
881 if (e.message_type == otk::Property::atoms.wm_change_state) {
882 // compress changes into a single change
883 bool compress = false;
884 XEvent ce;
885 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
886 // XXX: it would be nice to compress ALL messages of a type, not just
887 // messages in a row without other message types between.
888 if (ce.xclient.message_type != e.message_type) {
889 XPutBackEvent(**otk::display, &ce);
890 break;
891 }
892 compress = true;
893 }
894 if (compress)
895 setWMState(ce.xclient.data.l[0]); // use the found event
896 else
897 setWMState(e.data.l[0]); // use the original event
898 } else if (e.message_type == otk::Property::atoms.net_wm_desktop) {
899 // compress changes into a single change
900 bool compress = false;
901 XEvent ce;
902 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
903 // XXX: it would be nice to compress ALL messages of a type, not just
904 // messages in a row without other message types between.
905 if (ce.xclient.message_type != e.message_type) {
906 XPutBackEvent(**otk::display, &ce);
907 break;
908 }
909 compress = true;
910 }
911 if (compress)
912 setDesktop(e.data.l[0]); // use the found event
913 else
914 setDesktop(e.data.l[0]); // use the original event
915 } else if (e.message_type == otk::Property::atoms.net_wm_state) {
916 // can't compress these
917 #ifdef DEBUG
918 printf("net_wm_state %s %ld %ld for 0x%lx\n",
919 (e.data.l[0] == 0 ? "Remove" : e.data.l[0] == 1 ? "Add" :
920 e.data.l[0] == 2 ? "Toggle" : "INVALID"),
921 e.data.l[1], e.data.l[2], _window);
922 #endif
923 setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
924 } else if (e.message_type == otk::Property::atoms.net_close_window) {
925 #ifdef DEBUG
926 printf("net_close_window for 0x%lx\n", _window);
927 #endif
928 close();
929 } else if (e.message_type == otk::Property::atoms.net_active_window) {
930 #ifdef DEBUG
931 printf("net_active_window for 0x%lx\n", _window);
932 #endif
933 if (_iconic)
934 setDesktop(openbox->screen(_screen)->desktop());
935 if (_shaded)
936 shade(false);
937 // XXX: deiconify
938 focus();
939 openbox->screen(_screen)->raiseWindow(this);
940 }
941 }
942
943
944 #if defined(SHAPE)
945 void Client::shapeHandler(const XShapeEvent &e)
946 {
947 otk::EventHandler::shapeHandler(e);
948
949 if (e.kind == ShapeBounding) {
950 _shaped = e.shaped;
951 frame->adjustShape();
952 }
953 }
954 #endif
955
956
957 void Client::resize(Corner anchor, int w, int h)
958 {
959 if (!(_functions & Func_Resize)) return;
960 internal_resize(anchor, w, h);
961 }
962
963
964 void Client::internal_resize(Corner anchor, int w, int h, int x, int y)
965 {
966 w -= _base_size.x();
967 h -= _base_size.y();
968
969 // for interactive resizing. have to move half an increment in each
970 // direction.
971 w += _size_inc.x() / 2;
972 h += _size_inc.y() / 2;
973
974 // is the window resizable? if it is not, then don't check its sizes, the
975 // client can do what it wants and the user can't change it anyhow
976 if (_min_size.x() <= _max_size.x() && _min_size.y() <= _max_size.y()) {
977 // smaller than min size or bigger than max size?
978 if (w < _min_size.x()) w = _min_size.x();
979 else if (w > _max_size.x()) w = _max_size.x();
980 if (h < _min_size.y()) h = _min_size.y();
981 else if (h > _max_size.y()) h = _max_size.y();
982 }
983
984 // keep to the increments
985 w /= _size_inc.x();
986 h /= _size_inc.y();
987
988 // you cannot resize to nothing
989 if (w < 1) w = 1;
990 if (h < 1) h = 1;
991
992 // store the logical size
993 _logical_size.setPoint(w, h);
994
995 w *= _size_inc.x();
996 h *= _size_inc.y();
997
998 w += _base_size.x();
999 h += _base_size.y();
1000
1001 if (x == INT_MIN || y == INT_MIN) {
1002 x = _area.x();
1003 y = _area.y();
1004 switch (anchor) {
1005 case TopLeft:
1006 break;
1007 case TopRight:
1008 x -= w - _area.width();
1009 break;
1010 case BottomLeft:
1011 y -= h - _area.height();
1012 break;
1013 case BottomRight:
1014 x -= w - _area.width();
1015 y -= h - _area.height();
1016 break;
1017 }
1018 }
1019
1020 _area.setSize(w, h);
1021
1022 XResizeWindow(**otk::display, _window, w, h);
1023
1024 // resize the frame to match the request
1025 frame->adjustSize();
1026 internal_move(x, y);
1027 }
1028
1029
1030 void Client::move(int x, int y)
1031 {
1032 if (!(_functions & Func_Move)) return;
1033 internal_move(x, y);
1034 }
1035
1036
1037 void Client::internal_move(int x, int y)
1038 {
1039 _area.setPos(x, y);
1040
1041 // move the frame to be in the requested position
1042 if (frame) { // this can be called while mapping, before frame exists
1043 frame->adjustPosition();
1044
1045 // send synthetic configure notify (we don't need to if we aren't mapped
1046 // yet)
1047 XEvent event;
1048 event.type = ConfigureNotify;
1049 event.xconfigure.display = **otk::display;
1050 event.xconfigure.event = _window;
1051 event.xconfigure.window = _window;
1052 event.xconfigure.x = x;
1053 event.xconfigure.y = y;
1054 event.xconfigure.width = _area.width();
1055 event.xconfigure.height = _area.height();
1056 event.xconfigure.border_width = _border_width;
1057 event.xconfigure.above = frame->window();
1058 event.xconfigure.override_redirect = False;
1059 XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1060 StructureNotifyMask, &event);
1061 }
1062 }
1063
1064
1065 void Client::close()
1066 {
1067 XEvent ce;
1068
1069 if (!(_functions & Func_Close)) return;
1070
1071 // XXX: itd be cool to do timeouts and shit here for killing the client's
1072 // process off
1073 // like... if the window is around after 5 seconds, then the close button
1074 // turns a nice red, and if this function is called again, the client is
1075 // explicitly killed.
1076
1077 ce.xclient.type = ClientMessage;
1078 ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1079 ce.xclient.display = **otk::display;
1080 ce.xclient.window = _window;
1081 ce.xclient.format = 32;
1082 ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1083 ce.xclient.data.l[1] = CurrentTime;
1084 ce.xclient.data.l[2] = 0l;
1085 ce.xclient.data.l[3] = 0l;
1086 ce.xclient.data.l[4] = 0l;
1087 XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1088 }
1089
1090
1091 void Client::changeState()
1092 {
1093 unsigned long state[2];
1094 state[0] = _wmstate;
1095 state[1] = None;
1096 otk::Property::set(_window, otk::Property::atoms.wm_state,
1097 otk::Property::atoms.wm_state, state, 2);
1098
1099 Atom netstate[10];
1100 int num = 0;
1101 if (_modal)
1102 netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1103 if (_shaded)
1104 netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1105 if (_iconic)
1106 netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1107 if (_skip_taskbar)
1108 netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1109 if (_skip_pager)
1110 netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1111 if (_fullscreen)
1112 netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1113 if (_max_vert)
1114 netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1115 if (_max_horz)
1116 netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1117 if (_above)
1118 netstate[num++] = otk::Property::atoms.net_wm_state_above;
1119 if (_below)
1120 netstate[num++] = otk::Property::atoms.net_wm_state_below;
1121 otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1122 otk::Property::atoms.atom, netstate, num);
1123
1124 calcLayer();
1125
1126 if (frame)
1127 frame->adjustState();
1128 }
1129
1130
1131 void Client::changeAllowedActions(void)
1132 {
1133 Atom actions[9];
1134 int num = 0;
1135
1136 actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1137
1138 if (_functions & Func_Shade)
1139 actions[num++] = otk::Property::atoms.net_wm_action_shade;
1140 if (_functions & Func_Close)
1141 actions[num++] = otk::Property::atoms.net_wm_action_close;
1142 if (_functions & Func_Move)
1143 actions[num++] = otk::Property::atoms.net_wm_action_move;
1144 if (_functions & Func_Iconify)
1145 actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1146 if (_functions & Func_Resize)
1147 actions[num++] = otk::Property::atoms.net_wm_action_resize;
1148 if (_functions & Func_Fullscreen)
1149 actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1150 if (_functions & Func_Maximize) {
1151 actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1152 actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1153 }
1154
1155 otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1156 otk::Property::atoms.atom, actions, num);
1157 }
1158
1159
1160 void Client::applyStartupState()
1161 {
1162 // these are in a carefully crafted order..
1163
1164 if (_iconic) {
1165 printf("MAP ICONIC\n");
1166 _iconic = false;
1167 setDesktop(ICONIC_DESKTOP);
1168 }
1169 if (_fullscreen) {
1170 _fullscreen = false;
1171 fullscreen(true);
1172 }
1173 if (_shaded) {
1174 _shaded = false;
1175 shade(true);
1176 }
1177
1178 if (_max_vert); // XXX: incomplete
1179 if (_max_horz); // XXX: incomplete
1180
1181 if (_skip_taskbar); // nothing to do for this
1182 if (_skip_pager); // nothing to do for this
1183 if (_modal); // nothing to do for this
1184 if (_above); // nothing to do for this
1185 if (_below); // nothing to do for this
1186 }
1187
1188
1189 void Client::shade(bool shade)
1190 {
1191 if (!(_functions & Func_Shade) || // can't
1192 _shaded == shade) return; // already done
1193
1194 // when we're iconic, don't change the wmstate
1195 if (!_iconic)
1196 _wmstate = shade ? IconicState : NormalState;
1197 _shaded = shade;
1198 changeState();
1199 frame->adjustSize();
1200 }
1201
1202
1203 void Client::fullscreen(bool fs)
1204 {
1205 static FunctionFlags saved_func;
1206 static DecorationFlags saved_decor;
1207 static otk::Rect saved_area;
1208 static otk::Point saved_logical_size;
1209
1210 if (!(_functions & Func_Fullscreen) || // can't
1211 _fullscreen == fs) return; // already done
1212
1213 _fullscreen = fs;
1214 changeState(); // change the state hints on the client
1215
1216 if (fs) {
1217 // save the functions and remove them
1218 saved_func = _functions;
1219 _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1220 // save the decorations and remove them
1221 saved_decor = _decorations;
1222 _decorations = 0;
1223 // save the area and adjust it (we don't call internal resize here for
1224 // constraints on the size, etc, we just make it fullscreen).
1225 saved_area = _area;
1226 const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1227 _area.setRect(0, 0, info->width(), info->height());
1228 saved_logical_size = _logical_size;
1229 _logical_size.setPoint((info->width() - _base_size.x()) / _size_inc.x(),
1230 (info->height() - _base_size.y()) / _size_inc.y());
1231 } else {
1232 _functions = saved_func;
1233 _decorations = saved_decor;
1234 _area = saved_area;
1235 _logical_size = saved_logical_size;
1236 }
1237
1238 changeAllowedActions(); // based on the new _functions
1239
1240 frame->adjustSize(); // drop/replace the decor's and resize
1241 frame->adjustPosition(); // get (back) in position!
1242
1243 // raise (back) into our stacking layer
1244 openbox->screen(_screen)->raiseWindow(this);
1245
1246 // try focus us when we go into fullscreen mode
1247 if (fs) focus();
1248 }
1249
1250
1251 bool Client::focus()
1252 {
1253 // won't try focus if the client doesn't want it, or if the window isn't
1254 // visible on the screen
1255 if (!(frame->isVisible() && (_can_focus || _focus_notify))) return false;
1256
1257 if (_focused) return true;
1258
1259 // do a check to see if the window has already been unmapped or destroyed
1260 // do this intelligently while watching out for unmaps we've generated
1261 // (ignore_unmaps > 0)
1262 XEvent ev;
1263 if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1264 XPutBackEvent(**otk::display, &ev);
1265 return false;
1266 }
1267 while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1268 if (ignore_unmaps) {
1269 unmapHandler(ev.xunmap);
1270 } else {
1271 XPutBackEvent(**otk::display, &ev);
1272 return false;
1273 }
1274 }
1275
1276 if (_can_focus)
1277 XSetInputFocus(**otk::display, _window,
1278 RevertToNone, CurrentTime);
1279
1280 if (_focus_notify) {
1281 XEvent ce;
1282 ce.xclient.type = ClientMessage;
1283 ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1284 ce.xclient.display = **otk::display;
1285 ce.xclient.window = _window;
1286 ce.xclient.format = 32;
1287 ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1288 ce.xclient.data.l[1] = openbox->lastTime();
1289 ce.xclient.data.l[2] = 0l;
1290 ce.xclient.data.l[3] = 0l;
1291 ce.xclient.data.l[4] = 0l;
1292 XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1293 }
1294
1295 return true;
1296 }
1297
1298
1299 void Client::unfocus() const
1300 {
1301 if (!_focused) return;
1302
1303 assert(openbox->focusedClient() == this);
1304 openbox->setFocusedClient(0);
1305 }
1306
1307
1308 void Client::focusHandler(const XFocusChangeEvent &e)
1309 {
1310 #ifdef DEBUG
1311 // printf("FocusIn for 0x%lx\n", e.window);
1312 #endif // DEBUG
1313
1314 otk::EventHandler::focusHandler(e);
1315
1316 frame->focus();
1317 _focused = true;
1318
1319 openbox->setFocusedClient(this);
1320 }
1321
1322
1323 void Client::unfocusHandler(const XFocusChangeEvent &e)
1324 {
1325 #ifdef DEBUG
1326 // printf("FocusOut for 0x%lx\n", e.window);
1327 #endif // DEBUG
1328
1329 otk::EventHandler::unfocusHandler(e);
1330
1331 frame->unfocus();
1332 _focused = false;
1333
1334 if (openbox->focusedClient() == this)
1335 openbox->setFocusedClient(0);
1336 }
1337
1338
1339 void Client::configureRequestHandler(const XConfigureRequestEvent &e)
1340 {
1341 #ifdef DEBUG
1342 printf("ConfigureRequest for 0x%lx\n", e.window);
1343 #endif // DEBUG
1344
1345 otk::EventHandler::configureRequestHandler(e);
1346
1347 // if we are iconic (or shaded (fvwm does this)) ignore the event
1348 if (_iconic || _shaded) return;
1349
1350 if (e.value_mask & CWBorderWidth)
1351 _border_width = e.border_width;
1352
1353 // resize, then move, as specified in the EWMH section 7.7
1354 if (e.value_mask & (CWWidth | CWHeight)) {
1355 int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1356 int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1357
1358 Corner corner;
1359 switch (_gravity) {
1360 case NorthEastGravity:
1361 case EastGravity:
1362 corner = TopRight;
1363 break;
1364 case SouthWestGravity:
1365 case SouthGravity:
1366 corner = BottomLeft;
1367 break;
1368 case SouthEastGravity:
1369 corner = BottomRight;
1370 break;
1371 default: // NorthWest, Static, etc
1372 corner = TopLeft;
1373 }
1374
1375 // if moving AND resizing ...
1376 if (e.value_mask & (CWX | CWY)) {
1377 int x = (e.value_mask & CWX) ? e.x : _area.x();
1378 int y = (e.value_mask & CWY) ? e.y : _area.y();
1379 internal_resize(corner, w, h, x, y);
1380 } else // if JUST resizing...
1381 internal_resize(corner, w, h);
1382 } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1383 int x = (e.value_mask & CWX) ? e.x : _area.x();
1384 int y = (e.value_mask & CWY) ? e.y : _area.y();
1385 internal_move(x, y);
1386 }
1387
1388 if (e.value_mask & CWStackMode) {
1389 switch (e.detail) {
1390 case Below:
1391 case BottomIf:
1392 openbox->screen(_screen)->lowerWindow(this);
1393 break;
1394
1395 case Above:
1396 case TopIf:
1397 default:
1398 openbox->screen(_screen)->raiseWindow(this);
1399 break;
1400 }
1401 }
1402 }
1403
1404
1405 void Client::unmapHandler(const XUnmapEvent &e)
1406 {
1407 if (ignore_unmaps) {
1408 #ifdef DEBUG
1409 printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1410 #endif // DEBUG
1411 ignore_unmaps--;
1412 return;
1413 }
1414
1415 #ifdef DEBUG
1416 printf("UnmapNotify for 0x%lx\n", e.window);
1417 #endif // DEBUG
1418
1419 otk::EventHandler::unmapHandler(e);
1420
1421 // this deletes us etc
1422 openbox->screen(_screen)->unmanageWindow(this);
1423 }
1424
1425
1426 void Client::destroyHandler(const XDestroyWindowEvent &e)
1427 {
1428 #ifdef DEBUG
1429 printf("DestroyNotify for 0x%lx\n", e.window);
1430 #endif // DEBUG
1431
1432 otk::EventHandler::destroyHandler(e);
1433
1434 // this deletes us etc
1435 openbox->screen(_screen)->unmanageWindow(this);
1436 }
1437
1438
1439 void Client::reparentHandler(const XReparentEvent &e)
1440 {
1441 // this is when the client is first taken captive in the frame
1442 if (e.parent == frame->plate()) return;
1443
1444 #ifdef DEBUG
1445 printf("ReparentNotify for 0x%lx\n", e.window);
1446 #endif // DEBUG
1447
1448 otk::EventHandler::reparentHandler(e);
1449
1450 /*
1451 This event is quite rare and is usually handled in unmapHandler.
1452 However, if the window is unmapped when the reparent event occurs,
1453 the window manager never sees it because an unmap event is not sent
1454 to an already unmapped window.
1455 */
1456
1457 // we don't want the reparent event, put it back on the stack for the X
1458 // server to deal with after we unmanage the window
1459 XEvent ev;
1460 ev.xreparent = e;
1461 XPutBackEvent(**otk::display, &ev);
1462
1463 // this deletes us etc
1464 openbox->screen(_screen)->unmanageWindow(this);
1465 }
1466
1467 void Client::mapRequestHandler(const XMapRequestEvent &e)
1468 {
1469 #ifdef DEBUG
1470 printf("MapRequest for already managed 0x%lx\n", e.window);
1471 #endif // DEBUG
1472
1473 assert(_iconic); // we shouldn't be able to get this unless we're iconic
1474
1475 // move to the current desktop (uniconify)
1476 setDesktop(openbox->screen(_screen)->desktop());
1477 // XXX: should we focus/raise the window? (basically a net_wm_active_window)
1478 }
1479
1480 }
This page took 0.095519 seconds and 5 git commands to generate.