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