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