//
// Client.cc for pekwm
// Copyright © 2002-2009 Claes Nästén <me{@}pekdon{.}net>
//
// client.cc for aewm++
// Copyright (C) 2000 Frank Hale <frankhale@yahoo.com>
// http://sapphire.sourceforge.net/
//
// This program is licensed under the GNU GPL.
// See the LICENSE file for more information.
//

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H

#include <cstdio>
#include <iostream>

extern "C" {
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#ifdef HAVE_SHAPE
#include <X11/extensions/shape.h>
#endif // HAVE_SHAPE
}

#include "Compat.hh" // setenv, unsetenv
#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 "WindowManager.hh"
#include "PTexturePlain.hh"
#include "PImageIcon.hh"
#include "TextureHandler.hh"

#include "PMenu.hh"
#include "WORefMenu.hh"

using std::cerr;
using std::endl;
using std::find;
using std::list;
using std::string;
using std::vector;
using std::wstring;

list<Client*> Client::_client_list = list<Client*>();
vector<uint> Client::_clientid_list = vector<uint>();

Client::Client(Window new_client, bool is_new)
    : PWinObj(),
      _id(0), _size(0),
      _transient(None), _strut(0), _icon(0),
      _pid(0), _is_remote(false), _class_hint(0),
      _alive(false), _marked(false),
      _send_focus_message(false), _send_close_message(false),
      _wm_hints_input(true), _cfg_request_lock(false),
      _shaped(false), _extended_net_name(false)
{
    // PWinObj attributes, required by validate etc.
    _window = new_client;
    _type = WO_CLIENT;

    // Construct the client
    PScreen::instance()->grabServer();
    if (! validate() || ! getAndUpdateWindowAttributes()) {
        PScreen::instance()->ungrabServer(true);
        return;
    }

    // Get unique Client id
    _id = findClientID();
    _title.setId(_id);
    _title.infoAdd(PDecor::TitleItem::INFO_ID);

    if (PScreen::instance()->hasExtensionShape()) {
#ifdef HAVE_SHAPE
        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();
    readName();
    readIconName();

    // cyclic dependency, getting the name requires quiering autoprops
    _class_hint->title = _title.getReal();

    // Get Autoproperties before EWMH as we need the cfg_deny
    // property, however the _transient hint needs to be setup to
    // avoid auto-grouping to be to greedy.
    getTransientForHint();

    AutoProperty *ap = readAutoprops(WindowManager::instance()->isStartup()
                                     ? APPLY_ON_NEW : APPLY_ON_START);

    readHints();

    // We need to set the state before acquiring a frame,
    // so that Frame's state can match the state of the Client.
    setInitialState();

    findOrCreateFrame(ap);

    // Grab keybindings and mousebutton actions
    KeyGrabber::instance()->grabKeys(_window);
    grabButtons();

    // Tell the world about our state
    updateEwmhStates();

    PScreen::instance()->ungrabServer(true);

    setMappedStateAndFocus(is_new, ap);

    _alive = true;

    findAndRaiseIfTransient();

    // Finished creating the client, so now adding it to the client list.
    woListAdd(this);
    _wo_map[_window] = this;
    _client_list.push_back(this);
}

//! @brief Client destructor
Client::~Client(void)
{
    // Remove from lists
    if (_transient) {
        _transient->_transient_clients.remove(this);
        _transient->removeObserver(this);
    }
    _wo_map.erase(_window);
    woListRemove(this);
    _client_list.remove(this);

    returnClientID(_id);

    PScreen::instance()->grabServer();

    // 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<PDecor*>(_parent)->removeChild(this);
    }

    // Focus the parent if we had focus before
    if (focus && _transient) {
        Frame *trans_frame = static_cast<Frame*>(_transient->getParent());
        if (trans_frame->getActiveChild() == _transient) {
            trans_frame->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;
    }

    if (_icon) {
        TextureHandler::instance()->returnTexture(_icon);
        _icon = 0;
    }

    PScreen::instance()->ungrabServer(true);
}

/**
 * Read basic window attributes including geometry and update the
 * window attributes being listened to. Returns false if the client
 * disappears during the check.
 */
bool
Client::getAndUpdateWindowAttributes(void)
{
    XWindowAttributes attr;
    if (! XGetWindowAttributes(_dpy, _window, &attr)) {
        return false;
    }
    _gm.x = attr.x;
    _gm.y = attr.y;
    _gm.width = attr.width;
    _gm.height = attr.height;

    _cmap = attr.colormap;
    _size = XAllocSizeHints();

    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);

    return true;
}

/**
 * Find frame for client based on tagging, hints and
 * autoproperties. Create a new one if not found and add the client.
 */
void
Client::findOrCreateFrame(AutoProperty *autoproperty)
{
    if (! _parent) {
        findTaggedFrame();
    }
    if (! _parent) {
        findPreviousFrame();
    }

    // Apply Autoproperties again to override EWMH state. It's done twice as
    // we need the cfg_deny property when reading the EWMH state.
    if (autoproperty != 0) {
        applyAutoprops(autoproperty);
        if (! _parent) {
            findAutoGroupFrame(autoproperty);
        }
    }

    // if we don't have a frame already, create a new one
    if (! _parent) {
        _parent = new Frame(this, autoproperty);
    }
}

/**
 * Find from client from for the currently tagged client.
 */
bool
Client::findTaggedFrame(void)
{
    if (! WindowManager::instance()->isStartup()) {
        return false;
    }

    // Check for tagged frame
    Frame *frame = Frame::getTagFrame();
    if (frame && frame->isMapped()) {
        _parent = frame;
        frame->addChild(this);

        if (! Frame::getTagBehind()) {
            frame->activateChild(this);
        }
    }

    return _parent != 0;
}

/**
 * Find frame for client on PEKWM_FRAME_ID hint.
 */
bool
Client::findPreviousFrame(void)
{
    if (WindowManager::instance()->isStartup()) {
        return false;
    }

    long id;
    if (AtomUtil::getLong(_window, Atoms::getAtom(PEKWM_FRAME_ID), id)) {
        _parent = Frame::findFrameFromID(id);
        if (_parent) {
            Frame *frame = static_cast<Frame*>(_parent);
            frame->addChildOrdered(this);
            if (getPekwmFrameActive()) {
                frame->activateChild(this);
            }
        }
    }

    return _parent != 0;
}

/**
 * Find frame for client based on autoproperties.
 */
bool
Client::findAutoGroupFrame(AutoProperty *autoproperty)
{
    if (autoproperty->group_size < 0) {
        return false;
    }

    Frame* frame = WindowManager::instance()->findGroup(autoproperty);
    if (frame) {
        frame->addChild(this);

        if (! autoproperty->group_behind) {
            frame->activateChild(this);
        }
        if (autoproperty->group_raise) {
            frame->raise();
        }
    }

    return _parent != 0;
}

/**
 * Get the client state and set iconified and mapped flags.
 */
void
Client::setInitialState(void)
{
    // Set state either specified in hint
    ulong initial_state = readWmHints();
    if (getWmState() == IconicState) {
        _iconified = true;
    }

    if (_iconified || initial_state == IconicState) {
        _iconified = true;
        _mapped = true;
        unmapWindow();
    } else {
        setWmState(initial_state);
    }
}

/**
 * Ensure the Client is (un) mapped and give input focus if requested.
 */
void
Client::setMappedStateAndFocus(bool is_new, AutoProperty *autoproperty)
{
    // 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 whether or not to place the window
    if (! _iconified && _parent->isMapped()) {
        PWinObj::mapWindow();
    }

    // Let us hear what autoproperties has to say about focusing
    bool do_focus = is_new ? Config::instance()->isFocusNew() : false;
    if (is_new && autoproperty && autoproperty->isMask(AP_FOCUS_NEW)) {
        do_focus = autoproperty->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 && _transient->isFocused()
                   && Config::instance()->isFocusNewChild()) {
            _parent->giveInputFocus();
        }
    }
}

/**
 * Re-lookup transient client if window is set but not client, raise over
 * transient for window if found/set.
 */
void
Client::findAndRaiseIfTransient(void)
{
    if (_transient_window != None && ! _transient) {
        getTransientForHint();
    }

    if (_transient) {
        Frame *frame = static_cast<Frame*>(getParent());
        Frame *frame_transient = static_cast<Frame*>(_transient->getParent());
        if (frame->getActiveChild() == this) {
            frame->setLayer(frame_transient->getLayer() + 1);
            frame->raise();
        }
    }
}

// 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
        mapOrUnmapTransients(_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) {
        mapOrUnmapTransients(_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 request = ((_gm.x != x) || (_gm.y != y));

    _gm.x = x;
    _gm.y = y;

    if (request) {
        configureRequestSend();
    }
}

//! @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 Move and resizes the client window to specified size.
void
Client::moveResize(int x, int y, uint width, uint height)
{
    bool request = ((_gm.x != x) || (_gm.y != y) || (_gm.width != width) || (_gm.height != height));

    _gm.x = x;
    _gm.y = y;

    PWinObj::resize(width, height);

    if (request) {
        configureRequestSend();
    }
}

//! @brief Sets the workspace 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, Atoms::getAtom(NET_WM_DESKTOP), NET_WM_STICKY_WINDOW);
        } else {
            AtomUtil::setLong(_window, Atoms::getAtom(NET_WM_DESKTOP), _workspace);
        }
    }
}

//! @brief Gives the Client input focus.
void
Client::giveInputFocus(void)
{
    if (_wm_hints_input) {
        PWinObj::giveInputFocus();
    }

    sendTakeFocusMessage();
}

//! @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);
    _gm.x = parent->getX() + x;
    _gm.y = parent->getY() + y;
    XSelectInput(_dpy, _window,
                 PropertyChangeMask|StructureNotifyMask|FocusChangeMask);
}

ActionEvent*
Client::handleUnmapEvent(XUnmapEvent *ev)
{
    if ((ev->window != ev->event) && (ev->send_event != true)) {
        return 0;
    }

    // ICCCM 4.1.4 advices the window manager to trigger the transition to
    // Withdrawn state on real and synthetic UnmapNotify events.
    setWmState(WithdrawnState);

    // Extended Window Manager Hints 1.3 specifies that a window manager
    // should remove the _NET_WM_STATE property when a window is withdrawn.
    AtomUtil::unsetProperty(_window, Atoms::getAtom(STATE));

    // Extended Window Manager Hints 1.3 specifies that a window manager
    // should remove the _NET_WM_DESKTOP property when a window is withdrawn.
    // (to allow legacy applications to reuse a withdrawn window)
    AtomUtil::unsetProperty(_window, Atoms::getAtom(NET_WM_DESKTOP));

#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 0;
}

ActionEvent*
Client::handleMapRequest(XMapRequestEvent *ev)
{
       if (_parent && dynamic_cast<PDecor *>(_parent)) {
           dynamic_cast<PDecor*>(_parent)->deiconify();
       }
       return 0;
}

// END - PWinObj interface.

// START - Observer interface

void
Client::notify(Observable *observable, Observation *observation)
{
    if (observation) {
        LayerObservation *layer_observation = dynamic_cast<LayerObservation*>(observation);
        if (layer_observation) {
            setLayer(layer_observation->layer + 1);

            Frame *frame = static_cast<Frame*>(getParent());
            if (frame->getActiveChild() == this) {
                frame->setLayer(layer_observation->layer + 1);
                frame->raise();
            }
        }
    } else {
        Client *client = static_cast<Client*>(observable);
        if (client == _transient) {
            _transient = 0;
        } else {
            _transient_clients.remove(client);
        }
    }
}

// END - Observer interface

//! @brief Finds the Client which holds the Window w.
//! @param win Window to search for.
//! @return Pointer to the client if found, else 0
Client*
Client::findClient(Window win)
{
    // Validate input window.
    if ((win == None) || (win == PScreen::instance()->getRoot())) {
        return 0;
    }

    list<Client*>::iterator it(_client_list.begin());
    for (; it != _client_list.end(); ++it) {
        if (win == (*it)->getWindow()
            || ((*it)->getParent() && (*((*it)->getParent()) == win))) {
            return (*it);
        }
    }

    return 0;
}

//! @brief Finds the Client of Window win.
//! @param win Window to search for.
Client*
Client::findClientFromWindow(Window win)
{
    // Validate input window.
    if (! win || win == PScreen::instance()->getRoot()) {
        return 0;
    }

    list<Client*>::iterator it(_client_list.begin());
    for(; it != _client_list.end(); ++it) {
        if (win == (*it)->getWindow()) {
            return (*it);
        }
    }

    return 0;
}

//! @brief Finds Client with equal ClassHint.
//! @param class_hint ClassHint to search for.
//! @return Client if found, else 0.
Client*
Client::findClientFromHint(const ClassHint *class_hint)
{
    list<Client*>::iterator it(_client_list.begin());
    for (; it != _client_list.end(); ++it) {
        if (*class_hint == *(*it)->getClassHint()) {
            return *it;
        }
    }

    return 0;
}

//! @brief Finds Client with id.
//! @param id ID to search for.
//! @return Client if found, else 0.
Client*
Client::findClientFromID(uint id)
{
    list<Client*>::iterator it(_client_list.begin());
    for (; it != _client_list.end(); ++it) {
        if ((*it)->getClientID() == id) {
            return *it;
        }
    }

    return 0;
}

/**
 * Insert all clients with the transient for set to win.
 */
void
Client::findFamilyFromWindow(std::list<Client*> &client_list, Window win)
{
    list<Client*>::iterator it(Client::client_begin());
    for (; it != Client::client_end(); ++it) {
        if ((*it)->getTransientClientWindow() == win) {
            client_list.push_back(*it);
        }
    }
}


/**
 * (Un)Maps all windows having transient_for set to win
 */
void
Client::mapOrUnmapTransients(Window win, bool hide)
{
    list<Client*> client_list;
    findFamilyFromWindow(client_list, win);

    list<Client*>::iterator it(client_list.begin());
    for (; it != client_list.end(); ++it) {
        if (static_cast<Frame*>((*it)->getParent())->getActiveChild() == *it) {
            if (hide) {
                (*it)->getParent()->iconify();
            } else {
                (*it)->getParent()->mapWindow();
            }
        }
    }
}

//! @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<ActionEvent> *actions = Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_CHILD_FRAME);
    list<ActionEvent>::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)) {
        return;
    }

    if (_state.cfg_deny&deny) {
        _state.cfg_deny &= ~deny;
    } else {
        _state.cfg_deny |= deny;
    }
}

/**
 * Read "all" hints required during creation of Client.
 */
void
Client::readHints(void)
{
    readMwmHints();
    readEwmhHints();
    readPekwmHints();
    readIcon();
    readClientPid();
    readClientRemote();
    getWMProtocols();
}

/**
 * Read WM Hints, return the initial state of the window.
 */
ulong
Client::readWmHints(void)
{
    ulong initial_state = NormalState;
    XWMHints* hints = XGetWMHints(_dpy, _window);
    if (hints) {
        // get the input focus mode
        if (hints->flags&InputHint) { // FIXME: More logic needed
            _wm_hints_input = hints->input;
        }

        // Get initial state of the window
        if (hints->flags&StateHint) {
            initial_state = hints->initial_state;
        }

        XFree(hints);
    }
    return initial_state;
}

/**
 * Read the WM_CLASS hint and set information on the _class_hint used
 * in matching auto properties.
 */
void
Client::readClassRoleHints(void)
{
    // class hint
    XClassHint class_hint;
    if (XGetClassHint(_dpy, _window, &class_hint)) {
        _class_hint->h_name = Util::to_wide_str(class_hint.res_name);
        _class_hint->h_class = Util::to_wide_str(class_hint.res_class);
        XFree(class_hint.res_name);
        XFree(class_hint.res_class);
    }

    // wm window role
    string role;
    AtomUtil::getString(_window, Atoms::getAtom(WM_WINDOW_ROLE), role);

    _class_hint->h_role = Util::to_wide_str(role);
}

//! @brief Loads the Clients state from EWMH atoms.
void
Client::readEwmhHints(void)
{
    // which workspace do we belong to?
    long workspace = -1;
    AtomUtil::getLong(_window, Atoms::getAtom(NET_WM_DESKTOP), workspace);
    if (workspace < 0) {
        _workspace = Workspaces::instance()->getActive();
        AtomUtil::setLong(_window, Atoms::getAtom(NET_WM_DESKTOP), _workspace);
    } else {
        _workspace = workspace;
    }

    // try to figure out what kind of window we are and alter it accordingly
    int items;
    Atom *atoms = 0;

    AtomName window_type = WINDOW_TYPE;
    atoms = (Atom*) AtomUtil::getEwmhPropData(_window, Atoms::getAtom(WINDOW_TYPE), XA_ATOM, items);
    if (atoms) {
      for (int i = 0; window_type == WINDOW_TYPE && i < items; ++i) {
        if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_DESKTOP)) {
          window_type = WINDOW_TYPE_DESKTOP;
        } else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_DOCK)) {
          window_type = WINDOW_TYPE_DOCK;
        } else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_TOOLBAR)) {
          window_type = WINDOW_TYPE_TOOLBAR;
        } else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_MENU)) {
          window_type = WINDOW_TYPE_MENU;
        } else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_UTILITY)) {
          window_type = WINDOW_TYPE_UTILITY;
        } else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_DIALOG)) {
          window_type = WINDOW_TYPE_DIALOG;
        } else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_SPLASH)) {
          window_type = WINDOW_TYPE_SPLASH;
        }
      }

      XFree(atoms);
    }

    // Set window type to WINDOW_TYPE_NORMAL if it did not match
    if (window_type == WINDOW_TYPE) {
        window_type = WINDOW_TYPE_NORMAL;
        AtomUtil::setAtom(_window, Atoms::getAtom(WINDOW_TYPE), Atoms::getAtom(WINDOW_TYPE_NORMAL));
    }

    // Apply autoproperties for window type
    AutoProperty *auto_property = AutoProperties::instance()->findWindowTypeProperty(window_type);
    if (auto_property) {
        applyAutoprops(auto_property);
    }

    // The _NET_WM_STATE overrides the _NET_WM_TYPE
    NetWMStates win_states;
    if (getEwmhStates(win_states)) {
        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 |= SKIP_TASKBAR;
        if (win_states.skip_pager) _state.skip |= SKIP_PAGER;
        if (win_states.sticky) _sticky = true;
        if (win_states.above) {
            setLayer(LAYER_ABOVE_DOCK);
        }
        if (win_states.below) {
            setLayer(LAYER_BELOW);
        }
        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) {
        if (mwm_hints->flags&MWM_HINTS_FUNCTIONS) {
            bool state = ! (mwm_hints->functions&MWM_FUNC_ALL);

            _actions.resize   = (mwm_hints->functions&MWM_FUNC_RESIZE)  ? state : ! state;
            _actions.move     = (mwm_hints->functions&MWM_FUNC_MOVE)    ? state : ! state;
            _actions.minimize = (mwm_hints->functions&MWM_FUNC_ICONIFY) ? state : ! state;
            _actions.close    = (mwm_hints->functions&MWM_FUNC_CLOSE)   ? state : ! state;
            _actions.maximize_vert = (mwm_hints->functions&MWM_FUNC_MAXIMIZE) ? state : ! state;
            _actions.maximize_horz = (mwm_hints->functions&MWM_FUNC_MAXIMIZE) ? state : ! state;
        }

        // Check decoration flags
        if (mwm_hints->flags & MWM_HINTS_DECORATIONS
            && ! (mwm_hints->decorations & MWM_DECOR_ALL)) {
          if (! (mwm_hints->decorations & MWM_DECOR_TITLE)) {
            setTitlebar(false);
          }
          if (! (mwm_hints->decorations & MWM_DECOR_BORDER)) {
            setBorder(false);
          }

          // Do not handle HANDLE, MENU, ICONFIY or MAXIMIZE. Maybe
          // one should set the allowed actions for the client based
          // on this but that might be annoying so ignoring these.
        }

        XFree(mwm_hints);
    }
}

//! @brief Reads non-standard pekwm hints
void
Client::readPekwmHints(void)
{
    long value;
    string str;

    // Get decor state
    if (AtomUtil::getLong(_window, Atoms::getAtom(PEKWM_FRAME_DECOR), value)) {
        _state.decor = value;
    }
    // Get skip state
    if (AtomUtil::getLong(_window, Atoms::getAtom(PEKWM_FRAME_SKIP), value)) {
        _state.skip = value;
    }

    // Get custom title
    if (AtomUtil::getString(_window, Atoms::getAtom(PEKWM_TITLE), str)) {
        _title.setUser(Util::to_wide_str(str));
    }

    _state.initial_frame_order = getPekwmFrameOrder();
}

//! @brief Read _NET_WM_ICON from client window.
void
Client::readIcon(void)
{
    PImageIcon *image = new PImageIcon(_dpy);
    if (image->loadFromWindow(_window)) {
        if (! _icon) {
            _icon = new PTextureImage(_dpy);
            TextureHandler::instance()->referenceTexture(_icon);
        }

        _icon->setImage(image);
    } else {
        if (image) {
            delete image;
        }

        if (_icon) {
            TextureHandler::instance()->returnTexture(_icon);
            _icon = 0;
        }
    }
}

//! @brief Tries to find a AutoProp for the current client.
//! @param type Defaults to 0
//! @return AutoProperty if any is found, else 0.
AutoProperty*
Client::readAutoprops(uint type)
{
    AutoProperty *data =
        AutoProperties::instance()->findAutoProperty(_class_hint, _workspace, type);

    if (data) {
        // Make sure transient state matches
        if (isTransient()
                ? data->isApplyOn(APPLY_ON_TRANSIENT|APPLY_ON_TRANSIENT_ONLY)
                : ! data->isApplyOn(APPLY_ON_TRANSIENT_ONLY)) {
            applyAutoprops(data);
        } else {
            data = 0;
        }
    }

    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)) {
        setLayer(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;
    }
    if (ap->isMask(AP_ALLOWED_ACTIONS)) {
        applyActionAccessMask(ap->allowed_actions, true);
    }
    if (ap->isMask(AP_DISALLOWED_ACTIONS)) {
        applyActionAccessMask(ap->disallowed_actions, false);
    }
#ifdef OPACITY
    if (ap->isMask(AP_OPACITY)) {
        setOpacity(ap->focus_opacity, ap->unfocus_opacity);
    }
#endif // OPACITY
}

void
Client::applyActionAccessMask(uint mask, bool value)
{
    if (mask & ACTION_ACCESS_MOVE) {
        _actions.move = value;
    }
    if (mask & ACTION_ACCESS_RESIZE) {
        _actions.resize = value;
    }
    if (mask & ACTION_ACCESS_MINIMIZE) {
        _actions.minimize = value;
    }
    if (mask & ACTION_ACCESS_SHADE) {
        _actions.shade = value;
    }
    if (mask & ACTION_ACCESS_STICK) {
        _actions.stick = value;
    }
    if (mask & ACTION_ACCESS_MAXIMIZE_HORZ) {
        _actions.maximize_horz = value;
    }
    if (mask & ACTION_ACCESS_MAXIMIZE_VERT) {
        _actions.maximize_vert = value;
    }
    if (mask & ACTION_ACCESS_FULLSCREEN) {
        _actions.fullscreen = value;
    }
    if (mask & ACTION_ACCESS_CHANGE_DESKTOP) {
        _actions.change_desktop = value;
    }
    if (mask & ACTION_ACCESS_CLOSE) {
        _actions.close = value;
    }
}

/**
 * Read _NET_WM_PID.
 */
void
Client::readClientPid(void)
{
    AtomUtil::getLong(_window, Atoms::getAtom(NET_WM_PID), _pid);
}

/**
 * Read WM_CLIENT_MACHINE and check against local hostname and set
 * _is_remote if it does not match.
 */
void
Client::readClientRemote(void)
{
    string client_machine;
    if (AtomUtil::getTextProperty(_window, XA_WM_CLIENT_MACHINE, client_machine)) {
        _is_remote = Util::getHostname() != client_machine;
    }
}

//! @brief Finds free Client ID.
//! @return First free Client ID.
uint
Client::findClientID(void)
{
    uint id = 0;    

    if (_clientid_list.size()) {
        // Check for used Frame IDs
        id = _clientid_list.back();
        _clientid_list.pop_back();
    } else {
        // No free, get next number (Client is not in list when this is called.)
        id = _client_list.size() + 1;
    }

    return id;
}

//! @brief Returns Client ID to used client id list.
//! @param id ID to return.
void
Client::returnClientID(uint id)
{
    vector<uint>::iterator it(_clientid_list.begin());
    for (; it != _clientid_list.end() && id < *it; ++it)
        ;
    _clientid_list.insert(it, id);
}

/**
 * Tries to get the NET_WM name, else fall back to WM_NAME
 */
void
Client::readName(void)
{
    // Read title, bail out if it fails.
    wstring title;
    if (! AtomUtil::getUtf8String(_window, Atoms::getAtom(NET_WM_NAME), title)) {
        string mb_title;
        if (! AtomUtil::getTextProperty(_window, XA_WM_NAME, mb_title)) {
            return;
        }
        title = Util::to_wide_str(mb_title);
    }

    // Mirror it on the visible
    _title.setCustom(L"");
    _title.setCount(titleFindID(title));
    _title.setReal(title);

    // Apply title rules and find unique name, doesn't apply on
    // user-set titles
    if (titleApplyRule(title)) {
        _title.setCustom(title);
        AtomUtil::setUtf8String(_window, Atoms::getAtom(NET_WM_VISIBLE_NAME), title);
    } else {
        AtomUtil::unsetProperty(_window, Atoms::getAtom(NET_WM_VISIBLE_NAME));
    }
}

//! @brief Searches for an TitleRule and if found, applies it
//! @param title Title to apply rule on.
//! @return true if rule was applied, else false.
bool
Client::titleApplyRule(std::wstring &title)
{
    _class_hint->title = title;
    TitleProperty *data = AutoProperties::instance()->findTitleProperty(_class_hint);

    if (data) {
        return data->getTitleRule().ed_s(title);
    } else {
        return false;
    }
}

//! @brief Searches for a unique ID within Clients having the same title
//! @param title Title of client to find ID for.
//! @return Number of clients with that id.
uint
Client::titleFindID(std::wstring &title)
{
    // Do not search for unique IDs if it is not enabled.
    if (! Config::instance()->getClientUniqueName()) {
        return 0;
    }

    uint id_found = 0;
    list<uint> ids_used;

    list<Client*>::iterator it = _client_list.begin();
    for (; it != _client_list.end(); ++it) {
        if (*it != this) {
            if ((*it)->getTitle()->getReal() == title) {
                ids_used.push_back((*it)->getTitle()->getCount());
            }
        }
    }

    // more than one client having this name
    if (ids_used.size() > 0) {
        ids_used.sort();

        list<uint>::iterator ui_it( ids_used.begin());
        for (uint i = 0; ui_it != ids_used.end(); ++i, ++ui_it) {
            if (i < *ui_it) {
                id_found = i;
                break;
            }
        }

        if (ui_it == ids_used.end()) {
            id_found = ids_used.size();
        }
    }

    return id_found;
}

/**
 * Get the clients icon name to be displayed when the client is
 * iconified.
 */
void
Client::readIconName(void)
{
    wstring icon_name;

    if (! AtomUtil::getUtf8String(_window, Atoms::getAtom(NET_WM_ICON_NAME), icon_name)) {
        string mb_icon_name;
        if (AtomUtil::getTextProperty(_window, XA_WM_ICON_NAME, mb_icon_name)) {
            icon_name = Util::to_wide_str(mb_icon_name);
        }
    }

    // Set real name
    _icon_name.setReal(icon_name);
    _icon_name.setCustom(icon_name);

    if (_icon_name.getVisible() == _icon_name.getReal()) {
        AtomUtil::unsetProperty(_window, Atoms::getAtom(NET_WM_VISIBLE_ICON_NAME));
    } else {
        AtomUtil::setUtf8String(_window, Atoms::getAtom(NET_WM_VISIBLE_ICON_NAME), icon_name);
    }
}

//! @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,
                    Atoms::getAtom(WM_STATE),
                    Atoms::getAtom(WM_STATE),
                    32, PropModeReplace, (uchar*) data, 2);
}

// If we can't find a 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;
    uchar *udata;

    int status =
        XGetWindowProperty(_dpy, _window, Atoms::getAtom(WM_STATE),
                           0L, 2L, False, Atoms::getAtom(WM_STATE),
                           &real_type, &real_format, &items_read, &items_left,
                           &udata);
    if ((status  == Success) && items_read) {
        data = reinterpret_cast<long*>(udata);
        state = *data;
        XFree(udata);
    }

    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;

    XSendEvent(_dpy, _window, false, StructureNotifyMask, (XEvent *) &e);
}

//! @brief Send a TAKE_FOCUS client message to the client (if requested by it).
void Client::sendTakeFocusMessage(void)
{
    if (_send_focus_message) {
        {
            XEvent ev;
            XChangeProperty(_dpy, RootWindow(_dpy, DefaultScreen(_dpy)), XA_PRIMARY, XA_STRING, 8, PropModeAppend, 0, 0);
            XWindowEvent(_dpy, RootWindow(_dpy, DefaultScreen(_dpy)), PropertyChangeMask, &ev);
            PScreen::instance()->setLastEventTime(ev.xproperty.time);
        }
        sendXMessage(_window,
                     Atoms::getAtom(WM_PROTOCOLS), NoEventMask,
                     Atoms::getAtom(WM_TAKE_FOCUS),
                     PScreen::instance()->getLastEventTime());
    }
}

//! @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);
    }
}

/**
 * Toggles the clients always on top state
 */
void
Client::alwaysOnTop(bool top)
{
    setLayer(top ? LAYER_ONTOP : LAYER_NORMAL);
    updateEwmhStates();
}

/**
 * Toggles the clients always below state.
 */
void
Client::alwaysBelow(bool below)
{
    setLayer(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, Atoms::getAtom(PEKWM_FRAME_SKIP), _state.skip);
}

/**
 * Set demands attention state, this should be unset when client
 * recieves focus. This is ignored if client has focus.
 */
void
Client::setStateDemandsAttention(StateAction sa, bool attention)
{
    // FIXME: Demands attention state.
}

//! @brief Sends an WM_DELETE message to the client, else kills it.
void
Client::close(void)
{
    if (_send_close_message) {
        sendXMessage(_window, Atoms::getAtom(WM_PROTOCOLS), NoEventMask,
                     Atoms::getAtom(WM_DELETE_WINDOW), CurrentTime);
    } else {
        kill();
    }
}

//! @brief Kills the client using XKillClient
void
Client::kill(void)
{
    XKillClient(_dpy, _window);
}

//! @brief Sets the position 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 closest size confirming to the aspect ratio and ResizeInc (if applicable)
//! @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
//! @return true if width/height have changed
bool
Client::getAspectSize(uint *r_w, uint *r_h, uint w, uint h)
{
    // see ICCCM 4.1.2.3 for PAspect and {min,max}_aspect
    if (_size->flags & PAspect && Config::instance()->isHonourAspectRatio()) {
        // shorthand
        const uint amin_x = _size->min_aspect.x;
        const uint amin_y = _size->min_aspect.y;
        const uint amax_x = _size->max_aspect.x;
        const uint amax_y = _size->max_aspect.y;

        uint base_w = 0, base_h = 0;

        // If PBaseSize is specified, base_{width,height} should be subtracted
        // before checking the aspect ratio (c.f. ICCCM). The additional checks avoid
        // underflows in w and h. Keep in mind that _size->base_{width,height} are
        // guaranteed to be non-negative by getWMNormalHints().
        if (_size->flags & PBaseSize) {
            if (static_cast<uint>(_size->base_width) < w) {
                base_w = _size->base_width;
                w -= base_w;
            }
            if (static_cast<uint>(_size->base_height) < h) {
                base_h = _size->base_height;
                h -= base_h;
            }
        }

        double tmp;

        // We have to ensure: min_aspect.x/min_aspect.y <= w/h <= max_aspect.x/max_aspect.y

        // How do we calculate the new r_w, r_h?
        // Consider the plane spanned by width and height. The points with one specific
        // aspect ratio form a line and (w,h) is a point. So, we simply calculate the
        // point on the line that is closest to (w, h) under the Euclidean metric.

        // Lesson learned doing this: It is good to look at a different implementation
        // (thanks fluxbox!) and then let a friend go over your own calculation to
        // tell you what your mistake is (thanks Robert!). ;-)

        // Check if w/h is less than amin_x/amin_y
        if (w * amin_y < h * amin_x) {
            tmp = ((double)(w * amin_x + h * amin_y)) /
                  ((double)(amin_x * amin_x + amin_y * amin_y));

            w = static_cast<uint>(amin_x * tmp) + base_w;
            h = static_cast<uint>(amin_y * tmp) + base_h;

        // Check if w/h is greater than amax_x/amax_y
        } else if (w * amax_y > amax_x * h) {
            tmp = ((double)(w * amax_x + h * amax_y)) /
                  ((double)(amax_x * amax_x + amax_y * amax_y));

            w = static_cast<uint>(amax_x * tmp) + base_w;
            h = static_cast<uint>(amax_y * tmp) + base_h;
        }

        getIncSize(r_w, r_h, w, h, false);
        return true;
    }
    return getIncSize(r_w, r_h, w, h);
}

//! @brief Get the size closest to the ResizeInc incrementer
//! @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
//! @param incr If true, increase w,h to fulfil PResizeInc (instead of decreasing them)
bool
Client::getIncSize(uint *r_w, uint *r_h, uint w, uint h, bool incr)
{
    uint 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;

        if (w-basex < 0 || h-basey<0) {
            basex=basey=0;
        }

        uint dw = (w - basex) % _size->width_inc;
        uint dh = (h - basey) % _size->height_inc;

        *r_w = w - dw + ((incr && dw)?_size->width_inc:0);
        *r_h = h - dh + ((incr && dh)?_size->height_inc:0);
        return true;
    }

    *r_w = w;
    *r_h = h;
    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 = 0;
    uchar *udata;

    Atom hints_atom = Atoms::getAtom(MOTIF_WM_HINTS);

    int status = XGetWindowProperty(_dpy, win, hints_atom, 0L, 20L, False, hints_atom,
                                    &real_type, &real_format, &items_read, &items_left, &udata);

    if (status == Success) {
        if (items_read < MWM_HINTS_NUM) {
            XFree(udata);
            udata = 0;
        }
    } else {
        udata = 0;
    }

    if (udata) {
        data = reinterpret_cast<MwmHints*>(udata);
    }

    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, Atoms::getAtom(STATE),
                                       XA_ATOM, num);

    if (states) {
        for (int i = 0; i < num; ++i) {
            if (states[i] == Atoms::getAtom(STATE_MODAL)) {
                win_states.modal = true;
            } else if (states[i] == Atoms::getAtom(STATE_STICKY)) {
                win_states.sticky = true;
            } else if (states[i] == Atoms::getAtom(STATE_MAXIMIZED_VERT)
                       && ! isCfgDeny(CFG_DENY_STATE_MAXIMIZED_VERT)) {
                win_states.max_vert = true;
            } else if (states[i] == Atoms::getAtom(STATE_MAXIMIZED_HORZ)
                       && ! isCfgDeny(CFG_DENY_STATE_MAXIMIZED_HORZ)) {
                win_states.max_horz = true;
            } else if (states[i] == Atoms::getAtom(STATE_SHADED)) {
                win_states.shaded = true;
            } else if (states[i] == Atoms::getAtom(STATE_SKIP_TASKBAR)) {
                win_states.skip_taskbar = true;
            } else if (states[i] == Atoms::getAtom(STATE_SKIP_PAGER)) {
                win_states.skip_pager = true;
            } else if (states[i] == Atoms::getAtom(STATE_DEMANDS_ATTENTION)) {
                win_states.demands_attention = true;
            } else if (states[i] == Atoms::getAtom(STATE_HIDDEN)
                       && ! isCfgDeny(CFG_DENY_STATE_HIDDEN)) {
                win_states.hidden = true;
            } else if (states[i] == Atoms::getAtom(STATE_FULLSCREEN)
                       && ! isCfgDeny(CFG_DENY_STATE_FULLSCREEN)) {
                win_states.fullscreen = true;
            } else if (states[i] == Atoms::getAtom(STATE_ABOVE)
                       && ! isCfgDeny(CFG_DENY_STATE_ABOVE)) {
                win_states.above = true;
            } else if (states[i] == Atoms::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)
{
    list<Atom> states;

    if (false) // we don't yet support modal state
        states.push_back(Atoms::getAtom(STATE_MODAL));
    if (_sticky)
        states.push_back(Atoms::getAtom(STATE_STICKY));
    if (_state.maximized_vert)
        states.push_back(Atoms::getAtom(STATE_MAXIMIZED_VERT));
    if (_state.maximized_horz)
        states.push_back(Atoms::getAtom(STATE_MAXIMIZED_HORZ));
    if (_state.shaded)
        states.push_back(Atoms::getAtom(STATE_SHADED));
    if (isSkip(SKIP_TASKBAR))
        states.push_back(Atoms::getAtom(STATE_SKIP_TASKBAR));
    if (isSkip(SKIP_PAGER))
        states.push_back(Atoms::getAtom(STATE_SKIP_PAGER));
    if (_iconified)
        states.push_back(Atoms::getAtom(STATE_HIDDEN));
    if (_state.fullscreen)
        states.push_back(Atoms::getAtom(STATE_FULLSCREEN));
    if (getLayer() == LAYER_ABOVE_DOCK) {
        states.push_back(Atoms::getAtom(STATE_ABOVE));
    }
    if (getLayer() == LAYER_BELOW) {
        states.push_back(Atoms::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, Atoms::getAtom(STATE), atoms, states.size());
    delete [] atoms;
}

void
Client::getWMNormalHints(void)
{
    long dummy;
    XGetWMNormalHints(_dpy, _window, _size, &dummy);

    // let's do some sanity checking
    if (_size->flags & PBaseSize) {
        if (_size->base_width<0 || _size->base_height<0) {
            _size->base_width = _size->base_height = 0;
            _size->flags &= ~PBaseSize;
        }
    }
    if (_size->flags & PAspect) {
        if (_size->min_aspect.x < 0 || _size->min_aspect.y < 0 ||
            _size->max_aspect.x < 0 || _size->max_aspect.y < 0) {

            _size->min_aspect.x = _size->min_aspect.y = 0;
            _size->max_aspect.x = _size->max_aspect.y = 0;
            _size->flags &= ~PAspect;
        }
    }
    if (_size->flags & PResizeInc) {
        if (_size->width_inc <= 0 || _size->height_inc <= 0) {
            _size->width_inc = 0;
            _size->height_inc = 0;
            _size->flags &= ~PBaseSize;
        }
    }

    if (_size->flags & PMaxSize) {
        if (_size->max_width <= 0 || _size->max_height <= 0 ||
            ((_size->flags & PMinSize) &&
                (_size->max_width < _size->min_width ||
                 _size->max_height < _size->min_height) )) {
            _size->max_width = 0;
            _size->max_height = 0;
            _size->flags &= ~PMaxSize;
        }
    }
}

//! @brief
void
Client::getWMProtocols(void)
{
    int count;
    Atom *protocols;

    if (XGetWMProtocols(_dpy, _window, &protocols, &count) != 0) {
        for (int i = 0; i < count; ++i) {
            if (protocols[i] == Atoms::getAtom(WM_TAKE_FOCUS)) {
                _send_focus_message = true;
            } else if (protocols[i] == Atoms::getAtom(WM_DELETE_WINDOW)) {
                _send_close_message = true;
            }
        }

        XFree(protocols);
    }
}

/**
 * Read WM_TRANSIENT_FOR hint.
 */
void
Client::getTransientForHint(void)
{
    if (! _transient) {
        XGetTransientForHint(_dpy, _window, &_transient_window);

        if (_transient_window != None) {
            _transient = findClientFromWindow(_transient_window);
            if (_transient) {
               _transient->_transient_clients.push_back(this);
               _transient->addObserver(this);
            }
        }
    }
}

//! @brief
void
Client::getStrutHint(void)
{
    int num = 0;
    long *strut = static_cast<long*>(AtomUtil::getEwmhPropData(_window,
                                                               Atoms::getAtom(NET_WM_STRUT),
                                                               XA_CARDINAL, num));
    if (strut) {
        if (_strut) {
            PScreen::instance()->removeStrut(_strut);
        } else {
            _strut = new Strut();
        }

        *_strut = strut;
        PScreen::instance()->addStrut(_strut);

        XFree(strut);

    } else if (_strut) {
        PScreen::instance()->removeStrut(_strut);
        delete _strut;
        _strut = 0;
    }
}

//! @brief
void
Client::removeStrutHint(void)
{
    if (! _strut)
        return;

    PScreen::instance()->removeStrut(_strut);
    delete _strut;
    _strut = 0;
}

/**
 * Get _PEKWM_FRAME_ORDER hint from client, return < 0 on failure.
 */
long
Client::getPekwmFrameOrder(void)
{
    long num = -1;
    AtomUtil::getLong(_window, Atoms::getAtom(PEKWM_FRAME_ORDER), num);
    return num;
}

/**
 * Update _PEKWM_FRAME_ORDER hint on client window.
 */
void
Client::setPekwmFrameOrder(long num)
{
    AtomUtil::setLong(_window, Atoms::getAtom(PEKWM_FRAME_ORDER), num);
}

/**
 * Get _PEKWM_FRAME_ACTIVE hint from client window, return true if
 * client is treated as active.
 */
bool
Client::getPekwmFrameActive(void)
{
    long act = 0;
    return (AtomUtil::getLong(_window, Atoms::getAtom(PEKWM_FRAME_ACTIVE), act)
            && act == 1);
}

/**
 * Set _PEKWM_FRAME_ACTIVE hint on client window.
 */
void
Client::setPekwmFrameActive(bool act)
{
    AtomUtil::setLong(_window, Atoms::getAtom(PEKWM_FRAME_ACTIVE), act ? 1 : 0);
}

/**
 * Update the environment with CLIENT_PID and CLIENT_WINDOW.
 */
void
Client::setClientEnvironment(Client *client)
{
    if (client) {
        setenv("CLIENT_PID", Util::to_string<long>(client->isRemote() ? -1 : client->getPid()).c_str(), 1);
        setenv("CLIENT_WINDOW", Util::to_string<Window>(client->getWindow()).c_str(), 1);
    } else {
        unsetenv("CLIENT_PID");
        unsetenv("CLIENT_WINDOW");
    }
}
