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