]> Dogcows Code - chaz/openbox/blob - src/client.cc
support for pixmap icons, kwm_win_icon and the icon in wmhints
[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(StateAction 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 == State_Add || action == State_Remove ||
892 action == State_Toggle))
893 return; // an invalid action was passed to the client message, ignore it
894
895 for (int i = 0; i < 2; ++i) {
896 Atom state = i == 0 ? data1 : data2;
897
898 if (! state) continue;
899
900 // if toggling, then pick whether we're adding or removing
901 if (action == State_Toggle) {
902 if (state == otk::Property::atoms.net_wm_state_modal)
903 action = _modal ? State_Remove : State_Add;
904 else if (state == otk::Property::atoms.net_wm_state_maximized_vert)
905 action = _max_vert ? State_Remove : State_Add;
906 else if (state == otk::Property::atoms.net_wm_state_maximized_horz)
907 action = _max_horz ? State_Remove : State_Add;
908 else if (state == otk::Property::atoms.net_wm_state_shaded)
909 action = _shaded ? State_Remove : State_Add;
910 else if (state == otk::Property::atoms.net_wm_state_skip_taskbar)
911 action = _skip_taskbar ? State_Remove : State_Add;
912 else if (state == otk::Property::atoms.net_wm_state_skip_pager)
913 action = _skip_pager ? State_Remove : State_Add;
914 else if (state == otk::Property::atoms.net_wm_state_fullscreen)
915 action = _fullscreen ? State_Remove : State_Add;
916 else if (state == otk::Property::atoms.net_wm_state_above)
917 action = _above ? State_Remove : State_Add;
918 else if (state == otk::Property::atoms.net_wm_state_below)
919 action = _below ? State_Remove : State_Add;
920 }
921
922 if (action == State_Add) {
923 if (state == otk::Property::atoms.net_wm_state_modal) {
924 if (_modal) continue;
925 _modal = true;
926 } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
927 maxv = true;
928 } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
929 if (_max_horz) continue;
930 maxh = true;
931 } else if (state == otk::Property::atoms.net_wm_state_shaded) {
932 shadestate = true;
933 } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
934 _skip_taskbar = true;
935 } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
936 _skip_pager = true;
937 } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
938 fsstate = true;
939 } else if (state == otk::Property::atoms.net_wm_state_above) {
940 if (_above) continue;
941 _above = true;
942 } else if (state == otk::Property::atoms.net_wm_state_below) {
943 if (_below) continue;
944 _below = true;
945 }
946
947 } else { // action == State_Remove
948 if (state == otk::Property::atoms.net_wm_state_modal) {
949 if (!_modal) continue;
950 _modal = false;
951 } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
952 maxv = false;
953 } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
954 maxh = false;
955 } else if (state == otk::Property::atoms.net_wm_state_shaded) {
956 shadestate = false;
957 } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
958 _skip_taskbar = false;
959 } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
960 _skip_pager = false;
961 } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
962 fsstate = false;
963 } else if (state == otk::Property::atoms.net_wm_state_above) {
964 if (!_above) continue;
965 _above = false;
966 } else if (state == otk::Property::atoms.net_wm_state_below) {
967 if (!_below) continue;
968 _below = false;
969 }
970 }
971 }
972 if (maxh != _max_horz || maxv != _max_vert) {
973 if (maxh != _max_horz && maxv != _max_vert) { // toggling both
974 if (maxh == maxv) { // both going the same way
975 maximize(maxh, 0, true);
976 } else {
977 maximize(maxh, 1, true);
978 maximize(maxv, 2, true);
979 }
980 } else { // toggling one
981 if (maxh != _max_horz)
982 maximize(maxh, 1, true);
983 else
984 maximize(maxv, 2, true);
985 }
986 }
987 // change fullscreen state before shading, as it will affect if the window
988 // can shade or not
989 if (fsstate != _fullscreen)
990 fullscreen(fsstate, true);
991 if (shadestate != _shaded)
992 shade(shadestate);
993 calcLayer();
994 changeState(); // change the hint to relect these changes
995 }
996
997 void Client::toggleClientBorder(bool addborder)
998 {
999 // adjust our idea of where the client is, based on its border. When the
1000 // border is removed, the client should now be considered to be in a
1001 // different position.
1002 // when re-adding the border to the client, the same operation needs to be
1003 // reversed.
1004 int oldx = _area.x(), oldy = _area.y();
1005 int x = oldx, y = oldy;
1006 switch(_gravity) {
1007 default:
1008 case NorthWestGravity:
1009 case WestGravity:
1010 case SouthWestGravity:
1011 break;
1012 case NorthEastGravity:
1013 case EastGravity:
1014 case SouthEastGravity:
1015 if (addborder) x -= _border_width * 2;
1016 else x += _border_width * 2;
1017 break;
1018 case NorthGravity:
1019 case SouthGravity:
1020 case CenterGravity:
1021 case ForgetGravity:
1022 case StaticGravity:
1023 if (addborder) x -= _border_width;
1024 else x += _border_width;
1025 break;
1026 }
1027 switch(_gravity) {
1028 default:
1029 case NorthWestGravity:
1030 case NorthGravity:
1031 case NorthEastGravity:
1032 break;
1033 case SouthWestGravity:
1034 case SouthGravity:
1035 case SouthEastGravity:
1036 if (addborder) y -= _border_width * 2;
1037 else y += _border_width * 2;
1038 break;
1039 case WestGravity:
1040 case EastGravity:
1041 case CenterGravity:
1042 case ForgetGravity:
1043 case StaticGravity:
1044 if (addborder) y -= _border_width;
1045 else y += _border_width;
1046 break;
1047 }
1048 _area = otk::Rect(otk::Point(x, y), _area.size());
1049
1050 if (addborder) {
1051 XSetWindowBorderWidth(**otk::display, _window, _border_width);
1052
1053 // move the client so it is back it the right spot _with_ its border!
1054 if (x != oldx || y != oldy)
1055 XMoveWindow(**otk::display, _window, x, y);
1056 } else
1057 XSetWindowBorderWidth(**otk::display, _window, 0);
1058 }
1059
1060 void Client::clientMessageHandler(const XClientMessageEvent &e)
1061 {
1062 otk::EventHandler::clientMessageHandler(e);
1063
1064 // validate cuz we query stuff off the client here
1065 if (!validate()) return;
1066
1067 if (e.format != 32) return;
1068
1069 if (e.message_type == otk::Property::atoms.wm_change_state) {
1070 // compress changes into a single change
1071 bool compress = false;
1072 XEvent ce;
1073 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
1074 // XXX: it would be nice to compress ALL messages of a type, not just
1075 // messages in a row without other message types between.
1076 if (ce.xclient.message_type != e.message_type) {
1077 XPutBackEvent(**otk::display, &ce);
1078 break;
1079 }
1080 compress = true;
1081 }
1082 if (compress)
1083 setWMState(ce.xclient.data.l[0]); // use the found event
1084 else
1085 setWMState(e.data.l[0]); // use the original event
1086 } else if (e.message_type == otk::Property::atoms.net_wm_desktop) {
1087 // compress changes into a single change
1088 bool compress = false;
1089 XEvent ce;
1090 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
1091 // XXX: it would be nice to compress ALL messages of a type, not just
1092 // messages in a row without other message types between.
1093 if (ce.xclient.message_type != e.message_type) {
1094 XPutBackEvent(**otk::display, &ce);
1095 break;
1096 }
1097 compress = true;
1098 }
1099 if (compress)
1100 setDesktop(e.data.l[0]); // use the found event
1101 else
1102 setDesktop(e.data.l[0]); // use the original event
1103 } else if (e.message_type == otk::Property::atoms.net_wm_state) {
1104 // can't compress these
1105 #ifdef DEBUG
1106 printf("net_wm_state %s %ld %ld for 0x%lx\n",
1107 (e.data.l[0] == 0 ? "Remove" : e.data.l[0] == 1 ? "Add" :
1108 e.data.l[0] == 2 ? "Toggle" : "INVALID"),
1109 e.data.l[1], e.data.l[2], _window);
1110 #endif
1111 setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
1112 } else if (e.message_type == otk::Property::atoms.net_close_window) {
1113 #ifdef DEBUG
1114 printf("net_close_window for 0x%lx\n", _window);
1115 #endif
1116 close();
1117 } else if (e.message_type == otk::Property::atoms.net_active_window) {
1118 #ifdef DEBUG
1119 printf("net_active_window for 0x%lx\n", _window);
1120 #endif
1121 if (openbox->screen(_screen)->showingDesktop())
1122 openbox->screen(_screen)->showDesktop(false);
1123 if (_iconic)
1124 iconify(false);
1125 else if (!frame->visible()) // if its not visible for other reasons, then
1126 return; // don't mess with it
1127 if (_shaded)
1128 shade(false);
1129 focus();
1130 openbox->screen(_screen)->raiseWindow(this);
1131 } else if (e.message_type == otk::Property::atoms.openbox_active_window) {
1132 if (openbox->screen(_screen)->showingDesktop())
1133 openbox->screen(_screen)->showDesktop(false);
1134 if (_iconic)
1135 iconify(false);
1136 else if (!frame->visible()) // if its not visible for other reasons, then
1137 return; // don't mess with it
1138 if (e.data.l[0] && _shaded)
1139 shade(false);
1140 focus();
1141 if (e.data.l[1])
1142 openbox->screen(_screen)->raiseWindow(this);
1143 }
1144 }
1145
1146 #if defined(SHAPE)
1147 void Client::shapeHandler(const XShapeEvent &e)
1148 {
1149 otk::EventHandler::shapeHandler(e);
1150
1151 if (e.kind == ShapeBounding) {
1152 _shaped = e.shaped;
1153 frame->adjustShape();
1154 }
1155 }
1156 #endif
1157
1158 void Client::resize(Corner anchor, int w, int h)
1159 {
1160 if (!(_functions & Func_Resize)) return;
1161 internal_resize(anchor, w, h);
1162 }
1163
1164 void Client::internal_resize(Corner anchor, int w, int h,
1165 bool user, int x, int y)
1166 {
1167 w -= _base_size.width();
1168 h -= _base_size.height();
1169
1170 if (user) {
1171 // for interactive resizing. have to move half an increment in each
1172 // direction.
1173 int mw = w % _size_inc.width(); // how far we are towards the next size inc
1174 int mh = h % _size_inc.height();
1175 int aw = _size_inc.width() / 2; // amount to add
1176 int ah = _size_inc.height() / 2;
1177 // don't let us move into a new size increment
1178 if (mw + aw >= _size_inc.width()) aw = _size_inc.width() - mw - 1;
1179 if (mh + ah >= _size_inc.height()) ah = _size_inc.height() - mh - 1;
1180 w += aw;
1181 h += ah;
1182
1183 // if this is a user-requested resize, then check against min/max sizes
1184 // and aspect ratios
1185
1186 // smaller than min size or bigger than max size?
1187 if (w > _max_size.width()) w = _max_size.width();
1188 if (w < _min_size.width()) w = _min_size.width();
1189 if (h > _max_size.height()) h = _max_size.height();
1190 if (h < _min_size.height()) h = _min_size.height();
1191
1192 // adjust the height ot match the width for the aspect ratios
1193 if (_min_ratio)
1194 if (h * _min_ratio > w) h = static_cast<int>(w / _min_ratio);
1195 if (_max_ratio)
1196 if (h * _max_ratio < w) h = static_cast<int>(w / _max_ratio);
1197 }
1198
1199 // keep to the increments
1200 w /= _size_inc.width();
1201 h /= _size_inc.height();
1202
1203 // you cannot resize to nothing
1204 if (w < 1) w = 1;
1205 if (h < 1) h = 1;
1206
1207 // store the logical size
1208 _logical_size = otk::Size(w, h);
1209
1210 w *= _size_inc.width();
1211 h *= _size_inc.height();
1212
1213 w += _base_size.width();
1214 h += _base_size.height();
1215
1216 if (x == INT_MIN || y == INT_MIN) {
1217 x = _area.x();
1218 y = _area.y();
1219 switch (anchor) {
1220 case TopLeft:
1221 break;
1222 case TopRight:
1223 x -= w - _area.width();
1224 break;
1225 case BottomLeft:
1226 y -= h - _area.height();
1227 break;
1228 case BottomRight:
1229 x -= w - _area.width();
1230 y -= h - _area.height();
1231 break;
1232 }
1233 }
1234
1235 _area = otk::Rect(_area.position(), otk::Size(w, h));
1236
1237 XResizeWindow(**otk::display, _window, w, h);
1238
1239 // resize the frame to match the request
1240 frame->adjustSize();
1241 internal_move(x, y);
1242 }
1243
1244 const Icon *Client::icon(const otk::Size &s) const
1245 {
1246 unsigned long req = s.width() * s.height();
1247 // si is the smallest image >= req
1248 // li is the largest image < req
1249 unsigned long smallest = 0xffffffff, largest = 0, si = 0, li = 0;
1250
1251 assert(_nicons > 0); // there should always be a default..
1252 for (int i = 0; i < _nicons; ++i) {
1253 unsigned long size = _icons[i].w * _icons[i].h;
1254 if (size < smallest && size >= req) {
1255 smallest = size;
1256 si = i;
1257 }
1258 if (size > largest && size <= req) {
1259 largest = size;
1260 li = i;
1261 }
1262 }
1263 if (largest == 0) // didnt find one smaller than the requested size
1264 return &_icons[si];
1265 return &_icons[li];
1266 }
1267
1268 void Client::move(int x, int y)
1269 {
1270 if (!(_functions & Func_Move)) return;
1271 frame->frameGravity(x, y); // get the client's position based on x,y for the
1272 // frame
1273 internal_move(x, y);
1274 }
1275
1276 void Client::internal_move(int x, int y)
1277 {
1278 _area = otk::Rect(otk::Point(x, y), _area.size());
1279
1280 // move the frame to be in the requested position
1281 if (frame) { // this can be called while mapping, before frame exists
1282 frame->adjustPosition();
1283
1284 // send synthetic configure notify (we don't need to if we aren't mapped
1285 // yet)
1286 XEvent event;
1287 event.type = ConfigureNotify;
1288 event.xconfigure.display = **otk::display;
1289 event.xconfigure.event = _window;
1290 event.xconfigure.window = _window;
1291
1292 // root window coords with border in mind
1293 event.xconfigure.x = x - _border_width + frame->size().left;
1294 event.xconfigure.y = y - _border_width + frame->size().top;
1295
1296 event.xconfigure.width = _area.width();
1297 event.xconfigure.height = _area.height();
1298 event.xconfigure.border_width = _border_width;
1299 event.xconfigure.above = frame->plate();
1300 event.xconfigure.override_redirect = False;
1301 XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1302 StructureNotifyMask, &event);
1303 #if 0//def DEBUG
1304 printf("Sent synthetic ConfigureNotify %d,%d %d,%d to 0x%lx\n",
1305 event.xconfigure.x, event.xconfigure.y, event.xconfigure.width,
1306 event.xconfigure.height, event.xconfigure.window);
1307 #endif
1308 }
1309 }
1310
1311 void Client::close()
1312 {
1313 XEvent ce;
1314
1315 if (!(_functions & Func_Close)) return;
1316
1317 // XXX: itd be cool to do timeouts and shit here for killing the client's
1318 // process off
1319 // like... if the window is around after 5 seconds, then the close button
1320 // turns a nice red, and if this function is called again, the client is
1321 // explicitly killed.
1322
1323 ce.xclient.type = ClientMessage;
1324 ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1325 ce.xclient.display = **otk::display;
1326 ce.xclient.window = _window;
1327 ce.xclient.format = 32;
1328 ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1329 ce.xclient.data.l[1] = CurrentTime;
1330 ce.xclient.data.l[2] = 0l;
1331 ce.xclient.data.l[3] = 0l;
1332 ce.xclient.data.l[4] = 0l;
1333 XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1334 }
1335
1336 void Client::changeState()
1337 {
1338 unsigned long state[2];
1339 state[0] = _wmstate;
1340 state[1] = None;
1341 otk::Property::set(_window, otk::Property::atoms.wm_state,
1342 otk::Property::atoms.wm_state, state, 2);
1343
1344 Atom netstate[10];
1345 int num = 0;
1346 if (_modal)
1347 netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1348 if (_shaded)
1349 netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1350 if (_iconic)
1351 netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1352 if (_skip_taskbar)
1353 netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1354 if (_skip_pager)
1355 netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1356 if (_fullscreen)
1357 netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1358 if (_max_vert)
1359 netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1360 if (_max_horz)
1361 netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1362 if (_above)
1363 netstate[num++] = otk::Property::atoms.net_wm_state_above;
1364 if (_below)
1365 netstate[num++] = otk::Property::atoms.net_wm_state_below;
1366 otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1367 otk::Property::atoms.atom, netstate, num);
1368
1369 calcLayer();
1370
1371 if (frame)
1372 frame->adjustState();
1373 }
1374
1375 void Client::changeAllowedActions(void)
1376 {
1377 Atom actions[9];
1378 int num = 0;
1379
1380 actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1381
1382 if (_functions & Func_Shade)
1383 actions[num++] = otk::Property::atoms.net_wm_action_shade;
1384 if (_functions & Func_Close)
1385 actions[num++] = otk::Property::atoms.net_wm_action_close;
1386 if (_functions & Func_Move)
1387 actions[num++] = otk::Property::atoms.net_wm_action_move;
1388 if (_functions & Func_Iconify)
1389 actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1390 if (_functions & Func_Resize)
1391 actions[num++] = otk::Property::atoms.net_wm_action_resize;
1392 if (_functions & Func_Fullscreen)
1393 actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1394 if (_functions & Func_Maximize) {
1395 actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1396 actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1397 }
1398
1399 otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1400 otk::Property::atoms.atom, actions, num);
1401
1402 // make sure the window isn't breaking any rules now
1403
1404 if (!(_functions & Func_Shade) && _shaded)
1405 if (frame) shade(false);
1406 else _shaded = false;
1407 if (!(_functions & Func_Iconify) && _iconic)
1408 if (frame) setDesktop(openbox->screen(_screen)->desktop());
1409 else _iconic = false;
1410 if (!(_functions & Func_Fullscreen) && _fullscreen)
1411 if (frame) fullscreen(false);
1412 else _fullscreen = false;
1413 if (!(_functions & Func_Maximize) && (_max_horz || _max_vert))
1414 if (frame) maximize(false, 0);
1415 else _max_vert = _max_horz = false;
1416 }
1417
1418 void Client::remaximize()
1419 {
1420 int dir;
1421 if (_max_horz && _max_vert)
1422 dir = 0;
1423 else if (_max_horz)
1424 dir = 1;
1425 else if (_max_vert)
1426 dir = 2;
1427 else
1428 return; // not maximized
1429 _max_horz = _max_vert = false;
1430 maximize(true, dir, false);
1431 }
1432
1433 void Client::applyStartupState()
1434 {
1435 // these are in a carefully crafted order..
1436
1437 if (_iconic) {
1438 _iconic = false;
1439 iconify(true);
1440 }
1441 if (_fullscreen) {
1442 _fullscreen = false;
1443 fullscreen(true, false);
1444 }
1445 if (_shaded) {
1446 _shaded = false;
1447 shade(true);
1448 }
1449 if (_urgent)
1450 fireUrgent();
1451
1452 if (_max_vert && _max_horz) {
1453 _max_vert = _max_horz = false;
1454 maximize(true, 0, false);
1455 } else if (_max_vert) {
1456 _max_vert = false;
1457 maximize(true, 2, false);
1458 } else if (_max_horz) {
1459 _max_horz = false;
1460 maximize(true, 1, false);
1461 }
1462
1463 if (_skip_taskbar); // nothing to do for this
1464 if (_skip_pager); // nothing to do for this
1465 if (_modal); // nothing to do for this
1466 if (_above); // nothing to do for this
1467 if (_below); // nothing to do for this
1468 }
1469
1470 void Client::fireUrgent()
1471 {
1472 // call the python UrgentWindow callbacks
1473 EventData data(_screen, this, EventAction::UrgentWindow, 0);
1474 openbox->bindings()->fireEvent(&data);
1475 }
1476
1477 void Client::shade(bool shade)
1478 {
1479 if (!(_functions & Func_Shade) || // can't
1480 _shaded == shade) return; // already done
1481
1482 // when we're iconic, don't change the wmstate
1483 if (!_iconic)
1484 _wmstate = shade ? IconicState : NormalState;
1485 _shaded = shade;
1486 changeState();
1487 frame->adjustSize();
1488 }
1489
1490 void Client::maximize(bool max, int dir, bool savearea)
1491 {
1492 assert(dir == 0 || dir == 1 || dir == 2);
1493 if (!(_functions & Func_Maximize)) return; // can't
1494
1495 // check if already done
1496 if (max) {
1497 if (dir == 0 && _max_horz && _max_vert) return;
1498 if (dir == 1 && _max_horz) return;
1499 if (dir == 2 && _max_vert) return;
1500 } else {
1501 if (dir == 0 && !_max_horz && !_max_vert) return;
1502 if (dir == 1 && !_max_horz) return;
1503 if (dir == 2 && !_max_vert) return;
1504 }
1505
1506 const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1507 int x = frame->area().x(), y = frame->area().y(),
1508 w = _area.width(), h = _area.height();
1509
1510 if (max) {
1511 if (savearea) {
1512 long dimensions[4];
1513 long *readdim;
1514 unsigned long n = 4;
1515
1516 dimensions[0] = x;
1517 dimensions[1] = y;
1518 dimensions[2] = w;
1519 dimensions[3] = h;
1520
1521 // get the property off the window and use it for the dimentions we are
1522 // already maxed on
1523 if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1524 otk::Property::atoms.cardinal, &n,
1525 (long unsigned**) &readdim)) {
1526 if (n >= 4) {
1527 if (_max_horz) {
1528 dimensions[0] = readdim[0];
1529 dimensions[2] = readdim[2];
1530 }
1531 if (_max_vert) {
1532 dimensions[1] = readdim[1];
1533 dimensions[3] = readdim[3];
1534 }
1535 }
1536 delete readdim;
1537 }
1538
1539 otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1540 otk::Property::atoms.cardinal,
1541 (long unsigned*)dimensions, 4);
1542 }
1543 if (dir == 0 || dir == 1) { // horz
1544 x = a.x();
1545 w = a.width();
1546 }
1547 if (dir == 0 || dir == 2) { // vert
1548 y = a.y();
1549 h = a.height() - frame->size().top - frame->size().bottom;
1550 }
1551 } else {
1552 long *dimensions;
1553 long unsigned n = 4;
1554
1555 if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1556 otk::Property::atoms.cardinal, &n,
1557 (long unsigned**) &dimensions)) {
1558 if (n >= 4) {
1559 if (dir == 0 || dir == 1) { // horz
1560 x = (signed int)dimensions[0];
1561 w = (signed int)dimensions[2];
1562 }
1563 if (dir == 0 || dir == 2) { // vert
1564 y = (signed int)dimensions[1];
1565 h = (signed int)dimensions[3];
1566 }
1567 }
1568 delete dimensions;
1569 } else {
1570 // pick some fallbacks...
1571 if (dir == 0 || dir == 1) { // horz
1572 x = a.x() + a.width() / 4;
1573 w = a.width() / 2;
1574 }
1575 if (dir == 0 || dir == 2) { // vert
1576 y = a.y() + a.height() / 4;
1577 h = a.height() / 2;
1578 }
1579 }
1580 }
1581
1582 if (dir == 0 || dir == 1) // horz
1583 _max_horz = max;
1584 if (dir == 0 || dir == 2) // vert
1585 _max_vert = max;
1586
1587 if (!_max_horz && !_max_vert)
1588 otk::Property::erase(_window, otk::Property::atoms.openbox_premax);
1589
1590 changeState(); // change the state hints on the client
1591
1592 frame->frameGravity(x, y); // figure out where the client should be going
1593 internal_resize(TopLeft, w, h, true, x, y);
1594 }
1595
1596 void Client::fullscreen(bool fs, bool savearea)
1597 {
1598 static FunctionFlags saved_func;
1599 static DecorationFlags saved_decor;
1600
1601 if (!(_functions & Func_Fullscreen) || // can't
1602 _fullscreen == fs) return; // already done
1603
1604 _fullscreen = fs;
1605 changeState(); // change the state hints on the client
1606
1607 int x = _area.x(), y = _area.y(), w = _area.width(), h = _area.height();
1608
1609 if (fs) {
1610 // save the functions and remove them
1611 saved_func = _functions;
1612 _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1613 // save the decorations and remove them
1614 saved_decor = _decorations;
1615 _decorations = 0;
1616 if (savearea) {
1617 long dimensions[4];
1618 dimensions[0] = _area.x();
1619 dimensions[1] = _area.y();
1620 dimensions[2] = _area.width();
1621 dimensions[3] = _area.height();
1622 otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1623 otk::Property::atoms.cardinal,
1624 (long unsigned*)dimensions, 4);
1625 }
1626 const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1627 x = 0;
1628 y = 0;
1629 w = info->size().width();
1630 h = info->size().height();
1631 } else {
1632 _functions = saved_func;
1633 _decorations = saved_decor;
1634
1635 long *dimensions;
1636 long unsigned n = 4;
1637
1638 if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1639 otk::Property::atoms.cardinal, &n,
1640 (long unsigned**) &dimensions)) {
1641 if (n >= 4) {
1642 x = dimensions[0];
1643 y = dimensions[1];
1644 w = dimensions[2];
1645 h = dimensions[3];
1646 }
1647 delete dimensions;
1648 } else {
1649 // pick some fallbacks...
1650 const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1651 x = a.x() + a.width() / 4;
1652 y = a.y() + a.height() / 4;
1653 w = a.width() / 2;
1654 h = a.height() / 2;
1655 }
1656 }
1657
1658 changeAllowedActions(); // based on the new _functions
1659
1660 // when fullscreening, don't obey things like increments, fill the screen
1661 internal_resize(TopLeft, w, h, !fs, x, y);
1662
1663 // raise (back) into our stacking layer
1664 openbox->screen(_screen)->raiseWindow(this);
1665
1666 // try focus us when we go into fullscreen mode
1667 if (fs) focus();
1668 }
1669
1670 void Client::iconify(bool iconic, bool curdesk)
1671 {
1672 if (_iconic == iconic) return; // nothing to do
1673
1674 #ifdef DEBUG
1675 printf("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"), _window);
1676 #endif
1677
1678 _iconic = iconic;
1679
1680 if (_iconic) {
1681 _wmstate = IconicState;
1682 ignore_unmaps++;
1683 // we unmap the client itself so that we can get MapRequest events, and
1684 // because the ICCCM tells us to!
1685 XUnmapWindow(**otk::display, _window);
1686 } else {
1687 if (curdesk)
1688 setDesktop(openbox->screen(_screen)->desktop());
1689 _wmstate = NormalState;
1690 XMapWindow(**otk::display, _window);
1691 }
1692 changeState();
1693 showhide();
1694 openbox->screen(_screen)->updateStruts();
1695 }
1696
1697 void Client::disableDecorations(DecorationFlags flags)
1698 {
1699 _disabled_decorations = flags;
1700 setupDecorAndFunctions();
1701 }
1702
1703 void Client::installColormap(bool install) const
1704 {
1705 XWindowAttributes wa;
1706 if (XGetWindowAttributes(**otk::display, _window, &wa)) {
1707 if (install)
1708 XInstallColormap(**otk::display, wa.colormap);
1709 else
1710 XUninstallColormap(**otk::display, wa.colormap);
1711 }
1712 }
1713
1714 Client *Client::searchModalTree(Client *node, Client *skip)
1715 {
1716 List::const_iterator it, end = node->_transients.end();
1717 Client *ret;
1718
1719 for (it = node->_transients.begin(); it != end; ++it) {
1720 if (*it == skip) continue; // circular?
1721 if ((ret = searchModalTree(*it, skip))) return ret; // got one
1722 if ((*it)->_modal) return *it; // got one
1723 }
1724 return 0;
1725 }
1726
1727 Client *Client::findModalChild()
1728 {
1729 return searchModalTree(this, this);
1730 }
1731
1732
1733 bool Client::focus()
1734 {
1735 // if we have a modal child, then focus it, not us
1736 Client *c = findModalChild();
1737 if (c) return c->focus();
1738
1739 // won't try focus if the client doesn't want it, or if the window isn't
1740 // visible on the screen
1741 if (!(frame->visible() && (_can_focus || _focus_notify))) return false;
1742
1743 // do a check to see if the window has already been unmapped or destroyed
1744 // do this intelligently while watching out for unmaps we've generated
1745 // (ignore_unmaps > 0)
1746 XEvent ev;
1747 if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1748 XPutBackEvent(**otk::display, &ev);
1749 return false;
1750 }
1751 while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1752 if (ignore_unmaps) {
1753 unmapHandler(ev.xunmap);
1754 } else {
1755 XPutBackEvent(**otk::display, &ev);
1756 return false;
1757 }
1758 }
1759
1760 if (_can_focus)
1761 XSetInputFocus(**otk::display, _window,
1762 RevertToNone, CurrentTime);
1763
1764 if (_focus_notify) {
1765 XEvent ce;
1766 ce.xclient.type = ClientMessage;
1767 ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1768 ce.xclient.display = **otk::display;
1769 ce.xclient.window = _window;
1770 ce.xclient.format = 32;
1771 ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1772 ce.xclient.data.l[1] = openbox->lastTime();
1773 ce.xclient.data.l[2] = 0l;
1774 ce.xclient.data.l[3] = 0l;
1775 ce.xclient.data.l[4] = 0l;
1776 XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1777 }
1778
1779 XSync(**otk::display, False);
1780 return true;
1781 }
1782
1783
1784 void Client::unfocus() const
1785 {
1786 assert(openbox->focusedClient() == this);
1787 openbox->setFocusedClient(0);
1788 }
1789
1790
1791 void Client::focusHandler(const XFocusChangeEvent &e)
1792 {
1793 #ifdef DEBUG
1794 // printf("FocusIn for 0x%lx\n", e.window);
1795 #endif // DEBUG
1796
1797 otk::EventHandler::focusHandler(e);
1798
1799 _focused = true;
1800 frame->adjustFocus();
1801
1802 calcLayer(); // focus state can affect the stacking layer
1803
1804 openbox->setFocusedClient(this);
1805 }
1806
1807
1808 void Client::unfocusHandler(const XFocusChangeEvent &e)
1809 {
1810 #ifdef DEBUG
1811 // printf("FocusOut for 0x%lx\n", e.window);
1812 #endif // DEBUG
1813
1814 otk::EventHandler::unfocusHandler(e);
1815
1816 _focused = false;
1817 frame->adjustFocus();
1818
1819 calcLayer(); // focus state can affect the stacking layer
1820
1821 if (openbox->focusedClient() == this)
1822 openbox->setFocusedClient(0);
1823 }
1824
1825
1826 void Client::configureRequestHandler(const XConfigureRequestEvent &ec)
1827 {
1828 #ifdef DEBUG
1829 printf("ConfigureRequest for 0x%lx\n", ec.window);
1830 #endif // DEBUG
1831
1832 otk::EventHandler::configureRequestHandler(ec);
1833
1834 // compress these
1835 XConfigureRequestEvent e = ec;
1836 XEvent ev;
1837 while (XCheckTypedWindowEvent(**otk::display, window(), ConfigureRequest,
1838 &ev)) {
1839 // XXX if this causes bad things.. we can compress config req's with the
1840 // same mask.
1841 e.value_mask |= ev.xconfigurerequest.value_mask;
1842 if (ev.xconfigurerequest.value_mask & CWX)
1843 e.x = ev.xconfigurerequest.x;
1844 if (ev.xconfigurerequest.value_mask & CWY)
1845 e.y = ev.xconfigurerequest.y;
1846 if (ev.xconfigurerequest.value_mask & CWWidth)
1847 e.width = ev.xconfigurerequest.width;
1848 if (ev.xconfigurerequest.value_mask & CWHeight)
1849 e.height = ev.xconfigurerequest.height;
1850 if (ev.xconfigurerequest.value_mask & CWBorderWidth)
1851 e.border_width = ev.xconfigurerequest.border_width;
1852 if (ev.xconfigurerequest.value_mask & CWStackMode)
1853 e.detail = ev.xconfigurerequest.detail;
1854 }
1855
1856 // if we are iconic (or shaded (fvwm does this)) ignore the event
1857 if (_iconic || _shaded) return;
1858
1859 if (e.value_mask & CWBorderWidth)
1860 _border_width = e.border_width;
1861
1862 // resize, then move, as specified in the EWMH section 7.7
1863 if (e.value_mask & (CWWidth | CWHeight)) {
1864 int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1865 int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1866
1867 Corner corner;
1868 switch (_gravity) {
1869 case NorthEastGravity:
1870 case EastGravity:
1871 corner = TopRight;
1872 break;
1873 case SouthWestGravity:
1874 case SouthGravity:
1875 corner = BottomLeft;
1876 break;
1877 case SouthEastGravity:
1878 corner = BottomRight;
1879 break;
1880 default: // NorthWest, Static, etc
1881 corner = TopLeft;
1882 }
1883
1884 // if moving AND resizing ...
1885 if (e.value_mask & (CWX | CWY)) {
1886 int x = (e.value_mask & CWX) ? e.x : _area.x();
1887 int y = (e.value_mask & CWY) ? e.y : _area.y();
1888 internal_resize(corner, w, h, false, x, y);
1889 } else // if JUST resizing...
1890 internal_resize(corner, w, h, false);
1891 } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1892 int x = (e.value_mask & CWX) ? e.x : _area.x();
1893 int y = (e.value_mask & CWY) ? e.y : _area.y();
1894 internal_move(x, y);
1895 }
1896
1897 if (e.value_mask & CWStackMode) {
1898 switch (e.detail) {
1899 case Below:
1900 case BottomIf:
1901 openbox->screen(_screen)->lowerWindow(this);
1902 break;
1903
1904 case Above:
1905 case TopIf:
1906 default:
1907 openbox->screen(_screen)->raiseWindow(this);
1908 break;
1909 }
1910 }
1911 }
1912
1913
1914 void Client::unmapHandler(const XUnmapEvent &e)
1915 {
1916 if (ignore_unmaps) {
1917 #ifdef DEBUG
1918 // printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1919 #endif // DEBUG
1920 ignore_unmaps--;
1921 return;
1922 }
1923
1924 #ifdef DEBUG
1925 printf("UnmapNotify for 0x%lx\n", e.window);
1926 #endif // DEBUG
1927
1928 otk::EventHandler::unmapHandler(e);
1929
1930 // this deletes us etc
1931 openbox->screen(_screen)->unmanageWindow(this);
1932 }
1933
1934
1935 void Client::destroyHandler(const XDestroyWindowEvent &e)
1936 {
1937 #ifdef DEBUG
1938 printf("DestroyNotify for 0x%lx\n", e.window);
1939 #endif // DEBUG
1940
1941 otk::EventHandler::destroyHandler(e);
1942
1943 // this deletes us etc
1944 openbox->screen(_screen)->unmanageWindow(this);
1945 }
1946
1947
1948 void Client::reparentHandler(const XReparentEvent &e)
1949 {
1950 // this is when the client is first taken captive in the frame
1951 if (e.parent == frame->plate()) return;
1952
1953 #ifdef DEBUG
1954 printf("ReparentNotify for 0x%lx\n", e.window);
1955 #endif // DEBUG
1956
1957 otk::EventHandler::reparentHandler(e);
1958
1959 /*
1960 This event is quite rare and is usually handled in unmapHandler.
1961 However, if the window is unmapped when the reparent event occurs,
1962 the window manager never sees it because an unmap event is not sent
1963 to an already unmapped window.
1964 */
1965
1966 // we don't want the reparent event, put it back on the stack for the X
1967 // server to deal with after we unmanage the window
1968 XEvent ev;
1969 ev.xreparent = e;
1970 XPutBackEvent(**otk::display, &ev);
1971
1972 // this deletes us etc
1973 openbox->screen(_screen)->unmanageWindow(this);
1974 }
1975
1976 void Client::mapRequestHandler(const XMapRequestEvent &e)
1977 {
1978 #ifdef DEBUG
1979 printf("MapRequest for already managed 0x%lx\n", e.window);
1980 #endif // DEBUG
1981
1982 assert(_iconic); // we shouldn't be able to get this unless we're iconic
1983
1984 // move to the current desktop (uniconify)
1985 iconify(false);
1986 // XXX: should we focus/raise the window? (basically a net_wm_active_window)
1987 }
1988
1989 }
This page took 0.126319 seconds and 5 git commands to generate.