// WinClient.cc for Fluxbox - an X11 Window manager
// Copyright (c) 2003 Henrik Kinnunen (fluxgen(at)users.sourceforge.net)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

// $Id: WinClient.cc,v 1.23 2003/07/28 18:30:02 fluxgen Exp $

#include "WinClient.hh"

#include "Window.hh"
#include "fluxbox.hh"
#include "Screen.hh"
#include "I18n.hh"
#include "FbAtoms.hh"
#include "EventManager.hh"
#include "Xutil.hh"

#include <iostream>
#include <algorithm>
#include <iterator>
#include <cassert>

using namespace std;

WinClient::WinClient(Window win, BScreen &screen, FluxboxWindow *fbwin):FbTk::FbWindow(win),
                     transient_for(0),
                     window_group(0),
                     x(0), y(0), old_bw(0),
                     min_width(1), min_height(1),
                     max_width(1), max_height(1),
                     width_inc(1), height_inc(1),
                     min_aspect_x(1), min_aspect_y(1),
                     max_aspect_x(1), max_aspect_y(1),
                     base_width(1), base_height(1),
                     win_gravity(0),
                     initial_state(0),
                     normal_hint_flags(0),
                     wm_hint_flags(0),
                     m_win(fbwin),
                     m_modal(0),
                     send_focus_message(false),
                     closable(false),
                     m_title(""), m_icon_title(""),
                     m_class_name(""), m_instance_name(""),
                     m_blackbox_hint(0),
                     m_mwm_hint(0),
                     m_focus_mode(F_PASSIVE),
                     m_diesig(*this), m_screen(screen) {
    updateBlackboxHints();
    updateMWMHints();
    updateWMHints();
    updateWMNormalHints();
    updateWMClassHint();
    updateWMProtocols();
    updateTitle();
    updateIconTitle();
    Fluxbox::instance()->saveWindowSearch(win, this);
    if (window_group != None) 
        Fluxbox::instance()->saveGroupSearch(window_group, this);

}

WinClient::~WinClient() {
#ifdef DEBUG
    cerr<<__FILE__<<"(~"<<__FUNCTION__<<")[this="<<this<<"]"<<endl;
#endif // DEBUG

    FbTk::EventManager::instance()->remove(window());

    clearStrut();

    if (m_win != 0)
        m_win->removeClient(*this);

    // this takes care of any focus issues
    m_diesig.notify();

    Fluxbox *fluxbox = Fluxbox::instance();

    if (transient_for != 0) {
        assert(transient_for != this);
        transient_for->transientList().remove(this);
        transient_for = 0;
    }

    while (!transients.empty()) {
        transients.back()->transient_for = 0;
        transients.pop_back();
    }

    screen().removeNetizen(window());

    if (window_group != 0) {
        fluxbox->removeGroupSearch(window_group);
        window_group = 0;
    }

    if (m_mwm_hint != 0)
        XFree(m_mwm_hint);

    if (m_blackbox_hint != 0)
        XFree(m_blackbox_hint);

    if (window())
        fluxbox->removeWindowSearch(window());

    m_win = 0;
}

void WinClient::updateRect(int x, int y, 
                        unsigned int width, unsigned int height) {
    Display *disp = FbTk::App::instance()->display();
    XEvent event;
    event.type = ConfigureNotify;

    event.xconfigure.display = disp;
    event.xconfigure.event = window();
    event.xconfigure.window = window();
    event.xconfigure.x = x;
    event.xconfigure.y = y;
    event.xconfigure.width = width;
    event.xconfigure.height = height;
    //!! TODO
    event.xconfigure.border_width = 1;//client.old_bw;
    //!! TODO
    event.xconfigure.above = None; //m_frame.window().window();
    event.xconfigure.override_redirect = false;

    XSendEvent(disp, window(), False, StructureNotifyMask, &event);

}

bool WinClient::sendFocus() {
    if (!send_focus_message)
        return false;

    Display *disp = FbTk::App::instance()->display();
    // setup focus msg
    XEvent ce;
    ce.xclient.type = ClientMessage;
    ce.xclient.message_type = FbAtoms::instance()->getWMProtocolsAtom();
    ce.xclient.display = disp;
    ce.xclient.window = window();
    ce.xclient.format = 32;
    ce.xclient.data.l[0] = FbAtoms::instance()->getWMTakeFocusAtom();
    ce.xclient.data.l[1] = Fluxbox::instance()->getLastTime();
    ce.xclient.data.l[2] = 0l;
    ce.xclient.data.l[3] = 0l;
    ce.xclient.data.l[4] = 0l;
    // send focus msg
    XSendEvent(disp, window(), false, NoEventMask, &ce);
    return true;
}

void WinClient::sendClose(bool forceful) {
    if (forceful || !isClosable())
        XKillClient(FbTk::App::instance()->display(), window());
    else {
        // send WM_DELETE message
        Display *disp = FbTk::App::instance()->display();
        // fill in XClientMessage structure for delete message
        XEvent ce;
        ce.xclient.type = ClientMessage;
        ce.xclient.message_type = FbAtoms::instance()->getWMProtocolsAtom();
        ce.xclient.display = disp;
        ce.xclient.window = window();
        ce.xclient.format = 32;
        ce.xclient.data.l[0] = FbAtoms::instance()->getWMDeleteAtom();
        ce.xclient.data.l[1] = CurrentTime;
        ce.xclient.data.l[2] = 0l;
        ce.xclient.data.l[3] = 0l;
        ce.xclient.data.l[4] = 0l;
        // send event delete message to client window
        XSendEvent(disp, window(), false, NoEventMask, &ce);
    }
}

void WinClient::reparent(Window win, int x, int y) {
    XReparentWindow(FbTk::App::instance()->display(), window(), win, x, y);
}

bool WinClient::getAttrib(XWindowAttributes &attr) const {
    return XGetWindowAttributes(FbTk::App::instance()->display(), window(), &attr);
}

bool WinClient::getWMName(XTextProperty &textprop) const {
    return XGetWMName(FbTk::App::instance()->display(), window(), &textprop);
}

bool WinClient::getWMIconName(XTextProperty &textprop) const {
    return XGetWMName(FbTk::App::instance()->display(), window(), &textprop);
}

const std::string &WinClient::getWMClassName() const {
    return m_instance_name;
}

const std::string &WinClient::getWMClassClass() const {
    return m_class_name;
}

void WinClient::updateWMClassHint() {
    XClassHint ch;
    if (XGetClassHint(FbTk::App::instance()->display(), window(), &ch) == 0) {
#ifdef DEBUG
        cerr<<"WinClient: Failed to read class hint!"<<endl;
#endif //DEBUG
    } else {        

        if (ch.res_name != 0) {
            m_instance_name = const_cast<char *>(ch.res_name);
            XFree(ch.res_name);
            ch.res_name = 0;
        } else 
            m_instance_name = "";
        
        if (ch.res_class != 0) {
            m_class_name = const_cast<char *>(ch.res_class);
            XFree(ch.res_class);
            ch.res_class = 0;
        } else
            m_class_name = "";
    }
}

void WinClient::updateTransientInfo() {
    if (m_win == 0)
        return;
    // remove us from parent
    if (transientFor() != 0) {
        transientFor()->transientList().remove(this);
    }
    
    transient_for = 0;
    Display *disp = FbTk::App::instance()->display();
    // determine if this is a transient window
    Window win;
    if (!XGetTransientForHint(disp, window(), &win))
        return;

    // we can't be transient to ourself
    if (win == window())
        return;
	
    if (win != None && m_win->screen().rootWindow() == win) {
        // transient for root window... =  transient for group
        // I don't think we are group-aware yet
        return; 
    }


    transient_for = Fluxbox::instance()->searchWindow(win);

    // make sure we don't have deadlock loop in transient chain
    for (WinClient *w = this; w != 0; w = w->transient_for) {
        if (w == w->transient_for) {
            w->transient_for = 0;
            break;
        }
    }

    if (transientFor() != 0) {
        // we need to add ourself to the right client in
        // the transientFor() window so we search client
        transient_for->transientList().push_back(this);

        if (transientFor()->fbwindow() && transientFor()->fbwindow()->isStuck())
            m_win->stick();
    }
}


void WinClient::updateTitle() {
    m_title = Xutil::getWMName(window());
}

void WinClient::updateIconTitle() {
    XTextProperty text_prop;
    char **list = 0;
    int num = 0;

    if (getWMIconName(text_prop)) {
        if (text_prop.value && text_prop.nitems > 0) {
            if (text_prop.encoding != XA_STRING) {
                text_prop.nitems = strlen((char *) text_prop.value);

                if (XmbTextPropertyToTextList(FbTk::App::instance()->display(), &text_prop,
                                               &list, &num) == Success &&
                    num > 0 && *list) {
                    m_icon_title = (char *)*list;
                    XFreeStringList(list);
                } else
                    m_icon_title = text_prop.value ? (char *)text_prop.value : "";
            } else
                m_icon_title = text_prop.value ? (char *)text_prop.value : "";

            if (text_prop.value)
                XFree((char *) text_prop.value);
        } else
            m_icon_title = title();
    } else
        m_icon_title = title();

}

void WinClient::saveBlackboxAttribs(FluxboxWindow::BlackboxAttributes &blackbox_attribs) {
    changeProperty(FbAtoms::instance()->getFluxboxAttributesAtom(),
                   PropModeReplace, XA_CARDINAL, 32,
                   (unsigned char *)&blackbox_attribs,
                   FluxboxWindow::PropBlackboxAttributesElements
        );
}

void WinClient::updateBlackboxHints() {
    int format;
    Atom atom_return;
    unsigned long num, len;
    FbAtoms *atoms = FbAtoms::instance();

    if (m_blackbox_hint) {
        XFree(m_blackbox_hint);
        m_blackbox_hint = 0;
    }

    if (property(atoms->getFluxboxHintsAtom(), 0,
                 PropBlackboxHintsElements, False,
                 atoms->getFluxboxHintsAtom(), &atom_return,
                 &format, &num, &len,
                 (unsigned char **) &m_blackbox_hint) &&
        m_blackbox_hint) {

        if (num != (unsigned)PropBlackboxHintsElements) {
            XFree(m_blackbox_hint);
            m_blackbox_hint = 0;
        }
    }
}

void WinClient::updateMWMHints() {
    int format;
    Atom atom_return;
    unsigned long num = 0, len = 0;
    Atom  motif_wm_hints = XInternAtom(FbTk::App::instance()->display(), "_MOTIF_WM_HINTS", False);

    if (m_mwm_hint) {
        XFree(m_mwm_hint);
        m_mwm_hint = 0;
    }

    if (!(property(motif_wm_hints, 0,
                   PropMwmHintsElements, false,
                   motif_wm_hints, &atom_return,
                   &format, &num, &len,
                   (unsigned char **) &m_mwm_hint) &&
          m_mwm_hint)) {
        if (num != static_cast<unsigned int>(PropMwmHintsElements)) {
            XFree(m_mwm_hint);
            m_mwm_hint = 0;
            return;
        }
    }
}

void WinClient::updateWMHints() {
    XWMHints *wmhint = XGetWMHints(FbTk::App::instance()->display(), window());
    if (! wmhint) {
        m_focus_mode = F_PASSIVE;
        window_group = None;
        initial_state = NormalState;
    } else {
        wm_hint_flags = wmhint->flags;
        if (wmhint->flags & InputHint) {
            if (wmhint->input) {
                if (send_focus_message)
                    m_focus_mode = F_LOCALLYACTIVE;
                else
                    m_focus_mode = F_PASSIVE;
            } else {
                if (send_focus_message)
                    m_focus_mode = F_GLOBALLYACTIVE;
                else
                    m_focus_mode = F_NOINPUT;
            }
        } else
            m_focus_mode = F_PASSIVE;

        if (wmhint->flags & StateHint)
            initial_state = wmhint->initial_state;
        else
            initial_state = NormalState;

        if (wmhint->flags & WindowGroupHint) {
            if (! window_group)
                window_group = wmhint->window_group;
        } else
            window_group = None;

        XFree(wmhint);
    }
}


void WinClient::updateWMNormalHints() {
    long icccm_mask;
    XSizeHints sizehint;
    if (! XGetWMNormalHints(FbTk::App::instance()->display(), window(), &sizehint, &icccm_mask)) {
        min_width = min_height =
            base_width = base_height =
            width_inc = height_inc = 1;
        max_width = 0; // unbounded
        max_height = 0;
        min_aspect_x = min_aspect_y =
            max_aspect_x = max_aspect_y = 1;
        win_gravity = NorthWestGravity;
    } else {
        normal_hint_flags = sizehint.flags;

        if (sizehint.flags & PMinSize) {
            min_width = sizehint.min_width;
            min_height = sizehint.min_height;
        } else
            min_width = min_height = 1;

        if (sizehint.flags & PMaxSize) {
            max_width = sizehint.max_width;
            max_height = sizehint.max_height;
        } else {
            max_width = 0; // unbounded
            max_height = 0;
        }

        if (sizehint.flags & PResizeInc) {
            width_inc = sizehint.width_inc;
            height_inc = sizehint.height_inc;
        } else
            width_inc = height_inc = 1;

        if (sizehint.flags & PAspect) {
            min_aspect_x = sizehint.min_aspect.x;
            min_aspect_y = sizehint.min_aspect.y;
            max_aspect_x = sizehint.max_aspect.x;
            max_aspect_y = sizehint.max_aspect.y;
        } else
            min_aspect_x = min_aspect_y =
                max_aspect_x = max_aspect_y = 1;

        if (sizehint.flags & PBaseSize) {
            base_width = sizehint.base_width;
            base_height = sizehint.base_height;
        } else
            base_width = base_height = 0;

        if (sizehint.flags & PWinGravity)
            win_gravity = sizehint.win_gravity;
        else
            win_gravity = NorthWestGravity;
    }
}

Window WinClient::getGroupLeftWindow() const {
    int format;
    Atom atom_return;
    unsigned long num = 0, len = 0;
    Atom group_left_hint = XInternAtom(FbTk::App::instance()->display(), "_FLUXBOX_GROUP_LEFT", False);

    Window *data = 0;
    if (property(group_left_hint, 0,
                   1, false,
                   XA_WINDOW, &atom_return,
                   &format, &num, &len,
                   (unsigned char **) &data) &&
        data) {
        if (num != 1) {
            XFree(data);
            return None;
        } else {
            Window ret = *data;
            XFree(data);
            return ret;
        }
    }
    return None;
}


void WinClient::setGroupLeftWindow(Window win) {
    Atom group_left_hint = XInternAtom(FbTk::App::instance()->display(), "_FLUXBOX_GROUP_LEFT", False);
    changeProperty(group_left_hint, XA_WINDOW, 32, 
                   PropModeReplace, (unsigned char *) &win, 1);
}

bool WinClient::hasGroupLeftWindow() const {
    // try to find _FLUXBOX_GROUP_LEFT atom in window
    // if we have one then we have a group left window
    int format;
    Atom atom_return;
    unsigned long num = 0, len = 0;
    Atom group_left_hint = XInternAtom(FbTk::App::instance()->display(), "_FLUXBOX_GROUP_LEFT", False);

    Window *data = 0;
    if (property(group_left_hint, 0,
                   1, false,
                   XA_WINDOW, &atom_return,
                   &format, &num, &len,
                   (unsigned char **) &data) &&
        data) {
            XFree(data);
            if (num != 1)
                return false;
            else
                return true;
    }

    return false;
}

void WinClient::addModal() {
    ++m_modal;
    if (transient_for)
        transient_for->addModal();
}

void WinClient::removeModal() {
    --m_modal;
    if (transient_for)
        transient_for->removeModal();
}

bool WinClient::validateClient() const {
    Display *display = FbTk::App::instance()->display();
    XSync(display, false);

    XEvent e;
    if (( XCheckTypedWindowEvent(display, window(), DestroyNotify, &e) ||
          XCheckTypedWindowEvent(display, window(), UnmapNotify, &e)) 
        && XPutBackEvent(display, &e)) {
        Fluxbox::instance()->ungrab();
        return false;
    }

    return true;
}

void WinClient::setStrut(Strut *strut) {    
    clearStrut();
    m_strut = strut;
}

void WinClient::clearStrut() {
    if (m_strut != 0) {
        screen().clearStrut(m_strut);
        m_strut = 0;
    }
}

bool WinClient::focus() {
    if (m_win == 0)
        return false;
    else
        return m_win->setCurrentClient(*this, true);
}

void WinClient::updateWMProtocols() {
    Atom *proto = 0;
    int num_return = 0;
    FbAtoms *fbatoms = FbAtoms::instance();

    if (XGetWMProtocols(FbTk::App::instance()->display(), window(), &proto, &num_return)) {

        for (int i = 0; i < num_return; ++i) {
            if (proto[i] == fbatoms->getWMDeleteAtom())
                closable = true;
            else if (proto[i] == fbatoms->getWMTakeFocusAtom())
                send_focus_message = true;
            else if (proto[i] == fbatoms->getFluxboxStructureMessagesAtom())
                screen().addNetizen(window());
        }

        XFree(proto);
        if (m_win)
            m_win->updateFunctions();
    } else {
        cerr<<"Warning: Failed to read WM Protocols. "<<endl;
    }

}