X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fclient.cc;h=cdcbfbcc14bb1d8d7f5416d276c20c3ae811a60b;hb=cec568b076e903876a91d2922f9fa11e53e3df0b;hp=ec907538ccf9cf0876bd18dc86718d1c7624dd52;hpb=aa8047d68b9c558e632d37f3ebb3e2325978c9ff;p=chaz%2Fopenbox diff --git a/src/client.cc b/src/client.cc index ec907538..cdcbfbcc 100644 --- a/src/client.cc +++ b/src/client.cc @@ -1,6 +1,11 @@ // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*- +#ifdef HAVE_CONFIG_H +# include "../config.h" +#endif + #include "client.hh" +#include "frame.hh" #include "screen.hh" #include "openbox.hh" #include "otk/display.hh" @@ -18,71 +23,48 @@ extern "C" { namespace ob { -OBClient::OBClient(Window window) - : _window(window) +OBClient::OBClient(int screen, Window window) + : otk::OtkEventHandler(), + OBWidget(OBWidget::Type_Client), + frame(0), _screen(screen), _window(window) { + assert(screen >= 0); assert(window); + ignore_unmaps = 0; + // update EVERYTHING the first time!! // the state is kinda assumed to be normal. is this right? XXX - _wmstate = NormalState; + _wmstate = NormalState; _iconic = false; + // no default decors or functions, each has to be enabled + _decorations = _functions = 0; + // start unfocused + _focused = false; + // not a transient by default of course + _transient_for = 0; getArea(); getDesktop(); + + updateTransientFor(); getType(); + getMwmHints(); + + setupDecorAndFunctions(); + getState(); getShaped(); + updateProtocols(); updateNormalHints(); updateWMHints(); - // XXX: updateTransientFor(); updateTitle(); + updateIconTitle(); updateClass(); + updateStrut(); -#ifdef DEBUG - printf("Mapped window: 0x%lx\n" - " title: \t%s\t icon title: \t%s\n" - " app name: \t%s\t\t class: \t%s\n" - " position: \t%d, %d\t\t size: \t%d, %d\n" - " desktop: \t%lu\t\t group: \t0x%lx\n" - " type: \t%d\t\t min size \t%d, %d\n" - " base size \t%d, %d\t\t max size \t%d, %d\n" - " size incr \t%d, %d\t\t gravity \t%d\n" - " wm state \t%ld\t\t can be focused:\t%s\n" - " notify focus: \t%s\t\t urgent: \t%s\n" - " shaped: \t%s\t\t modal: \t%s\n" - " shaded: \t%s\t\t iconic: \t%s\n" - " vert maximized:\t%s\t\t horz maximized:\t%s\n" - " fullscreen: \t%s\t\t floating: \t%s\n", - _window, - _title.c_str(), - _icon_title.c_str(), - _app_name.c_str(), - _app_class.c_str(), - _area.x(), _area.y(), - _area.width(), _area.height(), - _desktop, - _group, - _type, - _min_x, _min_y, - _base_x, _base_y, - _max_x, _max_y, - _inc_x, _inc_y, - _gravity, - _wmstate, - _can_focus ? "yes" : "no", - _focus_notify ? "yes" : "no", - _urgent ? "yes" : "no", - _shaped ? "yes" : "no", - _modal ? "yes" : "no", - _shaded ? "yes" : "no", - _iconic ? "yes" : "no", - _max_vert ? "yes" : "no", - _max_horz ? "yes" : "no", - _fullscreen ? "yes" : "no", - _floating ? "yes" : "no"); -#endif + changeState(); } @@ -90,9 +72,15 @@ OBClient::~OBClient() { const otk::OBProperty *property = Openbox::instance->property(); - // these values should not be persisted across a window unmapping/mapping - property->erase(_window, otk::OBProperty::net_wm_desktop); - property->erase(_window, otk::OBProperty::net_wm_state); + // clean up parents reference to this + if (_transient_for) + _transient_for->_transients.remove(this); // remove from old parent + + if (Openbox::instance->state() != Openbox::State_Exiting) { + // these values should not be persisted across a window unmapping/mapping + property->erase(_window, otk::OBProperty::net_wm_desktop); + property->erase(_window, otk::OBProperty::net_wm_state); + } } @@ -101,11 +89,17 @@ void OBClient::getDesktop() const otk::OBProperty *property = Openbox::instance->property(); // defaults to the current desktop - _desktop = 0; // XXX: change this to the current desktop! - - property->get(_window, otk::OBProperty::net_wm_desktop, - otk::OBProperty::Atom_Cardinal, - &_desktop); + _desktop = Openbox::instance->screen(_screen)->desktop(); + + if (!property->get(_window, otk::OBProperty::net_wm_desktop, + otk::OBProperty::Atom_Cardinal, + (long unsigned*)&_desktop)) { + // make sure the hint exists + Openbox::instance->property()->set(_window, + otk::OBProperty::net_wm_desktop, + otk::OBProperty::Atom_Cardinal, + (unsigned)_desktop); + } } @@ -150,6 +144,8 @@ void OBClient::getType() // property->atom(otk::OBProperty::kde_net_wm_window_type_override)) // mwm_decorations = 0; // prevent this window from getting any decor // XXX: make this work again + if (_type != (WindowType) -1) + break; // grab the first known type } delete val; } @@ -159,21 +155,121 @@ void OBClient::getType() * the window type hint was not set, which means we either classify ourself * as a normal window or a dialog, depending on if we are a transient. */ - // XXX: make this code work! - //if (isTransient()) - // _type = Type_Dialog; - //else + if (_transient_for) + _type = Type_Dialog; + else _type = Type_Normal; } } +void OBClient::setupDecorAndFunctions() +{ + // start with everything + _decorations = Decor_Titlebar | Decor_Handle | Decor_Border | + Decor_Iconify | Decor_Maximize; + _functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize; + + switch (_type) { + case Type_Normal: + // normal windows retain all of the possible decorations and + // functionality + + case Type_Dialog: + // dialogs cannot be maximized + _decorations &= ~Decor_Maximize; + _functions &= ~Func_Maximize; + break; + + case Type_Menu: + case Type_Toolbar: + case Type_Utility: + // these windows get less functionality + _decorations &= ~(Decor_Iconify | Decor_Handle); + _functions &= ~(Func_Iconify | Func_Resize); + break; + + case Type_Desktop: + case Type_Dock: + case Type_Splash: + // none of these windows are manipulated by the window manager + _decorations = 0; + _functions = 0; + break; + } + + // Mwm Hints are applied subtractively to what has already been chosen for + // decor and functionality + if (_mwmhints.flags & MwmFlag_Decorations) { + if (! (_mwmhints.decorations & MwmDecor_All)) { + if (! (_mwmhints.decorations & MwmDecor_Border)) + _decorations &= ~Decor_Border; + if (! (_mwmhints.decorations & MwmDecor_Handle)) + _decorations &= ~Decor_Handle; + if (! (_mwmhints.decorations & MwmDecor_Title)) + _decorations &= ~Decor_Titlebar; + if (! (_mwmhints.decorations & MwmDecor_Iconify)) + _decorations &= ~Decor_Iconify; + if (! (_mwmhints.decorations & MwmDecor_Maximize)) + _decorations &= ~Decor_Maximize; + } + } + + if (_mwmhints.flags & MwmFlag_Functions) { + if (! (_mwmhints.functions & MwmFunc_All)) { + if (! (_mwmhints.functions & MwmFunc_Resize)) + _functions &= ~Func_Resize; + if (! (_mwmhints.functions & MwmFunc_Move)) + _functions &= ~Func_Move; + if (! (_mwmhints.functions & MwmFunc_Iconify)) + _functions &= ~Func_Iconify; + if (! (_mwmhints.functions & MwmFunc_Maximize)) + _functions &= ~Func_Maximize; + // dont let mwm hints kill the close button + //if (! (_mwmhints.functions & MwmFunc_Close)) + // _functions &= ~Func_Close; + } + } + + // XXX: changeAllowedActions(); +} + + +void OBClient::getMwmHints() +{ + const otk::OBProperty *property = Openbox::instance->property(); + + unsigned long num = MwmHints::elements; + unsigned long *hints; + + _mwmhints.flags = 0; // default to none + + if (!property->get(_window, otk::OBProperty::motif_wm_hints, + otk::OBProperty::motif_wm_hints, &num, + (unsigned long **)&hints)) + return; + + if (num >= MwmHints::elements) { + // retrieved the hints + _mwmhints.flags = hints[0]; + _mwmhints.functions = hints[1]; + _mwmhints.decorations = hints[2]; + } + + delete [] hints; +} + + void OBClient::getArea() { XWindowAttributes wattrib; - assert(XGetWindowAttributes(otk::OBDisplay::display, _window, &wattrib)); + Status ret; + + ret = XGetWindowAttributes(otk::OBDisplay::display, _window, &wattrib); + assert(ret != BadWindow); _area.setRect(wattrib.x, wattrib.y, wattrib.width, wattrib.height); + _border_width = wattrib.border_width; } @@ -181,7 +277,8 @@ void OBClient::getState() { const otk::OBProperty *property = Openbox::instance->property(); - _modal = _shaded = _max_horz = _max_vert = _fullscreen = _floating = false; + _modal = _shaded = _max_horz = _max_vert = _fullscreen = _above = _below = + _skip_taskbar = _skip_pager = false; unsigned long *state; unsigned long num = (unsigned) -1; @@ -192,8 +289,15 @@ void OBClient::getState() if (state[i] == property->atom(otk::OBProperty::net_wm_state_modal)) _modal = true; else if (state[i] == - property->atom(otk::OBProperty::net_wm_state_shaded)) + property->atom(otk::OBProperty::net_wm_state_shaded)) { _shaded = true; + _wmstate = IconicState; + } else if (state[i] == + property->atom(otk::OBProperty::net_wm_state_skip_taskbar)) + _skip_taskbar = true; + else if (state[i] == + property->atom(otk::OBProperty::net_wm_state_skip_pager)) + _skip_pager = true; else if (state[i] == property->atom(otk::OBProperty::net_wm_state_fullscreen)) _fullscreen = true; @@ -203,6 +307,12 @@ void OBClient::getState() else if (state[i] == property->atom(otk::OBProperty::net_wm_state_maximized_horz)) _max_horz = true; + else if (state[i] == + property->atom(otk::OBProperty::net_wm_state_above)) + _above = true; + else if (state[i] == + property->atom(otk::OBProperty::net_wm_state_below)) + _below = true; } delete [] state; @@ -217,46 +327,116 @@ void OBClient::getShaped() if (otk::OBDisplay::shape()) { int foo; unsigned int ufoo; + int s; - XShapeQueryExtents(otk::OBDisplay::display, client.window, &_shaped, &foo, + XShapeSelectInput(otk::OBDisplay::display, _window, ShapeNotifyMask); + + XShapeQueryExtents(otk::OBDisplay::display, _window, &s, &foo, &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo); + _shaped = (s != 0); } #endif // SHAPE } +void OBClient::calcLayer() { + StackLayer l; + + if (_iconic) l = Layer_Icon; + else if (_fullscreen) l = Layer_Fullscreen; + else if (_type == Type_Desktop) l = Layer_Desktop; + else if (_type == Type_Dock) { + if (!_below) l = Layer_Top; + else l = Layer_Normal; + } + else if (_above) l = Layer_Above; + else if (_below) l = Layer_Below; + else l = Layer_Normal; + + if (l != _layer) { + _layer = l; + if (frame) { + /* + if we don't have a frame, then we aren't mapped yet (and this would + SIGSEGV :) + */ + Openbox::instance->screen(_screen)->restack(true, this); // raise + } + } +} + + +void OBClient::updateProtocols() +{ + const otk::OBProperty *property = Openbox::instance->property(); + + Atom *proto; + int num_return = 0; + + _focus_notify = false; + _decorations &= ~Decor_Close; + _functions &= ~Func_Close; + + if (XGetWMProtocols(otk::OBDisplay::display, _window, &proto, &num_return)) { + for (int i = 0; i < num_return; ++i) { + if (proto[i] == property->atom(otk::OBProperty::wm_delete_window)) { + _decorations |= Decor_Close; + _functions |= Func_Close; + if (frame) + frame->adjustSize(); // update the decorations + } else if (proto[i] == property->atom(otk::OBProperty::wm_take_focus)) + // if this protocol is requested, then the window will be notified + // by the window manager whenever it receives focus + _focus_notify = true; + } + XFree(proto); + } +} + + void OBClient::updateNormalHints() { XSizeHints size; long ret; + int oldgravity = _gravity; // defaults _gravity = NorthWestGravity; - _inc_x = _inc_y = 1; - _base_x = _base_y = 0; - _min_x = _min_y = 0; - _max_x = _max_y = INT_MAX; + _size_inc.setPoint(1, 1); + _base_size.setPoint(0, 0); + _min_size.setPoint(0, 0); + _max_size.setPoint(INT_MAX, INT_MAX); + + // XXX: might want to cancel any interactive resizing of the window at this + // point.. // get the hints from the window if (XGetWMNormalHints(otk::OBDisplay::display, _window, &size, &ret)) { + _positioned = (size.flags & (PPosition|USPosition)); + if (size.flags & PWinGravity) _gravity = size.win_gravity; - if (size.flags & PMinSize) { - _min_x = size.min_width; - _min_y = size.min_height; - } - if (size.flags & PMaxSize) { - _max_x = size.max_width; - _max_y = size.max_height; - } - if (size.flags & PBaseSize) { - _base_x = size.base_width; - _base_y = size.base_height; - } - if (size.flags & PResizeInc) { - _inc_x = size.width_inc; - _inc_y = size.height_inc; - } + + if (size.flags & PMinSize) + _min_size.setPoint(size.min_width, size.min_height); + + if (size.flags & PMaxSize) + _max_size.setPoint(size.max_width, size.max_height); + + if (size.flags & PBaseSize) + _base_size.setPoint(size.base_width, size.base_height); + + if (size.flags & PResizeInc) + _size_inc.setPoint(size.width_inc, size.height_inc); + } + + // if the client has a frame, i.e. has already been mapped and is + // changing its gravity + if (frame && _gravity != oldgravity) { + // move our idea of the client's position based on its new gravity + int x, y; + frame->frameGravity(x, y); + _area.setPos(x, y); } } @@ -306,6 +486,28 @@ void OBClient::updateTitle() if (_title.empty()) _title = _("Unnamed Window"); + + if (frame) + frame->setTitle(_title); +} + + +void OBClient::updateIconTitle() +{ + const otk::OBProperty *property = Openbox::instance->property(); + + _icon_title = ""; + + // try netwm + if (! property->get(_window, otk::OBProperty::net_wm_icon_name, + otk::OBProperty::utf8, &_icon_title)) { + // try old x stuff + property->get(_window, otk::OBProperty::wm_icon_name, + otk::OBProperty::ascii, &_icon_title); + } + + if (_title.empty()) + _icon_title = _("Unnamed Window"); } @@ -314,36 +516,124 @@ void OBClient::updateClass() const otk::OBProperty *property = Openbox::instance->property(); // set the defaults - _app_name = _app_class = ""; + _app_name = _app_class = _role = ""; otk::OBProperty::StringVect v; unsigned long num = 2; - if (! property->get(_window, otk::OBProperty::wm_class, - otk::OBProperty::ascii, &num, &v)) + if (property->get(_window, otk::OBProperty::wm_class, + otk::OBProperty::ascii, &num, &v)) { + if (num > 0) _app_name = v[0]; + if (num > 1) _app_class = v[1]; + } + + v.clear(); + num = 1; + if (property->get(_window, otk::OBProperty::wm_window_role, + otk::OBProperty::ascii, &num, &v)) { + if (num > 0) _role = v[0]; + } +} + + +void OBClient::updateStrut() +{ + unsigned long num = 4; + unsigned long *data; + if (!Openbox::instance->property()->get(_window, + otk::OBProperty::net_wm_strut, + otk::OBProperty::Atom_Cardinal, + &num, &data)) return; - if (num > 0) _app_name = v[0]; - if (num > 1) _app_class = v[1]; + if (num == 4) { + _strut.left = data[0]; + _strut.right = data[1]; + _strut.top = data[2]; + _strut.bottom = data[3]; + + Openbox::instance->screen(_screen)->updateStrut(); + } + + delete [] data; +} + + +void OBClient::updateTransientFor() +{ + Window t = 0; + OBClient *c = 0; + + if (XGetTransientForHint(otk::OBDisplay::display, _window, &t) && + t != _window) { // cant be transient to itself! + c = Openbox::instance->findClient(t); + assert(c != this); // if this happens then we need to check for it + + if (!c /*XXX: && _group*/) { + // not transient to a client, see if it is transient for a group + if (//t == _group->leader() || + t == None || + t == otk::OBDisplay::screenInfo(_screen)->rootWindow()) { + // window is a transient for its group! + // XXX: for now this is treated as non-transient. + // this needs to be fixed! + } + } + } + + // if anything has changed... + if (c != _transient_for) { + if (_transient_for) + _transient_for->_transients.remove(this); // remove from old parent + _transient_for = c; + if (_transient_for) + _transient_for->_transients.push_back(this); // add to new parent + + // XXX: change decor status? + } } -void OBClient::update(const XPropertyEvent &e) +void OBClient::propertyHandler(const XPropertyEvent &e) { + otk::OtkEventHandler::propertyHandler(e); + const otk::OBProperty *property = Openbox::instance->property(); + // compress changes to a single property into a single change + XEvent ce; + while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) { + // XXX: it would be nice to compress ALL changes to a property, not just + // changes in a row without other props between. + if (ce.xproperty.atom != e.atom) { + XPutBackEvent(otk::OBDisplay::display, &ce); + break; + } + } + if (e.atom == XA_WM_NORMAL_HINTS) updateNormalHints(); else if (e.atom == XA_WM_HINTS) updateWMHints(); + else if (e.atom == XA_WM_TRANSIENT_FOR) { + updateTransientFor(); + getType(); + calcLayer(); // type may have changed, so update the layer + setupDecorAndFunctions(); + frame->adjustSize(); // this updates the frame for any new decor settings + } else if (e.atom == property->atom(otk::OBProperty::net_wm_name) || - e.atom == property->atom(otk::OBProperty::wm_name) || - e.atom == property->atom(otk::OBProperty::net_wm_icon_name) || - e.atom == property->atom(otk::OBProperty::wm_icon_name)) + e.atom == property->atom(otk::OBProperty::wm_name)) updateTitle(); + else if (e.atom == property->atom(otk::OBProperty::net_wm_icon_name) || + e.atom == property->atom(otk::OBProperty::wm_icon_name)) + updateIconTitle(); else if (e.atom == property->atom(otk::OBProperty::wm_class)) updateClass(); - // XXX: transient for hint + else if (e.atom == property->atom(otk::OBProperty::wm_protocols)) + updateProtocols(); + else if (e.atom == property->atom(otk::OBProperty::net_wm_strut)) + updateStrut(); } @@ -351,7 +641,8 @@ void OBClient::setWMState(long state) { if (state == _wmstate) return; // no change - switch (state) { + _wmstate = state; + switch (_wmstate) { case IconicState: // XXX: cause it to iconify break; @@ -359,23 +650,37 @@ void OBClient::setWMState(long state) // XXX: cause it to uniconify break; } - _wmstate = state; } void OBClient::setDesktop(long target) { - assert(target >= 0); - //assert(target == 0xffffffff || target < MAX); + if (target == _desktop) return; + + printf("Setting desktop %ld\n", target); + + if (!(target >= 0 || target == (signed)0xffffffff)) return; - // XXX: move the window to the new desktop _desktop = target; + + Openbox::instance->property()->set(_window, + otk::OBProperty::net_wm_desktop, + otk::OBProperty::Atom_Cardinal, + (unsigned)_desktop); + + // 'move' the window to the new desktop + if (_desktop == Openbox::instance->screen(_screen)->desktop() || + _desktop == (signed)0xffffffff) + frame->show(); + else + frame->hide(); } void OBClient::setState(StateAction action, long data1, long data2) { const otk::OBProperty *property = Openbox::instance->property(); + bool shadestate = _shaded; if (!(action == State_Add || action == State_Remove || action == State_Toggle)) @@ -398,11 +703,19 @@ void OBClient::setState(StateAction action, long data1, long data2) action = _max_horz ? State_Remove : State_Add; else if (state == property->atom(otk::OBProperty::net_wm_state_shaded)) action = _shaded ? State_Remove : State_Add; + else if (state == + property->atom(otk::OBProperty::net_wm_state_skip_taskbar)) + action = _skip_taskbar ? State_Remove : State_Add; + else if (state == + property->atom(otk::OBProperty::net_wm_state_skip_pager)) + action = _skip_pager ? State_Remove : State_Add; else if (state == property->atom(otk::OBProperty::net_wm_state_fullscreen)) action = _fullscreen ? State_Remove : State_Add; - else if (state == property->atom(otk::OBProperty::net_wm_state_floating)) - action = _floating ? State_Remove : State_Add; + else if (state == property->atom(otk::OBProperty::net_wm_state_above)) + action = _above ? State_Remove : State_Add; + else if (state == property->atom(otk::OBProperty::net_wm_state_below)) + action = _below ? State_Remove : State_Add; } if (action == State_Add) { @@ -423,18 +736,26 @@ void OBClient::setState(StateAction action, long data1, long data2) } else if (state == property->atom(otk::OBProperty::net_wm_state_shaded)) { if (_shaded) continue; - _shaded = true; - // XXX: hide the client window + // shade when we're all thru here + shadestate = true; + } else if (state == + property->atom(otk::OBProperty::net_wm_state_skip_taskbar)) { + _skip_taskbar = true; + } else if (state == + property->atom(otk::OBProperty::net_wm_state_skip_pager)) { + _skip_pager = true; } else if (state == property->atom(otk::OBProperty::net_wm_state_fullscreen)) { if (_fullscreen) continue; _fullscreen = true; - // XXX: raise the window n shit } else if (state == - property->atom(otk::OBProperty::net_wm_state_floating)) { - if (_floating) continue; - _floating = true; - // XXX: raise the window n shit + property->atom(otk::OBProperty::net_wm_state_above)) { + if (_above) continue; + _above = true; + } else if (state == + property->atom(otk::OBProperty::net_wm_state_below)) { + if (_below) continue; + _below = true; } } else { // action == State_Remove @@ -454,43 +775,514 @@ void OBClient::setState(StateAction action, long data1, long data2) } else if (state == property->atom(otk::OBProperty::net_wm_state_shaded)) { if (!_shaded) continue; - _shaded = false; - // XXX: show the client window + // unshade when we're all thru here + shadestate = false; + } else if (state == + property->atom(otk::OBProperty::net_wm_state_skip_taskbar)) { + _skip_taskbar = false; + } else if (state == + property->atom(otk::OBProperty::net_wm_state_skip_pager)) { + _skip_pager = false; } else if (state == property->atom(otk::OBProperty::net_wm_state_fullscreen)) { if (!_fullscreen) continue; _fullscreen = false; - // XXX: lower the window to its proper layer } else if (state == - property->atom(otk::OBProperty::net_wm_state_floating)) { - if (!_floating) continue; - _floating = false; - // XXX: lower the window to its proper layer + property->atom(otk::OBProperty::net_wm_state_above)) { + if (!_above) continue; + _above = false; + } else if (state == + property->atom(otk::OBProperty::net_wm_state_below)) { + if (!_below) continue; + _below = false; } } } + if (shadestate != _shaded) + shade(shadestate); + calcLayer(); +} + + +void OBClient::toggleClientBorder(bool addborder) +{ + // adjust our idea of where the client is, based on its border. When the + // border is removed, the client should now be considered to be in a + // different position. + // when re-adding the border to the client, the same operation needs to be + // reversed. + int x = _area.x(), y = _area.y(); + switch(_gravity) { + case NorthWestGravity: + case WestGravity: + case SouthWestGravity: + break; + case NorthEastGravity: + case EastGravity: + case SouthEastGravity: + if (addborder) x -= _border_width * 2; + else x += _border_width * 2; + break; + } + switch(_gravity) { + case NorthWestGravity: + case NorthGravity: + case NorthEastGravity: + break; + case SouthWestGravity: + case SouthGravity: + case SouthEastGravity: + if (addborder) y -= _border_width * 2; + else y += _border_width * 2; + break; + default: + // no change for StaticGravity etc. + break; + } + _area.setPos(x, y); + + if (addborder) { + XSetWindowBorderWidth(otk::OBDisplay::display, _window, _border_width); + + // move the client so it is back it the right spot _with_ its border! + XMoveWindow(otk::OBDisplay::display, _window, x, y); + } else + XSetWindowBorderWidth(otk::OBDisplay::display, _window, 0); } -void OBClient::update(const XClientMessageEvent &e) +void OBClient::clientMessageHandler(const XClientMessageEvent &e) { + otk::OtkEventHandler::clientMessageHandler(e); + if (e.format != 32) return; const otk::OBProperty *property = Openbox::instance->property(); - if (e.message_type == property->atom(otk::OBProperty::wm_change_state)) - setWMState(e.data.l[0]); - else if (e.message_type == - property->atom(otk::OBProperty::net_wm_desktop)) - setDesktop(e.data.l[0]); - else if (e.message_type == property->atom(otk::OBProperty::net_wm_state)) + if (e.message_type == property->atom(otk::OBProperty::wm_change_state)) { + // compress changes into a single change + bool compress = false; + XEvent ce; + while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) { + // XXX: it would be nice to compress ALL messages of a type, not just + // messages in a row without other message types between. + if (ce.xclient.message_type != e.message_type) { + XPutBackEvent(otk::OBDisplay::display, &ce); + break; + } + compress = true; + } + if (compress) + setWMState(ce.xclient.data.l[0]); // use the found event + else + setWMState(e.data.l[0]); // use the original event + } else if (e.message_type == + property->atom(otk::OBProperty::net_wm_desktop)) { + // compress changes into a single change + bool compress = false; + XEvent ce; + while (XCheckTypedEvent(otk::OBDisplay::display, e.type, &ce)) { + // XXX: it would be nice to compress ALL messages of a type, not just + // messages in a row without other message types between. + if (ce.xclient.message_type != e.message_type) { + XPutBackEvent(otk::OBDisplay::display, &ce); + break; + } + compress = true; + } + if (compress) + setDesktop(e.data.l[0]); // use the found event + else + setDesktop(e.data.l[0]); // use the original event + } else if (e.message_type == property->atom(otk::OBProperty::net_wm_state)) { + // can't compress these +#ifdef DEBUG + printf("net_wm_state %s %ld %ld for 0x%lx\n", + (e.data.l[0] == 0 ? "Remove" : e.data.l[0] == 1 ? "Add" : + e.data.l[0] == 2 ? "Toggle" : "INVALID"), + e.data.l[1], e.data.l[2], _window); +#endif setState((StateAction)e.data.l[0], e.data.l[1], e.data.l[2]); + } else if (e.message_type == + property->atom(otk::OBProperty::net_close_window)) { +#ifdef DEBUG + printf("net_close_window for 0x%lx\n", _window); +#endif + close(); + } else if (e.message_type == + property->atom(otk::OBProperty::net_active_window)) { +#ifdef DEBUG + printf("net_active_window for 0x%lx\n", _window); +#endif + focus(); + Openbox::instance->screen(_screen)->restack(true, this); // raise + } } -void OBClient::setArea(const otk::Rect &area) +#if defined(SHAPE) +void OBClient::shapeHandler(const XShapeEvent &e) { - _area = area; + otk::OtkEventHandler::shapeHandler(e); + + if (e.kind == ShapeBounding) { + _shaped = e.shaped; + frame->adjustShape(); + } +} +#endif + + +void OBClient::resize(Corner anchor, int w, int h, int x, int y) +{ + w -= _base_size.x(); + h -= _base_size.y(); + + // for interactive resizing. have to move half an increment in each + // direction. + w += _size_inc.x() / 2; + h += _size_inc.y() / 2; + + // is the window resizable? if it is not, then don't check its sizes, the + // client can do what it wants and the user can't change it anyhow + if (_min_size.x() <= _max_size.x() && _min_size.y() <= _max_size.y()) { + // smaller than min size or bigger than max size? + if (w < _min_size.x()) w = _min_size.x(); + else if (w > _max_size.x()) w = _max_size.x(); + if (h < _min_size.y()) h = _min_size.y(); + else if (h > _max_size.y()) h = _max_size.y(); + } + + // keep to the increments + w /= _size_inc.x(); + h /= _size_inc.y(); + + // store the logical size + _logical_size.setPoint(w, h); + + w *= _size_inc.x(); + h *= _size_inc.y(); + + w += _base_size.x(); + h += _base_size.y(); + + if (x == INT_MIN || y == INT_MIN) { + x = _area.x(); + y = _area.y(); + switch (anchor) { + case TopLeft: + break; + case TopRight: + x -= w - _area.width(); + break; + case BottomLeft: + y -= h - _area.height(); + break; + case BottomRight: + x -= w - _area.width(); + y -= h - _area.height(); + break; + } + } + + _area.setSize(w, h); + + XResizeWindow(otk::OBDisplay::display, _window, w, h); + + // resize the frame to match the request + frame->adjustSize(); + move(x, y); +} + + +void OBClient::move(int x, int y) +{ + _area.setPos(x, y); + + // move the frame to be in the requested position + if (frame) // this can be called while mapping, before frame exists + frame->adjustPosition(); +} + + +void OBClient::close() +{ + XEvent ce; + const otk::OBProperty *property = Openbox::instance->property(); + + if (!(_functions & Func_Close)) return; + + // XXX: itd be cool to do timeouts and shit here for killing the client's + // process off + // like... if the window is around after 5 seconds, then the close button + // turns a nice red, and if this function is called again, the client is + // explicitly killed. + + ce.xclient.type = ClientMessage; + ce.xclient.message_type = property->atom(otk::OBProperty::wm_protocols); + ce.xclient.display = otk::OBDisplay::display; + ce.xclient.window = _window; + ce.xclient.format = 32; + ce.xclient.data.l[0] = property->atom(otk::OBProperty::wm_delete_window); + ce.xclient.data.l[1] = CurrentTime; + ce.xclient.data.l[2] = 0l; + ce.xclient.data.l[3] = 0l; + ce.xclient.data.l[4] = 0l; + XSendEvent(otk::OBDisplay::display, _window, False, NoEventMask, &ce); +} + + +void OBClient::changeState() +{ + const otk::OBProperty *property = Openbox::instance->property(); + + unsigned long state[2]; + state[0] = _wmstate; + state[1] = None; + property->set(_window, otk::OBProperty::wm_state, otk::OBProperty::wm_state, + state, 2); + + Atom netstate[10]; + int num = 0; + if (_modal) + netstate[num++] = property->atom(otk::OBProperty::net_wm_state_modal); + if (_shaded) + netstate[num++] = property->atom(otk::OBProperty::net_wm_state_shaded); + if (_iconic) + netstate[num++] = property->atom(otk::OBProperty::net_wm_state_hidden); + if (_skip_taskbar) + netstate[num++] = + property->atom(otk::OBProperty::net_wm_state_skip_taskbar); + if (_skip_pager) + netstate[num++] = property->atom(otk::OBProperty::net_wm_state_skip_pager); + if (_fullscreen) + netstate[num++] = property->atom(otk::OBProperty::net_wm_state_fullscreen); + if (_max_vert) + netstate[num++] = + property->atom(otk::OBProperty::net_wm_state_maximized_vert); + if (_max_horz) + netstate[num++] = + property->atom(otk::OBProperty::net_wm_state_maximized_horz); + if (_above) + netstate[num++] = property->atom(otk::OBProperty::net_wm_state_above); + if (_below) + netstate[num++] = property->atom(otk::OBProperty::net_wm_state_below); + property->set(_window, otk::OBProperty::net_wm_state, + otk::OBProperty::Atom_Atom, netstate, num); + + calcLayer(); +} + + +void OBClient::shade(bool shade) +{ + if (shade == _shaded) return; // already done + + _wmstate = shade ? IconicState : NormalState; + _shaded = shade; + changeState(); + frame->adjustSize(); +} + + +bool OBClient::focus() const +{ + // won't try focus if the client doesn't want it, or if the window isn't + // visible on the screen + if (!(frame->isVisible() && (_can_focus || _focus_notify))) return false; + + if (_focused) return true; + + if (_can_focus) + XSetInputFocus(otk::OBDisplay::display, _window, + RevertToNone, CurrentTime); + + if (_focus_notify) { + XEvent ce; + const otk::OBProperty *property = Openbox::instance->property(); + + ce.xclient.type = ClientMessage; + ce.xclient.message_type = property->atom(otk::OBProperty::wm_protocols); + ce.xclient.display = otk::OBDisplay::display; + ce.xclient.window = _window; + ce.xclient.format = 32; + ce.xclient.data.l[0] = property->atom(otk::OBProperty::wm_take_focus); + ce.xclient.data.l[1] = Openbox::instance->lastTime(); + ce.xclient.data.l[2] = 0l; + ce.xclient.data.l[3] = 0l; + ce.xclient.data.l[4] = 0l; + XSendEvent(otk::OBDisplay::display, _window, False, NoEventMask, &ce); + } + + return true; +} + + +void OBClient::unfocus() const +{ + if (!_focused) return; + + assert(Openbox::instance->focusedClient() == this); + Openbox::instance->setFocusedClient(0); +} + + +void OBClient::focusHandler(const XFocusChangeEvent &e) +{ +#ifdef DEBUG +// printf("FocusIn for 0x%lx\n", e.window); +#endif // DEBUG + + OtkEventHandler::focusHandler(e); + + frame->focus(); + _focused = true; + + Openbox::instance->setFocusedClient(this); +} + + +void OBClient::unfocusHandler(const XFocusChangeEvent &e) +{ +#ifdef DEBUG +// printf("FocusOut for 0x%lx\n", e.window); +#endif // DEBUG + + OtkEventHandler::unfocusHandler(e); + + frame->unfocus(); + _focused = false; + + if (Openbox::instance->focusedClient() == this) + Openbox::instance->setFocusedClient(0); +} + + +void OBClient::configureRequestHandler(const XConfigureRequestEvent &e) +{ +#ifdef DEBUG + printf("ConfigureRequest for 0x%lx\n", e.window); +#endif // DEBUG + + OtkEventHandler::configureRequestHandler(e); + + // XXX: if we are iconic (or shaded? (fvwm does that)) ignore the event + + if (e.value_mask & CWBorderWidth) + _border_width = e.border_width; + + // resize, then move, as specified in the EWMH section 7.7 + if (e.value_mask & (CWWidth | CWHeight)) { + int w = (e.value_mask & CWWidth) ? e.width : _area.width(); + int h = (e.value_mask & CWHeight) ? e.height : _area.height(); + + Corner corner; + switch (_gravity) { + case NorthEastGravity: + case EastGravity: + corner = TopRight; + break; + case SouthWestGravity: + case SouthGravity: + corner = BottomLeft; + break; + case SouthEastGravity: + corner = BottomRight; + break; + default: // NorthWest, Static, etc + corner = TopLeft; + } + + // if moving AND resizing ... + if (e.value_mask & (CWX | CWY)) { + int x = (e.value_mask & CWX) ? e.x : _area.x(); + int y = (e.value_mask & CWY) ? e.y : _area.y(); + resize(corner, w, h, x, y); + } else // if JUST resizing... + resize(corner, w, h); + } else if (e.value_mask & (CWX | CWY)) { // if JUST moving... + int x = (e.value_mask & CWX) ? e.x : _area.x(); + int y = (e.value_mask & CWY) ? e.y : _area.y(); + move(x, y); + } + + if (e.value_mask & CWStackMode) { + switch (e.detail) { + case Below: + case BottomIf: + Openbox::instance->screen(_screen)->restack(false, this); // lower + break; + + case Above: + case TopIf: + default: + Openbox::instance->screen(_screen)->restack(true, this); // raise + break; + } + } +} + + +void OBClient::unmapHandler(const XUnmapEvent &e) +{ + if (ignore_unmaps) { +#ifdef DEBUG + printf("Ignored UnmapNotify for 0x%lx (event 0x%lx)\n", e.window, e.event); +#endif // DEBUG + ignore_unmaps--; + return; + } + +#ifdef DEBUG + printf("UnmapNotify for 0x%lx\n", e.window); +#endif // DEBUG + + OtkEventHandler::unmapHandler(e); + + // this deletes us etc + Openbox::instance->screen(_screen)->unmanageWindow(this); +} + + +void OBClient::destroyHandler(const XDestroyWindowEvent &e) +{ +#ifdef DEBUG + printf("DestroyNotify for 0x%lx\n", e.window); +#endif // DEBUG + + OtkEventHandler::destroyHandler(e); + + // this deletes us etc + Openbox::instance->screen(_screen)->unmanageWindow(this); +} + + +void OBClient::reparentHandler(const XReparentEvent &e) +{ + // this is when the client is first taken captive in the frame + if (e.parent == frame->plate()) return; + +#ifdef DEBUG + printf("ReparentNotify for 0x%lx\n", e.window); +#endif // DEBUG + + OtkEventHandler::reparentHandler(e); + + /* + This event is quite rare and is usually handled in unmapHandler. + However, if the window is unmapped when the reparent event occurs, + the window manager never sees it because an unmap event is not sent + to an already unmapped window. + */ + + // we don't want the reparent event, put it back on the stack for the X + // server to deal with after we unmanage the window + XEvent ev; + ev.xreparent = e; + XPutBackEvent(otk::OBDisplay::display, &ev); + + // this deletes us etc + Openbox::instance->screen(_screen)->unmanageWindow(this); } }