1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
4 # include "../config.h"
11 #include "bindings.hh"
12 #include "otk/display.hh"
13 #include "otk/property.hh"
17 #include <X11/Xutil.h>
18 #include <X11/Xatom.h>
23 #define _(str) gettext(str)
30 Client::Client(int screen, Window window)
31 : otk::EventHandler(),
32 WidgetBase(WidgetBase::Type_Client),
33 frame(0), _screen(screen), _window(window)
40 // update EVERYTHING the first time!!
42 // we default to NormalState, visible
43 _wmstate = NormalState;
46 // not a transient by default of course
48 // pick a layer to start from
49 _layer = Layer_Normal;
50 // default to not urgent
52 // not positioned unless specified
54 // nothing is disabled unless specified
55 _disabled_decorations = 0;
62 getType(); // this can change the mwmhints for special cases
69 getGravity(); // get the attribute gravity
70 updateNormalHints(); // this may override the attribute gravity
72 // got the type, the mwmhints, the protocols, and the normal hints (min/max
73 // sizes), so we're ready to set up
74 // the decorations/functions
75 setupDecorAndFunctions();
77 // also get the initial_state and set _iconic if we aren't "starting"
78 // when we're "starting" that means we should use whatever state was already
79 // on the window over the initial map state, because it was already mapped
80 updateWMHints(openbox->state() != Openbox::State_Starting);
86 // this makes sure that these windows appear on all desktops
87 if (/*_type == Type_Dock ||*/ _type == Type_Desktop)
88 _desktop = 0xffffffff;
90 // set the desktop hint, to make sure that it always exists, and to reflect
91 // any changes we've made here
92 otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
93 otk::Property::atoms.cardinal, (unsigned)_desktop);
101 // clean up childrens' references
102 while (!_transients.empty()) {
103 _transients.front()->_transient_for = 0;
104 _transients.pop_front();
107 // clean up parents reference to this
109 _transient_for->_transients.remove(this); // remove from old parent
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);
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
119 XMapWindow(**otk::display, _window);
124 bool Client::validate() const
126 XSync(**otk::display, false); // get all events on the server
129 if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &e) ||
130 XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &e)) {
131 XPutBackEvent(**otk::display, &e);
139 void Client::getGravity()
141 XWindowAttributes wattrib;
144 ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
145 assert(ret != BadWindow);
146 _gravity = wattrib.win_gravity;
150 void Client::getDesktop()
152 // defaults to the current desktop
153 _desktop = openbox->screen(_screen)->desktop();
155 if (otk::Property::get(_window, otk::Property::atoms.net_wm_desktop,
156 otk::Property::atoms.cardinal,
157 (long unsigned*)&_desktop)) {
159 // printf("Window requested desktop: %ld\n", _desktop);
165 void Client::getType()
167 _type = (WindowType) -1;
170 unsigned long num = (unsigned) -1;
171 if (otk::Property::get(_window, otk::Property::atoms.net_wm_window_type,
172 otk::Property::atoms.atom, &num, &val)) {
173 // use the first value that we know about in the array
174 for (unsigned long i = 0; i < num; ++i) {
175 if (val[i] == otk::Property::atoms.net_wm_window_type_desktop)
176 _type = Type_Desktop;
177 else if (val[i] == otk::Property::atoms.net_wm_window_type_dock)
179 else if (val[i] == otk::Property::atoms.net_wm_window_type_toolbar)
180 _type = Type_Toolbar;
181 else if (val[i] == otk::Property::atoms.net_wm_window_type_menu)
183 else if (val[i] == otk::Property::atoms.net_wm_window_type_utility)
184 _type = Type_Utility;
185 else if (val[i] == otk::Property::atoms.net_wm_window_type_splash)
187 else if (val[i] == otk::Property::atoms.net_wm_window_type_dialog)
189 else if (val[i] == otk::Property::atoms.net_wm_window_type_normal)
191 else if (val[i] == otk::Property::atoms.kde_net_wm_window_type_override){
192 // prevent this window from getting any decor or functionality
193 _mwmhints.flags &= MwmFlag_Functions | MwmFlag_Decorations;
194 _mwmhints.decorations = 0;
195 _mwmhints.functions = 0;
197 if (_type != (WindowType) -1)
198 break; // grab the first known type
203 if (_type == (WindowType) -1) {
205 * the window type hint was not set, which means we either classify ourself
206 * as a normal window or a dialog, depending on if we are a transient.
216 void Client::setupDecorAndFunctions()
218 // start with everything (cept fullscreen)
219 _decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
220 Decor_AllDesktops | Decor_Iconify | Decor_Maximize;
221 _functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize |
223 if (_delete_window) {
224 _decorations |= Decor_Close;
225 _functions |= Func_Close;
228 if (!(_min_size.x() < _max_size.x() || _min_size.y() < _max_size.y())) {
229 _decorations &= ~(Decor_Maximize | Decor_Handle);
230 _functions &= ~(Func_Resize | Func_Maximize);
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;
241 // dialogs cannot be maximized
242 _decorations &= ~Decor_Maximize;
243 _functions &= ~Func_Maximize;
249 // these windows get less functionality
250 _decorations &= ~(Decor_Iconify | Decor_Handle);
251 _functions &= ~(Func_Iconify | Func_Resize);
257 // none of these windows are manipulated by the window manager
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 we don't have a titlebar, then we cannot shade!
274 _functions &= ~Func_Shade;
276 if (! (_mwmhints.decorations & MwmDecor_Iconify))
277 _decorations &= ~Decor_Iconify;
278 if (! (_mwmhints.decorations & MwmDecor_Maximize))
279 _decorations &= ~Decor_Maximize;
283 if (_mwmhints.flags & MwmFlag_Functions) {
284 if (! (_mwmhints.functions & MwmFunc_All)) {
285 if (! (_mwmhints.functions & MwmFunc_Resize))
286 _functions &= ~Func_Resize;
287 if (! (_mwmhints.functions & MwmFunc_Move))
288 _functions &= ~Func_Move;
289 if (! (_mwmhints.functions & MwmFunc_Iconify))
290 _functions &= ~Func_Iconify;
291 if (! (_mwmhints.functions & MwmFunc_Maximize))
292 _functions &= ~Func_Maximize;
293 // dont let mwm hints kill the close button
294 //if (! (_mwmhints.functions & MwmFunc_Close))
295 // _functions &= ~Func_Close;
299 // finally, user specified disabled decorations are applied to subtract
301 if (_disabled_decorations & Decor_Titlebar)
302 _decorations &= ~Decor_Titlebar;
303 if (_disabled_decorations & Decor_Handle)
304 _decorations &= ~Decor_Handle;
305 if (_disabled_decorations & Decor_Border)
306 _decorations &= ~Decor_Border;
307 if (_disabled_decorations & Decor_Iconify)
308 _decorations &= ~Decor_Iconify;
309 if (_disabled_decorations & Decor_Maximize)
310 _decorations &= ~Decor_Maximize;
311 if (_disabled_decorations & Decor_AllDesktops)
312 _decorations &= ~Decor_AllDesktops;
313 if (_disabled_decorations & Decor_Close)
314 _decorations &= ~Decor_Close;
316 // You can't shade without a titlebar
317 if (!(_decorations & Decor_Titlebar))
318 _functions &= ~Func_Shade;
320 changeAllowedActions();
323 frame->adjustSize(); // change the decors on the frame
324 frame->adjustPosition(); // with more/less decorations, we may need to be
330 void Client::getMwmHints()
332 unsigned long num = MwmHints::elements;
333 unsigned long *hints;
335 _mwmhints.flags = 0; // default to none
337 if (!otk::Property::get(_window, otk::Property::atoms.motif_wm_hints,
338 otk::Property::atoms.motif_wm_hints, &num,
339 (unsigned long **)&hints))
342 if (num >= MwmHints::elements) {
343 // retrieved the hints
344 _mwmhints.flags = hints[0];
345 _mwmhints.functions = hints[1];
346 _mwmhints.decorations = hints[2];
353 void Client::getArea()
355 XWindowAttributes wattrib;
358 ret = XGetWindowAttributes(**otk::display, _window, &wattrib);
359 assert(ret != BadWindow);
361 _area.setRect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
362 _border_width = wattrib.border_width;
366 void Client::getState()
368 _modal = _shaded = _max_horz = _max_vert = _fullscreen = _above = _below =
369 _iconic = _skip_taskbar = _skip_pager = false;
371 unsigned long *state;
372 unsigned long num = (unsigned) -1;
374 if (otk::Property::get(_window, otk::Property::atoms.net_wm_state,
375 otk::Property::atoms.atom, &num, &state)) {
376 for (unsigned long i = 0; i < num; ++i) {
377 if (state[i] == otk::Property::atoms.net_wm_state_modal)
379 else if (state[i] == otk::Property::atoms.net_wm_state_shaded)
381 else if (state[i] == otk::Property::atoms.net_wm_state_hidden)
383 else if (state[i] == otk::Property::atoms.net_wm_state_skip_taskbar)
384 _skip_taskbar = true;
385 else if (state[i] == otk::Property::atoms.net_wm_state_skip_pager)
387 else if (state[i] == otk::Property::atoms.net_wm_state_fullscreen)
389 else if (state[i] == otk::Property::atoms.net_wm_state_maximized_vert)
391 else if (state[i] == otk::Property::atoms.net_wm_state_maximized_horz)
393 else if (state[i] == otk::Property::atoms.net_wm_state_above)
395 else if (state[i] == otk::Property::atoms.net_wm_state_below)
404 void Client::getShaped()
408 if (otk::display->shape()) {
413 XShapeSelectInput(**otk::display, _window, ShapeNotifyMask);
415 XShapeQueryExtents(**otk::display, _window, &s, &foo,
416 &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo);
423 void Client::calcLayer() {
426 if (_iconic) l = Layer_Icon;
427 else if (_fullscreen) l = Layer_Fullscreen;
428 else if (_type == Type_Desktop) l = Layer_Desktop;
429 else if (_type == Type_Dock) {
430 if (!_below) l = Layer_Top;
431 else l = Layer_Normal;
433 else if (_above) l = Layer_Above;
434 else if (_below) l = Layer_Below;
435 else l = Layer_Normal;
441 if we don't have a frame, then we aren't mapped yet (and this would
444 openbox->screen(_screen)->raiseWindow(this);
450 void Client::updateProtocols()
455 _focus_notify = false;
456 _delete_window = false;
458 if (XGetWMProtocols(**otk::display, _window, &proto, &num_return)) {
459 for (int i = 0; i < num_return; ++i) {
460 if (proto[i] == otk::Property::atoms.wm_delete_window) {
461 // this means we can request the window to close
462 _delete_window = true;
463 } else if (proto[i] == otk::Property::atoms.wm_take_focus)
464 // if this protocol is requested, then the window will be notified
465 // by the window manager whenever it receives focus
466 _focus_notify = true;
473 void Client::updateNormalHints()
477 int oldgravity = _gravity;
482 _size_inc.setPoint(1, 1);
483 _base_size.setPoint(0, 0);
484 _min_size.setPoint(0, 0);
485 _max_size.setPoint(INT_MAX, INT_MAX);
487 // get the hints from the window
488 if (XGetWMNormalHints(**otk::display, _window, &size, &ret)) {
489 _positioned = (size.flags & (PPosition|USPosition));
491 if (size.flags & PWinGravity) {
492 _gravity = size.win_gravity;
494 // if the client has a frame, i.e. has already been mapped and is
495 // changing its gravity
496 if (frame && _gravity != oldgravity) {
497 // move our idea of the client's position based on its new gravity
498 int x = frame->rect().x(), y = frame->rect().y();
499 frame->frameGravity(x, y);
504 if (size.flags & PAspect) {
505 if (size.min_aspect.y) _min_ratio = size.min_aspect.x/size.min_aspect.y;
506 if (size.max_aspect.y) _max_ratio = size.max_aspect.x/size.max_aspect.y;
509 if (size.flags & PMinSize)
510 _min_size.setPoint(size.min_width, size.min_height);
512 if (size.flags & PMaxSize)
513 _max_size.setPoint(size.max_width, size.max_height);
515 if (size.flags & PBaseSize)
516 _base_size.setPoint(size.base_width, size.base_height);
518 if (size.flags & PResizeInc)
519 _size_inc.setPoint(size.width_inc, size.height_inc);
524 void Client::updateWMHints(bool initstate)
528 // assume a window takes input if it doesnt specify
532 if ((hints = XGetWMHints(**otk::display, _window)) != NULL) {
533 if (hints->flags & InputHint)
534 _can_focus = hints->input;
536 // only do this when initstate is true!
537 if (initstate && (hints->flags & StateHint))
538 _iconic = hints->initial_state == IconicState;
540 if (hints->flags & XUrgencyHint)
543 if (hints->flags & WindowGroupHint) {
544 if (hints->window_group != _group) {
545 // XXX: remove from the old group if there was one
546 _group = hints->window_group;
547 // XXX: do stuff with the group
558 printf("DEBUG: Urgent Hint for 0x%lx: %s\n",
559 (long)_window, _urgent ? "ON" : "OFF");
561 // fire the urgent callback if we're mapped, otherwise, wait until after
569 void Client::updateTitle()
574 if (!otk::Property::get(_window, otk::Property::atoms.net_wm_name,
575 otk::Property::utf8, &_title)) {
577 otk::Property::get(_window, otk::Property::atoms.wm_name,
578 otk::Property::ascii, &_title);
582 _title = _("Unnamed Window");
585 frame->setTitle(_title);
589 void Client::updateIconTitle()
594 if (!otk::Property::get(_window, otk::Property::atoms.net_wm_icon_name,
595 otk::Property::utf8, &_icon_title)) {
597 otk::Property::get(_window, otk::Property::atoms.wm_icon_name,
598 otk::Property::ascii, &_icon_title);
602 _icon_title = _("Unnamed Window");
606 void Client::updateClass()
609 _app_name = _app_class = _role = "";
611 otk::Property::StringVect v;
612 unsigned long num = 2;
614 if (otk::Property::get(_window, otk::Property::atoms.wm_class,
615 otk::Property::ascii, &num, &v)) {
616 if (num > 0) _app_name = v[0].c_str();
617 if (num > 1) _app_class = v[1].c_str();
622 if (otk::Property::get(_window, otk::Property::atoms.wm_window_role,
623 otk::Property::ascii, &num, &v)) {
624 if (num > 0) _role = v[0].c_str();
629 void Client::updateStrut()
631 unsigned long num = 4;
633 if (!otk::Property::get(_window, otk::Property::atoms.net_wm_strut,
634 otk::Property::atoms.cardinal, &num, &data))
638 _strut.left = data[0];
639 _strut.right = data[1];
640 _strut.top = data[2];
641 _strut.bottom = data[3];
643 // updating here is pointless while we're being mapped cuz we're not in
644 // the screen's client list yet
646 openbox->screen(_screen)->updateStrut();
653 void Client::updateTransientFor()
658 if (XGetTransientForHint(**otk::display, _window, &t) &&
659 t != _window) { // cant be transient to itself!
660 c = openbox->findClient(t);
661 assert(c != this); // if this happens then we need to check for it
663 if (!c /*XXX: && _group*/) {
664 // not transient to a client, see if it is transient for a group
665 if (//t == _group->leader() ||
667 t == otk::display->screenInfo(_screen)->rootWindow()) {
668 // window is a transient for its group!
669 // XXX: for now this is treated as non-transient.
670 // this needs to be fixed!
675 // if anything has changed...
676 if (c != _transient_for) {
678 _transient_for->_transients.remove(this); // remove from old parent
681 _transient_for->_transients.push_back(this); // add to new parent
686 void Client::propertyHandler(const XPropertyEvent &e)
688 otk::EventHandler::propertyHandler(e);
690 // validate cuz we query stuff off the client here
691 if (!validate()) return;
693 // compress changes to a single property into a single change
695 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
696 // XXX: it would be nice to compress ALL changes to a property, not just
697 // changes in a row without other props between.
698 if (ce.xproperty.atom != e.atom) {
699 XPutBackEvent(**otk::display, &ce);
704 if (e.atom == XA_WM_NORMAL_HINTS) {
706 setupDecorAndFunctions(); // normal hints can make a window non-resizable
707 } else if (e.atom == XA_WM_HINTS)
709 else if (e.atom == XA_WM_TRANSIENT_FOR) {
710 updateTransientFor();
712 calcLayer(); // type may have changed, so update the layer
713 setupDecorAndFunctions();
715 else if (e.atom == otk::Property::atoms.net_wm_name ||
716 e.atom == otk::Property::atoms.wm_name)
718 else if (e.atom == otk::Property::atoms.net_wm_icon_name ||
719 e.atom == otk::Property::atoms.wm_icon_name)
721 else if (e.atom == otk::Property::atoms.wm_class)
723 else if (e.atom == otk::Property::atoms.wm_protocols) {
725 setupDecorAndFunctions();
727 else if (e.atom == otk::Property::atoms.net_wm_strut)
732 void Client::setWMState(long state)
734 if (state == _wmstate) return; // no change
738 setDesktop(ICONIC_DESKTOP);
741 setDesktop(openbox->screen(_screen)->desktop());
747 void Client::setDesktop(long target)
749 if (target == _desktop) return;
751 printf("Setting desktop %ld\n", target);
753 if (!(target >= 0 || target == (signed)0xffffffff ||
754 target == ICONIC_DESKTOP))
759 // set the desktop hint, but not if we're iconifying
760 if (_desktop != ICONIC_DESKTOP)
761 otk::Property::set(_window, otk::Property::atoms.net_wm_desktop,
762 otk::Property::atoms.cardinal, (unsigned)_desktop);
764 // 'move' the window to the new desktop
765 if (_desktop == openbox->screen(_screen)->desktop() ||
766 _desktop == (signed)0xffffffff)
771 // Handle Iconic state. Iconic state is maintained by the client being a
772 // member of the ICONIC_DESKTOP, so this is where we make iconifying and
773 // uniconifying happen.
774 bool i = _desktop == ICONIC_DESKTOP;
775 if (i != _iconic) { // has the state changed?
778 _wmstate = IconicState;
780 // we unmap the client itself so that we can get MapRequest events, and
781 // because the ICCCM tells us to!
782 XUnmapWindow(**otk::display, _window);
784 _wmstate = NormalState;
785 XMapWindow(**otk::display, _window);
790 frame->adjustState();
794 void Client::setState(StateAction action, long data1, long data2)
796 bool shadestate = _shaded;
797 bool fsstate = _fullscreen;
798 bool maxh = _max_horz;
799 bool maxv = _max_vert;
801 if (!(action == State_Add || action == State_Remove ||
802 action == State_Toggle))
803 return; // an invalid action was passed to the client message, ignore it
805 for (int i = 0; i < 2; ++i) {
806 Atom state = i == 0 ? data1 : data2;
808 if (! state) continue;
810 // if toggling, then pick whether we're adding or removing
811 if (action == State_Toggle) {
812 if (state == otk::Property::atoms.net_wm_state_modal)
813 action = _modal ? State_Remove : State_Add;
814 else if (state == otk::Property::atoms.net_wm_state_maximized_vert)
815 action = _max_vert ? State_Remove : State_Add;
816 else if (state == otk::Property::atoms.net_wm_state_maximized_horz)
817 action = _max_horz ? State_Remove : State_Add;
818 else if (state == otk::Property::atoms.net_wm_state_shaded)
819 action = _shaded ? State_Remove : State_Add;
820 else if (state == otk::Property::atoms.net_wm_state_skip_taskbar)
821 action = _skip_taskbar ? State_Remove : State_Add;
822 else if (state == otk::Property::atoms.net_wm_state_skip_pager)
823 action = _skip_pager ? State_Remove : State_Add;
824 else if (state == otk::Property::atoms.net_wm_state_fullscreen)
825 action = _fullscreen ? State_Remove : State_Add;
826 else if (state == otk::Property::atoms.net_wm_state_above)
827 action = _above ? State_Remove : State_Add;
828 else if (state == otk::Property::atoms.net_wm_state_below)
829 action = _below ? State_Remove : State_Add;
832 if (action == State_Add) {
833 if (state == otk::Property::atoms.net_wm_state_modal) {
834 if (_modal) continue;
836 // XXX: give it focus if another window has focus that shouldnt now
837 } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
839 } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
840 if (_max_horz) continue;
842 } else if (state == otk::Property::atoms.net_wm_state_shaded) {
844 } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
845 _skip_taskbar = true;
846 } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
848 } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
850 } else if (state == otk::Property::atoms.net_wm_state_above) {
851 if (_above) continue;
853 } else if (state == otk::Property::atoms.net_wm_state_below) {
854 if (_below) continue;
858 } else { // action == State_Remove
859 if (state == otk::Property::atoms.net_wm_state_modal) {
860 if (!_modal) continue;
862 } else if (state == otk::Property::atoms.net_wm_state_maximized_vert) {
864 } else if (state == otk::Property::atoms.net_wm_state_maximized_horz) {
866 } else if (state == otk::Property::atoms.net_wm_state_shaded) {
868 } else if (state == otk::Property::atoms.net_wm_state_skip_taskbar) {
869 _skip_taskbar = false;
870 } else if (state == otk::Property::atoms.net_wm_state_skip_pager) {
872 } else if (state == otk::Property::atoms.net_wm_state_fullscreen) {
874 } else if (state == otk::Property::atoms.net_wm_state_above) {
875 if (!_above) continue;
877 } else if (state == otk::Property::atoms.net_wm_state_below) {
878 if (!_below) continue;
883 if (maxh != _max_horz || maxv != _max_vert) {
884 if (maxh != _max_horz && maxv != _max_vert) { // toggling both
885 if (maxh == maxv) { // both going the same way
886 maximize(maxh, 0, true);
888 maximize(maxh, 1, true);
889 maximize(maxv, 2, true);
891 } else { // toggling one
892 if (maxh != _max_horz)
893 maximize(maxh, 1, true);
895 maximize(maxv, 2, true);
898 // change fullscreen state before shading, as it will affect if the window
900 if (fsstate != _fullscreen)
901 fullscreen(fsstate, true);
902 if (shadestate != _shaded)
905 changeState(); // change the hint to relect these changes
909 void Client::toggleClientBorder(bool addborder)
911 // adjust our idea of where the client is, based on its border. When the
912 // border is removed, the client should now be considered to be in a
913 // different position.
914 // when re-adding the border to the client, the same operation needs to be
916 int oldx = _area.x(), oldy = _area.y();
917 int x = oldx, y = oldy;
920 case NorthWestGravity:
922 case SouthWestGravity:
924 case NorthEastGravity:
926 case SouthEastGravity:
927 if (addborder) x -= _border_width * 2;
928 else x += _border_width * 2;
935 if (addborder) x -= _border_width;
936 else x += _border_width;
941 case NorthWestGravity:
943 case NorthEastGravity:
945 case SouthWestGravity:
947 case SouthEastGravity:
948 if (addborder) y -= _border_width * 2;
949 else y += _border_width * 2;
956 if (addborder) y -= _border_width;
957 else y += _border_width;
963 XSetWindowBorderWidth(**otk::display, _window, _border_width);
965 // move the client so it is back it the right spot _with_ its border!
966 if (x != oldx || y != oldy)
967 XMoveWindow(**otk::display, _window, x, y);
969 XSetWindowBorderWidth(**otk::display, _window, 0);
973 void Client::clientMessageHandler(const XClientMessageEvent &e)
975 otk::EventHandler::clientMessageHandler(e);
977 // validate cuz we query stuff off the client here
978 if (!validate()) return;
980 if (e.format != 32) return;
982 if (e.message_type == otk::Property::atoms.wm_change_state) {
983 // compress changes into a single change
984 bool compress = false;
986 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
987 // XXX: it would be nice to compress ALL messages of a type, not just
988 // messages in a row without other message types between.
989 if (ce.xclient.message_type != e.message_type) {
990 XPutBackEvent(**otk::display, &ce);
996 setWMState(ce.xclient.data.l[0]); // use the found event
998 setWMState(e.data.l[0]); // use the original event
999 } else if (e.message_type == otk::Property::atoms.net_wm_desktop) {
1000 // compress changes into a single change
1001 bool compress = false;
1003 while (XCheckTypedEvent(**otk::display, e.type, &ce)) {
1004 // XXX: it would be nice to compress ALL messages of a type, not just
1005 // messages in a row without other message types between.
1006 if (ce.xclient.message_type != e.message_type) {
1007 XPutBackEvent(**otk::display, &ce);
1013 setDesktop(e.data.l[0]); // use the found event
1015 setDesktop(e.data.l[0]); // use the original event
1016 } else if (e.message_type == otk::Property::atoms.net_wm_state) {
1017 // can't compress these
1019 printf("net_wm_state %s %ld %ld for 0x%lx\n",
1020 (e.data.l[0] == 0 ? "Remove" : e.data.l[0] == 1 ? "Add" :
1021 e.data.l[0] == 2 ? "Toggle" : "INVALID"),
1022 e.data.l[1], e.data.l[2], _window);
1024 setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]);
1025 } else if (e.message_type == otk::Property::atoms.net_close_window) {
1027 printf("net_close_window for 0x%lx\n", _window);
1030 } else if (e.message_type == otk::Property::atoms.net_active_window) {
1032 printf("net_active_window for 0x%lx\n", _window);
1035 setDesktop(openbox->screen(_screen)->desktop());
1040 openbox->screen(_screen)->raiseWindow(this);
1046 void Client::shapeHandler(const XShapeEvent &e)
1048 otk::EventHandler::shapeHandler(e);
1050 if (e.kind == ShapeBounding) {
1052 frame->adjustShape();
1058 void Client::resize(Corner anchor, int w, int h)
1060 if (!(_functions & Func_Resize)) return;
1061 internal_resize(anchor, w, h);
1065 void Client::internal_resize(Corner anchor, int w, int h, bool user,
1068 w -= _base_size.x();
1069 h -= _base_size.y();
1072 // for interactive resizing. have to move half an increment in each
1074 int mw = w % _size_inc.x(); // how far we are towards the next size inc
1075 int mh = h % _size_inc.y();
1076 int aw = _size_inc.x() / 2; // amount to add
1077 int ah = _size_inc.y() / 2;
1078 // don't let us move into a new size increment
1079 if (mw + aw >= _size_inc.x()) aw = _size_inc.x() - mw - 1;
1080 if (mh + ah >= _size_inc.y()) ah = _size_inc.y() - mh - 1;
1084 // if this is a user-requested resize, then check against min/max sizes
1085 // and aspect ratios
1087 // smaller than min size or bigger than max size?
1088 if (w < _min_size.x()) w = _min_size.x();
1089 else if (w > _max_size.x()) w = _max_size.x();
1090 if (h < _min_size.y()) h = _min_size.y();
1091 else if (h > _max_size.y()) h = _max_size.y();
1093 // adjust the height ot match the width for the aspect ratios
1095 if (h * _min_ratio > w) h = static_cast<int>(w / _min_ratio);
1097 if (h * _max_ratio < w) h = static_cast<int>(w / _max_ratio);
1100 // keep to the increments
1104 // you cannot resize to nothing
1108 // store the logical size
1109 _logical_size.setPoint(w, h);
1114 w += _base_size.x();
1115 h += _base_size.y();
1117 if (x == INT_MIN || y == INT_MIN) {
1124 x -= w - _area.width();
1127 y -= h - _area.height();
1130 x -= w - _area.width();
1131 y -= h - _area.height();
1136 _area.setSize(w, h);
1138 XResizeWindow(**otk::display, _window, w, h);
1140 // resize the frame to match the request
1141 frame->adjustSize();
1142 internal_move(x, y);
1146 void Client::move(int x, int y)
1148 if (!(_functions & Func_Move)) return;
1149 frame->frameGravity(x, y); // get the client's position based on x,y for the
1151 internal_move(x, y);
1155 void Client::internal_move(int x, int y)
1159 // move the frame to be in the requested position
1160 if (frame) { // this can be called while mapping, before frame exists
1161 frame->adjustPosition();
1163 // send synthetic configure notify (we don't need to if we aren't mapped
1166 event.type = ConfigureNotify;
1167 event.xconfigure.display = **otk::display;
1168 event.xconfigure.event = _window;
1169 event.xconfigure.window = _window;
1171 // root window coords with border in mind
1172 event.xconfigure.x = x - _border_width + frame->size().left;
1173 event.xconfigure.y = y - _border_width + frame->size().top;
1175 event.xconfigure.width = _area.width();
1176 event.xconfigure.height = _area.height();
1177 event.xconfigure.border_width = _border_width;
1178 event.xconfigure.above = frame->plate();
1179 event.xconfigure.override_redirect = False;
1180 XSendEvent(event.xconfigure.display, event.xconfigure.window, False,
1181 StructureNotifyMask, &event);
1183 printf("Sent synthetic ConfigureNotify %d,%d %d,%d to 0x%lx\n",
1184 event.xconfigure.x, event.xconfigure.y, event.xconfigure.width,
1185 event.xconfigure.height, event.xconfigure.window);
1191 void Client::close()
1195 if (!(_functions & Func_Close)) return;
1197 // XXX: itd be cool to do timeouts and shit here for killing the client's
1199 // like... if the window is around after 5 seconds, then the close button
1200 // turns a nice red, and if this function is called again, the client is
1201 // explicitly killed.
1203 ce.xclient.type = ClientMessage;
1204 ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1205 ce.xclient.display = **otk::display;
1206 ce.xclient.window = _window;
1207 ce.xclient.format = 32;
1208 ce.xclient.data.l[0] = otk::Property::atoms.wm_delete_window;
1209 ce.xclient.data.l[1] = CurrentTime;
1210 ce.xclient.data.l[2] = 0l;
1211 ce.xclient.data.l[3] = 0l;
1212 ce.xclient.data.l[4] = 0l;
1213 XSendEvent(**otk::display, _window, false, NoEventMask, &ce);
1217 void Client::changeState()
1219 unsigned long state[2];
1220 state[0] = _wmstate;
1222 otk::Property::set(_window, otk::Property::atoms.wm_state,
1223 otk::Property::atoms.wm_state, state, 2);
1228 netstate[num++] = otk::Property::atoms.net_wm_state_modal;
1230 netstate[num++] = otk::Property::atoms.net_wm_state_shaded;
1232 netstate[num++] = otk::Property::atoms.net_wm_state_hidden;
1234 netstate[num++] = otk::Property::atoms.net_wm_state_skip_taskbar;
1236 netstate[num++] = otk::Property::atoms.net_wm_state_skip_pager;
1238 netstate[num++] = otk::Property::atoms.net_wm_state_fullscreen;
1240 netstate[num++] = otk::Property::atoms.net_wm_state_maximized_vert;
1242 netstate[num++] = otk::Property::atoms.net_wm_state_maximized_horz;
1244 netstate[num++] = otk::Property::atoms.net_wm_state_above;
1246 netstate[num++] = otk::Property::atoms.net_wm_state_below;
1247 otk::Property::set(_window, otk::Property::atoms.net_wm_state,
1248 otk::Property::atoms.atom, netstate, num);
1253 frame->adjustState();
1257 void Client::changeAllowedActions(void)
1262 actions[num++] = otk::Property::atoms.net_wm_action_change_desktop;
1264 if (_functions & Func_Shade)
1265 actions[num++] = otk::Property::atoms.net_wm_action_shade;
1266 if (_functions & Func_Close)
1267 actions[num++] = otk::Property::atoms.net_wm_action_close;
1268 if (_functions & Func_Move)
1269 actions[num++] = otk::Property::atoms.net_wm_action_move;
1270 if (_functions & Func_Iconify)
1271 actions[num++] = otk::Property::atoms.net_wm_action_minimize;
1272 if (_functions & Func_Resize)
1273 actions[num++] = otk::Property::atoms.net_wm_action_resize;
1274 if (_functions & Func_Fullscreen)
1275 actions[num++] = otk::Property::atoms.net_wm_action_fullscreen;
1276 if (_functions & Func_Maximize) {
1277 actions[num++] = otk::Property::atoms.net_wm_action_maximize_horz;
1278 actions[num++] = otk::Property::atoms.net_wm_action_maximize_vert;
1281 otk::Property::set(_window, otk::Property::atoms.net_wm_allowed_actions,
1282 otk::Property::atoms.atom, actions, num);
1286 void Client::remaximize()
1289 if (_max_horz && _max_vert)
1296 return; // not maximized
1297 _max_horz = _max_vert = false;
1298 maximize(true, dir, false);
1302 void Client::applyStartupState()
1304 // these are in a carefully crafted order..
1308 setDesktop(ICONIC_DESKTOP);
1311 _fullscreen = false;
1312 fullscreen(true, false);
1321 if (_max_vert && _max_horz) {
1322 _max_vert = _max_horz = false;
1323 maximize(true, 0, false);
1324 } else if (_max_vert) {
1326 maximize(true, 2, false);
1327 } else if (_max_horz) {
1329 maximize(true, 1, false);
1332 if (_skip_taskbar); // nothing to do for this
1333 if (_skip_pager); // nothing to do for this
1334 if (_modal); // nothing to do for this
1335 if (_above); // nothing to do for this
1336 if (_below); // nothing to do for this
1340 void Client::fireUrgent()
1342 // call the python UrgentWindow callbacks
1343 EventData data(_screen, this, EventAction::UrgentWindow, 0);
1344 openbox->bindings()->fireEvent(&data);
1348 void Client::shade(bool shade)
1350 if (!(_functions & Func_Shade) || // can't
1351 _shaded == shade) return; // already done
1353 // when we're iconic, don't change the wmstate
1355 _wmstate = shade ? IconicState : NormalState;
1358 frame->adjustSize();
1362 void Client::maximize(bool max, int dir, bool savearea)
1364 assert(dir == 0 || dir == 1 || dir == 2);
1365 if (!(_functions & Func_Maximize)) return; // can't
1367 // check if already done
1369 if (dir == 0 && _max_horz && _max_vert) return;
1370 if (dir == 1 && _max_horz) return;
1371 if (dir == 2 && _max_vert) return;
1373 if (dir == 0 && !_max_horz && !_max_vert) return;
1374 if (dir == 1 && !_max_horz) return;
1375 if (dir == 2 && !_max_vert) return;
1378 const otk::Rect &a = openbox->screen(_screen)->area();
1379 int x = frame->rect().x(), y = frame->rect().y(),
1380 w = _area.width(), h = _area.height();
1386 unsigned long n = 4;
1393 // get the property off the window and use it for the dimentions we are
1395 if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1396 otk::Property::atoms.cardinal, &n,
1397 (long unsigned**) &readdim)) {
1400 dimensions[0] = readdim[0];
1401 dimensions[2] = readdim[2];
1404 dimensions[1] = readdim[1];
1405 dimensions[3] = readdim[3];
1411 otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1412 otk::Property::atoms.cardinal,
1413 (long unsigned*)dimensions, 4);
1415 if (dir == 0 || dir == 1) { // horz
1419 if (dir == 0 || dir == 2) { // vert
1421 h = a.height() - frame->size().top - frame->size().bottom;
1425 long unsigned n = 4;
1427 if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1428 otk::Property::atoms.cardinal, &n,
1429 (long unsigned**) &dimensions)) {
1431 if (dir == 0 || dir == 1) { // horz
1432 x = (signed int)dimensions[0];
1433 w = (signed int)dimensions[2];
1435 if (dir == 0 || dir == 2) { // vert
1436 y = (signed int)dimensions[1];
1437 h = (signed int)dimensions[3];
1442 // pick some fallbacks...
1443 if (dir == 0 || dir == 1) { // horz
1444 x = a.x() + a.width() / 4;
1447 if (dir == 0 || dir == 2) { // vert
1448 y = a.y() + a.height() / 4;
1454 if (dir == 0 || dir == 1) // horz
1456 if (dir == 0 || dir == 2) // vert
1459 if (!_max_horz && !_max_vert)
1460 otk::Property::erase(_window, otk::Property::atoms.openbox_premax);
1462 changeState(); // change the state hints on the client
1464 frame->frameGravity(x, y); // figure out where the client should be going
1465 internal_resize(TopLeft, w, h, true, x, y);
1469 void Client::fullscreen(bool fs, bool savearea)
1471 static FunctionFlags saved_func;
1472 static DecorationFlags saved_decor;
1474 if (!(_functions & Func_Fullscreen) || // can't
1475 _fullscreen == fs) return; // already done
1478 changeState(); // change the state hints on the client
1480 int x = _area.x(), y = _area.y(), w = _area.width(), h = _area.height();
1483 // save the functions and remove them
1484 saved_func = _functions;
1485 _functions = _functions & (Func_Close | Func_Fullscreen | Func_Iconify);
1486 // save the decorations and remove them
1487 saved_decor = _decorations;
1491 dimensions[0] = _area.x();
1492 dimensions[1] = _area.y();
1493 dimensions[2] = _area.width();
1494 dimensions[3] = _area.height();
1495 otk::Property::set(_window, otk::Property::atoms.openbox_premax,
1496 otk::Property::atoms.cardinal,
1497 (long unsigned*)dimensions, 4);
1499 const otk::ScreenInfo *info = otk::display->screenInfo(_screen);
1505 _functions = saved_func;
1506 _decorations = saved_decor;
1509 long unsigned n = 4;
1511 if (otk::Property::get(_window, otk::Property::atoms.openbox_premax,
1512 otk::Property::atoms.cardinal, &n,
1513 (long unsigned**) &dimensions)) {
1522 // pick some fallbacks...
1523 const otk::Rect &a = openbox->screen(_screen)->area();
1524 x = a.x() + a.width() / 4;
1525 y = a.y() + a.height() / 4;
1531 changeAllowedActions(); // based on the new _functions
1533 // when fullscreening, don't obey things like increments, fill the screen
1534 internal_resize(TopLeft, w, h, !fs, x, y);
1536 // raise (back) into our stacking layer
1537 openbox->screen(_screen)->raiseWindow(this);
1539 // try focus us when we go into fullscreen mode
1544 void Client::disableDecorations(DecorationFlags flags)
1546 _disabled_decorations = flags;
1547 setupDecorAndFunctions();
1551 bool Client::focusModalChild()
1553 // find a modal child recursively and try focus it
1554 List::iterator it, end = _transients.end();
1555 for (it = _transients.begin(); it != end; ++it)
1556 if ((*it)->focusModalChild())
1557 return true; // got one
1558 // none of our grand-children are modal, try our direct children
1559 for (it = _transients.begin(); it != end; ++it)
1560 if ((*it)->modal() && (*it)->focus())
1561 return true; // got one
1566 bool Client::focus()
1568 // won't try focus if the client doesn't want it, or if the window isn't
1569 // visible on the screen
1570 if (!(frame->isVisible() && (_can_focus || _focus_notify))) return false;
1572 if (_focused) return true;
1575 if (focusModalChild())
1578 // do a check to see if the window has already been unmapped or destroyed
1579 // do this intelligently while watching out for unmaps we've generated
1580 // (ignore_unmaps > 0)
1582 if (XCheckTypedWindowEvent(**otk::display, _window, DestroyNotify, &ev)) {
1583 XPutBackEvent(**otk::display, &ev);
1586 while (XCheckTypedWindowEvent(**otk::display, _window, UnmapNotify, &ev)) {
1587 if (ignore_unmaps) {
1588 unmapHandler(ev.xunmap);
1590 XPutBackEvent(**otk::display, &ev);
1596 XSetInputFocus(**otk::display, _window,
1597 RevertToNone, CurrentTime);
1599 if (_focus_notify) {
1601 ce.xclient.type = ClientMessage;
1602 ce.xclient.message_type = otk::Property::atoms.wm_protocols;
1603 ce.xclient.display = **otk::display;
1604 ce.xclient.window = _window;
1605 ce.xclient.format = 32;
1606 ce.xclient.data.l[0] = otk::Property::atoms.wm_take_focus;
1607 ce.xclient.data.l[1] = openbox->lastTime();
1608 ce.xclient.data.l[2] = 0l;
1609 ce.xclient.data.l[3] = 0l;
1610 ce.xclient.data.l[4] = 0l;
1611 XSendEvent(**otk::display, _window, False, NoEventMask, &ce);
1614 XSync(**otk::display, False);
1619 void Client::unfocus() const
1621 if (!_focused) return;
1623 assert(openbox->focusedClient() == this);
1624 openbox->setFocusedClient(0);
1628 void Client::focusHandler(const XFocusChangeEvent &e)
1631 // printf("FocusIn for 0x%lx\n", e.window);
1634 otk::EventHandler::focusHandler(e);
1639 openbox->setFocusedClient(this);
1643 void Client::unfocusHandler(const XFocusChangeEvent &e)
1646 // printf("FocusOut for 0x%lx\n", e.window);
1649 otk::EventHandler::unfocusHandler(e);
1654 if (openbox->focusedClient() == this)
1655 openbox->setFocusedClient(0);
1659 void Client::configureRequestHandler(const XConfigureRequestEvent &e)
1662 printf("ConfigureRequest for 0x%lx\n", e.window);
1665 otk::EventHandler::configureRequestHandler(e);
1667 // if we are iconic (or shaded (fvwm does this)) ignore the event
1668 if (_iconic || _shaded) return;
1670 if (e.value_mask & CWBorderWidth)
1671 _border_width = e.border_width;
1673 // resize, then move, as specified in the EWMH section 7.7
1674 if (e.value_mask & (CWWidth | CWHeight)) {
1675 int w = (e.value_mask & CWWidth) ? e.width : _area.width();
1676 int h = (e.value_mask & CWHeight) ? e.height : _area.height();
1680 case NorthEastGravity:
1684 case SouthWestGravity:
1686 corner = BottomLeft;
1688 case SouthEastGravity:
1689 corner = BottomRight;
1691 default: // NorthWest, Static, etc
1695 // if moving AND resizing ...
1696 if (e.value_mask & (CWX | CWY)) {
1697 int x = (e.value_mask & CWX) ? e.x : _area.x();
1698 int y = (e.value_mask & CWY) ? e.y : _area.y();
1699 internal_resize(corner, w, h, false, x, y);
1700 } else // if JUST resizing...
1701 internal_resize(corner, w, h, false);
1702 } else if (e.value_mask & (CWX | CWY)) { // if JUST moving...
1703 int x = (e.value_mask & CWX) ? e.x : _area.x();
1704 int y = (e.value_mask & CWY) ? e.y : _area.y();
1705 internal_move(x, y);
1708 if (e.value_mask & CWStackMode) {
1712 openbox->screen(_screen)->lowerWindow(this);
1718 openbox->screen(_screen)->raiseWindow(this);
1725 void Client::unmapHandler(const XUnmapEvent &e)
1727 if (ignore_unmaps) {
1729 // printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event);
1736 printf("UnmapNotify for 0x%lx\n", e.window);
1739 otk::EventHandler::unmapHandler(e);
1741 // this deletes us etc
1742 openbox->screen(_screen)->unmanageWindow(this);
1746 void Client::destroyHandler(const XDestroyWindowEvent &e)
1749 printf("DestroyNotify for 0x%lx\n", e.window);
1752 otk::EventHandler::destroyHandler(e);
1754 // this deletes us etc
1755 openbox->screen(_screen)->unmanageWindow(this);
1759 void Client::reparentHandler(const XReparentEvent &e)
1761 // this is when the client is first taken captive in the frame
1762 if (e.parent == frame->plate()) return;
1765 printf("ReparentNotify for 0x%lx\n", e.window);
1768 otk::EventHandler::reparentHandler(e);
1771 This event is quite rare and is usually handled in unmapHandler.
1772 However, if the window is unmapped when the reparent event occurs,
1773 the window manager never sees it because an unmap event is not sent
1774 to an already unmapped window.
1777 // we don't want the reparent event, put it back on the stack for the X
1778 // server to deal with after we unmanage the window
1781 XPutBackEvent(**otk::display, &ev);
1783 // this deletes us etc
1784 openbox->screen(_screen)->unmanageWindow(this);
1787 void Client::mapRequestHandler(const XMapRequestEvent &e)
1790 printf("MapRequest for already managed 0x%lx\n", e.window);
1793 assert(_iconic); // we shouldn't be able to get this unless we're iconic
1795 // move to the current desktop (uniconify)
1796 setDesktop(openbox->screen(_screen)->desktop());
1797 // XXX: should we focus/raise the window? (basically a net_wm_active_window)