]> Dogcows Code - chaz/openbox/blob - src/client.cc
add support for the openbox_restack_window message.
[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 = (unsigned) -1;
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 = (unsigned) -1;
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 = (unsigned) -1;
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)
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);
1292 }
1293
1294 void Client::internal_move(int x, int y)
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 XEvent event;
1305 event.type = ConfigureNotify;
1306 event.xconfigure.display = **otk::display;
1307 event.xconfigure.event = _window;
1308 event.xconfigure.window = _window;
1309
1310 // root window coords with border in mind
1311 event.xconfigure.x = x - _border_width + frame->size().left;
1312 event.xconfigure.y = y - _border_width + frame->size().top;
1313
1314 event.xconfigure.width = _area.width();
1315 event.xconfigure.height = _area.height();
1316 event.xconfigure.border_width = _border_width;
1317 event.xconfigure.above = frame->plate();
1318 event.xconfigure.override_redirect = False;
1319 XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1320 StructureNotifyMask, &event);
1321 #if 0//def DEBUG
1322 printf("Sent synthetic ConfigureNotify %d,%d %d,%d to 0x%lx\n",
1323 event.xconfigure.x, event.xconfigure.y, event.xconfigure.width,
1324 event.xconfigure.height, event.xconfigure.window);
1325 #endif
1326 }
1327 }
1328
1329 void Client::close()
1330 {
1331 XEvent ce;
1332
1333 if (!(_functions & Func_Close)) return;
1334
1335 // XXX: itd be cool to do timeouts and shit here for killing the client's
1336 // process off
1337 // like... if the window is around after 5 seconds, then the close button
1338 // turns a nice red, and if this function is called again, the client is
1339 // explicitly killed.
1340
1341 ce.xclient.type = ClientMessage;
1342 ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1343 ce.xclient.display = **otk::display;
1344 ce.xclient.window = _window;
1345 ce.xclient.format = 32;
1346 ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1347 ce.xclient.data.l[1] = CurrentTime;
1348 ce.xclient.data.l[2] = 0l;
1349 ce.xclient.data.l[3] = 0l;
1350 ce.xclient.data.l[4] = 0l;
1351 XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1352 }
1353
1354 void Client::changeState()
1355 {
1356 unsigned long state[2];
1357 state[0] = _wmstate;
1358 state[1] = None;
1359 otk::Property::set(_window, otk::Property::atoms.wm_state,
1360 otk::Property::atoms.wm_state, state, 2);
1361
1362 Atom netstate[10];
1363 int num = 0;
1364 if (_modal)
1365 netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1366 if (_shaded)
1367 netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1368 if (_iconic)
1369 netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1370 if (_skip_taskbar)
1371 netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1372 if (_skip_pager)
1373 netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1374 if (_fullscreen)
1375 netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1376 if (_max_vert)
1377 netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1378 if (_max_horz)
1379 netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1380 if (_above)
1381 netstate[num++] = otk::Property::atoms.net_wm_state_above;
1382 if (_below)
1383 netstate[num++] = otk::Property::atoms.net_wm_state_below;
1384 otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1385 otk::Property::atoms.atom, netstate, num);
1386
1387 calcLayer();
1388
1389 if (frame)
1390 frame->adjustState();
1391 }
1392
1393 void Client::changeAllowedActions(void)
1394 {
1395 Atom actions[9];
1396 int num = 0;
1397
1398 actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1399
1400 if (_functions & Func_Shade)
1401 actions[num++] = otk::Property::atoms.net_wm_action_shade;
1402 if (_functions & Func_Close)
1403 actions[num++] = otk::Property::atoms.net_wm_action_close;
1404 if (_functions & Func_Move)
1405 actions[num++] = otk::Property::atoms.net_wm_action_move;
1406 if (_functions & Func_Iconify)
1407 actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1408 if (_functions & Func_Resize)
1409 actions[num++] = otk::Property::atoms.net_wm_action_resize;
1410 if (_functions & Func_Fullscreen)
1411 actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1412 if (_functions & Func_Maximize) {
1413 actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1414 actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1415 }
1416
1417 otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1418 otk::Property::atoms.atom, actions, num);
1419
1420 // make sure the window isn't breaking any rules now
1421
1422 if (!(_functions & Func_Shade) && _shaded)
1423 if (frame) shade(false);
1424 else _shaded = false;
1425 if (!(_functions & Func_Iconify) && _iconic)
1426 if (frame) setDesktop(openbox->screen(_screen)->desktop());
1427 else _iconic = false;
1428 if (!(_functions & Func_Fullscreen) && _fullscreen)
1429 if (frame) fullscreen(false);
1430 else _fullscreen = false;
1431 if (!(_functions & Func_Maximize) && (_max_horz || _max_vert))
1432 if (frame) maximize(false, 0);
1433 else _max_vert = _max_horz = false;
1434 }
1435
1436 void Client::remaximize()
1437 {
1438 int dir;
1439 if (_max_horz && _max_vert)
1440 dir = 0;
1441 else if (_max_horz)
1442 dir = 1;
1443 else if (_max_vert)
1444 dir = 2;
1445 else
1446 return; // not maximized
1447 _max_horz = _max_vert = false;
1448 maximize(true, dir, false);
1449 }
1450
1451 void Client::applyStartupState()
1452 {
1453 // these are in a carefully crafted order..
1454
1455 if (_iconic) {
1456 _iconic = false;
1457 iconify(true);
1458 }
1459 if (_fullscreen) {
1460 _fullscreen = false;
1461 fullscreen(true, false);
1462 }
1463 if (_shaded) {
1464 _shaded = false;
1465 shade(true);
1466 }
1467 if (_urgent)
1468 fireUrgent();
1469
1470 if (_max_vert && _max_horz) {
1471 _max_vert = _max_horz = false;
1472 maximize(true, 0, false);
1473 } else if (_max_vert) {
1474 _max_vert = false;
1475 maximize(true, 2, false);
1476 } else if (_max_horz) {
1477 _max_horz = false;
1478 maximize(true, 1, false);
1479 }
1480
1481 if (_skip_taskbar); // nothing to do for this
1482 if (_skip_pager); // nothing to do for this
1483 if (_modal); // nothing to do for this
1484 if (_above); // nothing to do for this
1485 if (_below); // nothing to do for this
1486 }
1487
1488 void Client::fireUrgent()
1489 {
1490 // call the python UrgentWindow callbacks
1491 EventData data(_screen, this, EventAction::UrgentWindow, 0);
1492 openbox->bindings()->fireEvent(&data);
1493 }
1494
1495 void Client::shade(bool shade)
1496 {
1497 if (!(_functions & Func_Shade) || // can't
1498 _shaded == shade) return; // already done
1499
1500 // when we're iconic, don't change the wmstate
1501 if (!_iconic)
1502 _wmstate = shade ? IconicState : NormalState;
1503 _shaded = shade;
1504 changeState();
1505 frame->adjustSize();
1506 }
1507
1508 void Client::maximize(bool max, int dir, bool savearea)
1509 {
1510 assert(dir == 0 || dir == 1 || dir == 2);
1511 if (!(_functions & Func_Maximize)) return; // can't
1512
1513 // check if already done
1514 if (max) {
1515 if (dir == 0 && _max_horz && _max_vert) return;
1516 if (dir == 1 && _max_horz) return;
1517 if (dir == 2 && _max_vert) return;
1518 } else {
1519 if (dir == 0 && !_max_horz && !_max_vert) return;
1520 if (dir == 1 && !_max_horz) return;
1521 if (dir == 2 && !_max_vert) return;
1522 }
1523
1524 const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1525 int x = frame->area().x(), y = frame->area().y(),
1526 w = _area.width(), h = _area.height();
1527
1528 if (max) {
1529 if (savearea) {
1530 long dimensions[4];
1531 long *readdim;
1532 unsigned long n = 4;
1533
1534 dimensions[0] = x;
1535 dimensions[1] = y;
1536 dimensions[2] = w;
1537 dimensions[3] = h;
1538
1539 // get the property off the window and use it for the dimentions we are
1540 // already maxed on
1541 if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1542 otk::Property::atoms.cardinal, &n,
1543 (long unsigned**) &readdim)) {
1544 if (n >= 4) {
1545 if (_max_horz) {
1546 dimensions[0] = readdim[0];
1547 dimensions[2] = readdim[2];
1548 }
1549 if (_max_vert) {
1550 dimensions[1] = readdim[1];
1551 dimensions[3] = readdim[3];
1552 }
1553 }
1554 delete readdim;
1555 }
1556
1557 otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1558 otk::Property::atoms.cardinal,
1559 (long unsigned*)dimensions, 4);
1560 }
1561 if (dir == 0 || dir == 1) { // horz
1562 x = a.x();
1563 w = a.width();
1564 }
1565 if (dir == 0 || dir == 2) { // vert
1566 y = a.y();
1567 h = a.height() - frame->size().top - frame->size().bottom;
1568 }
1569 } else {
1570 long *dimensions;
1571 long unsigned n = 4;
1572
1573 if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1574 otk::Property::atoms.cardinal, &n,
1575 (long unsigned**) &dimensions)) {
1576 if (n >= 4) {
1577 if (dir == 0 || dir == 1) { // horz
1578 x = (signed int)dimensions[0];
1579 w = (signed int)dimensions[2];
1580 }
1581 if (dir == 0 || dir == 2) { // vert
1582 y = (signed int)dimensions[1];
1583 h = (signed int)dimensions[3];
1584 }
1585 }
1586 delete dimensions;
1587 } else {
1588 // pick some fallbacks...
1589 if (dir == 0 || dir == 1) { // horz
1590 x = a.x() + a.width() / 4;
1591 w = a.width() / 2;
1592 }
1593 if (dir == 0 || dir == 2) { // vert
1594 y = a.y() + a.height() / 4;
1595 h = a.height() / 2;
1596 }
1597 }
1598 }
1599
1600 if (dir == 0 || dir == 1) // horz
1601 _max_horz = max;
1602 if (dir == 0 || dir == 2) // vert
1603 _max_vert = max;
1604
1605 if (!_max_horz && !_max_vert)
1606 otk::Property::erase(_window, otk::Property::atoms.openbox_premax);
1607
1608 changeState(); // change the state hints on the client
1609
1610 frame->frameGravity(x, y); // figure out where the client should be going
1611 internal_resize(TopLeft, w, h, true, x, y);
1612 }
1613
1614 void Client::fullscreen(bool fs, bool savearea)
1615 {
1616 static FunctionFlags saved_func;
1617 static DecorationFlags saved_decor;
1618
1619 if (!(_functions & Func_Fullscreen) || // can't
1620 _fullscreen == fs) return; // already done
1621
1622 _fullscreen = fs;
1623 changeState(); // change the state hints on the client
1624
1625 int x = _area.x(), y = _area.y(), w = _area.width(), h = _area.height();
1626
1627 if (fs) {
1628 // save the functions and remove them
1629 saved_func = _functions;
1630 _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1631 // save the decorations and remove them
1632 saved_decor = _decorations;
1633 _decorations = 0;
1634 if (savearea) {
1635 long dimensions[4];
1636 dimensions[0] = _area.x();
1637 dimensions[1] = _area.y();
1638 dimensions[2] = _area.width();
1639 dimensions[3] = _area.height();
1640 otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1641 otk::Property::atoms.cardinal,
1642 (long unsigned*)dimensions, 4);
1643 }
1644 const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1645 x = 0;
1646 y = 0;
1647 w = info->size().width();
1648 h = info->size().height();
1649 } else {
1650 _functions = saved_func;
1651 _decorations = saved_decor;
1652
1653 long *dimensions;
1654 long unsigned n = 4;
1655
1656 if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1657 otk::Property::atoms.cardinal, &n,
1658 (long unsigned**) &dimensions)) {
1659 if (n >= 4) {
1660 x = dimensions[0];
1661 y = dimensions[1];
1662 w = dimensions[2];
1663 h = dimensions[3];
1664 }
1665 delete dimensions;
1666 } else {
1667 // pick some fallbacks...
1668 const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1669 x = a.x() + a.width() / 4;
1670 y = a.y() + a.height() / 4;
1671 w = a.width() / 2;
1672 h = a.height() / 2;
1673 }
1674 }
1675
1676 changeAllowedActions(); // based on the new _functions
1677
1678 // when fullscreening, don't obey things like increments, fill the screen
1679 internal_resize(TopLeft, w, h, !fs, x, y);
1680
1681 // raise (back) into our stacking layer
1682 openbox->screen(_screen)->raiseWindow(this);
1683
1684 // try focus us when we go into fullscreen mode
1685 if (fs) focus();
1686 }
1687
1688 void Client::iconify(bool iconic, bool curdesk)
1689 {
1690 if (_iconic == iconic) return; // nothing to do
1691
1692 #ifdef DEBUG
1693 printf("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"), _window);
1694 #endif
1695
1696 _iconic = iconic;
1697
1698 if (_iconic) {
1699 _wmstate = IconicState;
1700 ignore_unmaps++;
1701 // we unmap the client itself so that we can get MapRequest events, and
1702 // because the ICCCM tells us to!
1703 XUnmapWindow(**otk::display, _window);
1704 } else {
1705 if (curdesk)
1706 setDesktop(openbox->screen(_screen)->desktop());
1707 _wmstate = NormalState;
1708 XMapWindow(**otk::display, _window);
1709 }
1710 changeState();
1711 showhide();
1712 openbox->screen(_screen)->updateStruts();
1713 }
1714
1715 void Client::disableDecorations(DecorationFlags flags)
1716 {
1717 _disabled_decorations = flags;
1718 setupDecorAndFunctions();
1719 }
1720
1721 void Client::installColormap(bool install) const
1722 {
1723 XWindowAttributes wa;
1724 if (XGetWindowAttributes(**otk::display, _window, &wa)) {
1725 if (install)
1726 XInstallColormap(**otk::display, wa.colormap);
1727 else
1728 XUninstallColormap(**otk::display, wa.colormap);
1729 }
1730 }
1731
1732 Client *Client::searchModalTree(Client *node, Client *skip)
1733 {
1734 List::const_iterator it, end = node->_transients.end();
1735 Client *ret;
1736
1737 for (it = node->_transients.begin(); it != end; ++it) {
1738 if (*it == skip) continue; // circular?
1739 if ((ret = searchModalTree(*it, skip))) return ret; // got one
1740 if ((*it)->_modal) return *it; // got one
1741 }
1742 return 0;
1743 }
1744
1745 Client *Client::findModalChild()
1746 {
1747 return searchModalTree(this, this);
1748 }
1749
1750
1751 bool Client::focus()
1752 {
1753 // if we have a modal child, then focus it, not us
1754 Client *c = findModalChild();
1755 if (c) return c->focus();
1756
1757 // won't try focus if the client doesn't want it, or if the window isn't
1758 // visible on the screen
1759 if (!(frame->visible() && (_can_focus || _focus_notify))) return false;
1760
1761 // do a check to see if the window has already been unmapped or destroyed
1762 // do this intelligently while watching out for unmaps we've generated
1763 // (ignore_unmaps > 0)
1764 XEvent ev;
1765 if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1766 XPutBackEvent(**otk::display, &ev);
1767 return false;
1768 }
1769 while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1770 if (ignore_unmaps) {
1771 unmapHandler(ev.xunmap);
1772 } else {
1773 XPutBackEvent(**otk::display, &ev);
1774 return false;
1775 }
1776 }
1777
1778 if (_can_focus)
1779 XSetInputFocus(**otk::display, _window,
1780 RevertToNone, CurrentTime);
1781
1782 if (_focus_notify) {
1783 XEvent ce;
1784 ce.xclient.type = ClientMessage;
1785 ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1786 ce.xclient.display = **otk::display;
1787 ce.xclient.window = _window;
1788 ce.xclient.format = 32;
1789 ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1790 ce.xclient.data.l[1] = openbox->lastTime();
1791 ce.xclient.data.l[2] = 0l;
1792 ce.xclient.data.l[3] = 0l;
1793 ce.xclient.data.l[4] = 0l;
1794 XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1795 }
1796
1797 XSync(**otk::display, False);
1798 return true;
1799 }
1800
1801
1802 void Client::unfocus() const
1803 {
1804 assert(openbox->focusedClient() == this);
1805 openbox->setFocusedClient(0);
1806 }
1807
1808
1809 void Client::focusHandler(const XFocusChangeEvent &e)
1810 {
1811 #ifdef DEBUG
1812 // printf("FocusIn for 0x%lx\n", e.window);
1813 #endif // DEBUG
1814
1815 otk::EventHandler::focusHandler(e);
1816
1817 _focused = true;
1818 frame->adjustFocus();
1819
1820 calcLayer(); // focus state can affect the stacking layer
1821
1822 openbox->setFocusedClient(this);
1823 }
1824
1825
1826 void Client::unfocusHandler(const XFocusChangeEvent &e)
1827 {
1828 #ifdef DEBUG
1829 // printf("FocusOut for 0x%lx\n", e.window);
1830 #endif // DEBUG
1831
1832 otk::EventHandler::unfocusHandler(e);
1833
1834 _focused = false;
1835 frame->adjustFocus();
1836
1837 calcLayer(); // focus state can affect the stacking layer
1838
1839 if (openbox->focusedClient() == this)
1840 openbox->setFocusedClient(0);
1841 }
1842
1843
1844 void Client::configureRequestHandler(const XConfigureRequestEvent &ec)
1845 {
1846 #ifdef DEBUG
1847 printf("ConfigureRequest for 0x%lx\n", ec.window);
1848 #endif // DEBUG
1849
1850 otk::EventHandler::configureRequestHandler(ec);
1851
1852 // compress these
1853 XConfigureRequestEvent e = ec;
1854 XEvent ev;
1855 while (XCheckTypedWindowEvent(**otk::display, window(), ConfigureRequest,
1856 &ev)) {
1857 // XXX if this causes bad things.. we can compress config req's with the
1858 // same mask.
1859 e.value_mask |= ev.xconfigurerequest.value_mask;
1860 if (ev.xconfigurerequest.value_mask & CWX)
1861 e.x = ev.xconfigurerequest.x;
1862 if (ev.xconfigurerequest.value_mask & CWY)
1863 e.y = ev.xconfigurerequest.y;
1864 if (ev.xconfigurerequest.value_mask & CWWidth)
1865 e.width = ev.xconfigurerequest.width;
1866 if (ev.xconfigurerequest.value_mask & CWHeight)
1867 e.height = ev.xconfigurerequest.height;
1868 if (ev.xconfigurerequest.value_mask & CWBorderWidth)
1869 e.border_width = ev.xconfigurerequest.border_width;
1870 if (ev.xconfigurerequest.value_mask & CWStackMode)
1871 e.detail = ev.xconfigurerequest.detail;
1872 }
1873
1874 // if we are iconic (or shaded (fvwm does this)) ignore the event
1875 if (_iconic || _shaded) return;
1876
1877 if (e.value_mask & CWBorderWidth)
1878 _border_width = e.border_width;
1879
1880 // resize, then move, as specified in the EWMH section 7.7
1881 if (e.value_mask & (CWWidth | CWHeight)) {
1882 int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1883 int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1884
1885 Corner corner;
1886 switch (_gravity) {
1887 case NorthEastGravity:
1888 case EastGravity:
1889 corner = TopRight;
1890 break;
1891 case SouthWestGravity:
1892 case SouthGravity:
1893 corner = BottomLeft;
1894 break;
1895 case SouthEastGravity:
1896 corner = BottomRight;
1897 break;
1898 default: // NorthWest, Static, etc
1899 corner = TopLeft;
1900 }
1901
1902 // if moving AND resizing ...
1903 if (e.value_mask & (CWX | CWY)) {
1904 int x = (e.value_mask & CWX) ? e.x : _area.x();
1905 int y = (e.value_mask & CWY) ? e.y : _area.y();
1906 internal_resize(corner, w, h, false, x, y);
1907 } else // if JUST resizing...
1908 internal_resize(corner, w, h, false);
1909 } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1910 int x = (e.value_mask & CWX) ? e.x : _area.x();
1911 int y = (e.value_mask & CWY) ? e.y : _area.y();
1912 internal_move(x, y);
1913 }
1914
1915 if (e.value_mask & CWStackMode) {
1916 switch (e.detail) {
1917 case Below:
1918 case BottomIf:
1919 openbox->screen(_screen)->lowerWindow(this);
1920 break;
1921
1922 case Above:
1923 case TopIf:
1924 default:
1925 openbox->screen(_screen)->raiseWindow(this);
1926 break;
1927 }
1928 }
1929 }
1930
1931
1932 void Client::unmapHandler(const XUnmapEvent &e)
1933 {
1934 if (ignore_unmaps) {
1935 #ifdef DEBUG
1936 // printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1937 #endif // DEBUG
1938 ignore_unmaps--;
1939 return;
1940 }
1941
1942 #ifdef DEBUG
1943 printf("UnmapNotify for 0x%lx\n", e.window);
1944 #endif // DEBUG
1945
1946 otk::EventHandler::unmapHandler(e);
1947
1948 // this deletes us etc
1949 openbox->screen(_screen)->unmanageWindow(this);
1950 }
1951
1952
1953 void Client::destroyHandler(const XDestroyWindowEvent &e)
1954 {
1955 #ifdef DEBUG
1956 printf("DestroyNotify for 0x%lx\n", e.window);
1957 #endif // DEBUG
1958
1959 otk::EventHandler::destroyHandler(e);
1960
1961 // this deletes us etc
1962 openbox->screen(_screen)->unmanageWindow(this);
1963 }
1964
1965
1966 void Client::reparentHandler(const XReparentEvent &e)
1967 {
1968 // this is when the client is first taken captive in the frame
1969 if (e.parent == frame->plate()) return;
1970
1971 #ifdef DEBUG
1972 printf("ReparentNotify for 0x%lx\n", e.window);
1973 #endif // DEBUG
1974
1975 otk::EventHandler::reparentHandler(e);
1976
1977 /*
1978 This event is quite rare and is usually handled in unmapHandler.
1979 However, if the window is unmapped when the reparent event occurs,
1980 the window manager never sees it because an unmap event is not sent
1981 to an already unmapped window.
1982 */
1983
1984 // we don't want the reparent event, put it back on the stack for the X
1985 // server to deal with after we unmanage the window
1986 XEvent ev;
1987 ev.xreparent = e;
1988 XPutBackEvent(**otk::display, &ev);
1989
1990 // this deletes us etc
1991 openbox->screen(_screen)->unmanageWindow(this);
1992 }
1993
1994 void Client::mapRequestHandler(const XMapRequestEvent &e)
1995 {
1996 #ifdef DEBUG
1997 printf("MapRequest for already managed 0x%lx\n", e.window);
1998 #endif // DEBUG
1999
2000 assert(_iconic); // we shouldn't be able to get this unless we're iconic
2001
2002 // move to the current desktop (uniconify)
2003 iconify(false);
2004 // XXX: should we focus/raise the window? (basically a net_wm_active_window)
2005 }
2006
2007 }
This page took 0.136552 seconds and 4 git commands to generate.