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