]> Dogcows Code - chaz/openbox/blob - src/client.cc
make parentrelative decorations work
[chaz/openbox] / src / client.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #include "config.h"
4
5 #include "client.hh"
6 #include "frame.hh"
7 #include "screen.hh"
8 #include "openbox.hh"
9 #include "bindings.hh"
10 #include "otk/display.hh"
11 #include "otk/property.hh"
12
13 extern "C" {
14 #include <X11/Xlib.h>
15 #include <X11/Xutil.h>
16 #include <X11/Xatom.h>
17
18 #include "gettext.h"
19 #define _(str) gettext(str)
20 }
21
22 #include <cstring> // for memcpy
23 #include <climits>
24 #include <cassert>
25 #include <algorithm>
26
27 namespace ob {
28
29 Client::Client(int screen, Window window)
30 : otk::EventHandler(),
31 frame(0), _screen(screen), _window(window)
32 {
33 assert(screen >= 0);
34 assert(window);
35
36 ignore_unmaps = 0;
37
38 // update EVERYTHING the first time!!
39
40 // defaults
41 _wmstate = NormalState;
42 _focused = false;
43 _transient_for = 0;
44 _layer = Layer_Normal;
45 _urgent = false;
46 _positioned = false;
47 _disabled_decorations = 0;
48 _group = None;
49 _desktop = 0;
50 _nicons = 0;
51
52 getArea();
53 getDesktop();
54 getState(); // do this before updateTransientFor! (for _modal)
55 getShaped();
56
57 updateTransientFor();
58 getMwmHints();
59 getType(); // this can change the mwmhints for special cases
60
61 updateProtocols();
62
63 getGravity(); // get the attribute gravity
64 updateNormalHints(); // this may override the attribute gravity
65
66 // got the type, the mwmhints, the protocols, and the normal hints (min/max
67 // sizes), so we're ready to set up
68 // the decorations/functions
69 setupDecorAndFunctions();
70
71 // also get the initial_state and set _iconic if we aren't "starting"
72 // when we're "starting" that means we should use whatever state was already
73 // on the window over the initial map state, because it was already mapped
74 updateWMHints(openbox->state() != Openbox::State_Starting);
75 updateTitle();
76 updateIconTitle();
77 updateClass();
78 updateStrut();
79 updateIcons();
80 updateKwmIcon();
81
82 // this makes sure that these windows appear on all desktops
83 if (/*_type == Type_Dock ||*/ _type == Type_Desktop)
84 _desktop = 0xffffffff;
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 changeState();
91 }
92
93
94 Client::~Client()
95 {
96 assert(_nicons > 0); // there should always be a default..
97 for (int j = 0; j < _nicons; ++j)
98 delete [] _icons[j].data;
99 delete [] _icons;
100
101 // clean up childrens' references
102 while (!_transients.empty()) {
103 _transients.front()->_transient_for = 0;
104 _transients.pop_front();
105 }
106
107 // clean up parents reference to this
108 if (_transient_for)
109 _transient_for->_transients.remove(this); // remove from old parent
110
111 if (openbox->state() != Openbox::State_Exiting) {
112 // these values should not be persisted across a window unmapping/mapping
113 otk::Property::erase(_window, otk::Property::atoms.net_wm_desktop);
114 otk::Property::erase(_window, otk::Property::atoms.net_wm_state);
115 } else {
116 // if we're left in an iconic state, the client wont be mapped. this is
117 // bad, since we will no longer be managing the window on restart
118 if (_iconic)
119 XMapWindow(**otk::display, _window);
120 }
121 }
122
123
124 bool Client::validate() const
125 {
126 XSync(**otk::display, false); // get all events on the server
127
128 XEvent e;
129 if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &e) ||
130 XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &e)) {
131 XPutBackEvent(**otk::display, &e);
132 return false;
133 }
134
135 return true;
136 }
137
138
139 void Client::getGravity()
140 {
141 XWindowAttributes wattrib;
142 Status ret;
143
144 ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
145 assert(ret != BadWindow);
146 _gravity = wattrib.win_gravity;
147 }
148
149
150 void Client::getDesktop()
151 {
152 // defaults to the current desktop
153 _desktop = openbox->screen(_screen)->desktop();
154
155 if (otk::Property::get(_window, otk::Property::atoms.net_wm_desktop,
156 otk::Property::atoms.cardinal,
157 (long unsigned*)&_desktop)) {
158 #ifdef DEBUG
159 // printf("Window requested desktop: %ld\n", _desktop);
160 #endif
161 }
162 }
163
164
165 void Client::getType()
166 {
167 _type = (WindowType) -1;
168
169 unsigned long *val;
170 unsigned long num;
171 if (otk::Property::get(_window, otk::Property::atoms.net_wm_window_type,
172 otk::Property::atoms.atom, &num, &val)) {
173 // use the first value that we know about in the array
174 for (unsigned long i = 0; i < num; ++i) {
175 if (val[i] == otk::Property::atoms.net_wm_window_type_desktop)
176 _type = Type_Desktop;
177 else if (val[i] == otk::Property::atoms.net_wm_window_type_dock)
178 _type = Type_Dock;
179 else if (val[i] == otk::Property::atoms.net_wm_window_type_toolbar)
180 _type = Type_Toolbar;
181 else if (val[i] == otk::Property::atoms.net_wm_window_type_menu)
182 _type = Type_Menu;
183 else if (val[i] == otk::Property::atoms.net_wm_window_type_utility)
184 _type = Type_Utility;
185 else if (val[i] == otk::Property::atoms.net_wm_window_type_splash)
186 _type = Type_Splash;
187 else if (val[i] == otk::Property::atoms.net_wm_window_type_dialog)
188 _type = Type_Dialog;
189 else if (val[i] == otk::Property::atoms.net_wm_window_type_normal)
190 _type = Type_Normal;
191 else if (val[i] == otk::Property::atoms.kde_net_wm_window_type_override){
192 // prevent this window from getting any decor or functionality
193 _mwmhints.flags &= MwmFlag_Functions | MwmFlag_Decorations;
194 _mwmhints.decorations = 0;
195 _mwmhints.functions = 0;
196 }
197 if (_type != (WindowType) -1)
198 break; // grab the first known type
199 }
200 delete val;
201 }
202
203 if (_type == (WindowType) -1) {
204 /*
205 * the window type hint was not set, which means we either classify ourself
206 * as a normal window or a dialog, depending on if we are a transient.
207 */
208 if (_transient_for)
209 _type = Type_Dialog;
210 else
211 _type = Type_Normal;
212 }
213 }
214
215
216 void Client::setupDecorAndFunctions()
217 {
218 // start with everything (cept fullscreen)
219 _decorations = Decor_Titlebar | Decor_Handle | Decor_Border | Decor_Icon |
220 Decor_AllDesktops | Decor_Iconify | Decor_Maximize;
221 _functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize |
222 Func_Shade;
223 if (_delete_window) {
224 _decorations |= Decor_Close;
225 _functions |= Func_Close;
226 }
227
228 if (!(_min_size.width() < _max_size.width() ||
229 _min_size.height() < _max_size.height())) {
230 _decorations &= ~(Decor_Maximize | Decor_Handle);
231 _functions &= ~(Func_Resize | Func_Maximize);
232 }
233
234 switch (_type) {
235 case Type_Normal:
236 // normal windows retain all of the possible decorations and
237 // functionality, and are the only windows that you can fullscreen
238 _functions |= Func_Fullscreen;
239 break;
240
241 case Type_Dialog:
242 // dialogs cannot be maximized
243 _decorations &= ~Decor_Maximize;
244 _functions &= ~Func_Maximize;
245 break;
246
247 case Type_Menu:
248 case Type_Toolbar:
249 case Type_Utility:
250 // these windows get less functionality
251 _decorations &= ~(Decor_Iconify | Decor_Handle);
252 _functions &= ~(Func_Iconify | Func_Resize);
253 break;
254
255 case Type_Desktop:
256 case Type_Dock:
257 case Type_Splash:
258 // none of these windows are manipulated by the window manager
259 _decorations = 0;
260 _functions = 0;
261 break;
262 }
263
264 // Mwm Hints are applied subtractively to what has already been chosen for
265 // decor and functionality
266 if (_mwmhints.flags & MwmFlag_Decorations) {
267 if (! (_mwmhints.decorations & MwmDecor_All)) {
268 if (! (_mwmhints.decorations & MwmDecor_Border))
269 _decorations &= ~Decor_Border;
270 if (! (_mwmhints.decorations & MwmDecor_Handle))
271 _decorations &= ~Decor_Handle;
272 if (! (_mwmhints.decorations & MwmDecor_Title))
273 _decorations &= ~Decor_Titlebar;
274 if (! (_mwmhints.decorations & MwmDecor_Iconify))
275 _decorations &= ~Decor_Iconify;
276 if (! (_mwmhints.decorations & MwmDecor_Maximize))
277 _decorations &= ~Decor_Maximize;
278 }
279 }
280
281 if (_mwmhints.flags & MwmFlag_Functions) {
282 if (! (_mwmhints.functions & MwmFunc_All)) {
283 if (! (_mwmhints.functions & MwmFunc_Resize))
284 _functions &= ~Func_Resize;
285 if (! (_mwmhints.functions & MwmFunc_Move))
286 _functions &= ~Func_Move;
287 if (! (_mwmhints.functions & MwmFunc_Iconify))
288 _functions &= ~Func_Iconify;
289 if (! (_mwmhints.functions & MwmFunc_Maximize))
290 _functions &= ~Func_Maximize;
291 // dont let mwm hints kill the close button
292 //if (! (_mwmhints.functions & MwmFunc_Close))
293 // _functions &= ~Func_Close;
294 }
295 }
296
297 // can't maximize without moving/resizing
298 if (!((_functions & Func_Move) && (_functions & Func_Resize)))
299 _functions &= ~Func_Maximize;
300
301 // finally, user specified disabled decorations are applied to subtract
302 // decorations
303 if (_disabled_decorations & Decor_Titlebar)
304 _decorations &= ~Decor_Titlebar;
305 if (_disabled_decorations & Decor_Handle)
306 _decorations &= ~Decor_Handle;
307 if (_disabled_decorations & Decor_Border)
308 _decorations &= ~Decor_Border;
309 if (_disabled_decorations & Decor_Iconify)
310 _decorations &= ~Decor_Iconify;
311 if (_disabled_decorations & Decor_Maximize)
312 _decorations &= ~Decor_Maximize;
313 if (_disabled_decorations & Decor_AllDesktops)
314 _decorations &= ~Decor_AllDesktops;
315 if (_disabled_decorations & Decor_Close)
316 _decorations &= ~Decor_Close;
317
318 // if we don't have a titlebar, then we cannot shade!
319 if (!(_decorations & Decor_Titlebar))
320 _functions &= ~Func_Shade;
321
322 changeAllowedActions();
323
324 if (frame) {
325 frame->adjustSize(); // change the decors on the frame
326 frame->adjustPosition(); // with more/less decorations, we may need to be
327 // moved
328 remaximize(); // with new decor, the window's maximized size may change
329 }
330 }
331
332
333 void Client::getMwmHints()
334 {
335 unsigned long num = MwmHints::elements;
336 unsigned long *hints;
337
338 _mwmhints.flags = 0; // default to none
339
340 if (!otk::Property::get(_window, otk::Property::atoms.motif_wm_hints,
341 otk::Property::atoms.motif_wm_hints, &num,
342 (unsigned long **)&hints))
343 return;
344
345 if (num >= MwmHints::elements) {
346 // retrieved the hints
347 _mwmhints.flags = hints[0];
348 _mwmhints.functions = hints[1];
349 _mwmhints.decorations = hints[2];
350 }
351
352 delete [] hints;
353 }
354
355
356 void Client::getArea()
357 {
358 XWindowAttributes wattrib;
359 Status ret;
360
361 ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
362 assert(ret != BadWindow);
363
364 _area = otk::Rect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
365 _border_width = wattrib.border_width;
366 }
367
368
369 void Client::getState()
370 {
371 _modal = _shaded = _max_horz = _max_vert = _fullscreen = _above = _below =
372 _iconic = _skip_taskbar = _skip_pager = false;
373
374 unsigned long *state;
375 unsigned long num;
376
377 if (otk::Property::get(_window, otk::Property::atoms.net_wm_state,
378 otk::Property::atoms.atom, &num, &state)) {
379 for (unsigned long i = 0; i < num; ++i) {
380 if (state[i] == otk::Property::atoms.net_wm_state_modal)
381 _modal = true;
382 else if (state[i] == otk::Property::atoms.net_wm_state_shaded)
383 _shaded = true;
384 else if (state[i] == otk::Property::atoms.net_wm_state_hidden)
385 _iconic = true;
386 else if (state[i] == otk::Property::atoms.net_wm_state_skip_taskbar)
387 _skip_taskbar = true;
388 else if (state[i] == otk::Property::atoms.net_wm_state_skip_pager)
389 _skip_pager = true;
390 else if (state[i] == otk::Property::atoms.net_wm_state_fullscreen)
391 _fullscreen = true;
392 else if (state[i] == otk::Property::atoms.net_wm_state_maximized_vert)
393 _max_vert = true;
394 else if (state[i] == otk::Property::atoms.net_wm_state_maximized_horz)
395 _max_horz = true;
396 else if (state[i] == otk::Property::atoms.net_wm_state_above)
397 _above = true;
398 else if (state[i] == otk::Property::atoms.net_wm_state_below)
399 _below = true;
400 }
401
402 delete [] state;
403 }
404 }
405
406 void Client::getShaped()
407 {
408 _shaped = false;
409 #ifdef SHAPE
410 if (otk::display->shape()) {
411 int foo;
412 unsigned int ufoo;
413 int s;
414
415 XShapeSelectInput(**otk::display, _window, ShapeNotifyMask);
416
417 XShapeQueryExtents(**otk::display, _window, &s, &foo,
418 &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo);
419 _shaped = (s != 0);
420 }
421 #endif // SHAPE
422 }
423
424 Client *Client::searchFocusTree(Client *node, Client *skip)
425 {
426 List::const_iterator it, end = node->_transients.end();
427 Client *ret;
428
429 for (it = node->_transients.begin(); it != end; ++it) {
430 if (*it == skip) continue; // circular?
431 if ((ret = searchModalTree(*it, skip))) return ret; // got one
432 if ((*it)->_focused) return *it; // got one
433 }
434 return 0;
435 }
436
437 void Client::calcLayer() {
438 StackLayer l;
439 bool fs = false;
440
441 // are we fullscreen, or do we have a fullscreen transient parent?
442 Client *c = this;
443 while (c) {
444 if (c->_fullscreen) {
445 fs =true;
446 break;
447 }
448 c = c->_transient_for;
449 }
450 if (!fs && _fullscreen) {
451 // is one of our transients focused?
452 c = searchFocusTree(this, this);
453 if (c) fs = true;
454 }
455
456 if (_iconic) l = Layer_Icon;
457 else if (fs) l = Layer_Fullscreen;
458 else if (_type == Type_Desktop) l = Layer_Desktop;
459 else if (_type == Type_Dock) {
460 if (!_below) l = Layer_Top;
461 else l = Layer_Normal;
462 }
463 else if (_above) l = Layer_Above;
464 else if (_below) l = Layer_Below;
465 else l = Layer_Normal;
466
467 if (l != _layer) {
468 _layer = l;
469 if (frame) {
470 /*
471 if we don't have a frame, then we aren't mapped yet (and this would
472 SIGSEGV :)
473 */
474 openbox->screen(_screen)->raiseWindow(this);
475 }
476 }
477 }
478
479 void Client::updateProtocols()
480 {
481 Atom *proto;
482 int num_return = 0;
483
484 _focus_notify = false;
485 _delete_window = false;
486
487 if (XGetWMProtocols(**otk::display, _window, &proto, &num_return)) {
488 for (int i = 0; i < num_return; ++i) {
489 if (proto[i] == otk::Property::atoms.wm_delete_window) {
490 // this means we can request the window to close
491 _delete_window = true;
492 } else if (proto[i] == otk::Property::atoms.wm_take_focus)
493 // if this protocol is requested, then the window will be notified
494 // by the window manager whenever it receives focus
495 _focus_notify = true;
496 }
497 XFree(proto);
498 }
499 }
500
501 void Client::updateNormalHints()
502 {
503 XSizeHints size;
504 long ret;
505 int oldgravity = _gravity;
506
507 // defaults
508 _min_ratio = 0.0;
509 _max_ratio = 0.0;
510 _size_inc = otk::Size(1, 1);
511 _base_size = otk::Size(0, 0);
512 _min_size = otk::Size(0, 0);
513 _max_size = otk::Size(INT_MAX, INT_MAX);
514
515 // get the hints from the window
516 if (XGetWMNormalHints(**otk::display, _window, &size, &ret)) {
517 _positioned = (size.flags & (PPosition|USPosition));
518
519 if (size.flags & PWinGravity) {
520 _gravity = size.win_gravity;
521
522 // if the client has a frame, i.e. has already been mapped and is
523 // changing its gravity
524 if (frame && _gravity != oldgravity) {
525 // move our idea of the client's position based on its new gravity
526 int x = frame->area().x(), y = frame->area().y();
527 frame->frameGravity(x, y);
528 _area = otk::Rect(otk::Point(x, y), _area.size());
529 }
530 }
531
532 if (size.flags & PAspect) {
533 if (size.min_aspect.y) _min_ratio = size.min_aspect.x/size.min_aspect.y;
534 if (size.max_aspect.y) _max_ratio = size.max_aspect.x/size.max_aspect.y;
535 }
536
537 if (size.flags & PMinSize)
538 _min_size = otk::Size(size.min_width, size.min_height);
539
540 if (size.flags & PMaxSize)
541 _max_size = otk::Size(size.max_width, size.max_height);
542
543 if (size.flags & PBaseSize)
544 _base_size = otk::Size(size.base_width, size.base_height);
545
546 if (size.flags & PResizeInc)
547 _size_inc = otk::Size(size.width_inc, size.height_inc);
548 }
549 }
550
551 void Client::updateWMHints(bool initstate)
552 {
553 XWMHints *hints;
554
555 // assume a window takes input if it doesnt specify
556 _can_focus = true;
557 bool ur = false;
558
559 if ((hints = XGetWMHints(**otk::display, _window)) != NULL) {
560 if (hints->flags & InputHint)
561 _can_focus = hints->input;
562
563 // only do this when initstate is true!
564 if (initstate && (hints->flags & StateHint))
565 _iconic = hints->initial_state == IconicState;
566
567 if (hints->flags & XUrgencyHint)
568 ur = true;
569
570 if (hints->flags & WindowGroupHint) {
571 if (hints->window_group != _group) {
572 // XXX: remove from the old group if there was one
573 _group = hints->window_group;
574 // XXX: do stuff with the group
575 }
576 } else // no group!
577 _group = None;
578
579 if (hints->flags & IconPixmapHint) {
580 updateKwmIcon(); // try get the kwm icon first, this is a fallback only
581 if (_pixmap_icon == None) {
582 _pixmap_icon = hints->icon_pixmap;
583 if (hints->flags & IconMaskHint)
584 _pixmap_icon_mask = hints->icon_mask;
585 else
586 _pixmap_icon_mask = None;
587 }
588 }
589
590 XFree(hints);
591 }
592
593 if (ur != _urgent) {
594 _urgent = ur;
595 #ifdef DEBUG
596 printf("Urgent Hint for 0x%lx: %s\n",
597 (long)_window, _urgent ? "ON" : "OFF");
598 #endif
599 // fire the urgent callback if we're mapped, otherwise, wait until after
600 // we're mapped
601 if (frame)
602 fireUrgent();
603 }
604 }
605
606 void Client::updateTitle()
607 {
608 _title = "";
609
610 // try netwm
611 if (!otk::Property::get(_window, otk::Property::atoms.net_wm_name,
612 otk::Property::utf8, &_title)) {
613 // try old x stuff
614 otk::Property::get(_window, otk::Property::atoms.wm_name,
615 otk::Property::ascii, &_title);
616 }
617
618 if (_title.empty())
619 _title = _("Unnamed Window");
620
621 if (frame)
622 frame->adjustTitle();
623 }
624
625 void Client::updateIconTitle()
626 {
627 _icon_title = "";
628
629 // try netwm
630 if (!otk::Property::get(_window, otk::Property::atoms.net_wm_icon_name,
631 otk::Property::utf8, &_icon_title)) {
632 // try old x stuff
633 otk::Property::get(_window, otk::Property::atoms.wm_icon_name,
634 otk::Property::ascii, &_icon_title);
635 }
636
637 if (_title.empty())
638 _icon_title = _("Unnamed Window");
639 }
640
641 void Client::updateClass()
642 {
643 // set the defaults
644 _app_name = _app_class = _role = "";
645
646 otk::Property::StringVect v;
647 unsigned long num = 2;
648
649 if (otk::Property::get(_window, otk::Property::atoms.wm_class,
650 otk::Property::ascii, &num, &v)) {
651 if (num > 0) _app_name = v[0].c_str();
652 if (num > 1) _app_class = v[1].c_str();
653 }
654
655 v.clear();
656 num = 1;
657 if (otk::Property::get(_window, otk::Property::atoms.wm_window_role,
658 otk::Property::ascii, &num, &v)) {
659 if (num > 0) _role = v[0].c_str();
660 }
661 }
662
663 void Client::updateStrut()
664 {
665 unsigned long num = 4;
666 unsigned long *data;
667 if (!otk::Property::get(_window, otk::Property::atoms.net_wm_strut,
668 otk::Property::atoms.cardinal, &num, &data))
669 return;
670
671 if (num == 4) {
672 _strut.left = data[0];
673 _strut.right = data[1];
674 _strut.top = data[2];
675 _strut.bottom = data[3];
676
677 // updating here is pointless while we're being mapped cuz we're not in
678 // the screen's client list yet
679 if (frame)
680 openbox->screen(_screen)->updateStruts();
681 }
682
683 delete [] data;
684 }
685
686 void Client::updateTransientFor()
687 {
688 Window t = 0;
689 Client *c = 0;
690
691 if (XGetTransientForHint(**otk::display, _window, &t) &&
692 t != _window) { // cant be transient to itself!
693 c = openbox->findClient(t);
694 assert(c != this); // if this happens then we need to check for it
695
696 if (!c /*XXX: && _group*/) {
697 // not transient to a client, see if it is transient for a group
698 if (//t == _group->leader() ||
699 t == None ||
700 t == otk::display->screenInfo(_screen)->rootWindow()) {
701 // window is a transient for its group!
702 // XXX: for now this is treated as non-transient.
703 // this needs to be fixed!
704 }
705 }
706 }
707
708 // if anything has changed...
709 if (c != _transient_for) {
710 if (_transient_for)
711 _transient_for->_transients.remove(this); // remove from old parent
712 _transient_for = c;
713 if (_transient_for)
714 _transient_for->_transients.push_back(this); // add to new parent
715 }
716 }
717
718 void Client::updateIcons()
719 {
720 unsigned long num;
721 unsigned long *data;
722 unsigned long w, h, i = 0;
723
724 for (int j = 0; j < _nicons; ++j)
725 delete [] _icons[j].data;
726 if (_nicons > 0)
727 delete [] _icons;
728 _nicons = 0;
729
730 if (otk::Property::get(_window, otk::Property::atoms.net_wm_icon,
731 otk::Property::atoms.cardinal, &num, &data)) {
732 // figure out how man valid icons are in here
733 while (num - i > 2) {
734 w = data[i++];
735 h = data[i++];
736 i += w * h;
737 if (i > num) break;
738 ++_nicons;
739 }
740
741 _icons = new Icon[_nicons];
742
743 // store the icons
744 i = 0;
745 for (int j = 0; j < _nicons; ++j) {
746 w = _icons[j].w = data[i++];
747 h = _icons[j].h = data[i++];
748 _icons[j].data = new unsigned long[w * h];
749 ::memcpy(_icons[j].data, &data[i], w * h * sizeof(unsigned long));
750 i += w * h;
751 assert(i <= num);
752 }
753
754 delete [] data;
755 }
756
757 if (_nicons <= 0) {
758 _nicons = 1;
759 _icons = new Icon[1];
760 _icons[i].w = 0;
761 _icons[i].h = 0;
762 _icons[i].data = 0;
763 }
764
765 assert(_nicons > 0); // there should always be a default..
766
767 if (frame) frame->adjustIcon();
768 }
769
770 void Client::updateKwmIcon()
771 {
772 _pixmap_icon = _pixmap_icon_mask = None;
773
774 unsigned long num = 2;
775 Pixmap *data;
776 if (otk::Property::get(_window, otk::Property::atoms.kwm_win_icon,
777 otk::Property::atoms.kwm_win_icon, &num, &data)) {
778 if (num == 2) {
779 _pixmap_icon = data[0];
780 _pixmap_icon_mask = data[1];
781 }
782 delete [] data;
783 }
784 }
785
786 void Client::propertyHandler(const XPropertyEvent &e)
787 {
788 otk::EventHandler::propertyHandler(e);
789
790 // validate cuz we query stuff off the client here
791 if (!validate()) return;
792
793 // compress changes to a single property into a single change
794 XEvent ce;
795 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
796 // XXX: it would be nice to compress ALL changes to a property, not just
797 // changes in a row without other props between.
798 if (ce.xproperty.atom != e.atom) {
799 XPutBackEvent(**otk::display, &ce);
800 break;
801 }
802 }
803
804 if (e.atom == XA_WM_NORMAL_HINTS) {
805 updateNormalHints();
806 setupDecorAndFunctions(); // normal hints can make a window non-resizable
807 } else if (e.atom == XA_WM_HINTS)
808 updateWMHints();
809 else if (e.atom == XA_WM_TRANSIENT_FOR) {
810 updateTransientFor();
811 getType();
812 calcLayer(); // type may have changed, so update the layer
813 setupDecorAndFunctions();
814 }
815 else if (e.atom == otk::Property::atoms.net_wm_name ||
816 e.atom == otk::Property::atoms.wm_name)
817 updateTitle();
818 else if (e.atom == otk::Property::atoms.net_wm_icon_name ||
819 e.atom == otk::Property::atoms.wm_icon_name)
820 updateIconTitle();
821 else if (e.atom == otk::Property::atoms.wm_class)
822 updateClass();
823 else if (e.atom == otk::Property::atoms.wm_protocols) {
824 updateProtocols();
825 setupDecorAndFunctions();
826 }
827 else if (e.atom == otk::Property::atoms.net_wm_strut)
828 updateStrut();
829 else if (e.atom == otk::Property::atoms.net_wm_icon)
830 updateIcons();
831 else if (e.atom == otk::Property::atoms.kwm_win_icon)
832 updateKwmIcon();
833 }
834
835 void Client::setWMState(long state)
836 {
837 if (state == _wmstate) return; // no change
838
839 switch (state) {
840 case IconicState:
841 iconify(true);
842 break;
843 case NormalState:
844 iconify(false);
845 break;
846 }
847 }
848
849 void Client::setDesktop(unsigned int target)
850 {
851 if (target == _desktop) return;
852
853 printf("Setting desktop %u\n", target);
854
855 if (!(target < openbox->screen(_screen)->numDesktops() ||
856 target == 0xffffffff))
857 return;
858
859 _desktop = target;
860 // set the desktop hint
861 otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
862 otk::Property::atoms.cardinal, _desktop);
863 frame->adjustState(); // the frame can display the current desktop state
864 // 'move' the window to the new desktop
865 showhide();
866 openbox->screen(_screen)->updateStruts();
867 }
868
869 void Client::showhide()
870 {
871 bool show;
872 Screen *s = openbox->screen(_screen);
873
874 if (_iconic) show = false;
875 else if (!(_desktop == s->desktop() ||
876 _desktop == 0xffffffff)) show = false;
877 else if (normal() && s->showingDesktop()) show = false;
878 else show = true;
879
880 if (show) frame->show();
881 else frame->hide();
882 }
883
884 void Client::setState(Atom action, long data1, long data2)
885 {
886 bool shadestate = _shaded;
887 bool fsstate = _fullscreen;
888 bool maxh = _max_horz;
889 bool maxv = _max_vert;
890
891 if (!(action == otk::Property::atoms.net_wm_state_add ||
892 action == otk::Property::atoms.net_wm_state_remove ||
893 action == otk::Property::atoms.net_wm_state_toggle))
894 return; // an invalid action was passed to the client message, ignore it
895
896 for (int i = 0; i < 2; ++i) {
897 Atom state = i == 0 ? data1 : data2;
898
899 if (! state) continue;
900
901 // if toggling, then pick whether we're adding or removing
902 if (action == otk::Property::atoms.net_wm_state_toggle) {
903 if (state == otk::Property::atoms.net_wm_state_modal)
904 action = _modal ? otk::Property::atoms.net_wm_state_remove :
905 otk::Property::atoms.net_wm_state_add;
906 else if (state == otk::Property::atoms.net_wm_state_maximized_vert)
907 action = _max_vert ? otk::Property::atoms.net_wm_state_remove :
908 otk::Property::atoms.net_wm_state_add;
909 else if (state == otk::Property::atoms.net_wm_state_maximized_horz)
910 action = _max_horz ? otk::Property::atoms.net_wm_state_remove :
911 otk::Property::atoms.net_wm_state_add;
912 else if (state == otk::Property::atoms.net_wm_state_shaded)
913 action = _shaded ? otk::Property::atoms.net_wm_state_remove :
914 otk::Property::atoms.net_wm_state_add;
915 else if (state == otk::Property::atoms.net_wm_state_skip_taskbar)
916 action = _skip_taskbar ? otk::Property::atoms.net_wm_state_remove :
917 otk::Property::atoms.net_wm_state_add;
918 else if (state == otk::Property::atoms.net_wm_state_skip_pager)
919 action = _skip_pager ? otk::Property::atoms.net_wm_state_remove :
920 otk::Property::atoms.net_wm_state_add;
921 else if (state == otk::Property::atoms.net_wm_state_fullscreen)
922 action = _fullscreen ? otk::Property::atoms.net_wm_state_remove :
923 otk::Property::atoms.net_wm_state_add;
924 else if (state == otk::Property::atoms.net_wm_state_above)
925 action = _above ? otk::Property::atoms.net_wm_state_remove :
926 otk::Property::atoms.net_wm_state_add;
927 else if (state == otk::Property::atoms.net_wm_state_below)
928 action = _below ? otk::Property::atoms.net_wm_state_remove :
929 otk::Property::atoms.net_wm_state_add;
930 }
931
932 if (action == otk::Property::atoms.net_wm_state_add) {
933 if (state == otk::Property::atoms.net_wm_state_modal) {
934 if (_modal) continue;
935 _modal = true;
936 } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
937 maxv = true;
938 } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
939 if (_max_horz) continue;
940 maxh = true;
941 } else if (state == otk::Property::atoms.net_wm_state_shaded) {
942 shadestate = true;
943 } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
944 _skip_taskbar = true;
945 } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
946 _skip_pager = true;
947 } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
948 fsstate = true;
949 } else if (state == otk::Property::atoms.net_wm_state_above) {
950 if (_above) continue;
951 _above = true;
952 } else if (state == otk::Property::atoms.net_wm_state_below) {
953 if (_below) continue;
954 _below = true;
955 }
956
957 } else { // action == otk::Property::atoms.net_wm_state_remove
958 if (state == otk::Property::atoms.net_wm_state_modal) {
959 if (!_modal) continue;
960 _modal = false;
961 } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
962 maxv = false;
963 } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
964 maxh = false;
965 } else if (state == otk::Property::atoms.net_wm_state_shaded) {
966 shadestate = false;
967 } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
968 _skip_taskbar = false;
969 } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
970 _skip_pager = false;
971 } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
972 fsstate = false;
973 } else if (state == otk::Property::atoms.net_wm_state_above) {
974 if (!_above) continue;
975 _above = false;
976 } else if (state == otk::Property::atoms.net_wm_state_below) {
977 if (!_below) continue;
978 _below = false;
979 }
980 }
981 }
982 if (maxh != _max_horz || maxv != _max_vert) {
983 if (maxh != _max_horz && maxv != _max_vert) { // toggling both
984 if (maxh == maxv) { // both going the same way
985 maximize(maxh, 0, true);
986 } else {
987 maximize(maxh, 1, true);
988 maximize(maxv, 2, true);
989 }
990 } else { // toggling one
991 if (maxh != _max_horz)
992 maximize(maxh, 1, true);
993 else
994 maximize(maxv, 2, true);
995 }
996 }
997 // change fullscreen state before shading, as it will affect if the window
998 // can shade or not
999 if (fsstate != _fullscreen)
1000 fullscreen(fsstate, true);
1001 if (shadestate != _shaded)
1002 shade(shadestate);
1003 calcLayer();
1004 changeState(); // change the hint to relect these changes
1005 }
1006
1007 void Client::toggleClientBorder(bool addborder)
1008 {
1009 // adjust our idea of where the client is, based on its border. When the
1010 // border is removed, the client should now be considered to be in a
1011 // different position.
1012 // when re-adding the border to the client, the same operation needs to be
1013 // reversed.
1014 int oldx = _area.x(), oldy = _area.y();
1015 int x = oldx, y = oldy;
1016 switch(_gravity) {
1017 default:
1018 case NorthWestGravity:
1019 case WestGravity:
1020 case SouthWestGravity:
1021 break;
1022 case NorthEastGravity:
1023 case EastGravity:
1024 case SouthEastGravity:
1025 if (addborder) x -= _border_width * 2;
1026 else x += _border_width * 2;
1027 break;
1028 case NorthGravity:
1029 case SouthGravity:
1030 case CenterGravity:
1031 case ForgetGravity:
1032 case StaticGravity:
1033 if (addborder) x -= _border_width;
1034 else x += _border_width;
1035 break;
1036 }
1037 switch(_gravity) {
1038 default:
1039 case NorthWestGravity:
1040 case NorthGravity:
1041 case NorthEastGravity:
1042 break;
1043 case SouthWestGravity:
1044 case SouthGravity:
1045 case SouthEastGravity:
1046 if (addborder) y -= _border_width * 2;
1047 else y += _border_width * 2;
1048 break;
1049 case WestGravity:
1050 case EastGravity:
1051 case CenterGravity:
1052 case ForgetGravity:
1053 case StaticGravity:
1054 if (addborder) y -= _border_width;
1055 else y += _border_width;
1056 break;
1057 }
1058 _area = otk::Rect(otk::Point(x, y), _area.size());
1059
1060 if (addborder) {
1061 XSetWindowBorderWidth(**otk::display, _window, _border_width);
1062
1063 // move the client so it is back it the right spot _with_ its border!
1064 if (x != oldx || y != oldy)
1065 XMoveWindow(**otk::display, _window, x, y);
1066 } else
1067 XSetWindowBorderWidth(**otk::display, _window, 0);
1068 }
1069
1070 void Client::clientMessageHandler(const XClientMessageEvent &e)
1071 {
1072 otk::EventHandler::clientMessageHandler(e);
1073
1074 // validate cuz we query stuff off the client here
1075 if (!validate()) return;
1076
1077 if (e.format != 32) return;
1078
1079 if (e.message_type == otk::Property::atoms.wm_change_state) {
1080 // compress changes into a single change
1081 bool compress = false;
1082 XEvent ce;
1083 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
1084 // XXX: it would be nice to compress ALL messages of a type, not just
1085 // messages in a row without other message types between.
1086 if (ce.xclient.message_type != e.message_type) {
1087 XPutBackEvent(**otk::display, &ce);
1088 break;
1089 }
1090 compress = true;
1091 }
1092 if (compress)
1093 setWMState(ce.xclient.data.l[0]); // use the found event
1094 else
1095 setWMState(e.data.l[0]); // use the original event
1096 } else if (e.message_type == otk::Property::atoms.net_wm_desktop) {
1097 // compress changes into a single change
1098 bool compress = false;
1099 XEvent ce;
1100 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
1101 // XXX: it would be nice to compress ALL messages of a type, not just
1102 // messages in a row without other message types between.
1103 if (ce.xclient.message_type != e.message_type) {
1104 XPutBackEvent(**otk::display, &ce);
1105 break;
1106 }
1107 compress = true;
1108 }
1109 if (compress)
1110 setDesktop(e.data.l[0]); // use the found event
1111 else
1112 setDesktop(e.data.l[0]); // use the original event
1113 } else if (e.message_type == otk::Property::atoms.net_wm_state) {
1114 // can't compress these
1115 #ifdef DEBUG
1116 printf("net_wm_state %s %ld %ld for 0x%lx\n",
1117 (e.data.l[0] == 0 ? "Remove" : e.data.l[0] == 1 ? "Add" :
1118 e.data.l[0] == 2 ? "Toggle" : "INVALID"),
1119 e.data.l[1], e.data.l[2], _window);
1120 #endif
1121 setState(e.data.l[0], e.data.l[1], e.data.l[2]);
1122 } else if (e.message_type == otk::Property::atoms.net_close_window) {
1123 #ifdef DEBUG
1124 printf("net_close_window for 0x%lx\n", _window);
1125 #endif
1126 close();
1127 } else if (e.message_type == otk::Property::atoms.net_active_window) {
1128 #ifdef DEBUG
1129 printf("net_active_window for 0x%lx\n", _window);
1130 #endif
1131 if (openbox->screen(_screen)->showingDesktop())
1132 openbox->screen(_screen)->showDesktop(false);
1133 if (_iconic)
1134 iconify(false);
1135 else if (!frame->visible()) // if its not visible for other reasons, then
1136 return; // don't mess with it
1137 if (_shaded)
1138 shade(false);
1139 focus();
1140 openbox->screen(_screen)->raiseWindow(this);
1141 } else if (e.message_type == otk::Property::atoms.openbox_active_window) {
1142 if (openbox->screen(_screen)->showingDesktop())
1143 openbox->screen(_screen)->showDesktop(false);
1144 if (_iconic)
1145 iconify(false);
1146 else if (!frame->visible()) // if its not visible for other reasons, then
1147 return; // don't mess with it
1148 if (e.data.l[0] && _shaded)
1149 shade(false);
1150 focus();
1151 if (e.data.l[1])
1152 openbox->screen(_screen)->raiseWindow(this);
1153 } else if (e.message_type == otk::Property::atoms.openbox_restack_window) {
1154 #ifdef DEBUG
1155 printf("openbox_restack_window for 0x%lx\n", _window);
1156 #endif
1157 if (e.data.l[0] == 0)
1158 openbox->screen(_screen)->raiseWindow(this);
1159 else if (e.data.l[0] == 1)
1160 openbox->screen(_screen)->lowerWindow(this);
1161 }
1162 }
1163
1164 #if defined(SHAPE)
1165 void Client::shapeHandler(const XShapeEvent &e)
1166 {
1167 otk::EventHandler::shapeHandler(e);
1168
1169 if (e.kind == ShapeBounding) {
1170 _shaped = e.shaped;
1171 frame->adjustShape();
1172 }
1173 }
1174 #endif
1175
1176 void Client::resize(Corner anchor, int w, int h)
1177 {
1178 if (!(_functions & Func_Resize)) return;
1179 internal_resize(anchor, w, h);
1180 }
1181
1182 void Client::internal_resize(Corner anchor, int w, int h,
1183 bool user, int x, int y)
1184 {
1185 w -= _base_size.width();
1186 h -= _base_size.height();
1187
1188 if (user) {
1189 // for interactive resizing. have to move half an increment in each
1190 // direction.
1191 int mw = w % _size_inc.width(); // how far we are towards the next size inc
1192 int mh = h % _size_inc.height();
1193 int aw = _size_inc.width() / 2; // amount to add
1194 int ah = _size_inc.height() / 2;
1195 // don't let us move into a new size increment
1196 if (mw + aw >= _size_inc.width()) aw = _size_inc.width() - mw - 1;
1197 if (mh + ah >= _size_inc.height()) ah = _size_inc.height() - mh - 1;
1198 w += aw;
1199 h += ah;
1200
1201 // if this is a user-requested resize, then check against min/max sizes
1202 // and aspect ratios
1203
1204 // smaller than min size or bigger than max size?
1205 if (w > _max_size.width()) w = _max_size.width();
1206 if (w < _min_size.width()) w = _min_size.width();
1207 if (h > _max_size.height()) h = _max_size.height();
1208 if (h < _min_size.height()) h = _min_size.height();
1209
1210 // adjust the height ot match the width for the aspect ratios
1211 if (_min_ratio)
1212 if (h * _min_ratio > w) h = static_cast<int>(w / _min_ratio);
1213 if (_max_ratio)
1214 if (h * _max_ratio < w) h = static_cast<int>(w / _max_ratio);
1215 }
1216
1217 // keep to the increments
1218 w /= _size_inc.width();
1219 h /= _size_inc.height();
1220
1221 // you cannot resize to nothing
1222 if (w < 1) w = 1;
1223 if (h < 1) h = 1;
1224
1225 // store the logical size
1226 _logical_size = otk::Size(w, h);
1227
1228 w *= _size_inc.width();
1229 h *= _size_inc.height();
1230
1231 w += _base_size.width();
1232 h += _base_size.height();
1233
1234 if (x == INT_MIN || y == INT_MIN) {
1235 x = _area.x();
1236 y = _area.y();
1237 switch (anchor) {
1238 case TopLeft:
1239 break;
1240 case TopRight:
1241 x -= w - _area.width();
1242 break;
1243 case BottomLeft:
1244 y -= h - _area.height();
1245 break;
1246 case BottomRight:
1247 x -= w - _area.width();
1248 y -= h - _area.height();
1249 break;
1250 }
1251 }
1252
1253 _area = otk::Rect(_area.position(), otk::Size(w, h));
1254
1255 XResizeWindow(**otk::display, _window, w, h);
1256
1257 // resize the frame to match the request
1258 frame->adjustSize();
1259 internal_move(x, y);
1260 }
1261
1262 const Icon *Client::icon(const otk::Size &s) const
1263 {
1264 unsigned long req = s.width() * s.height();
1265 // si is the smallest image >= req
1266 // li is the largest image < req
1267 unsigned long smallest = 0xffffffff, largest = 0, si = 0, li = 0;
1268
1269 assert(_nicons > 0); // there should always be a default..
1270 for (int i = 0; i < _nicons; ++i) {
1271 unsigned long size = _icons[i].w * _icons[i].h;
1272 if (size < smallest && size >= req) {
1273 smallest = size;
1274 si = i;
1275 }
1276 if (size > largest && size <= req) {
1277 largest = size;
1278 li = i;
1279 }
1280 }
1281 if (largest == 0) // didnt find one smaller than the requested size
1282 return &_icons[si];
1283 return &_icons[li];
1284 }
1285
1286 void Client::move(int x, int y, bool final)
1287 {
1288 if (!(_functions & Func_Move)) return;
1289 frame->frameGravity(x, y); // get the client's position based on x,y for the
1290 // frame
1291 internal_move(x, y, final);
1292 }
1293
1294 void Client::internal_move(int x, int y, bool final)
1295 {
1296 _area = otk::Rect(otk::Point(x, y), _area.size());
1297
1298 // move the frame to be in the requested position
1299 if (frame) { // this can be called while mapping, before frame exists
1300 frame->adjustPosition();
1301
1302 // send synthetic configure notify (we don't need to if we aren't mapped
1303 // yet)
1304 if (final) {
1305 XEvent event;
1306 event.type = ConfigureNotify;
1307 event.xconfigure.display = **otk::display;
1308 event.xconfigure.event = _window;
1309 event.xconfigure.window = _window;
1310
1311 // root window coords with border in mind
1312 event.xconfigure.x = x - _border_width + frame->size().left;
1313 event.xconfigure.y = y - _border_width + frame->size().top;
1314
1315 event.xconfigure.width = _area.width();
1316 event.xconfigure.height = _area.height();
1317 event.xconfigure.border_width = _border_width;
1318 event.xconfigure.above = frame->plate();
1319 event.xconfigure.override_redirect = False;
1320 XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1321 StructureNotifyMask, &event);
1322 #if 0//def DEBUG
1323 printf("Sent synthetic ConfigureNotify %d,%d %d,%d to 0x%lx\n",
1324 event.xconfigure.x, event.xconfigure.y, event.xconfigure.width,
1325 event.xconfigure.height, event.xconfigure.window);
1326 #endif
1327 }
1328 }
1329 }
1330
1331 void Client::close()
1332 {
1333 XEvent ce;
1334
1335 if (!(_functions & Func_Close)) return;
1336
1337 // XXX: itd be cool to do timeouts and shit here for killing the client's
1338 // process off
1339 // like... if the window is around after 5 seconds, then the close button
1340 // turns a nice red, and if this function is called again, the client is
1341 // explicitly killed.
1342
1343 ce.xclient.type = ClientMessage;
1344 ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1345 ce.xclient.display = **otk::display;
1346 ce.xclient.window = _window;
1347 ce.xclient.format = 32;
1348 ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1349 ce.xclient.data.l[1] = CurrentTime;
1350 ce.xclient.data.l[2] = 0l;
1351 ce.xclient.data.l[3] = 0l;
1352 ce.xclient.data.l[4] = 0l;
1353 XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1354 }
1355
1356 void Client::changeState()
1357 {
1358 unsigned long state[2];
1359 state[0] = _wmstate;
1360 state[1] = None;
1361 otk::Property::set(_window, otk::Property::atoms.wm_state,
1362 otk::Property::atoms.wm_state, state, 2);
1363
1364 Atom netstate[10];
1365 int num = 0;
1366 if (_modal)
1367 netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1368 if (_shaded)
1369 netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1370 if (_iconic)
1371 netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1372 if (_skip_taskbar)
1373 netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1374 if (_skip_pager)
1375 netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1376 if (_fullscreen)
1377 netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1378 if (_max_vert)
1379 netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1380 if (_max_horz)
1381 netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1382 if (_above)
1383 netstate[num++] = otk::Property::atoms.net_wm_state_above;
1384 if (_below)
1385 netstate[num++] = otk::Property::atoms.net_wm_state_below;
1386 otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1387 otk::Property::atoms.atom, netstate, num);
1388
1389 calcLayer();
1390
1391 if (frame)
1392 frame->adjustState();
1393 }
1394
1395 void Client::changeAllowedActions(void)
1396 {
1397 Atom actions[9];
1398 int num = 0;
1399
1400 actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1401
1402 if (_functions & Func_Shade)
1403 actions[num++] = otk::Property::atoms.net_wm_action_shade;
1404 if (_functions & Func_Close)
1405 actions[num++] = otk::Property::atoms.net_wm_action_close;
1406 if (_functions & Func_Move)
1407 actions[num++] = otk::Property::atoms.net_wm_action_move;
1408 if (_functions & Func_Iconify)
1409 actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1410 if (_functions & Func_Resize)
1411 actions[num++] = otk::Property::atoms.net_wm_action_resize;
1412 if (_functions & Func_Fullscreen)
1413 actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1414 if (_functions & Func_Maximize) {
1415 actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1416 actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1417 }
1418
1419 otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1420 otk::Property::atoms.atom, actions, num);
1421
1422 // make sure the window isn't breaking any rules now
1423
1424 if (!(_functions & Func_Shade) && _shaded)
1425 if (frame) shade(false);
1426 else _shaded = false;
1427 if (!(_functions & Func_Iconify) && _iconic)
1428 if (frame) setDesktop(openbox->screen(_screen)->desktop());
1429 else _iconic = false;
1430 if (!(_functions & Func_Fullscreen) && _fullscreen)
1431 if (frame) fullscreen(false);
1432 else _fullscreen = false;
1433 if (!(_functions & Func_Maximize) && (_max_horz || _max_vert))
1434 if (frame) maximize(false, 0);
1435 else _max_vert = _max_horz = false;
1436 }
1437
1438 void Client::remaximize()
1439 {
1440 int dir;
1441 if (_max_horz && _max_vert)
1442 dir = 0;
1443 else if (_max_horz)
1444 dir = 1;
1445 else if (_max_vert)
1446 dir = 2;
1447 else
1448 return; // not maximized
1449 _max_horz = _max_vert = false;
1450 maximize(true, dir, false);
1451 }
1452
1453 void Client::applyStartupState()
1454 {
1455 // these are in a carefully crafted order..
1456
1457 if (_iconic) {
1458 _iconic = false;
1459 iconify(true);
1460 }
1461 if (_fullscreen) {
1462 _fullscreen = false;
1463 fullscreen(true, false);
1464 }
1465 if (_shaded) {
1466 _shaded = false;
1467 shade(true);
1468 }
1469 if (_urgent)
1470 fireUrgent();
1471
1472 if (_max_vert && _max_horz) {
1473 _max_vert = _max_horz = false;
1474 maximize(true, 0, false);
1475 } else if (_max_vert) {
1476 _max_vert = false;
1477 maximize(true, 2, false);
1478 } else if (_max_horz) {
1479 _max_horz = false;
1480 maximize(true, 1, false);
1481 }
1482
1483 if (_skip_taskbar); // nothing to do for this
1484 if (_skip_pager); // nothing to do for this
1485 if (_modal); // nothing to do for this
1486 if (_above); // nothing to do for this
1487 if (_below); // nothing to do for this
1488 }
1489
1490 void Client::fireUrgent()
1491 {
1492 // call the python UrgentWindow callbacks
1493 EventData data(_screen, this, EventAction::UrgentWindow, 0);
1494 openbox->bindings()->fireEvent(&data);
1495 }
1496
1497 void Client::shade(bool shade)
1498 {
1499 if (!(_functions & Func_Shade) || // can't
1500 _shaded == shade) return; // already done
1501
1502 // when we're iconic, don't change the wmstate
1503 if (!_iconic)
1504 _wmstate = shade ? IconicState : NormalState;
1505 _shaded = shade;
1506 changeState();
1507 frame->adjustSize();
1508 }
1509
1510 void Client::maximize(bool max, int dir, bool savearea)
1511 {
1512 assert(dir == 0 || dir == 1 || dir == 2);
1513 if (!(_functions & Func_Maximize)) return; // can't
1514
1515 // check if already done
1516 if (max) {
1517 if (dir == 0 && _max_horz && _max_vert) return;
1518 if (dir == 1 && _max_horz) return;
1519 if (dir == 2 && _max_vert) return;
1520 } else {
1521 if (dir == 0 && !_max_horz && !_max_vert) return;
1522 if (dir == 1 && !_max_horz) return;
1523 if (dir == 2 && !_max_vert) return;
1524 }
1525
1526 const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1527 int x = frame->area().x(), y = frame->area().y(),
1528 w = _area.width(), h = _area.height();
1529
1530 if (max) {
1531 if (savearea) {
1532 long dimensions[4];
1533 long *readdim;
1534 unsigned long n = 4;
1535
1536 dimensions[0] = x;
1537 dimensions[1] = y;
1538 dimensions[2] = w;
1539 dimensions[3] = h;
1540
1541 // get the property off the window and use it for the dimentions we are
1542 // already maxed on
1543 if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1544 otk::Property::atoms.cardinal, &n,
1545 (long unsigned**) &readdim)) {
1546 if (n == 4) {
1547 if (_max_horz) {
1548 dimensions[0] = readdim[0];
1549 dimensions[2] = readdim[2];
1550 }
1551 if (_max_vert) {
1552 dimensions[1] = readdim[1];
1553 dimensions[3] = readdim[3];
1554 }
1555 }
1556 delete readdim;
1557 }
1558
1559 otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1560 otk::Property::atoms.cardinal,
1561 (long unsigned*)dimensions, 4);
1562 }
1563 if (dir == 0 || dir == 1) { // horz
1564 x = a.x();
1565 w = a.width();
1566 }
1567 if (dir == 0 || dir == 2) { // vert
1568 y = a.y();
1569 h = a.height() - frame->size().top - frame->size().bottom;
1570 }
1571 } else {
1572 long *dimensions;
1573 long unsigned n = 4;
1574
1575 if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1576 otk::Property::atoms.cardinal, &n,
1577 (long unsigned**) &dimensions)) {
1578 if (n == 4) {
1579 if (dir == 0 || dir == 1) { // horz
1580 x = (signed int)dimensions[0];
1581 w = (signed int)dimensions[2];
1582 }
1583 if (dir == 0 || dir == 2) { // vert
1584 y = (signed int)dimensions[1];
1585 h = (signed int)dimensions[3];
1586 }
1587 }
1588 delete dimensions;
1589 } else {
1590 // pick some fallbacks...
1591 if (dir == 0 || dir == 1) { // horz
1592 x = a.x() + a.width() / 4;
1593 w = a.width() / 2;
1594 }
1595 if (dir == 0 || dir == 2) { // vert
1596 y = a.y() + a.height() / 4;
1597 h = a.height() / 2;
1598 }
1599 }
1600 }
1601
1602 if (dir == 0 || dir == 1) // horz
1603 _max_horz = max;
1604 if (dir == 0 || dir == 2) // vert
1605 _max_vert = max;
1606
1607 if (!_max_horz && !_max_vert)
1608 otk::Property::erase(_window, otk::Property::atoms.openbox_premax);
1609
1610 changeState(); // change the state hints on the client
1611
1612 frame->frameGravity(x, y); // figure out where the client should be going
1613 internal_resize(TopLeft, w, h, true, x, y);
1614 }
1615
1616 void Client::fullscreen(bool fs, bool savearea)
1617 {
1618 static FunctionFlags saved_func;
1619 static DecorationFlags saved_decor;
1620
1621 if (!(_functions & Func_Fullscreen) || // can't
1622 _fullscreen == fs) return; // already done
1623
1624 _fullscreen = fs;
1625 changeState(); // change the state hints on the client
1626
1627 int x = _area.x(), y = _area.y(), w = _area.width(), h = _area.height();
1628
1629 if (fs) {
1630 // save the functions and remove them
1631 saved_func = _functions;
1632 _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1633 // save the decorations and remove them
1634 saved_decor = _decorations;
1635 _decorations = 0;
1636 if (savearea) {
1637 long dimensions[4];
1638 dimensions[0] = _area.x();
1639 dimensions[1] = _area.y();
1640 dimensions[2] = _area.width();
1641 dimensions[3] = _area.height();
1642 otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1643 otk::Property::atoms.cardinal,
1644 (long unsigned*)dimensions, 4);
1645 }
1646 const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1647 x = 0;
1648 y = 0;
1649 w = info->size().width();
1650 h = info->size().height();
1651 } else {
1652 _functions = saved_func;
1653 _decorations = saved_decor;
1654
1655 long *dimensions;
1656 long unsigned n = 4;
1657
1658 if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1659 otk::Property::atoms.cardinal, &n,
1660 (long unsigned**) &dimensions)) {
1661 if (n == 4) {
1662 x = dimensions[0];
1663 y = dimensions[1];
1664 w = dimensions[2];
1665 h = dimensions[3];
1666 }
1667 delete dimensions;
1668 } else {
1669 // pick some fallbacks...
1670 const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1671 x = a.x() + a.width() / 4;
1672 y = a.y() + a.height() / 4;
1673 w = a.width() / 2;
1674 h = a.height() / 2;
1675 }
1676 }
1677
1678 changeAllowedActions(); // based on the new _functions
1679
1680 // when fullscreening, don't obey things like increments, fill the screen
1681 internal_resize(TopLeft, w, h, !fs, x, y);
1682
1683 // raise (back) into our stacking layer
1684 openbox->screen(_screen)->raiseWindow(this);
1685
1686 // try focus us when we go into fullscreen mode
1687 if (fs) focus();
1688 }
1689
1690 void Client::iconify(bool iconic, bool curdesk)
1691 {
1692 if (_iconic == iconic) return; // nothing to do
1693
1694 #ifdef DEBUG
1695 printf("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"), _window);
1696 #endif
1697
1698 _iconic = iconic;
1699
1700 if (_iconic) {
1701 _wmstate = IconicState;
1702 ignore_unmaps++;
1703 // we unmap the client itself so that we can get MapRequest events, and
1704 // because the ICCCM tells us to!
1705 XUnmapWindow(**otk::display, _window);
1706 } else {
1707 if (curdesk)
1708 setDesktop(openbox->screen(_screen)->desktop());
1709 _wmstate = NormalState;
1710 XMapWindow(**otk::display, _window);
1711 }
1712 changeState();
1713 showhide();
1714 openbox->screen(_screen)->updateStruts();
1715 }
1716
1717 void Client::disableDecorations(DecorationFlags flags)
1718 {
1719 _disabled_decorations = flags;
1720 setupDecorAndFunctions();
1721 }
1722
1723 void Client::installColormap(bool install) const
1724 {
1725 XWindowAttributes wa;
1726 if (XGetWindowAttributes(**otk::display, _window, &wa)) {
1727 if (install)
1728 XInstallColormap(**otk::display, wa.colormap);
1729 else
1730 XUninstallColormap(**otk::display, wa.colormap);
1731 }
1732 }
1733
1734 Client *Client::searchModalTree(Client *node, Client *skip)
1735 {
1736 List::const_iterator it, end = node->_transients.end();
1737 Client *ret;
1738
1739 for (it = node->_transients.begin(); it != end; ++it) {
1740 if (*it == skip) continue; // circular?
1741 if ((ret = searchModalTree(*it, skip))) return ret; // got one
1742 if ((*it)->_modal) return *it; // got one
1743 }
1744 return 0;
1745 }
1746
1747 Client *Client::findModalChild()
1748 {
1749 return searchModalTree(this, this);
1750 }
1751
1752
1753 bool Client::focus()
1754 {
1755 // if we have a modal child, then focus it, not us
1756 Client *c = findModalChild();
1757 if (c) return c->focus();
1758
1759 // won't try focus if the client doesn't want it, or if the window isn't
1760 // visible on the screen
1761 if (!(frame->visible() && (_can_focus || _focus_notify))) return false;
1762
1763 // do a check to see if the window has already been unmapped or destroyed
1764 // do this intelligently while watching out for unmaps we've generated
1765 // (ignore_unmaps > 0)
1766 XEvent ev;
1767 if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1768 XPutBackEvent(**otk::display, &ev);
1769 return false;
1770 }
1771 while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1772 if (ignore_unmaps) {
1773 unmapHandler(ev.xunmap);
1774 } else {
1775 XPutBackEvent(**otk::display, &ev);
1776 return false;
1777 }
1778 }
1779
1780 if (_can_focus)
1781 XSetInputFocus(**otk::display, _window,
1782 RevertToNone, CurrentTime);
1783
1784 if (_focus_notify) {
1785 XEvent ce;
1786 ce.xclient.type = ClientMessage;
1787 ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1788 ce.xclient.display = **otk::display;
1789 ce.xclient.window = _window;
1790 ce.xclient.format = 32;
1791 ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1792 ce.xclient.data.l[1] = openbox->lastTime();
1793 ce.xclient.data.l[2] = 0l;
1794 ce.xclient.data.l[3] = 0l;
1795 ce.xclient.data.l[4] = 0l;
1796 XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1797 }
1798
1799 XSync(**otk::display, False);
1800 return true;
1801 }
1802
1803
1804 void Client::unfocus() const
1805 {
1806 assert(openbox->focusedClient() == this);
1807 openbox->setFocusedClient(0);
1808 }
1809
1810
1811 void Client::focusHandler(const XFocusChangeEvent &e)
1812 {
1813 #ifdef DEBUG
1814 // printf("FocusIn for 0x%lx\n", e.window);
1815 #endif // DEBUG
1816
1817 otk::EventHandler::focusHandler(e);
1818
1819 _focused = true;
1820 frame->adjustFocus();
1821
1822 calcLayer(); // focus state can affect the stacking layer
1823
1824 openbox->setFocusedClient(this);
1825 }
1826
1827
1828 void Client::unfocusHandler(const XFocusChangeEvent &e)
1829 {
1830 #ifdef DEBUG
1831 // printf("FocusOut for 0x%lx\n", e.window);
1832 #endif // DEBUG
1833
1834 otk::EventHandler::unfocusHandler(e);
1835
1836 _focused = false;
1837 frame->adjustFocus();
1838
1839 calcLayer(); // focus state can affect the stacking layer
1840
1841 if (openbox->focusedClient() == this)
1842 openbox->setFocusedClient(0);
1843 }
1844
1845
1846 void Client::configureRequestHandler(const XConfigureRequestEvent &ec)
1847 {
1848 #ifdef DEBUG
1849 printf("ConfigureRequest for 0x%lx\n", ec.window);
1850 #endif // DEBUG
1851
1852 otk::EventHandler::configureRequestHandler(ec);
1853
1854 // compress these
1855 XConfigureRequestEvent e = ec;
1856 XEvent ev;
1857 while (XCheckTypedWindowEvent(**otk::display, window(), ConfigureRequest,
1858 &ev)) {
1859 // XXX if this causes bad things.. we can compress config req's with the
1860 // same mask.
1861 e.value_mask |= ev.xconfigurerequest.value_mask;
1862 if (ev.xconfigurerequest.value_mask & CWX)
1863 e.x = ev.xconfigurerequest.x;
1864 if (ev.xconfigurerequest.value_mask & CWY)
1865 e.y = ev.xconfigurerequest.y;
1866 if (ev.xconfigurerequest.value_mask & CWWidth)
1867 e.width = ev.xconfigurerequest.width;
1868 if (ev.xconfigurerequest.value_mask & CWHeight)
1869 e.height = ev.xconfigurerequest.height;
1870 if (ev.xconfigurerequest.value_mask & CWBorderWidth)
1871 e.border_width = ev.xconfigurerequest.border_width;
1872 if (ev.xconfigurerequest.value_mask & CWStackMode)
1873 e.detail = ev.xconfigurerequest.detail;
1874 }
1875
1876 // if we are iconic (or shaded (fvwm does this)) ignore the event
1877 if (_iconic || _shaded) return;
1878
1879 if (e.value_mask & CWBorderWidth)
1880 _border_width = e.border_width;
1881
1882 // resize, then move, as specified in the EWMH section 7.7
1883 if (e.value_mask & (CWWidth | CWHeight)) {
1884 int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1885 int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1886
1887 Corner corner;
1888 switch (_gravity) {
1889 case NorthEastGravity:
1890 case EastGravity:
1891 corner = TopRight;
1892 break;
1893 case SouthWestGravity:
1894 case SouthGravity:
1895 corner = BottomLeft;
1896 break;
1897 case SouthEastGravity:
1898 corner = BottomRight;
1899 break;
1900 default: // NorthWest, Static, etc
1901 corner = TopLeft;
1902 }
1903
1904 // if moving AND resizing ...
1905 if (e.value_mask & (CWX | CWY)) {
1906 int x = (e.value_mask & CWX) ? e.x : _area.x();
1907 int y = (e.value_mask & CWY) ? e.y : _area.y();
1908 internal_resize(corner, w, h, false, x, y);
1909 } else // if JUST resizing...
1910 internal_resize(corner, w, h, false);
1911 } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1912 int x = (e.value_mask & CWX) ? e.x : _area.x();
1913 int y = (e.value_mask & CWY) ? e.y : _area.y();
1914 internal_move(x, y);
1915 }
1916
1917 if (e.value_mask & CWStackMode) {
1918 switch (e.detail) {
1919 case Below:
1920 case BottomIf:
1921 openbox->screen(_screen)->lowerWindow(this);
1922 break;
1923
1924 case Above:
1925 case TopIf:
1926 default:
1927 openbox->screen(_screen)->raiseWindow(this);
1928 break;
1929 }
1930 }
1931 }
1932
1933
1934 void Client::unmapHandler(const XUnmapEvent &e)
1935 {
1936 if (ignore_unmaps) {
1937 #ifdef DEBUG
1938 // printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1939 #endif // DEBUG
1940 ignore_unmaps--;
1941 return;
1942 }
1943
1944 #ifdef DEBUG
1945 printf("UnmapNotify for 0x%lx\n", e.window);
1946 #endif // DEBUG
1947
1948 otk::EventHandler::unmapHandler(e);
1949
1950 // this deletes us etc
1951 openbox->screen(_screen)->unmanageWindow(this);
1952 }
1953
1954
1955 void Client::destroyHandler(const XDestroyWindowEvent &e)
1956 {
1957 #ifdef DEBUG
1958 printf("DestroyNotify for 0x%lx\n", e.window);
1959 #endif // DEBUG
1960
1961 otk::EventHandler::destroyHandler(e);
1962
1963 // this deletes us etc
1964 openbox->screen(_screen)->unmanageWindow(this);
1965 }
1966
1967
1968 void Client::reparentHandler(const XReparentEvent &e)
1969 {
1970 // this is when the client is first taken captive in the frame
1971 if (e.parent == frame->plate()) return;
1972
1973 #ifdef DEBUG
1974 printf("ReparentNotify for 0x%lx\n", e.window);
1975 #endif // DEBUG
1976
1977 otk::EventHandler::reparentHandler(e);
1978
1979 /*
1980 This event is quite rare and is usually handled in unmapHandler.
1981 However, if the window is unmapped when the reparent event occurs,
1982 the window manager never sees it because an unmap event is not sent
1983 to an already unmapped window.
1984 */
1985
1986 // we don't want the reparent event, put it back on the stack for the X
1987 // server to deal with after we unmanage the window
1988 XEvent ev;
1989 ev.xreparent = e;
1990 XPutBackEvent(**otk::display, &ev);
1991
1992 // this deletes us etc
1993 openbox->screen(_screen)->unmanageWindow(this);
1994 }
1995
1996 void Client::mapRequestHandler(const XMapRequestEvent &e)
1997 {
1998 #ifdef DEBUG
1999 printf("MapRequest for already managed 0x%lx\n", e.window);
2000 #endif // DEBUG
2001
2002 assert(_iconic); // we shouldn't be able to get this unless we're iconic
2003
2004 // move to the current desktop (uniconify)
2005 iconify(false);
2006 // XXX: should we focus/raise the window? (basically a net_wm_active_window)
2007 }
2008
2009 }
This page took 0.124893 seconds and 4 git commands to generate.