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