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