// // Client.cc for pekwm // Copyright (C) 2002-2005 Claes Nasten // // client.cc for aewm++ // Copyright (C) 2000 Frank Hale // http://sapphire.sourceforge.net/ // // This program is licensed under the GNU GPL. // See the LICENSE file for more information. // #include "../config.h" #include "PWinObj.hh" #include "PDecor.hh" // PDecor::TitleItem #include "Client.hh" #include "PScreen.hh" #include "Config.hh" #include "KeyGrabber.hh" #include "Theme.hh" #include "AutoProperties.hh" #include "Frame.hh" #include "Workspaces.hh" #include "Viewport.hh" #include "WindowManager.hh" #ifdef MENUS #include "PMenu.hh" #include "WORefMenu.hh" #endif // MENUS #include extern "C" { #include #ifdef HAVE_SHAPE #include #endif // HAVE_SHAPE } #ifdef DEBUG #include using std::cerr; using std::endl; #endif // DEBUG using std::vector; using std::list; using std::find; Client::Client(WindowManager *w, Window new_client, bool is_new) : PWinObj(w->getScreen()->getDpy()), _wm(w), _size(NULL), _transient(None), _strut(NULL), _class_hint(NULL), _alive(false), _marked(false), _send_focus_message(false), _send_close_message(false), _cfg_request_lock(false), #ifdef HAVE_SHAPE _shaped(false), #endif // HAVE_SHAPE _extended_net_name(false) { // Construct the client PScreen::instance()->grabServer(); // PWinObj attributes _window = new_client; _type = WO_CLIENT; if (!validate()) { #ifdef DEBUG cerr << __FILE__ << "@" << __LINE__ << ": " << "Couldn't validate client." << endl; #endif // DEBUG PScreen::instance()->ungrabServer(true); return; } XWindowAttributes attr; if (!XGetWindowAttributes(_dpy, _window, &attr)) { #ifdef DEBUG cerr << __FILE__ << "@" << __LINE__ << ": " << "Client died." << endl; #endif // DEBUG PScreen::instance()->ungrabServer(true); return; } _gm.x = attr.x; _gm.y = attr.y; _gm.width = attr.width; _gm.height = attr.height; _cmap = attr.colormap; _size = XAllocSizeHints(); // get trans window and the window attributes getTransientForHint(); XSetWindowAttributes sattr; sattr.event_mask = PropertyChangeMask|StructureNotifyMask|FocusChangeMask; sattr.do_not_propagate_mask = ButtonPressMask|ButtonReleaseMask|ButtonMotionMask; // We don't want these masks to be propagated down to the frame XChangeWindowAttributes(_dpy, _window, CWEventMask|CWDontPropagate, &sattr); #ifdef HAVE_SHAPE if (PScreen::instance()->hasExtensionShape()) { XShapeSelectInput(_dpy, _window, ShapeNotifyMask); } #endif // HAVE_SHAPE XAddToSaveSet(_dpy, _window); XSetWindowBorderWidth(_dpy, _window, 0); // Load the Class hint before loading the autoprops and // getting client title as we search for TitleRule in the autoprops _class_hint = new ClassHint(); readClassRoleHints(); getWMNormalHints(); getXClientName(); getXIconName(); // cyclic dependency, getting the name requires quiering autoprops _class_hint->title = _title.getReal(); // Get Autoproperties before EWMH as we need the cfg_deny property. AutoProperty *ap = readAutoprops(_wm->isStartup() ? APPLY_ON_NEW : APPLY_ON_START); readMwmHints(); // read atoms readEwmhHints(); readPekwmHints(); getWMProtocols(); XWMHints* hints = XGetWMHints(_dpy, _window); if (hints != NULL) { // get the input focus mode if (hints->flags&InputHint) { // FIXME: More logic needed // if (hints->input) { // _focusable = true; // } else { // _focusable = false; // } } // get initial state of the window if (hints->flags&StateHint) { setWmState(hints->initial_state); } else { setWmState(NormalState); } XFree(hints); } // are we iconified if (getWmState() == IconicState) { _iconified = true; } // grab keybindings and mousebutton actions KeyGrabber::instance()->grabKeys(_window); grabButtons(); // don't want to spread the giveInputFocuses bool do_focus = is_new ? Config::instance()->isFocusNew() : false; bool do_autogroup = true; // If pekwm allready is running, check against autoproperty then // tagged frame. If starting up, check if it has a FRAME_ID and if not // use autoproperties. if (_wm->isStartup()) { // Check against tagged frame. bool activate; _parent = _wm->findTaggedFrame(activate); if (_parent) { Frame *frame = static_cast(_parent); frame->addChild(this); if (activate) { frame->activateChild(this); do_focus = frame->isFocused(); } } // Not startup, pekwm (re)start it is. } else { long id; if (AtomUtil::getLong(_window, PekwmAtoms::instance()->getAtom(PEKWM_FRAME_ID), id)) { do_autogroup = false; _parent = _wm->findFrameFromId(id); if (_parent) { Frame *frame = static_cast(_parent); frame->addChild(this); frame->activateChild(this); do_focus = frame->isFocused(); } } } // Apply Autoproperties again to override EWMH state. It's done twice as // we need the cfg_deny property when reading the EWMH state. if (ap) { applyAutoprops(ap); if (do_autogroup && !_parent && (ap->group_size >= 0)) { Frame* frame = _wm->findGroup(ap); if (frame) { frame->addChild(this); if (ap->group_behind == false) { frame->activateChild(this); do_focus = frame->isFocused(); } if (ap->group_raise) { frame->raise(); } } } } // Tell the world about our state updateEwmhStates(); PScreen::instance()->ungrabServer(true); // if we don't have a frame allready, create a new one if (_parent == NULL) { _parent = new Frame(_wm, this, ap); } // make sure the window is mapped, this is done after it has been // added to the decor/frame as otherwise IsViewable state won't // be correct and we don't know wheter or not to place the window PWinObj::mapWindow(); // Let us hear what autoproperties has to say about focusing if (is_new && (ap != NULL) && ap->isMask(AP_FOCUS_NEW)) { do_focus = ap->focus_new; } // Only can give input focus to mapped windows if (_parent->isMapped()) { // Ordinary focus if (do_focus) { _parent->giveInputFocus(); // Check if we are transient, and if we want to focus } else if ((_transient != None) && Config::instance()->isFocusNewChild()) { Client *trans_for = _wm->findClientFromWindow(_transient); if (trans_for && trans_for->isFocused()) { _parent->giveInputFocus(); } } } // finished creating the client, so now adding it to the client list _wm->addToClientList(this); _alive = true; _wo_list.push_back(this); } //! @brief Client destructor Client::~Client(void) { // remove from lists _wo_list.remove(this); _wm->removeFromClientList(this); PScreen::instance()->grabServer(); #ifdef MENUS WORefMenu *wo_ref_menu; wo_ref_menu = static_cast(_wm->getMenu(WINDOWMENU_TYPE)); if (this == wo_ref_menu->getWORef()) { wo_ref_menu->setWORef(NULL); wo_ref_menu->unmapAll(); } wo_ref_menu = static_cast(_wm->getMenu(DECORMENU_TYPE)); if (this == wo_ref_menu->getWORef()) { wo_ref_menu->setWORef(NULL); wo_ref_menu->unmapAll(); } #endif // MENUS // removes gravity and moves it back to root if we are alive bool focus = false; if (_parent && (_parent->getType() == PWinObj::WO_FRAME)) { focus = _parent->isFocused(); static_cast(_parent)->removeChild(this); } // Focus the parent if we had focus before if (focus && (_transient != None)) { Client *trans_cli = _wm->findClientFromWindow(_transient); if ((trans_cli != NULL) && (((Frame*) trans_cli->getParent())->getActiveChild() == trans_cli)) { trans_cli->getParent()->giveInputFocus(); } } // Clean up if the client still is alive, it'll be dead all times // except when we exit pekwm if (_alive) { XUngrabButton(_dpy, AnyButton, AnyModifier, _window); KeyGrabber::instance()->ungrabKeys(_window); XRemoveFromSaveSet(_dpy, _window); PWinObj::mapWindow(); } // free names and size hint if (_size) XFree(_size); removeStrutHint(); if (_class_hint) delete _class_hint; PScreen::instance()->ungrabServer(true); } // START - PWinObj interface. //! @brief Maps the window. void Client::mapWindow(void) { if (_mapped) return; if (_iconified) { _iconified = false; setWmState(NormalState); updateEwmhStates(); } if(!_transient) // unmap our transient windows if we have any _wm->findTransientsToMapOrUnmap(_window, false); XSelectInput(_dpy, _window, NoEventMask); PWinObj::mapWindow(); XSelectInput(_dpy, _window, PropertyChangeMask|StructureNotifyMask|FocusChangeMask); } //! @brief Sets the client to WithdrawnState and unmaps it. void Client::unmapWindow(void) { if (!_mapped) return; if (_iconified) { // set the state of the window setWmState(IconicState); updateEwmhStates(); } XSelectInput(_dpy, _window, NoEventMask); PWinObj::unmapWindow(); XSelectInput(_dpy, _window, PropertyChangeMask|StructureNotifyMask|FocusChangeMask); } //! @brief Iconifies the client and adds it to the iconmenu void Client::iconify(void) { if (_iconified) return; _iconified = true; if (!_transient) _wm->findTransientsToMapOrUnmap(_window, true); unmapWindow(); } //! @brief Toggle client sticky state void Client::stick(void) { PWinObj::stick(); updateEwmhStates(); } //! @brief Update the position variables. void Client::move(int x, int y, bool do_virtual) { bool request = ((_gm.x != x) || (_gm.y != y)); _gm.x = x; _gm.y = y; if (request) configureRequestSend(); } //! @brief void Client::moveVirtual(int x, int y) { AtomUtil::setPosition(_window, PekwmAtoms::instance()->getAtom(PEKWM_FRAME_VPOS), x, y); } //! @brief Resizes the client window to specified size. void Client::resize(uint width, uint height) { bool request = ((_gm.width != width) || (_gm.height != height)); PWinObj::resize(width, height); if (request) configureRequestSend(); } //! @brief Sets the workspce and updates the _NET_WM_DESKTOP hint. void Client::setWorkspace(uint workspace) { if (workspace != NET_WM_STICKY_WINDOW) { if (workspace >= Workspaces::instance()->size()) { workspace = Workspaces::instance()->size() - 1; } _workspace = workspace; if (_sticky) { AtomUtil::setLong(_window, EwmhAtoms::instance()->getAtom(NET_WM_DESKTOP), NET_WM_STICKY_WINDOW); } else { AtomUtil::setLong(_window, EwmhAtoms::instance()->getAtom(NET_WM_DESKTOP), _workspace); } } } //! @brief Gives the Client input focus. bool Client::giveInputFocus(void) { return PWinObj::giveInputFocus(); } //! @brief Reparents and sets _parent member, filtering unmap events void Client::reparent(PWinObj *parent, int x, int y) { XSelectInput(_dpy, _window, NoEventMask); PWinObj::reparent(parent, x, y); XSelectInput(_dpy, _window, PropertyChangeMask|StructureNotifyMask|FocusChangeMask); } //! @brief ActionEvent* Client::handleUnmapEvent(XUnmapEvent *ev) { if ((ev->window != ev->event) && (ev->send_event != true)) { return NULL; } #ifdef DEBUG cerr << __FILE__ << "@" << __LINE__ << ": " << "Client(" << this << ")::handleUnmapEvent(" << ev << ")" << endl << " *** unmapping client, window: " << _window << endl; #endif // DEBUG // FIXME: Listen mask should change as this doesn't work? //_alive = false; delete this; return NULL; } // END - PWinObj interface. //! @brief Checks if the window has any Destroy or Unmap notifys. bool Client::validate(void) { XSync(_dpy, false); XEvent ev; if (XCheckTypedWindowEvent(_dpy, _window, DestroyNotify, &ev) || XCheckTypedWindowEvent(_dpy, _window, UnmapNotify, &ev)) { XPutBackEvent(_dpy, &ev); return false; } return true; } //! @brief Checks if the window has attribute IsViewable set bool Client::isViewable(void) { XWindowAttributes attr; XGetWindowAttributes(_dpy, _window, &attr); return (attr.map_state == IsViewable); } //! @brief Grabs all the mouse button actions on the client. void Client::grabButtons(void) { // Make sure we don't have any buttons grabbed. XUngrabButton(_dpy, AnyButton, AnyModifier, _window); list *actions = Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_CHILD_FRAME); list::iterator it(actions->begin()); for (; it != actions->end(); ++it) { if ((it->type == MOUSE_EVENT_PRESS) || (it->type == MOUSE_EVENT_RELEASE)) { // No need to grab mod less events, replied with the frame if ((it->mod == 0) || (it->mod == MOD_ANY)) { continue; } grabButton(it->sym, it->mod, ButtonPressMask|ButtonReleaseMask, _window, None); } else if (it->type == MOUSE_EVENT_MOTION) { // FIXME: Add support for MOD_ANY grabButton(it->sym, it->mod, ButtonPressMask|ButtonReleaseMask|ButtonMotionMask, _window, None); } } } //! @brief Sets CfgDeny state on Client void Client::setStateCfgDeny(StateAction sa, uint deny) { if (ActionUtil::needToggle(sa, _state.cfg_deny&deny) == false) { return; } if (_state.cfg_deny&deny) { _state.cfg_deny &= ~deny; } else { _state.cfg_deny |= deny; } } void Client::readClassRoleHints(void) { // class hint XClassHint class_hint; if (XGetClassHint(_dpy, _window, &class_hint)) { _class_hint->h_name = class_hint.res_name; _class_hint->h_class = class_hint.res_class; XFree(class_hint.res_name); XFree(class_hint.res_class); } // wm window role _class_hint->h_role = ""; AtomUtil::getString(_window, IcccmAtoms::instance()->getAtom(WM_WINDOW_ROLE), _class_hint->h_role); } //! @brief Loads the Clients state from EWMH atoms. void Client::readEwmhHints(void) { EwmhAtoms *ewmh = EwmhAtoms::instance(); // convenience // which workspace do we belong to? long workspace = -1; AtomUtil::getLong(_window, ewmh->getAtom(NET_WM_DESKTOP), workspace); if (workspace < 0) { _workspace = Workspaces::instance()->getActive(); AtomUtil::setLong(_window, ewmh->getAtom(NET_WM_DESKTOP), _workspace); } else { _workspace = workspace; } // try to figure out what kind of window we are and alter it acordingly int items; Atom *atoms = NULL; bool found_window_type = false; atoms = (Atom*) AtomUtil::getEwmhPropData(_window, ewmh->getAtom(WINDOW_TYPE), XA_ATOM, items); if (atoms) { for (int i = 0; i < items; ++i) { found_window_type = true; if (atoms[i] == ewmh->getAtom(WINDOW_TYPE_DESKTOP)) { // desktop windows, make it the same size as the screen and place // it below all windows withouth any decorations, also make it sticky _gm.x = _gm.y = 0; _gm.width = PScreen::instance()->getWidth(); _gm.height = PScreen::instance()->getHeight(); setTitlebar(false); setBorder(false); _sticky = true; _state.placed = true; _state.skip = SKIP_MENUS|SKIP_FOCUS_TOGGLE|SKIP_SNAP; _layer = LAYER_DESKTOP; break; } else if (atoms[i] == ewmh->getAtom(WINDOW_TYPE_DOCK)) { setTitlebar(false); setBorder(false); _sticky = true; _layer = LAYER_DOCK; break; } else if (atoms[i] == ewmh->getAtom(WINDOW_TYPE_TOOLBAR)) { setTitlebar(false); setBorder(false); _state.skip = SKIP_MENUS|SKIP_FOCUS_TOGGLE; break; } else if (atoms[i] == ewmh->getAtom(WINDOW_TYPE_MENU)) { _layer = LAYER_MENU; _state.skip = SKIP_MENUS|SKIP_FOCUS_TOGGLE|SKIP_SNAP; break; // } else if (atoms[i] == ewmh->getAtom(WINDOW_TYPE_UTILITY)) { // } // } else if (atoms[i] == ewmh->getAtom(WINDOW_TYPE_DIALOG)) { // } } else if (atoms[i] == ewmh->getAtom(WINDOW_TYPE_SPLASH)) { setTitlebar(false); setBorder(false); } else { found_window_type = false; } } XFree(atoms); } if (!found_window_type) { Atom type = ewmh->getAtom(WINDOW_TYPE_NORMAL); AtomUtil::setAtom(_window, ewmh->getAtom(WINDOW_TYPE), type); } // The _NET_WM_STATE overides the _NET_WM_TYPE NetWMStates win_states; if (getEwmhStates(win_states) == true) { if (win_states.hidden) _iconified = true; if (win_states.shaded) _state.shaded = true; if (win_states.max_vert) _state.maximized_vert = true; if (win_states.max_horz) _state.maximized_horz = true; if (win_states.skip_taskbar) _state.skip_taskbar = true; if (win_states.skip_pager) _state.skip_pager = true; if (win_states.sticky) _sticky = true; if (win_states.above) _layer = LAYER_BELOW; if (win_states.below) _layer = LAYER_ABOVE_DOCK; if (win_states.fullscreen) _state.fullscreen = true; } // check if we have a strut getStrutHint(); } //! @brief Loads the Clients state from MWM atoms. void Client::readMwmHints(void) { MwmHints *mwm_hints = getMwmHints(_window); if (mwm_hints != NULL) { if (mwm_hints->flags&MWM_HINTS_FUNCTIONS) { if (mwm_hints->functions&MWM_FUNC_ALL != 0) { if (mwm_hints->functions&MWM_FUNC_RESIZE == 0) _actions.resize = false; if (mwm_hints->functions&MWM_FUNC_MOVE == 0) _actions.move = false; if (mwm_hints->functions&MWM_FUNC_ICONIFY == 0) _actions.minimize = false; if (mwm_hints->functions&MWM_FUNC_CLOSE == 0) _actions.close = false; if (mwm_hints->functions&MWM_FUNC_MAXIMIZE == 0) { _actions.maximize_vert = true; _actions.maximize_horz = true; } } } if (mwm_hints->flags&MWM_HINTS_DECORATIONS) { if (!mwm_hints->decorations&MWM_DECOR_ALL) { setTitlebar(mwm_hints->decorations&MWM_DECOR_TITLE); setBorder(mwm_hints->decorations&MWM_DECOR_BORDER); } } XFree(mwm_hints); } } //! @brief Reads non-standard pekwm hints void Client::readPekwmHints(void) { long value; // virtual position overides real position AtomUtil::getPosition(_window, PekwmAtoms::instance()->getAtom(PEKWM_FRAME_VPOS), _gm.x, _gm.y); // get decor state if (AtomUtil::getLong(_window, PekwmAtoms::instance()->getAtom(PEKWM_FRAME_DECOR), value) == true) { _state.decor = value; } // get skip state if (AtomUtil::getLong(_window, PekwmAtoms::instance()->getAtom(PEKWM_FRAME_SKIP), value) == true) { _state.skip = value; } } //! @brief Tries to find a AutoProp for the current client. //! @param type Defaults to 0 AutoProperty* Client::readAutoprops(uint type) { AutoProperty *data = AutoProperties::instance()->findAutoProperty(_class_hint, _workspace, type); if ((data == NULL) || (_transient ? !data->isApplyOn(APPLY_ON_TRANSIENT|APPLY_ON_TRANSIENT_ONLY) : data->isApplyOn(APPLY_ON_TRANSIENT_ONLY))) { return NULL; } applyAutoprops(data); return data; } //! @brief Applies AutoPropery to this Client. void Client::applyAutoprops(AutoProperty *ap) { // Set the correct group of the window _class_hint->group = ap->group_name; // We only apply grouping if it's a new client or if we are restarting // and have APPLY_ON_START set if (ap->isMask(AP_STICKY)) _sticky = ap->sticky; if (ap->isMask(AP_SHADED)) _state.shaded = ap->shaded; if (ap->isMask(AP_MAXIMIZED_VERTICAL)) _state.maximized_vert = ap->maximized_vertical; if (ap->isMask(AP_MAXIMIZED_HORIZONTAL)) _state.maximized_horz = ap->maximized_horizontal; if (ap->isMask(AP_FULLSCREEN)) _state.fullscreen = ap->fullscreen; if (ap->isMask(AP_ICONIFIED)) _iconified = ap->iconified; if (ap->isMask(AP_TITLEBAR)) setTitlebar(ap->titlebar); if (ap->isMask(AP_BORDER)) setBorder(ap->border); if (ap->isMask(AP_LAYER) && (ap->layer <= LAYER_MENU)) _layer = ap->layer; if (ap->isMask(AP_SKIP)) _state.skip = ap->skip; if (ap->isMask(AP_FOCUSABLE)) _focusable = ap->focusable; if (ap->isMask(AP_WORKSPACE)) { _workspace = ap->workspace; } if (ap->isMask(AP_CFG_DENY)) { _state.cfg_deny = ap->cfg_deny; } } //! @brief Tries to get the NET_WM name, else fall back to XA_WM_NAME void Client::getXClientName(void) { // reset title data _title.setReal(""); if (_title.isUserSet() == false) { _title.setVisible(""); } _title.setCount(0); // read X11 property XTextProperty text_property; if ((XGetWMName(_dpy, _window, &text_property) == False) || (text_property.value == NULL) || (text_property.nitems == 0)){ return; } if (text_property.encoding == XA_STRING) { _title.setReal((const char*) text_property.value); } else { char **list; int num; XmbTextPropertyToTextList(_dpy, &text_property, &list, &num); if ((num > 0) && (*list != NULL)) { _title.setReal(*list); XFreeStringList(list); } } XFree(text_property.value); // mirror it on the visible if (_title.isUserSet() == false) { _title.setVisible(_title.getReal()); // apply title rules and find unique name, doesn't apply on user-set titles titleApplyRule(); titleFindID(); } } //! @brief Searches for an TitleRule and if found, applies it void Client::titleApplyRule(void) { _class_hint->title = _title.getReal (); TitleProperty *data = AutoProperties::instance()->findTitleProperty (_class_hint); _class_hint->title = ""; if (data) _title.setRuleApplied (data->getTitleRule ().ed_s (_title.getWVisible ())); else _title.setRuleApplied (false); } //! @brief Searches for a unique ID within Clients having the same title void Client::titleFindID(void) { if (Config::instance()->getClientUniqueName() == false) { return; } list ids_used; list::iterator it = _wm->client_begin(); for (; it != _wm->client_end(); ++it) { if (*it != this) { if ((*it)->getTitle()->getReal() == _title.getReal()) { ids_used.push_back((*it)->getTitle()->getCount()); } } } // more than one client having this name if (ids_used.size() > 0) { ids_used.sort(); list::iterator it( ids_used.begin()); for (uint i = 0; it != ids_used.end(); ++i, ++it) { if (i < *it) { _title.setCount(i); break; } } if (it == ids_used.end()) { _title.setCount(ids_used.size()); } } // more than one client, add identifier if (_title.getCount() > 0) { char buf[8]; snprintf(buf, 8, "%i", _title.getCount()); _title.getWVisible().append(Config::instance()->getClientUniqueNamePre()); _title.getWVisible().append(buf); _title.getWVisible().append(Config::instance()->getClientUniqueNamePost()); } } //! @brief Tries to get Client's icon name, and puts it in _icon_name. void Client::getXIconName(void) { _icon_name = ""; XTextProperty text_property; if (!XGetWMIconName(_dpy, _window, &text_property) || !text_property.value || !text_property.nitems) return; if (text_property.encoding == XA_STRING) { _icon_name = (const char*) text_property.value; } else { char **list; int num; XmbTextPropertyToTextList(_dpy, &text_property, &list, &num); if (num && *list) { _icon_name = *list; XFreeStringList(list); } } XFree (text_property.value); } //! @brief Sets the WM_STATE of the client to state //! @param state State to set. void Client::setWmState(ulong state) { ulong data[2]; data[0] = state; data[1] = None; // No Icon XChangeProperty(_dpy, _window, IcccmAtoms::instance()->getAtom(WM_STATE), IcccmAtoms::instance()->getAtom(WM_STATE), 32, PropModeReplace, (uchar*) data, 2); } // If we can't find a _wm->wm_state we're going to have to assume // Withdrawn. This is not exactly optimal, since we can't really // distinguish between the case where no WM has run yet and when the // state was explicitly removed (Clients are allowed to either set the // atom to Withdrawn or just remove it... yuck.) long Client::getWmState(void) { Atom real_type; int real_format; long *data, state = WithdrawnState; ulong items_read, items_left; int status = XGetWindowProperty(_dpy, _window, IcccmAtoms::instance()->getAtom(WM_STATE), 0L, 2L, False, IcccmAtoms::instance()->getAtom(WM_STATE), &real_type, &real_format, &items_read, &items_left, (uchar **) &data); if ((status == Success) && items_read) { state = *data; XFree(data); } return state; } //! @brief Send XConfigureEvent, letting the client know about changes void Client::configureRequestSend(void) { if (_cfg_request_lock) return; XConfigureEvent e; e.type = ConfigureNotify; e.event = _window; e.window = _window; e.x = _gm.x; e.y = _gm.y; e.width = _gm.width; e.height = _gm.height; e.border_width = 0; e.above = None; e.override_redirect = False; #ifdef DEBUG cerr << __FILE__ << "@" << __LINE__ << ": " << "Client(" << this << ")::sendConfigureRequest()" << endl << " *** x: " << e.x << " y: " << e.y << " width: " << e.width << " height: " << e.height << endl; #endif // DEBUG XSendEvent(_dpy, _window, false, StructureNotifyMask, (XEvent *) &e); } //! @brief Grabs a button on the window win //! Grabs the button button, with the mod mod and mask mask on the window win //! and cursor curs with "all" possible extra modifiers void Client::grabButton(int button, int mod, int mask, Window win, Cursor curs) { uint num_lock = PScreen::instance()->getNumLock(); uint scroll_lock = PScreen::instance()->getScrollLock(); XGrabButton(_dpy, button, mod, win, true, mask, GrabModeAsync, GrabModeAsync, None, curs); XGrabButton(_dpy, button, mod|LockMask, win, true, mask, GrabModeAsync, GrabModeAsync, None, curs); if (num_lock) { XGrabButton(_dpy, button, mod|num_lock, win, true, mask, GrabModeAsync, GrabModeAsync, None, curs); XGrabButton(_dpy, button, mod|num_lock|LockMask, win, true, mask, GrabModeAsync, GrabModeAsync, None, curs); } if (scroll_lock) { XGrabButton(_dpy, button, mod|scroll_lock, win, true, mask, GrabModeAsync, GrabModeAsync, None, curs); XGrabButton(_dpy, button, mod|scroll_lock|LockMask, win, true, mask, GrabModeAsync, GrabModeAsync, None, curs); } if (num_lock && scroll_lock) { XGrabButton(_dpy, button, mod|num_lock|scroll_lock, win, true, mask, GrabModeAsync, GrabModeAsync, None, curs); XGrabButton(_dpy, button, mod|num_lock|scroll_lock|LockMask, win, true, mask, GrabModeAsync, GrabModeAsync, None, curs); } } //! @brief Toggles the clients always on top state void Client::alwaysOnTop(bool top) { _layer = top ? LAYER_ONTOP : LAYER_NORMAL; updateEwmhStates(); } //! @brief Toggles the clients always below state. void Client::alwaysBelow(bool below) { _layer = below ? LAYER_BELOW : LAYER_NORMAL; updateEwmhStates(); } //! @brief Sets the skip state, and updates the _PEKWM_FRAME_SKIP atom void Client::setSkip(uint skip) { _state.skip = skip; AtomUtil::setLong(_window, PekwmAtoms::instance()->getAtom(PEKWM_FRAME_SKIP), _state.skip); } //! @brief Sends an WM_DELETE message to the client, else kills it. void Client::close(void) { IcccmAtoms *icccm = IcccmAtoms::instance(); // convenience if (_send_close_message) { sendXMessage(_window, icccm->getAtom(WM_PROTOCOLS), NoEventMask, icccm->getAtom(WM_DELETE_WINDOW), CurrentTime); } else { kill(); } } //! @brief Kills the client using XKillClient void Client::kill(void) { XKillClient(_dpy, _window); } //! @brief Sets the positoin based on P or U position. //! @return Returns true on success, else false. bool Client::setPUPosition(void) { if ((_size->flags&PPosition) || (_size->flags&USPosition)) { if (_gm.x == 0) { _gm.x = _size->x; } if (_gm.y == 0) { _gm.y = _size->y; } return true; } return false; } //! @brief Get the size closest to the ResizeInc incremeter //! @param r_w Pointer to put the new width in //! @param r_h Pointer to put the new height in //! @param w Width to calculate from //! @param h Height to calculate from bool Client::getIncSize(uint *r_w, uint *r_h, uint w, uint h) { int basex, basey; if (_size->flags&PResizeInc) { basex = (_size->flags&PBaseSize) ? _size->base_width : (_size->flags&PMinSize) ? _size->min_width : 0; basey = (_size->flags&PBaseSize) ? _size->base_height : (_size->flags&PMinSize) ? _size->min_height : 0; *r_w = w - ((w - basex) % _size->width_inc); *r_h = h - ((h - basey) % _size->height_inc); return true; } return false; } //! @brief Gets a MwmHint structure from a window. Doesn't free memory. Client::MwmHints* Client::getMwmHints(Window win) { Atom real_type; int real_format; ulong items_read, items_left; MwmHints *data; int status = XGetWindowProperty(_dpy, win, _wm->getMwmHintsAtom(), 0L, 20L, False, _wm->getMwmHintsAtom(), &real_type, &real_format, &items_read, &items_left, (uchar **) &data); if (status == Success) { if (items_read < MWM_HINTS_NUM) { XFree(data); data = NULL; } } else { data = NULL; } return data; } // This happens when a window is iconified and destroys itself. An // Unmap event wouldn't happen in that case because the window is // already unmapped. void Client::handleDestroyEvent(XDestroyWindowEvent *e) { _alive = false; delete this; } void Client::handleColormapChange(XColormapEvent *e) { if (e->c_new) { _cmap = e->colormap; XInstallColormap(_dpy, _cmap); } } int Client::sendXMessage(Window window, Atom atom, long mask, long v1, long v2, long v3, long v4, long v5) { XEvent e; e.type = e.xclient.type = ClientMessage; e.xclient.display = _dpy; e.xclient.window = window; e.xclient.format = 32; e.xclient.message_type = atom; e.xclient.data.l[0] = v1; e.xclient.data.l[1] = v2; e.xclient.data.l[2] = v3; e.xclient.data.l[3] = v4; e.xclient.data.l[4] = v5; return XSendEvent(_dpy, window, false, mask, &e); } //! @brief bool Client::getEwmhStates(NetWMStates &win_states) { int num = 0; Atom *states; states = (Atom*) AtomUtil::getEwmhPropData(_window, EwmhAtoms::instance()->getAtom(STATE), XA_ATOM, num); if (states != NULL) { EwmhAtoms *ewmh = EwmhAtoms::instance(); // convenience for (int i = 0; i < num; ++i) { if (states[i] == ewmh->getAtom(STATE_MODAL)) { win_states.modal = true; } else if (states[i] == ewmh->getAtom(STATE_STICKY)) { win_states.sticky = true; } else if (states[i] == ewmh->getAtom(STATE_MAXIMIZED_VERT) && !isCfgDeny(CFG_DENY_STATE_MAXIMIZED_VERT)) { win_states.max_vert = true; } else if (states[i] == ewmh->getAtom(STATE_MAXIMIZED_HORZ) && !isCfgDeny(CFG_DENY_STATE_MAXIMIZED_HORZ)) { win_states.max_horz = true; } else if (states[i] == ewmh->getAtom(STATE_SHADED)) { win_states.shaded = true; } else if (states[i] == ewmh->getAtom(STATE_SKIP_TASKBAR)) { win_states.skip_taskbar = true; } else if (states[i] == ewmh->getAtom(STATE_SKIP_PAGER)) { win_states.skip_pager = true; } else if (states[i] == ewmh->getAtom(STATE_HIDDEN) && !isCfgDeny(CFG_DENY_STATE_HIDDEN)) { win_states.hidden = true; } else if (states[i] == ewmh->getAtom(STATE_FULLSCREEN) && !isCfgDeny(CFG_DENY_STATE_FULLSCREEN)) { win_states.fullscreen = true; } else if (states[i] == ewmh->getAtom(STATE_ABOVE) && !isCfgDeny(CFG_DENY_STATE_ABOVE)) { win_states.above = true; } else if (states[i] == ewmh->getAtom(STATE_BELOW) && !isCfgDeny(CFG_DENY_STATE_BELOW)) { win_states.below = true; } } XFree(states); return true; } else { return false; } } //! @brief Tells the world about our states, such as shaded etc. void Client::updateEwmhStates(void) { EwmhAtoms *ewmh = EwmhAtoms::instance(); list states; if (false) // we don't yet support modal state states.push_back(ewmh->getAtom(STATE_MODAL)); if (_sticky) states.push_back(ewmh->getAtom(STATE_STICKY)); if (_state.maximized_vert) states.push_back(ewmh->getAtom(STATE_MAXIMIZED_VERT)); if (_state.maximized_horz) states.push_back(ewmh->getAtom(STATE_MAXIMIZED_HORZ)); if (_state.shaded) states.push_back(ewmh->getAtom(STATE_SHADED)); if (_state.skip_taskbar) states.push_back(ewmh->getAtom(STATE_SKIP_TASKBAR)); if (_state.skip_pager) states.push_back(ewmh->getAtom(STATE_SKIP_PAGER)); if (_iconified) states.push_back(ewmh->getAtom(STATE_HIDDEN)); if (_state.fullscreen) states.push_back(ewmh->getAtom(STATE_FULLSCREEN)); if (_layer == LAYER_ABOVE_DOCK) states.push_back(ewmh->getAtom(STATE_ABOVE)); if (_layer == LAYER_BELOW) states.push_back(ewmh->getAtom(STATE_BELOW)); Atom *atoms = new Atom[(states.size() > 0) ? states.size() : 1]; if (states.size() > 0) { copy(states.begin(), states.end(), atoms); } AtomUtil::setAtoms(_window, ewmh->getAtom(STATE), atoms, states.size()); delete [] atoms; } //! @brief void Client::getWMNormalHints(void) { long dummy; XGetWMNormalHints(_dpy, _window, _size, &dummy); } //! @brief void Client::getWMProtocols(void) { int count; Atom *protocols; if (XGetWMProtocols(_dpy, _window, &protocols, &count) != 0) { IcccmAtoms *icccm = IcccmAtoms::instance(); // convenience for (int i = 0; i < count; ++i) { if (protocols[i] == icccm->getAtom(WM_TAKE_FOCUS)) { _send_focus_message = true; } else if (protocols[i] == icccm->getAtom(WM_DELETE_WINDOW)) { _send_close_message = true; } } XFree(protocols); } } //! @brief void Client::getTransientForHint(void) { if (!_transient) XGetTransientForHint(_dpy, _window, &_transient); } //! @brief void Client::getStrutHint(void) { int num = 0; CARD32 *strut = NULL; strut = (CARD32*) AtomUtil::getEwmhPropData(_window, EwmhAtoms::instance()->getAtom(NET_WM_STRUT), XA_CARDINAL, num); if (strut != NULL) { if (_strut != NULL) { PScreen::instance()->removeStrut(_strut); } else { _strut = new Strut(); } *_strut = strut; PScreen::instance()->addStrut(_strut); XFree(strut); } else if (_strut != NULL) { PScreen::instance()->removeStrut(_strut); delete _strut; _strut = NULL; } Workspaces::instance()->getActiveViewport()->hintSetWorkarea(); } //! @brief void Client::removeStrutHint(void) { if (_strut == NULL) return; PScreen::instance()->removeStrut(_strut); delete _strut; _strut = NULL; Workspaces::instance()->getActiveViewport()->hintSetWorkarea(); }