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