]> Dogcows Code - chaz/openbox/blob - src/client.cc
Add the "obsetroot" tool. Use it to set the root background.
[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 && _fullscreen) {
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("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 bool freeit = false;
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 // use default icon(s)
722 num = openbox->screen(_screen)->config().icon_length;
723 data = openbox->screen(_screen)->config().default_icon;
724 } else
725 freeit = true;
726
727 // figure out how man valid icons are in here
728 while (num - i > 2) {
729 w = data[i++];
730 h = data[i++];
731 i += w * h;
732 if (i > num) break;
733 ++_nicons;
734 }
735
736 _icons = new Icon[_nicons];
737
738 // store the icons
739 i = 0;
740 for (int j = 0; j < _nicons; ++j) {
741 w = _icons[j].w = data[i++];
742 h = _icons[j].h = data[i++];
743 _icons[j].data = new unsigned long[w * h];
744 ::memcpy(_icons[j].data, &data[i], w * h * sizeof(unsigned long));
745 i += w * h;
746 assert(i <= num);
747 }
748
749 if (freeit)
750 delete [] data;
751
752 if (_nicons <= 0) {
753 _nicons = 1;
754 _icons = new Icon[1];
755 _icons[i].w = 0;
756 _icons[i].h = 0;
757 _icons[i].data = 0;
758 }
759
760 assert(_nicons > 0); // there should always be a default..
761
762 if (frame) frame->adjustIcon();
763 }
764
765 void Client::propertyHandler(const XPropertyEvent &e)
766 {
767 otk::EventHandler::propertyHandler(e);
768
769 // validate cuz we query stuff off the client here
770 if (!validate()) return;
771
772 // compress changes to a single property into a single change
773 XEvent ce;
774 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
775 // XXX: it would be nice to compress ALL changes to a property, not just
776 // changes in a row without other props between.
777 if (ce.xproperty.atom != e.atom) {
778 XPutBackEvent(**otk::display, &ce);
779 break;
780 }
781 }
782
783 if (e.atom == XA_WM_NORMAL_HINTS) {
784 updateNormalHints();
785 setupDecorAndFunctions(); // normal hints can make a window non-resizable
786 } else if (e.atom == XA_WM_HINTS)
787 updateWMHints();
788 else if (e.atom == XA_WM_TRANSIENT_FOR) {
789 updateTransientFor();
790 getType();
791 calcLayer(); // type may have changed, so update the layer
792 setupDecorAndFunctions();
793 }
794 else if (e.atom == otk::Property::atoms.net_wm_name ||
795 e.atom == otk::Property::atoms.wm_name)
796 updateTitle();
797 else if (e.atom == otk::Property::atoms.net_wm_icon_name ||
798 e.atom == otk::Property::atoms.wm_icon_name)
799 updateIconTitle();
800 else if (e.atom == otk::Property::atoms.wm_class)
801 updateClass();
802 else if (e.atom == otk::Property::atoms.wm_protocols) {
803 updateProtocols();
804 setupDecorAndFunctions();
805 }
806 else if (e.atom == otk::Property::atoms.net_wm_strut)
807 updateStrut();
808 else if (e.atom == otk::Property::atoms.net_wm_icon)
809 updateIcons();
810 }
811
812 void Client::setWMState(long state)
813 {
814 if (state == _wmstate) return; // no change
815
816 switch (state) {
817 case IconicState:
818 iconify(true);
819 break;
820 case NormalState:
821 iconify(false);
822 break;
823 }
824 }
825
826 void Client::setDesktop(unsigned int target)
827 {
828 if (target == _desktop) return;
829
830 printf("Setting desktop %u\n", target);
831
832 if (!(target < openbox->screen(_screen)->numDesktops() ||
833 target == 0xffffffff))
834 return;
835
836 _desktop = target;
837 // set the desktop hint
838 otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
839 otk::Property::atoms.cardinal, _desktop);
840 frame->adjustState(); // the frame can display the current desktop state
841 // 'move' the window to the new desktop
842 showhide();
843 openbox->screen(_screen)->updateStruts();
844 }
845
846 void Client::showhide()
847 {
848 bool show;
849 Screen *s = openbox->screen(_screen);
850
851 if (_iconic) show = false;
852 else if (!(_desktop == s->desktop() ||
853 _desktop == 0xffffffff)) show = false;
854 else if (normal() && s->showingDesktop()) show = false;
855 else show = true;
856
857 if (show) frame->show();
858 else frame->hide();
859 }
860
861 void Client::setState(StateAction action, long data1, long data2)
862 {
863 bool shadestate = _shaded;
864 bool fsstate = _fullscreen;
865 bool maxh = _max_horz;
866 bool maxv = _max_vert;
867
868 if (!(action == State_Add || action == State_Remove ||
869 action == State_Toggle))
870 return; // an invalid action was passed to the client message, ignore it
871
872 for (int i = 0; i < 2; ++i) {
873 Atom state = i == 0 ? data1 : data2;
874
875 if (! state) continue;
876
877 // if toggling, then pick whether we're adding or removing
878 if (action == State_Toggle) {
879 if (state == otk::Property::atoms.net_wm_state_modal)
880 action = _modal ? State_Remove : State_Add;
881 else if (state == otk::Property::atoms.net_wm_state_maximized_vert)
882 action = _max_vert ? State_Remove : State_Add;
883 else if (state == otk::Property::atoms.net_wm_state_maximized_horz)
884 action = _max_horz ? State_Remove : State_Add;
885 else if (state == otk::Property::atoms.net_wm_state_shaded)
886 action = _shaded ? State_Remove : State_Add;
887 else if (state == otk::Property::atoms.net_wm_state_skip_taskbar)
888 action = _skip_taskbar ? State_Remove : State_Add;
889 else if (state == otk::Property::atoms.net_wm_state_skip_pager)
890 action = _skip_pager ? State_Remove : State_Add;
891 else if (state == otk::Property::atoms.net_wm_state_fullscreen)
892 action = _fullscreen ? State_Remove : State_Add;
893 else if (state == otk::Property::atoms.net_wm_state_above)
894 action = _above ? State_Remove : State_Add;
895 else if (state == otk::Property::atoms.net_wm_state_below)
896 action = _below ? State_Remove : State_Add;
897 }
898
899 if (action == State_Add) {
900 if (state == otk::Property::atoms.net_wm_state_modal) {
901 if (_modal) continue;
902 _modal = true;
903 } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
904 maxv = true;
905 } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
906 if (_max_horz) continue;
907 maxh = true;
908 } else if (state == otk::Property::atoms.net_wm_state_shaded) {
909 shadestate = true;
910 } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
911 _skip_taskbar = true;
912 } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
913 _skip_pager = true;
914 } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
915 fsstate = true;
916 } else if (state == otk::Property::atoms.net_wm_state_above) {
917 if (_above) continue;
918 _above = true;
919 } else if (state == otk::Property::atoms.net_wm_state_below) {
920 if (_below) continue;
921 _below = true;
922 }
923
924 } else { // action == State_Remove
925 if (state == otk::Property::atoms.net_wm_state_modal) {
926 if (!_modal) continue;
927 _modal = false;
928 } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
929 maxv = false;
930 } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
931 maxh = false;
932 } else if (state == otk::Property::atoms.net_wm_state_shaded) {
933 shadestate = false;
934 } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
935 _skip_taskbar = false;
936 } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
937 _skip_pager = false;
938 } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
939 fsstate = false;
940 } else if (state == otk::Property::atoms.net_wm_state_above) {
941 if (!_above) continue;
942 _above = false;
943 } else if (state == otk::Property::atoms.net_wm_state_below) {
944 if (!_below) continue;
945 _below = false;
946 }
947 }
948 }
949 if (maxh != _max_horz || maxv != _max_vert) {
950 if (maxh != _max_horz && maxv != _max_vert) { // toggling both
951 if (maxh == maxv) { // both going the same way
952 maximize(maxh, 0, true);
953 } else {
954 maximize(maxh, 1, true);
955 maximize(maxv, 2, true);
956 }
957 } else { // toggling one
958 if (maxh != _max_horz)
959 maximize(maxh, 1, true);
960 else
961 maximize(maxv, 2, true);
962 }
963 }
964 // change fullscreen state before shading, as it will affect if the window
965 // can shade or not
966 if (fsstate != _fullscreen)
967 fullscreen(fsstate, true);
968 if (shadestate != _shaded)
969 shade(shadestate);
970 calcLayer();
971 changeState(); // change the hint to relect these changes
972 }
973
974 void Client::toggleClientBorder(bool addborder)
975 {
976 // adjust our idea of where the client is, based on its border. When the
977 // border is removed, the client should now be considered to be in a
978 // different position.
979 // when re-adding the border to the client, the same operation needs to be
980 // reversed.
981 int oldx = _area.x(), oldy = _area.y();
982 int x = oldx, y = oldy;
983 switch(_gravity) {
984 default:
985 case NorthWestGravity:
986 case WestGravity:
987 case SouthWestGravity:
988 break;
989 case NorthEastGravity:
990 case EastGravity:
991 case SouthEastGravity:
992 if (addborder) x -= _border_width * 2;
993 else x += _border_width * 2;
994 break;
995 case NorthGravity:
996 case SouthGravity:
997 case CenterGravity:
998 case ForgetGravity:
999 case StaticGravity:
1000 if (addborder) x -= _border_width;
1001 else x += _border_width;
1002 break;
1003 }
1004 switch(_gravity) {
1005 default:
1006 case NorthWestGravity:
1007 case NorthGravity:
1008 case NorthEastGravity:
1009 break;
1010 case SouthWestGravity:
1011 case SouthGravity:
1012 case SouthEastGravity:
1013 if (addborder) y -= _border_width * 2;
1014 else y += _border_width * 2;
1015 break;
1016 case WestGravity:
1017 case EastGravity:
1018 case CenterGravity:
1019 case ForgetGravity:
1020 case StaticGravity:
1021 if (addborder) y -= _border_width;
1022 else y += _border_width;
1023 break;
1024 }
1025 _area = otk::Rect(otk::Point(x, y), _area.size());
1026
1027 if (addborder) {
1028 XSetWindowBorderWidth(**otk::display, _window, _border_width);
1029
1030 // move the client so it is back it the right spot _with_ its border!
1031 if (x != oldx || y != oldy)
1032 XMoveWindow(**otk::display, _window, x, y);
1033 } else
1034 XSetWindowBorderWidth(**otk::display, _window, 0);
1035 }
1036
1037 void Client::clientMessageHandler(const XClientMessageEvent &e)
1038 {
1039 otk::EventHandler::clientMessageHandler(e);
1040
1041 // validate cuz we query stuff off the client here
1042 if (!validate()) return;
1043
1044 if (e.format != 32) return;
1045
1046 if (e.message_type == otk::Property::atoms.wm_change_state) {
1047 // compress changes into a single change
1048 bool compress = false;
1049 XEvent ce;
1050 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
1051 // XXX: it would be nice to compress ALL messages of a type, not just
1052 // messages in a row without other message types between.
1053 if (ce.xclient.message_type != e.message_type) {
1054 XPutBackEvent(**otk::display, &ce);
1055 break;
1056 }
1057 compress = true;
1058 }
1059 if (compress)
1060 setWMState(ce.xclient.data.l[0]); // use the found event
1061 else
1062 setWMState(e.data.l[0]); // use the original event
1063 } else if (e.message_type == otk::Property::atoms.net_wm_desktop) {
1064 // compress changes into a single change
1065 bool compress = false;
1066 XEvent ce;
1067 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
1068 // XXX: it would be nice to compress ALL messages of a type, not just
1069 // messages in a row without other message types between.
1070 if (ce.xclient.message_type != e.message_type) {
1071 XPutBackEvent(**otk::display, &ce);
1072 break;
1073 }
1074 compress = true;
1075 }
1076 if (compress)
1077 setDesktop(e.data.l[0]); // use the found event
1078 else
1079 setDesktop(e.data.l[0]); // use the original event
1080 } else if (e.message_type == otk::Property::atoms.net_wm_state) {
1081 // can't compress these
1082 #ifdef DEBUG
1083 printf("net_wm_state %s %ld %ld for 0x%lx\n",
1084 (e.data.l[0] == 0 ? "Remove" : e.data.l[0] == 1 ? "Add" :
1085 e.data.l[0] == 2 ? "Toggle" : "INVALID"),
1086 e.data.l[1], e.data.l[2], _window);
1087 #endif
1088 setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
1089 } else if (e.message_type == otk::Property::atoms.net_close_window) {
1090 #ifdef DEBUG
1091 printf("net_close_window for 0x%lx\n", _window);
1092 #endif
1093 close();
1094 } else if (e.message_type == otk::Property::atoms.net_active_window) {
1095 #ifdef DEBUG
1096 printf("net_active_window for 0x%lx\n", _window);
1097 #endif
1098 if (openbox->screen(_screen)->showingDesktop())
1099 openbox->screen(_screen)->showDesktop(false);
1100 if (_iconic)
1101 iconify(false);
1102 else if (!frame->visible()) // if its not visible for other reasons, then
1103 return; // don't mess with it
1104 if (_shaded)
1105 shade(false);
1106 focus();
1107 openbox->screen(_screen)->raiseWindow(this);
1108 } else if (e.message_type == otk::Property::atoms.openbox_active_window) {
1109 if (openbox->screen(_screen)->showingDesktop())
1110 openbox->screen(_screen)->showDesktop(false);
1111 if (_iconic)
1112 iconify(false);
1113 else if (!frame->visible()) // if its not visible for other reasons, then
1114 return; // don't mess with it
1115 if (e.data.l[0] && _shaded)
1116 shade(false);
1117 focus();
1118 if (e.data.l[1])
1119 openbox->screen(_screen)->raiseWindow(this);
1120 }
1121 }
1122
1123 #if defined(SHAPE)
1124 void Client::shapeHandler(const XShapeEvent &e)
1125 {
1126 otk::EventHandler::shapeHandler(e);
1127
1128 if (e.kind == ShapeBounding) {
1129 _shaped = e.shaped;
1130 frame->adjustShape();
1131 }
1132 }
1133 #endif
1134
1135 void Client::resize(Corner anchor, int w, int h)
1136 {
1137 if (!(_functions & Func_Resize)) return;
1138 internal_resize(anchor, w, h);
1139 }
1140
1141 void Client::internal_resize(Corner anchor, int w, int h,
1142 bool user, int x, int y)
1143 {
1144 w -= _base_size.width();
1145 h -= _base_size.height();
1146
1147 if (user) {
1148 // for interactive resizing. have to move half an increment in each
1149 // direction.
1150 int mw = w % _size_inc.width(); // how far we are towards the next size inc
1151 int mh = h % _size_inc.height();
1152 int aw = _size_inc.width() / 2; // amount to add
1153 int ah = _size_inc.height() / 2;
1154 // don't let us move into a new size increment
1155 if (mw + aw >= _size_inc.width()) aw = _size_inc.width() - mw - 1;
1156 if (mh + ah >= _size_inc.height()) ah = _size_inc.height() - mh - 1;
1157 w += aw;
1158 h += ah;
1159
1160 // if this is a user-requested resize, then check against min/max sizes
1161 // and aspect ratios
1162
1163 // smaller than min size or bigger than max size?
1164 if (w > _max_size.width()) w = _max_size.width();
1165 if (w < _min_size.width()) w = _min_size.width();
1166 if (h > _max_size.height()) h = _max_size.height();
1167 if (h < _min_size.height()) h = _min_size.height();
1168
1169 // adjust the height ot match the width for the aspect ratios
1170 if (_min_ratio)
1171 if (h * _min_ratio > w) h = static_cast<int>(w / _min_ratio);
1172 if (_max_ratio)
1173 if (h * _max_ratio < w) h = static_cast<int>(w / _max_ratio);
1174 }
1175
1176 // keep to the increments
1177 w /= _size_inc.width();
1178 h /= _size_inc.height();
1179
1180 // you cannot resize to nothing
1181 if (w < 1) w = 1;
1182 if (h < 1) h = 1;
1183
1184 // store the logical size
1185 _logical_size = otk::Size(w, h);
1186
1187 w *= _size_inc.width();
1188 h *= _size_inc.height();
1189
1190 w += _base_size.width();
1191 h += _base_size.height();
1192
1193 if (x == INT_MIN || y == INT_MIN) {
1194 x = _area.x();
1195 y = _area.y();
1196 switch (anchor) {
1197 case TopLeft:
1198 break;
1199 case TopRight:
1200 x -= w - _area.width();
1201 break;
1202 case BottomLeft:
1203 y -= h - _area.height();
1204 break;
1205 case BottomRight:
1206 x -= w - _area.width();
1207 y -= h - _area.height();
1208 break;
1209 }
1210 }
1211
1212 _area = otk::Rect(_area.position(), otk::Size(w, h));
1213
1214 XResizeWindow(**otk::display, _window, w, h);
1215
1216 // resize the frame to match the request
1217 frame->adjustSize();
1218 internal_move(x, y);
1219 }
1220
1221 const Icon *Client::icon(const otk::Size &s) const
1222 {
1223 unsigned long req = s.width() * s.height();
1224 // si is the smallest image >= req
1225 // li is the largest image < req
1226 unsigned long smallest = 0xffffffff, largest = 0, si = 0, li = 0;
1227
1228 assert(_nicons > 0); // there should always be a default..
1229 for (int i = 0; i < _nicons; ++i) {
1230 unsigned long size = _icons[i].w * _icons[i].h;
1231 if (size < smallest && size >= req) {
1232 smallest = size;
1233 si = i;
1234 }
1235 if (size > largest && size <= req) {
1236 largest = size;
1237 li = i;
1238 }
1239 }
1240 if (largest == 0) // didnt find one smaller than the requested size
1241 return &_icons[si];
1242 return &_icons[li];
1243 }
1244
1245 void Client::move(int x, int y)
1246 {
1247 if (!(_functions & Func_Move)) return;
1248 frame->frameGravity(x, y); // get the client's position based on x,y for the
1249 // frame
1250 internal_move(x, y);
1251 }
1252
1253 void Client::internal_move(int x, int y)
1254 {
1255 _area = otk::Rect(otk::Point(x, y), _area.size());
1256
1257 // move the frame to be in the requested position
1258 if (frame) { // this can be called while mapping, before frame exists
1259 frame->adjustPosition();
1260
1261 // send synthetic configure notify (we don't need to if we aren't mapped
1262 // yet)
1263 XEvent event;
1264 event.type = ConfigureNotify;
1265 event.xconfigure.display = **otk::display;
1266 event.xconfigure.event = _window;
1267 event.xconfigure.window = _window;
1268
1269 // root window coords with border in mind
1270 event.xconfigure.x = x - _border_width + frame->size().left;
1271 event.xconfigure.y = y - _border_width + frame->size().top;
1272
1273 event.xconfigure.width = _area.width();
1274 event.xconfigure.height = _area.height();
1275 event.xconfigure.border_width = _border_width;
1276 event.xconfigure.above = frame->plate();
1277 event.xconfigure.override_redirect = False;
1278 XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1279 StructureNotifyMask, &event);
1280 #if 0//def DEBUG
1281 printf("Sent synthetic ConfigureNotify %d,%d %d,%d to 0x%lx\n",
1282 event.xconfigure.x, event.xconfigure.y, event.xconfigure.width,
1283 event.xconfigure.height, event.xconfigure.window);
1284 #endif
1285 }
1286 }
1287
1288 void Client::close()
1289 {
1290 XEvent ce;
1291
1292 if (!(_functions & Func_Close)) return;
1293
1294 // XXX: itd be cool to do timeouts and shit here for killing the client's
1295 // process off
1296 // like... if the window is around after 5 seconds, then the close button
1297 // turns a nice red, and if this function is called again, the client is
1298 // explicitly killed.
1299
1300 ce.xclient.type = ClientMessage;
1301 ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1302 ce.xclient.display = **otk::display;
1303 ce.xclient.window = _window;
1304 ce.xclient.format = 32;
1305 ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1306 ce.xclient.data.l[1] = CurrentTime;
1307 ce.xclient.data.l[2] = 0l;
1308 ce.xclient.data.l[3] = 0l;
1309 ce.xclient.data.l[4] = 0l;
1310 XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1311 }
1312
1313 void Client::changeState()
1314 {
1315 unsigned long state[2];
1316 state[0] = _wmstate;
1317 state[1] = None;
1318 otk::Property::set(_window, otk::Property::atoms.wm_state,
1319 otk::Property::atoms.wm_state, state, 2);
1320
1321 Atom netstate[10];
1322 int num = 0;
1323 if (_modal)
1324 netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1325 if (_shaded)
1326 netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1327 if (_iconic)
1328 netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1329 if (_skip_taskbar)
1330 netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1331 if (_skip_pager)
1332 netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1333 if (_fullscreen)
1334 netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1335 if (_max_vert)
1336 netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1337 if (_max_horz)
1338 netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1339 if (_above)
1340 netstate[num++] = otk::Property::atoms.net_wm_state_above;
1341 if (_below)
1342 netstate[num++] = otk::Property::atoms.net_wm_state_below;
1343 otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1344 otk::Property::atoms.atom, netstate, num);
1345
1346 calcLayer();
1347
1348 if (frame)
1349 frame->adjustState();
1350 }
1351
1352 void Client::changeAllowedActions(void)
1353 {
1354 Atom actions[9];
1355 int num = 0;
1356
1357 actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1358
1359 if (_functions & Func_Shade)
1360 actions[num++] = otk::Property::atoms.net_wm_action_shade;
1361 if (_functions & Func_Close)
1362 actions[num++] = otk::Property::atoms.net_wm_action_close;
1363 if (_functions & Func_Move)
1364 actions[num++] = otk::Property::atoms.net_wm_action_move;
1365 if (_functions & Func_Iconify)
1366 actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1367 if (_functions & Func_Resize)
1368 actions[num++] = otk::Property::atoms.net_wm_action_resize;
1369 if (_functions & Func_Fullscreen)
1370 actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1371 if (_functions & Func_Maximize) {
1372 actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1373 actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1374 }
1375
1376 otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1377 otk::Property::atoms.atom, actions, num);
1378
1379 // make sure the window isn't breaking any rules now
1380
1381 if (!(_functions & Func_Shade) && _shaded)
1382 if (frame) shade(false);
1383 else _shaded = false;
1384 if (!(_functions & Func_Iconify) && _iconic)
1385 if (frame) setDesktop(openbox->screen(_screen)->desktop());
1386 else _iconic = false;
1387 if (!(_functions & Func_Fullscreen) && _fullscreen)
1388 if (frame) fullscreen(false);
1389 else _fullscreen = false;
1390 if (!(_functions & Func_Maximize) && (_max_horz || _max_vert))
1391 if (frame) maximize(false, 0);
1392 else _max_vert = _max_horz = false;
1393 }
1394
1395 void Client::remaximize()
1396 {
1397 int dir;
1398 if (_max_horz && _max_vert)
1399 dir = 0;
1400 else if (_max_horz)
1401 dir = 1;
1402 else if (_max_vert)
1403 dir = 2;
1404 else
1405 return; // not maximized
1406 _max_horz = _max_vert = false;
1407 maximize(true, dir, false);
1408 }
1409
1410 void Client::applyStartupState()
1411 {
1412 // these are in a carefully crafted order..
1413
1414 if (_iconic) {
1415 _iconic = false;
1416 iconify(true);
1417 }
1418 if (_fullscreen) {
1419 _fullscreen = false;
1420 fullscreen(true, false);
1421 }
1422 if (_shaded) {
1423 _shaded = false;
1424 shade(true);
1425 }
1426 if (_urgent)
1427 fireUrgent();
1428
1429 if (_max_vert && _max_horz) {
1430 _max_vert = _max_horz = false;
1431 maximize(true, 0, false);
1432 } else if (_max_vert) {
1433 _max_vert = false;
1434 maximize(true, 2, false);
1435 } else if (_max_horz) {
1436 _max_horz = false;
1437 maximize(true, 1, false);
1438 }
1439
1440 if (_skip_taskbar); // nothing to do for this
1441 if (_skip_pager); // nothing to do for this
1442 if (_modal); // nothing to do for this
1443 if (_above); // nothing to do for this
1444 if (_below); // nothing to do for this
1445 }
1446
1447 void Client::fireUrgent()
1448 {
1449 // call the python UrgentWindow callbacks
1450 EventData data(_screen, this, EventAction::UrgentWindow, 0);
1451 openbox->bindings()->fireEvent(&data);
1452 }
1453
1454 void Client::shade(bool shade)
1455 {
1456 if (!(_functions & Func_Shade) || // can't
1457 _shaded == shade) return; // already done
1458
1459 // when we're iconic, don't change the wmstate
1460 if (!_iconic)
1461 _wmstate = shade ? IconicState : NormalState;
1462 _shaded = shade;
1463 changeState();
1464 frame->adjustSize();
1465 }
1466
1467 void Client::maximize(bool max, int dir, bool savearea)
1468 {
1469 assert(dir == 0 || dir == 1 || dir == 2);
1470 if (!(_functions & Func_Maximize)) return; // can't
1471
1472 // check if already done
1473 if (max) {
1474 if (dir == 0 && _max_horz && _max_vert) return;
1475 if (dir == 1 && _max_horz) return;
1476 if (dir == 2 && _max_vert) return;
1477 } else {
1478 if (dir == 0 && !_max_horz && !_max_vert) return;
1479 if (dir == 1 && !_max_horz) return;
1480 if (dir == 2 && !_max_vert) return;
1481 }
1482
1483 const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1484 int x = frame->area().x(), y = frame->area().y(),
1485 w = _area.width(), h = _area.height();
1486
1487 if (max) {
1488 if (savearea) {
1489 long dimensions[4];
1490 long *readdim;
1491 unsigned long n = 4;
1492
1493 dimensions[0] = x;
1494 dimensions[1] = y;
1495 dimensions[2] = w;
1496 dimensions[3] = h;
1497
1498 // get the property off the window and use it for the dimentions we are
1499 // already maxed on
1500 if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1501 otk::Property::atoms.cardinal, &n,
1502 (long unsigned**) &readdim)) {
1503 if (n >= 4) {
1504 if (_max_horz) {
1505 dimensions[0] = readdim[0];
1506 dimensions[2] = readdim[2];
1507 }
1508 if (_max_vert) {
1509 dimensions[1] = readdim[1];
1510 dimensions[3] = readdim[3];
1511 }
1512 }
1513 delete readdim;
1514 }
1515
1516 otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1517 otk::Property::atoms.cardinal,
1518 (long unsigned*)dimensions, 4);
1519 }
1520 if (dir == 0 || dir == 1) { // horz
1521 x = a.x();
1522 w = a.width();
1523 }
1524 if (dir == 0 || dir == 2) { // vert
1525 y = a.y();
1526 h = a.height() - frame->size().top - frame->size().bottom;
1527 }
1528 } else {
1529 long *dimensions;
1530 long unsigned n = 4;
1531
1532 if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1533 otk::Property::atoms.cardinal, &n,
1534 (long unsigned**) &dimensions)) {
1535 if (n >= 4) {
1536 if (dir == 0 || dir == 1) { // horz
1537 x = (signed int)dimensions[0];
1538 w = (signed int)dimensions[2];
1539 }
1540 if (dir == 0 || dir == 2) { // vert
1541 y = (signed int)dimensions[1];
1542 h = (signed int)dimensions[3];
1543 }
1544 }
1545 delete dimensions;
1546 } else {
1547 // pick some fallbacks...
1548 if (dir == 0 || dir == 1) { // horz
1549 x = a.x() + a.width() / 4;
1550 w = a.width() / 2;
1551 }
1552 if (dir == 0 || dir == 2) { // vert
1553 y = a.y() + a.height() / 4;
1554 h = a.height() / 2;
1555 }
1556 }
1557 }
1558
1559 if (dir == 0 || dir == 1) // horz
1560 _max_horz = max;
1561 if (dir == 0 || dir == 2) // vert
1562 _max_vert = max;
1563
1564 if (!_max_horz && !_max_vert)
1565 otk::Property::erase(_window, otk::Property::atoms.openbox_premax);
1566
1567 changeState(); // change the state hints on the client
1568
1569 frame->frameGravity(x, y); // figure out where the client should be going
1570 internal_resize(TopLeft, w, h, true, x, y);
1571 }
1572
1573 void Client::fullscreen(bool fs, bool savearea)
1574 {
1575 static FunctionFlags saved_func;
1576 static DecorationFlags saved_decor;
1577
1578 if (!(_functions & Func_Fullscreen) || // can't
1579 _fullscreen == fs) return; // already done
1580
1581 _fullscreen = fs;
1582 changeState(); // change the state hints on the client
1583
1584 int x = _area.x(), y = _area.y(), w = _area.width(), h = _area.height();
1585
1586 if (fs) {
1587 // save the functions and remove them
1588 saved_func = _functions;
1589 _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1590 // save the decorations and remove them
1591 saved_decor = _decorations;
1592 _decorations = 0;
1593 if (savearea) {
1594 long dimensions[4];
1595 dimensions[0] = _area.x();
1596 dimensions[1] = _area.y();
1597 dimensions[2] = _area.width();
1598 dimensions[3] = _area.height();
1599 otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1600 otk::Property::atoms.cardinal,
1601 (long unsigned*)dimensions, 4);
1602 }
1603 const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1604 x = 0;
1605 y = 0;
1606 w = info->size().width();
1607 h = info->size().height();
1608 } else {
1609 _functions = saved_func;
1610 _decorations = saved_decor;
1611
1612 long *dimensions;
1613 long unsigned n = 4;
1614
1615 if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1616 otk::Property::atoms.cardinal, &n,
1617 (long unsigned**) &dimensions)) {
1618 if (n >= 4) {
1619 x = dimensions[0];
1620 y = dimensions[1];
1621 w = dimensions[2];
1622 h = dimensions[3];
1623 }
1624 delete dimensions;
1625 } else {
1626 // pick some fallbacks...
1627 const otk::Rect &a = openbox->screen(_screen)->area(_desktop);
1628 x = a.x() + a.width() / 4;
1629 y = a.y() + a.height() / 4;
1630 w = a.width() / 2;
1631 h = a.height() / 2;
1632 }
1633 }
1634
1635 changeAllowedActions(); // based on the new _functions
1636
1637 // when fullscreening, don't obey things like increments, fill the screen
1638 internal_resize(TopLeft, w, h, !fs, x, y);
1639
1640 // raise (back) into our stacking layer
1641 openbox->screen(_screen)->raiseWindow(this);
1642
1643 // try focus us when we go into fullscreen mode
1644 if (fs) focus();
1645 }
1646
1647 void Client::iconify(bool iconic, bool curdesk)
1648 {
1649 if (_iconic == iconic) return; // nothing to do
1650
1651 #ifdef DEBUG
1652 printf("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"), _window);
1653 #endif
1654
1655 _iconic = iconic;
1656
1657 if (_iconic) {
1658 _wmstate = IconicState;
1659 ignore_unmaps++;
1660 // we unmap the client itself so that we can get MapRequest events, and
1661 // because the ICCCM tells us to!
1662 XUnmapWindow(**otk::display, _window);
1663 } else {
1664 if (curdesk)
1665 setDesktop(openbox->screen(_screen)->desktop());
1666 _wmstate = NormalState;
1667 XMapWindow(**otk::display, _window);
1668 }
1669 changeState();
1670 showhide();
1671 openbox->screen(_screen)->updateStruts();
1672 }
1673
1674 void Client::disableDecorations(DecorationFlags flags)
1675 {
1676 _disabled_decorations = flags;
1677 setupDecorAndFunctions();
1678 }
1679
1680 void Client::installColormap(bool install) const
1681 {
1682 XWindowAttributes wa;
1683 if (XGetWindowAttributes(**otk::display, _window, &wa)) {
1684 if (install)
1685 XInstallColormap(**otk::display, wa.colormap);
1686 else
1687 XUninstallColormap(**otk::display, wa.colormap);
1688 }
1689 }
1690
1691 Client *Client::searchModalTree(Client *node, Client *skip)
1692 {
1693 List::const_iterator it, end = node->_transients.end();
1694 Client *ret;
1695
1696 for (it = node->_transients.begin(); it != end; ++it) {
1697 if (*it == skip) continue; // circular?
1698 if ((ret = searchModalTree(*it, skip))) return ret; // got one
1699 if ((*it)->_modal) return *it; // got one
1700 }
1701 return 0;
1702 }
1703
1704 Client *Client::findModalChild()
1705 {
1706 return searchModalTree(this, this);
1707 }
1708
1709
1710 bool Client::focus()
1711 {
1712 // if we have a modal child, then focus it, not us
1713 Client *c = findModalChild();
1714 if (c) return c->focus();
1715
1716 // won't try focus if the client doesn't want it, or if the window isn't
1717 // visible on the screen
1718 if (!(frame->visible() && (_can_focus || _focus_notify))) return false;
1719
1720 // do a check to see if the window has already been unmapped or destroyed
1721 // do this intelligently while watching out for unmaps we've generated
1722 // (ignore_unmaps > 0)
1723 XEvent ev;
1724 if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1725 XPutBackEvent(**otk::display, &ev);
1726 return false;
1727 }
1728 while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1729 if (ignore_unmaps) {
1730 unmapHandler(ev.xunmap);
1731 } else {
1732 XPutBackEvent(**otk::display, &ev);
1733 return false;
1734 }
1735 }
1736
1737 if (_can_focus)
1738 XSetInputFocus(**otk::display, _window,
1739 RevertToNone, CurrentTime);
1740
1741 if (_focus_notify) {
1742 XEvent ce;
1743 ce.xclient.type = ClientMessage;
1744 ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1745 ce.xclient.display = **otk::display;
1746 ce.xclient.window = _window;
1747 ce.xclient.format = 32;
1748 ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1749 ce.xclient.data.l[1] = openbox->lastTime();
1750 ce.xclient.data.l[2] = 0l;
1751 ce.xclient.data.l[3] = 0l;
1752 ce.xclient.data.l[4] = 0l;
1753 XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1754 }
1755
1756 XSync(**otk::display, False);
1757 return true;
1758 }
1759
1760
1761 void Client::unfocus() const
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.13378 seconds and 4 git commands to generate.