/*
 * synergy -- mouse and keyboard sharing utility
 * Copyright (C) 2002 Chris Schoeneman
 * Copyright (C) 2006 Knut St. Osmundsen
 *
 * This package is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * found in the file COPYING that should have accompanied this file.
 *
 * This package is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include "CPMScreen.h"
#include "CPMClipboard.h"
#include "CPMEventQueueBuffer.h"
#include "CPMKeyState.h"
#include "CPMScreenSaver.h"
#include "CClipboard.h"
#include "CKeyMap.h"
#include "XScreen.h"
#include "CLock.h"
#include "CThread.h"
#include "CFunctionJob.h"
#include "CLog.h"
#include "CString.h"
#include "CStringUtil.h"
#include "IEventQueue.h"
#include "TMethodEventJob.h"
#include "TMethodJob.h"
#include "CArch.h"
#include "CPMUtil.h"
#include <string.h>
#include <unistd.h>

//
// CPMScreen
//

CPMScreen*      CPMScreen::s_screen   = NULL;

CPMScreen::CPMScreen(bool isPrimary) :
    m_isPrimary(isPrimary),
    m_isOnScreen(m_isPrimary),
    m_x(0), m_y(0),
    m_cx(0), m_cy(0),
    m_xCenter(0), m_yCenter(0),
    m_multimon(false),
    m_xCursor(0), m_yCursor(0),
    m_sequenceNumber(0),
    m_mark(0),
    m_markReceived(0),
    m_screensaver(NULL),
    m_screensaverNotify(false),
    m_screensaverActive(false),
    m_window(NULLHANDLE),
    m_nextClipboardWindow(NULLHANDLE),
    m_ownClipboard(false),
    m_hmodHook(NULLHANDLE),
    m_init(NULL),
    m_cleanup(NULL),
    m_setSides(NULL),
    m_setZone(NULL),
    m_setMode(NULL),
    m_keyState(NULL),
    m_hasMouse(WinQuerySysValue(HWND_DESKTOP, SV_MOUSEPRESENT) != FALSE),
    m_showingMouse(false)
{
    assert(s_screen == NULL);
    s_screen = this;
    LOG((CLOG_DEBUG1 "CPMScreen: isPrimary=%d", isPrimary));

    // create the event queue buffer first so we know there is a message queue.
    CPMEventQueueBuffer *eventQueue = new CPMEventQueueBuffer();

    // query curren thread bits.
    m_threadID = _gettid();
    m_hab = WinQueryAnchorBlock(HWND_DESKTOP);
    //if (m_hab == NULLHANDLE) {
    //    m_hab = WinInitialize(0);
    //}
    m_hmq = WinQueueFromID(m_hab, getpid(), _gettid());
    //if (m_hab != NULLHANDLE && m_hmq == NULLHANDLE) {
    //    m_hmq = WinCreateMsgQueue(m_hab, 0);
    //}
    if (m_hab == NULLHANDLE || m_hmq == NULLHANDLE) {
        LOG((CLOG_CRIT "couldn't get the hab(%ld)/hmq(%ld) of the current thread! %d", m_hab, m_hmq));
        delete eventQueue;
        s_screen = NULL;
        throw XScreenOpenFailure();
    }

    try {
        m_hmodHook = openHookLibrary("synrgyhk");
        m_screensaver = new CPMScreenSaver();
        m_keyState    = new CPMKeyState(getEventTarget(), m_fakeMsg);
        updateScreenShape();
        m_window      = createWindow("Synergy");
        forceShowCursor();
        LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_cx, m_cy, m_multimon ? "(multi-monitor)" : ""));
        LOG((CLOG_DEBUG "window is 0x%08x", m_window));
    } catch (...) {
        delete m_keyState;
        delete m_screensaver;
        destroyWindow(m_window);
        closeHookLibrary(m_hmodHook);
        delete eventQueue;
        s_screen = NULL;
        throw;
    }

    // install event handlers
    EVENTQUEUE->adoptHandler(CEvent::kSystem, IEventQueue::getSystemTarget(),
                            new TMethodEventJob<CPMScreen>(this, &CPMScreen::handleSystemEvent));

    // install the platform event queue
    EVENTQUEUE->adoptBuffer(new CPMEventQueueBuffer);
}

CPMScreen::~CPMScreen()
{
    assert(s_screen != NULL);

    disable();
    EVENTQUEUE->adoptBuffer(NULL);
    EVENTQUEUE->removeHandler(CEvent::kSystem, IEventQueue::getSystemTarget());
    delete m_keyState;
    delete m_screensaver;
    destroyWindow(m_window);
    closeHookLibrary(m_hmodHook);
    s_screen = NULL;
}

void
CPMScreen::enable()
{
    assert(m_isOnScreen == m_isPrimary);

    // install our clipboard snooper
    HAB hab = CPMUtil::getHAB();
    m_nextClipboardWindow = WinQueryClipbrdViewer(hab);
    WinSetClipbrdViewer(hab, m_window);

    if (m_isPrimary) {
        // set jump zones
        m_setZone(m_x, m_y, m_cx, m_cy, getJumpZoneSize());

        // watch jump zones
        m_setMode(kHOOK_WATCH_JUMP_ZONE);
    }
}

void
CPMScreen::disable()
{
    if (m_isPrimary) {
        // disable hooks
        m_setMode(kHOOK_DISABLE);
    }

    // tell key state
    m_keyState->disable();

    // stop snooping the clipboard
    WinSetClipbrdViewer(m_window, m_nextClipboardWindow);
    m_nextClipboardWindow = NULL;

    m_isOnScreen = m_isPrimary;
    forceShowCursor();
}

void
CPMScreen::enter()
{
    // show the pointer (?), hide the synergy window and restore focus.
    WinShowPointer(HWND_DESKTOP, TRUE);
    WinSetWindowPos(m_window, HWND_BOTTOM, 0, 0, 0, 0, SWP_HIDE);
    //?? WinSetFocus(
    //?? WinEnableWindow

    if (m_isPrimary) {
        // watch jump zones
        m_setMode(kHOOK_WATCH_JUMP_ZONE);

        // all messages prior to now are invalid
        nextMark();
    }

    // now on screen
    m_isOnScreen = true;
    forceShowCursor();
}

bool
CPMScreen::leave()
{
    /// @todo save focus window.

    if (m_isPrimary) {
        // warp to center
        warpCursor(m_xCenter, m_yCenter);

        // all messages prior to now are invalid
        nextMark();

        // remember the modifier state.  this is the modifier state
        // reflected in the internal keyboard state.
        m_keyState->saveModifiers();

        // capture events
        m_setMode(kHOOK_RELAY_EVENTS);
    }

    // now off screen
    m_isOnScreen = false;
    forceShowCursor();

    return true;
}

bool
CPMScreen::setClipboard(ClipboardID, const IClipboard* src)
{
    CPMClipboard dst(m_window);
    if (src != NULL) {
        // save clipboard data
        return CClipboard::copy(&dst, src);
    }
    // assert clipboard ownership
    if (!dst.open(0)) {
        return false;
    }
    dst.empty();
    dst.close();
    return true;
}

void
CPMScreen::checkClipboards()
{
    // be careful, even if we don't have the NT bugs.
    if (m_ownClipboard && !CPMClipboard::isOwnedBySynergy()) {
        LOG((CLOG_DEBUG "clipboard changed: lost ownership and no notification received"));
        m_ownClipboard = false;
        sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardClipboard);
        sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardSelection);
    }
}

void
CPMScreen::openScreensaver(bool notify)
{
    m_screensaverNotify = notify;
    if (!m_screensaverNotify)
        m_screensaver->disable();
}

void
CPMScreen::closeScreensaver()
{
    if (!m_screensaverNotify)
        m_screensaver->enable();
}

void
CPMScreen::screensaver(bool activate)
{
    if (activate)
        m_screensaver->activate();
    else
        m_screensaver->deactivate();
}

void
CPMScreen::resetOptions()
{
//example    m_xtestIsXineramaUnaware = true;
}

void
CPMScreen::setOptions(const COptionsList& options)
{
    for (UInt32 i = 0, n = options.size(); i < n; i += 2) {
//example        if (options[i] == kOptionXTestXineramaUnaware) {
//example            m_xtestIsXineramaUnaware = (options[i + 1] != 0);
//example            LOG((CLOG_DEBUG1 "XTest is Xinerama unaware %s", m_xtestIsXineramaUnaware ? "true" : "false"));
//example        }
    }
}

void
CPMScreen::setSequenceNumber(UInt32 seqNum)
{
    m_sequenceNumber = seqNum;
}

bool
CPMScreen::isPrimary() const
{
    return m_isPrimary;
}

void*
CPMScreen::getEventTarget() const
{
    return const_cast<CPMScreen*>(this);
}

bool
CPMScreen::getClipboard(ClipboardID, IClipboard* dst) const
{
    CPMClipboard src(m_window);
    return CClipboard::copy(dst, &src);
}

void
CPMScreen::getShape(SInt32& x, SInt32& y, SInt32& cx, SInt32& cy) const
{
    x = m_x;
    y = m_y;
    cx= m_cx;
    cy = m_cy;
}

void
CPMScreen::getCursorPos(SInt32& x, SInt32& y) const
{
    CURSORINFO Info;
    if (WinQueryCursorInfo(HWND_DESKTOP, &Info)) {
        x = Info.x;
        y = m_cy - Info.y;
    } else {
        x = 0;
        y = 0;
    }
}

void
CPMScreen::reconfigure(UInt32 activeSides)
{
    // do nothing.
}

void
CPMScreen::warpCursor(SInt32 x, SInt32 y)
{
    // warp mouse
    warpCursorNoFlush(x, y);

    // remove all input events before and including warp
    QMSG qmsg;
    while (WinPeekMsg(m_hab, &qmsg, NULLHANDLE, SYNERGY_PM_MSG_INPUT_FIRST, SYNERGY_PM_MSG_INPUT_LAST, PM_REMOVE)) {
        // do nothing
    }

    // save position as last position
    m_xCursor = x;
    m_yCursor = y;
}

UInt32
CPMScreen::registerHotKey(KeyID key, KeyModifierMask mask)
{
    // only allow certain modifiers
    if ((mask & ~(KeyModifierShift | KeyModifierControl |
                  KeyModifierAlt   | KeyModifierSuper)) != 0) {
        LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask));
        return 0;
    }

    // fail if no keys
    if (key == kKeyNone && mask == 0) {
        return 0;
    }

#if 1
    LOG((CLOG_WARN "not implemented id=%04x mask=%04x", key, mask));
    return 0;
#else
    // convert to win32
    UINT modifiers = 0;
    if ((mask & KeyModifierShift) != 0) {
        modifiers |= MOD_SHIFT;
    }
    if ((mask & KeyModifierControl) != 0) {
        modifiers |= MOD_CONTROL;
    }
    if ((mask & KeyModifierAlt) != 0) {
        modifiers |= MOD_ALT;
    }
    if ((mask & KeyModifierSuper) != 0) {
        modifiers |= MOD_WIN;
    }
    UINT vk = m_keyState->mapKeyToVirtualKey(key);
    if (key != kKeyNone && vk == 0) {
        // can't map key
        LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask));
        return 0;
    }

    // choose hotkey id
    UInt32 id;
    if (!m_oldHotKeyIDs.empty()) {
        id = m_oldHotKeyIDs.back();
        m_oldHotKeyIDs.pop_back();
    }
    else {
        id = m_hotKeys.size() + 1;
    }

    // if this hot key has modifiers only then we'll handle it specially
    bool err;
    if (key == kKeyNone) {
        // check if already registered
        err = (m_hotKeyToIDMap.count(CHotKeyItem(vk, modifiers)) > 0);
    }
    else {
        // register with OS
        err = (RegisterHotKey(NULL, id, modifiers, vk) == 0);
    }

    if (!err) {
        m_hotKeys.insert(std::make_pair(id, CHotKeyItem(vk, modifiers)));
        m_hotKeyToIDMap[CHotKeyItem(vk, modifiers)] = id;
    }
    else {
        m_oldHotKeyIDs.push_back(id);
        m_hotKeys.erase(id);
        LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", CKeyMap::formatKey(key, mask).c_str(), key, mask));
        return 0;
    }

    LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", CKeyMap::formatKey(key, mask).c_str(), key, mask, id));
    return id;
#endif
}

void
CPMScreen::unregisterHotKey(UInt32 id)
{
    // look up hotkey
    HotKeyMap::iterator i = m_hotKeys.find(id);
    if (i == m_hotKeys.end()) {
        return;
    }

    // unregister with OS
    bool err;
#if 1
    err = true;
#else
    if (i->second.getVirtualKey() != 0) {
        err = !UnregisterHotKey(NULL, id);
    } else {
        err = false;
    }
#endif
    if (err) {
        LOG((CLOG_WARN "failed to unregister hotkey id=%d", id));
    }
    else {
        LOG((CLOG_DEBUG "unregistered hotkey id=%d", id));
    }

    // discard hot key from map and record old id for reuse
    m_hotKeyToIDMap.erase(i->second);
    m_hotKeys.erase(i);
    m_oldHotKeyIDs.push_back(id);
}

void
CPMScreen::fakeInputBegin()
{
    assert(m_isPrimary);

    if (!m_isOnScreen) {
        m_keyState->useSavedModifiers(true);
    }
    //m_desks->fakeInputBegin();
}

void
CPMScreen::fakeInputEnd()
{
    assert(m_isPrimary);

    //m_desks->fakeInputEnd();
    if (!m_isOnScreen) {
        m_keyState->useSavedModifiers(false);
    }
}

SInt32
CPMScreen::getJumpZoneSize() const
{
    return 1;
}

bool
CPMScreen::isAnyMouseButtonDown() const
{
    static const char* buttonToName[] = {
        "<invalid>",
        "Left Button",
        "Middle Button",
        "Right Button",
        "X Button 1",
        "X Button 2"
    };

    for (UInt32 i = 1; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) {
        if (m_buttons[i]) {
            LOG((CLOG_DEBUG "locked by \"%s\"", buttonToName[i]));
            return true;
        }
    }

    return false;
}

void
CPMScreen::getCursorCenter(SInt32& x, SInt32& y) const
{
    x = m_xCenter;
    y = m_yCenter;
}

/**
 * Injects a fake input message.
 *
 * @param   msg     The message id.
 * @param   mp1     Message param 1.
 * @param   mp1     Message param 2.
 */
void
CPMScreen::fakeMessage(ULONG msg, MPARAM mp1, MPARAM mp2) const
{
    QMSG qmsg;
    qmsg.hwnd = NULLHANDLE;
    qmsg.msg = msg;
    qmsg.mp1 = mp1;
    qmsg.mp2 = mp2;
    qmsg.time = WinGetCurrentTime(m_hab);
    qmsg.ptl.x = 0;     ///@todo check the qmsg.ptl.
    qmsg.ptl.y = 0;
    qmsg.reserved = 0;
    LOG((CLOG_DEBUG "fakeMessage: msg=%#lx mp1=%#lx mp2=%#lx...", msg, mp1, mp2));
    const char *pszError = m_fakeMsg(m_hab, &qmsg);
    if (pszError) {
        LOG((CLOG_CRIT "fakeMessage: msg=%#lx mp1=%#lx mp2=%#lx failed: %s", msg, mp1, mp2, pszError));
    }
}

/**
 * Injects a fake input message generating mp2.
 * @param   msg     The message id.
 * @param   mp1     The mouse coordinates.
 */
void
CPMScreen::fakeMouseMessage(ULONG msg, MPARAM mp1) const
{
    /* hittest stuff is taken care off.. dunno about KC_*. */
    fakeMessage(msg, mp1, 0);
}

void
CPMScreen::fakeMouseButton(ButtonID id, bool press) const
{
    // determin the message.
    ULONG msg;
    switch (id) {
        case kButtonLeft:
            msg = press ? WM_BUTTON1DOWN : WM_BUTTON1UP;
            break;
        case kButtonRight:
            msg = press ? WM_BUTTON2DOWN : WM_BUTTON2UP;
            break;
        case kButtonMiddle:
            msg = press ? WM_BUTTON3DOWN : WM_BUTTON3UP;
            break;
        default:
            return;
    }

    // mp1
    POINTL ptl;
    if (!WinQueryPointerPos(HWND_DESKTOP, &ptl)) {
        LOG((CLOG_CRIT "fakeMouseButton: failed %#lx", WinGetLastError(CPMUtil::getHAB())));
        ptl.x = m_xCenter;
        ptl.y = m_yCenter;
    }

    // forge the message.
    fakeMouseMessage(msg, MPFROM2SHORT(ptl.x, ptl.y));
}

void
CPMScreen::fakeMouseMove(SInt32 x, SInt32 y) const
{
    LOG((CLOG_DEBUG "fakeMouseMove: %d,%d (y=%d)", x, m_cy - y, y));
    if (WinSetPointerPos(HWND_DESKTOP, x, m_cy - y)) {
        // check x limits
        if (x > 0x7fff) x = 0x7fff;
        else if (x < -0x8000) x = -0x8000;

        // wrap y and check the limits.
        y = m_cy - y;
        if (y > 0x7fff) y = 0x7fff;
        else if (y < -0x8000) y = -0x8000;

        // forge the message.
        fakeMouseMessage(WM_MOUSEMOVE, MPFROM2SHORT(x, y));
        return;
    }
    LOG((CLOG_CRIT "fakeMouseMove: failed %#lx", WinGetLastError(CPMUtil::getHAB())));
}

void
CPMScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const
{
    POINTL ptl;
    if (WinQueryPointerPos(HWND_DESKTOP, &ptl)) {
        LOG((CLOG_DEBUG1 "fakeMouseRelativeMove: %d,%d (%d,%d +/- %d,%d)", ptl.x + dx, ptl.y - dy, ptl.y, ptl.x, dx, dy));
        fakeMouseMove(ptl.x + dx, ptl.y - dy - m_cy);
        return;
    }
    LOG((CLOG_CRIT "fakeMouseRelativeMove: failed %#lx", WinGetLastError(CPMUtil::getHAB())));
}

void
CPMScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const
{
#if 0 /** @todo send scroll messages the the window beneath the cursor. */
    POINTL ptl;
    if (WinQueryPointerPos(HWND_DESKTOP, &ptl)) {
    }
#else
    /* dead simple arrow movement. */
    yDelta /= 30;
    while (yDelta != 0) {
        if (yDelta < 0) {
            yDelta++;
            fakeMessage(WM_CHAR, MPFROMSH2CH(KC_VIRTUALKEY | KC_SCANCODE, 1, 0x66), MPFROM2SHORT(0x50e0, VK_DOWN));
            fakeMessage(WM_CHAR, MPFROMSH2CH(KC_VIRTUALKEY | KC_SCANCODE | KC_KEYUP | KC_LONEKEY | KC_TOGGLE, 1, 0x66), MPFROM2SHORT(0x50e0, VK_DOWN));
        } else {
            yDelta--;
            fakeMessage(WM_CHAR, MPFROMSH2CH(KC_VIRTUALKEY | KC_SCANCODE, 1, 0x61), MPFROM2SHORT(0x48e0, VK_UP));
            fakeMessage(WM_CHAR, MPFROMSH2CH(KC_VIRTUALKEY | KC_SCANCODE | KC_KEYUP | KC_LONEKEY | KC_TOGGLE, 1, 0x61), MPFROM2SHORT(0x48e0, VK_UP));
        }
    }
#endif
}

void
CPMScreen::updateKeys()
{
    //m_desks->updateKeys();
}

void
CPMScreen::fakeKeyDown(KeyID id, KeyModifierMask mask,
                KeyButton button)
{
    CPlatformScreen::fakeKeyDown(id, mask, button);
    updateForceShowCursor();
}

void
CPMScreen::fakeKeyRepeat(KeyID id, KeyModifierMask mask,
                SInt32 count, KeyButton button)
{
    CPlatformScreen::fakeKeyRepeat(id, mask, count, button);
    updateForceShowCursor();
}

void
CPMScreen::fakeKeyUp(KeyButton button)
{
    CPlatformScreen::fakeKeyUp(button);
    updateForceShowCursor();
}

void
CPMScreen::fakeAllKeysUp()
{
    CPlatformScreen::fakeAllKeysUp();
    updateForceShowCursor();
}

/**
 * Gets the path to the directory with the executable.
 * Has trailing slash.
 *
 * @returns pszBuf.
 * @returns NULL on buffer overflow.
 * @param   pszBuf      Where to store the path.
 * @param   cchBuf      The buffer size.
 */
static char *
getExecDir(char *pszBuf, size_t cchBuf)
{
    char *psz = NULL;
    if (!_execname(pszBuf,cchBuf)) {
        char *psz = strrchr(pszBuf, '\\');
        if (!psz || strrchr(psz, '/')) {
            psz = strrchr(psz, '/');
        }
        if (psz) {
            psz[1] = '\0';
            return pszBuf;
        }
    }
    return NULL;
}

HMODULE
CPMScreen::openHookLibrary(const char* name)
{
    //
    // Load the hook dll as a global DLL.
    //
    // The kernel/PM will fuck up if it's loaded with a path (i.e. non global).
    // What happend was that the DLL wasn't actually loaded into the other
    // processes for some stupid reason, leading to nasty crashes and even
    // weirder behaviour.
    //
    char szPath[CCHMAXPATH + 10];
    char szCwd[CCHMAXPATH];
    if (    getcwd(szCwd, sizeof(szCwd)) == NULL
        ||  getExecDir(szPath, sizeof(szPath)) == NULL
        ||  chdir(szPath) != 0) {
        LOG((CLOG_ERR "can't get cwd or exec dir or failed to change path to the exec dir!"));
        throw XScreenOpenFailure();
    }
    HMODULE hmod;
    APIRET rc = DosLoadModule(NULL, 0, (PCSZ)name, &hmod);
    chdir(szCwd);
    if (rc != NO_ERROR) {
        LOG((CLOG_ERR "Failed to load hook library (%s), rc=%d ", name, rc));
        throw XScreenOpenFailure();
    }

    // resolve the functions
    if (    (rc = DosQueryProcAddr(hmod, 0, (PCSZ)"_fakeMsg",   (PPFN)&m_fakeMsg )) != NO_ERROR
        ||  (rc = DosQueryProcAddr(hmod, 0, (PCSZ)"_setSides",  (PPFN)&m_setSides)) != NO_ERROR
        ||  (rc = DosQueryProcAddr(hmod, 0, (PCSZ)"_setZone",   (PPFN)&m_setZone )) != NO_ERROR
        ||  (rc = DosQueryProcAddr(hmod, 0, (PCSZ)"_setMode",   (PPFN)&m_setMode )) != NO_ERROR
        ||  (rc = DosQueryProcAddr(hmod, 0, (PCSZ)"_init",      (PPFN)&m_init    )) != NO_ERROR
        ||  (rc = DosQueryProcAddr(hmod, 0, (PCSZ)"_cleanup",   (PPFN)&m_cleanup )) != NO_ERROR) {
        LOG((CLOG_ERR "Invalid hook library;  use a newer %s.dll (rc=%d)", name, rc));
        DosFreeModule(hmod);
        throw XScreenOpenFailure();
    }

    // initialize hook library
    if (m_init(m_hmq, _gettid(), getpid()) == 0) {
        LOG((CLOG_ERR "Cannot initialize hook library; is synergy already running?"));
        DosFreeModule(hmod);
        throw XScreenOpenFailure();
    }
    LOG((CLOG_DEBUG2 "openHookLibrary: hmod=%#lx '%s'", hmod, name));
    return hmod;
}

void
CPMScreen::closeHookLibrary(HMODULE hmod) const
{
    LOG((CLOG_DEBUG2 "closeHookLibrary: hmod=%#lx\n", hmod));
    if (hmod != NULL) {
        m_cleanup(m_hab);
        DosFreeModule(hmod);
    }
}

HWND
CPMScreen::createWindow(const char* name)
{
    WinRegisterClass(m_hab, (PCSZ)"Synergy", CPMScreen::wndProc, CS_MOVENOTIFY, sizeof(uintptr_t));
    HWND hwnd = WinCreateWindow(HWND_DESKTOP,
                                (PCSZ)"Synergy",
                                NULL,
                                0, //WS_?,
                                0, 0, 1, 1,
                                NULLHANDLE,
                                HWND_TOP,
                                0,
                                this,
                                NULL);
    if (hwnd == NULLHANDLE) {
        LOG((CLOG_ERR "failed to create window: %lx", WinGetLastError(m_hab)));
        throw XScreenOpenFailure();
    }
    return hwnd;
}

void
CPMScreen::destroyWindow(HWND hwnd) const
{
    if (hwnd != NULL) {
        WinDestroyWindow(hwnd);
    }
}

void
CPMScreen::sendEvent(CEvent::Type type, void* data)
{
    EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), data));
}

void
CPMScreen::sendClipboardEvent(CEvent::Type type, ClipboardID id)
{
    CClipboardInfo* info   = (CClipboardInfo*)malloc(sizeof(CClipboardInfo));
    info->m_id             = id;
    info->m_sequenceNumber = m_sequenceNumber;
    sendEvent(type, info);
}

void
CPMScreen::handleSystemEvent(const CEvent& event, void*)
{
    PQMSG pqmsg = (PQMSG)event.getData();
    assert(pqmsg != NULL);
    if (onPreDispatch(pqmsg->hwnd, pqmsg->msg, pqmsg->mp1, pqmsg->mp2))
        return;
    WinDispatchMsg(CPMUtil::getHAB(), pqmsg);
}

void
CPMScreen::updateButtons()
{
    m_buttons[kButtonNone]       = false;
    m_buttons[kButtonLeft]       = (WinGetKeyState(HWND_DESKTOP, VK_BUTTON1) & 0x8000) != 0;
    m_buttons[kButtonRight]      = (WinGetKeyState(HWND_DESKTOP, VK_BUTTON2) & 0x8000) != 0;
    m_buttons[kButtonMiddle]     = (WinGetKeyState(HWND_DESKTOP, VK_BUTTON3) & 0x8000) != 0;
    m_buttons[kButtonExtra0 + 0] = false;
    m_buttons[kButtonExtra0 + 1] = false;
}

IKeyState*
CPMScreen::getKeyState() const
{
    return m_keyState;
}

bool
CPMScreen::onPreDispatch(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    // handle event
    switch (msg) {
    case SYNERGY_PM_MSG_DEBUG:
        LOG((CLOG_DEBUG1 "hook: 0x%08x 0x%08x", mp1, mp2));
        return true;
    }

    if (m_isPrimary) {
        return onPreDispatchPrimary(hwnd, msg, mp1, mp2);
    }

    return false;
}

bool
CPMScreen::onPreDispatchPrimary(HWND, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    // handle event
    switch (msg) {
    case SYNERGY_PM_MSG_MARK:
        return onMark((uintptr_t)mp1);

    case SYNERGY_PM_MSG_KEY:
        return onKey(SHORT1FROMMP(mp1), CHAR3FROMMP(mp1), CHAR4FROMMP(mp1), SHORT1FROMMP(mp2), SHORT2FROMMP(mp2));

    case SYNERGY_PM_MSG_MOUSE_BUTTON:
        return onMouseButton(SHORT1FROMMP(mp1), (SHORT)SHORT1FROMMP(mp2), m_cy - (SHORT)SHORT2FROMMP(mp2));

    case SYNERGY_PM_MSG_MOUSE_MOVE:
        return onMouseMove(SHORT1FROMMP(mp1), (SHORT)SHORT1FROMMP(mp2), m_cy - (SHORT)SHORT2FROMMP(mp2));

    case SYNERGY_PM_MSG_PRE_WARP:
        {
            // Save position to compute delta of next motion
            m_xCursor = (SInt32)mp1;
            m_yCursor = (SInt32)mp2;

            // We warped the mouse. Discard events until we find the
            // matching post warp event. See warpCursorNoFlush() for
            // where the events are sent. We discard the matching
            // post warp event and can be sure we've skipped the warp
            // event.
            QMSG qmsg;
            while (   WinGetMsg(m_hab, &qmsg, NULLHANDLE, SYNERGY_PM_MSG_MOUSE_MOVE, SYNERGY_PM_MSG_POST_WARP)
                   && qmsg.msg != SYNERGY_PM_MSG_POST_WARP)
                /* nothing */;
        }
        return true;

    case SYNERGY_PM_MSG_POST_WARP:
        LOG((CLOG_WARN "unmatched post warp"));
        return true;
    }

    return false;
}

bool
CPMScreen::onEvent(HWND, ULONG msg, MPARAM mp1, MPARAM mp2, MRESULT *result)
{
    switch (msg) {

        //
        // Just freak out and close the clipboard if we get any of these.
        //
    case WM_RENDERFMT:
    case WM_RENDERALLFMTS:
    case WM_PAINTCLIPBOARD:
    case WM_SIZECLIPBOARD:
    case WM_HSCROLLCLIPBOARD:
    case WM_VSCROLLCLIPBOARD:
        LOG((CLOG_DEBUG "msg=%#x -> closing clipboard.\n", msg));
        WinCloseClipbrd(m_hab);
        break;

        //
        // The content has changed.
        //
    //case WM_DESTROYCLIPBOARD:
	case WM_DRAWCLIPBOARD:
        //// first pass on the message
        //if (m_nextClipboardWindow != NULL) {
        //  WinSendMessage(m_nextClipboardWindow, msg, mp1, mp2);
        //}

        // now handle the message
        return onClipboardChange();
    }

    return false;
}

bool
CPMScreen::onMark(UInt32 mark)
{
    m_markReceived = mark;
    return true;
}

bool
CPMScreen::onKey(USHORT fsFlags, UCHAR ucRepeat, UCHAR ucScanCode, USHORT usch, USHORT usvk)
{
#if 0
    static const KeyModifierMask s_ctrlAlt = KeyModifierControl | KeyModifierAlt;

    LOG((CLOG_DEBUG1 "event: Key char=%d, vk=0x%02x, nagr=%d, mp2=0x%08x", (mp1 & 0xff00u) >> 8, mp1 & 0xffu, (mp1 & 0x10000u) ? 1 : 0, mp2));

    // get event info
    KeyButton button         = (KeyButton)((mp2 & 0x01ff0000) >> 16);
    bool down                = ((mp2 & 0x80000000u) == 0x00000000u);
    bool wasDown             = isKeyDown(button);
    KeyModifierMask oldState = pollActiveModifiers();

    // check for autorepeat
    if (m_keyState->testAutoRepeat(down, (mp2 & 0x40000000u) == 1, button)) {
        mp2 |= 0x40000000u;
    }

    // if the button is zero then guess what the button should be.
    // these are badly synthesized key events and logitech software
    // that maps mouse buttons to keys is known to do this.
    // alternatively, we could just throw these events out.
    if (button == 0) {
        button = m_keyState->virtualKeyToButton(mp1 & 0xffu);
        if (button == 0) {
            return true;
        }
        wasDown = isKeyDown(button);
    }

    // record keyboard state
    m_keyState->onKey(button, down, oldState);

    // windows doesn't tell us the modifier key state on mouse or key
    // events so we have to figure it out.  most apps would use
    // GetKeyState() or even GetAsyncKeyState() for that but we can't
    // because our hook doesn't pass on key events for several modifiers.
    // it can't otherwise the system would interpret them normally on
    // the primary screen even when on a secondary screen.  so tapping
    // alt would activate menus and tapping the windows key would open
    // the start menu.  if you don't pass those events on in the hook
    // then GetKeyState() understandably doesn't reflect the effect of
    // the event.  curiously, neither does GetAsyncKeyState(), which is
    // surprising.
    //
    // so anyway, we have to track the modifier state ourselves for
    // at least those modifiers we don't pass on.  pollActiveModifiers()
    // does that but we have to update the keyboard state before calling
    // pollActiveModifiers() to get the right answer.  but the only way
    // to set the modifier state or to set the up/down state of a key
    // is via onKey().  so we have to call onKey() twice.
    KeyModifierMask state = pollActiveModifiers();
    m_keyState->onKey(button, down, state);

    // check for hot keys
    if (oldState != state) {
        // modifier key was pressed/released
        if (onHotKey(0, mp2)) {
            return true;
        }
    }
    else {
        // non-modifier was pressed/released
        if (onHotKey(mp1, mp2)) {
            return true;
        }
    }

    // ignore message if posted prior to last mark change
    if (!ignore()) {
        // check for ctrl+alt+del.  we do not want to pass that to the
        // client.  the user can use ctrl+alt+pause to emulate it.
        UINT virtKey = (mp1 & 0xffu);
        if (virtKey == VK_DELETE && (state & s_ctrlAlt) == s_ctrlAlt) {
            LOG((CLOG_DEBUG "discard ctrl+alt+del"));
            return true;
        }

        // check for ctrl+alt+del emulation
        if ((virtKey == VK_PAUSE || virtKey == VK_CANCEL) &&
            (state & s_ctrlAlt) == s_ctrlAlt) {
            LOG((CLOG_DEBUG "emulate ctrl+alt+del"));
            // switch mp1 and mp2 to be as if VK_DELETE was
            // pressed or released.  when mapping the key we require that
            // we not use AltGr (the 0x10000 flag in mp1) and we not
            // use the keypad delete key (the 0x01000000 flag in mp2).
            mp1  = VK_DELETE | 0x00010000u;
            mp2 &= 0xfe000000;
            mp2 |= m_keyState->virtualKeyToButton(mp1 & 0xffu) << 16;
            mp2 |= 0x01000001;
        }

        // process key
        KeyModifierMask mask;
        KeyID key = m_keyState->mapKeyFromEvent((ULONG)mp1, (ULONG)mp2, &mask);
        button    = static_cast<KeyButton>((mp2 & 0x01ff0000u) >> 16);
        if (key != kKeyNone) {
            // fix key up.  if the key isn't down according to
            // our table then we never got the key press event
            // for it.  if it's not a modifier key then we'll
            // synthesize the press first.  only do this on
            // the windows 95 family, which eats certain special
            // keys like alt+tab, ctrl+esc, etc.
            if (m_is95Family && !wasDown && !down) {
                switch (virtKey) {
                case VK_SHIFT:
                case VK_LSHIFT:
                case VK_RSHIFT:
                case VK_CONTROL:
                case VK_LCONTROL:
                case VK_RCONTROL:
                case VK_MENU:
                case VK_LMENU:
                case VK_RMENU:
                case VK_LWIN:
                case VK_RWIN:
                case VK_CAPITAL:
                case VK_NUMLOCK:
                case VK_SCROLL:
                    break;

                default:
                    m_keyState->sendKeyEvent(getEventTarget(),
                            true, false, key, mask, 1, button);
                    break;
                }
            }

            // do it
            m_keyState->sendKeyEvent(getEventTarget(),
                            ((mp2 & 0x80000000u) == 0),
                            ((mp2 & 0x40000000u) != 0),
                            key, mask, (SInt32)(mp2 & 0xffff), button);
        }
        else {
            LOG((CLOG_DEBUG1 "cannot map key"));
        }
    }
#endif
    return true;
}

bool
CPMScreen::onHotKey(MPARAM mp1, MPARAM mp2)
{
#if 0
    // get the key info
    KeyModifierMask state = getActiveModifiers();
    UINT virtKey   = (mp1 & 0xffu);
    UINT modifiers = 0;
    if ((state & KeyModifierShift) != 0) {
        modifiers |= MOD_SHIFT;
    }
    if ((state & KeyModifierControl) != 0) {
        modifiers |= MOD_CONTROL;
    }
    if ((state & KeyModifierAlt) != 0) {
        modifiers |= MOD_ALT;
    }
    if ((state & KeyModifierSuper) != 0) {
        modifiers |= MOD_WIN;
    }

    // find the hot key id
    HotKeyToIDMap::const_iterator i =
        m_hotKeyToIDMap.find(CHotKeyItem(virtKey, modifiers));
    if (i == m_hotKeyToIDMap.end()) {
        return false;
    }

    // find what kind of event
    CEvent::Type type;
    if ((mp2 & 0x80000000u) == 0u) {
        if ((mp2 & 0x40000000u) != 0u) {
            // ignore key repeats but it counts as a hot key
            return true;
        }
        type = getHotKeyDownEvent();
    }
    else {
        type = getHotKeyUpEvent();
    }

    // generate event
    EVENTQUEUE->addEvent(CEvent(type, getEventTarget(),
                            CHotKeyInfo::alloc(i->second)));

#endif
    return true;
}

bool
CPMScreen::onMouseButton(ULONG msg, SInt32 ax, SInt32 ay)
{
    // get which button
    bool pressed    = mapPressFromEvent(msg);
    ButtonID button = mapButtonFromEvent(msg);

    // keep our shadow key state up to date
    if (button >= kButtonLeft && button <= kButtonExtra0 + 1) {
        if (pressed) {
            m_buttons[button] = true;
        }
        else {
            m_buttons[button] = false;
        }
    }

    // ignore message if posted prior to last mark change
    if (!ignore()) {
        KeyModifierMask mask = m_keyState->getActiveModifiers();
        if (pressed) {
            LOG((CLOG_DEBUG1 "event: button press button=%d", button));
            if (button != kButtonNone) {
                sendEvent(getButtonDownEvent(),
                                CButtonInfo::alloc(button, mask));
            }
        }
        else {
            LOG((CLOG_DEBUG1 "event: button release button=%d", button));
            if (button != kButtonNone) {
                sendEvent(getButtonUpEvent(),
                                CButtonInfo::alloc(button, mask));
            }
        }
    }

    return true;
}

bool
CPMScreen::onMouseMove(ULONG msg, SInt32 ax, SInt32 ay)
{
    // compute motion delta (relative to the last known
    // mouse position)
    SInt32 x = ax - m_xCursor;
    SInt32 y = ay - m_yCursor;

    // ignore if the mouse didn't move or if message posted prior
    // to last mark change.
    if (ignore() || (x == 0 && y == 0)) {
        return true;
    }

    // save position to compute delta of next motion
    m_xCursor = ax;
    m_yCursor = ay;

    if (m_isOnScreen) {
        // motion on primary screen
        sendEvent(getMotionOnPrimaryEvent(), CMotionInfo::alloc(m_xCursor, m_yCursor));
    }
    else {
        // motion on secondary screen.  warp mouse back to
        // center.
        warpCursorNoFlush(m_xCenter, m_yCenter);

        // examine the motion.  if it's about the distance
        // from the center of the screen to an edge then
        // it's probably a bogus motion that we want to
        // ignore (see warpCursorNoFlush() for a further
        // description).
        static SInt32 bogusZoneSize = 10;
        if (-x + bogusZoneSize > m_xCenter - m_x ||
             x + bogusZoneSize > m_x + m_cx - m_xCenter ||
            -y + bogusZoneSize > m_yCenter - m_y ||
             y + bogusZoneSize > m_y + m_cy - m_yCenter) {
            LOG((CLOG_DEBUG "dropped bogus motion %+d,%+d", x, y));
        }
        else {
            // send motion
            sendEvent(getMotionOnSecondaryEvent(), CMotionInfo::alloc(x, y));
        }
    }

    return true;
}

bool
CPMScreen::onClipboardChange()
{
    // now notify client that somebody changed the clipboard (unless
    // we're the owner).
    if (!CPMClipboard::isOwnedBySynergy()) {
        if (m_ownClipboard) {
            LOG((CLOG_DEBUG "clipboard changed: lost ownership"));
            m_ownClipboard = false;
            sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardClipboard);
            sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardSelection);
        }
    }
    else if (!m_ownClipboard) {
        LOG((CLOG_DEBUG "clipboard changed: synergy owned"));
        m_ownClipboard = true;
    }

    return true;
}

void
CPMScreen::warpCursorNoFlush(SInt32 x, SInt32 y)
{
    // send an event that we can recognize before the mouse warp
    WinPostQueueMsg(m_hmq, SYNERGY_PM_MSG_PRE_WARP, (MPARAM)x, (MPARAM)y);

    // warp mouse.  hopefully this inserts a mouse motion event
    // between the previous message and the following message.
    WinSetPointerPos(HWND_DESKTOP, x, y);

    // yield the CPU.  there's a race condition when warping:
    //   a hardware mouse event occurs
    //   the mouse hook is not called because that process doesn't have the CPU
    //   we send PRE_WARP, SetCursorPos(), send POST_WARP
    //   we process all of those events and update m_x, m_y
    //   we finish our time slice
    //   the hook is called
    //   the hook sends us a mouse event from the pre-warp position
    //   we get the CPU
    //   we compute a bogus warp
    // we need the hook to process all mouse events that occur
    // before we warp before we do the warp but i'm not sure how
    // to guarantee that.  yielding the CPU here may reduce the
    // chance of undesired behavior.  we'll also check for very
    // large motions that look suspiciously like about half width
    // or height of the screen.
    ARCH->sleep(0.0);

    // send an event that we can recognize after the mouse warp
    WinPostQueueMsg(m_hmq, SYNERGY_PM_MSG_POST_WARP, 0, 0);
}

void
CPMScreen::nextMark()
{
    // next mark
    ++m_mark;

    // mark point in message queue where the mark was changed
    WinPostQueueMsg(m_hmq, SYNERGY_PM_MSG_MARK, (MPARAM)m_mark, 0);
}

bool
CPMScreen::ignore() const
{
    return (m_mark != m_markReceived);
}

void
CPMScreen::updateScreenShape()
{
    // get shape
    m_x = 0;
    m_y = 0;
    m_cx = WinQuerySysValue(HWND_DESKTOP, SV_CXSCREEN);
    m_cy = WinQuerySysValue(HWND_DESKTOP, SV_CYSCREEN);

    // get center for cursor
    m_xCenter = m_cx / 2;
    m_yCenter = m_cy / 2;

    // check for multiple monitors
    m_multimon = false;
}

ButtonID
CPMScreen::mapButtonFromEvent(ULONG msg) const
{
    /** @todo query which is left and which is right. */

    switch (msg) {
        case WM_BUTTON1DOWN:
        case WM_BUTTON1UP:
        case WM_BUTTON1DBLCLK:
        case WM_BUTTON1CLICK: //??
            return kButtonLeft;

        case WM_BUTTON2DOWN:
        case WM_BUTTON2UP:
        case WM_BUTTON2DBLCLK:
        case WM_BUTTON2CLICK: //??
            return kButtonLeft;

        case WM_BUTTON3DOWN:
        case WM_BUTTON3UP:
        case WM_BUTTON3DBLCLK:
        case WM_BUTTON3CLICK: //??
            return kButtonMiddle;
        default:
            return kButtonNone;
    }
}

bool
CPMScreen::mapPressFromEvent(ULONG msg) const
{
    switch (msg) {
        case WM_BUTTON1DOWN:
        case WM_BUTTON1CLICK: //??
        case WM_BUTTON1DBLCLK:
        case WM_BUTTON2DOWN:
        case WM_BUTTON2DBLCLK:
        case WM_BUTTON2CLICK: //??
        case WM_BUTTON3DOWN:
        case WM_BUTTON3DBLCLK:
        case WM_BUTTON3CLICK: //??
            return true;
        case WM_BUTTON1UP:
        case WM_BUTTON2UP:
        case WM_BUTTON3UP:
            return false;
        default:
            return false;
    }
}

void
CPMScreen::updateKeysCB(void*)
{
    // record which keys we think are down
    bool down[IKeyState::kNumButtons];
    bool sendFixes = (isPrimary() && !m_isOnScreen);
    if (sendFixes) {
        for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) {
            down[i] = m_keyState->isKeyDown(i);
        }
    }

    // now update the keyboard state
    CPlatformScreen::updateKeyState();

    // now see which keys we thought were down but now think are up.
    // send key releases for these keys to the active client.
    if (sendFixes) {
        KeyModifierMask mask = pollActiveModifiers();
        for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) {
            if (down[i] && !m_keyState->isKeyDown(i)) {
                m_keyState->sendKeyEvent(getEventTarget(),
                            false, false, kKeyNone, mask, 1, i);
            }
        }
    }
}

void
CPMScreen::forceShowCursor()
{
    // check for mouse - probably not required.
    m_hasMouse = WinQuerySysValue(HWND_DESKTOP, SV_MOUSEPRESENT) != FALSE;

    // decide if we should show the mouse
    bool showMouse = (!m_hasMouse && !m_isPrimary && m_isOnScreen);

    // show/hide the mouse
    if (showMouse != m_showingMouse) {
        WinShowPointer(HWND_DESKTOP, showMouse);
        m_showingMouse = WinQuerySysValue(HWND_DESKTOP, SV_POINTERLEVEL) != 0;
    }
}

void
CPMScreen::updateForceShowCursor()
{
    //???
}

MRESULT EXPENTRY
CPMScreen::wndProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    assert(s_screen != NULL);

    MRESULT mr = 0;
    if (!s_screen->onEvent(hwnd, msg, mp1, mp2, &mr)) {
        mr = WinDefWindowProc(hwnd, msg, mp1, mp2);
    }

    return mr;
}


//
// CPMScreen::CHotKeyItem
//

CPMScreen::CHotKeyItem::CHotKeyItem(ULONG keycode, ULONG mask) :
    m_keycode(keycode),
    m_mask(mask)
{
    // do nothing
}

ULONG
CPMScreen::CHotKeyItem::getVirtualKey() const
{
    return m_keycode;
}

bool
CPMScreen::CHotKeyItem::operator<(const CHotKeyItem& x) const
{
    return (m_keycode < x.m_keycode ||
            (m_keycode == x.m_keycode && m_mask < x.m_mask));
}


/*
 * Local Variables:
 *  mode: c
 *  c-file-style: "k&r"
 *  c-basic-offset: 4
 *  tab-width: 4
 *  indent-tabs-mode: s
 * End:
 */

