/*
 * synergy -- mouse and keyboard sharing utility
 * Copyright (C) 2003 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 "CPMKeyState.h"
#include "CThread.h"
#include "CFunctionJob.h"
#include "CLog.h"
#include "CStringUtil.h"
#include "CPMUtil.h"
#include "IEventQueue.h"
#include "TMethodEventJob.h"

//
// CPMKeyState
//

// map virtual keys to synergy key enumeration
const CPMKeyState::VirtualKey   CPMKeyState::s_virtualKey[0x42] =
{
{ /* 0x000 */ kKeyNone,         0,    0,    0 },    // reserved
{ /* 0x001 */ kKeyNone,         0,    0,    0 },    // VK_BUTTON1
{ /* 0x002 */ kKeyNone,         0,    0,    0 },    // VK_BUTTON2
{ /* 0x003 */ kKeyNone,         0,    0,    0 },    // VK_BUTTON3
{ /* 0x004 */ kKeyBreak,        0,    0,    0 },    // VK_BREAK
{ /* 0x005 */ kKeyBackSpace,    8,    0,    0 },    // VK_BACKSPACE
{ /* 0x006 */ kKeyTab,          9,    0,    0 },    // VK_TAB
{ /* 0x007 */ kKeyLeftTab,      9,    0,    1 },    // VK_BACKTAB
{ /* 0x008 */ kKeyReturn,    0x0d,    0,    0 },    // VK_NEWLINE
{ /* 0x009 */ kKeyShift_L,      0,    0,    0 },    // VK_SHIFT
{ /* 0x00a */ kKeyControl_L,    0,    0,    0 },    // VK_CTRL
{ /* 0x00b */ kKeyAlt_L,        0,    0,    0 },    // VK_ALT
{ /* 0x00c */ kKeyAltGr,        0,    0,    0 },    // VK_ALTGRAF
{ /* 0x00d */ kKeyPause,        0,    0,    0 },    // VK_PAUSE
{ /* 0x00e */ kKeyCapsLock,     0,    0,    0 },    // VK_CAPSLOCK
{ /* 0x00f */ kKeyEscape,    0x1b,    0,    0 },    // VK_ESC
{ /* 0x010 */ ' ',           0x20,    0,    0 },    // VK_SPACE
{ /* 0x011 */ kKeyPageUp,    0xe0,    1,   -1 },    // VK_PAGEUP
{ /* 0x012 */ kKeyPageDown,  0xe0,    1,   -1 },    // VK_PAGEDOWN
{ /* 0x013 */ kKeyEnd,       0xe0,    1,   -1 },    // VK_END
{ /* 0x014 */ kKeyHome,      0xe0,    1,   -1 },    // VK_HOME
{ /* 0x015 */ kKeyLeft,      0xe0,    1,   -1 },    // VK_LEFT
{ /* 0x016 */ kKeyUp,        0xe0,    1,   -1 },    // VK_UP
{ /* 0x017 */ kKeyRight,     0xe0,    1,   -1 },    // VK_RIGHT
{ /* 0x018 */ kKeyDown,      0xe0,    1,   -1 },    // VK_DOWN
{ /* 0x019 */ kKeyNone,      0xe0,    0,    0 },    // VK_PRINTSCRN
{ /* 0x01a */ kKeyInsert,    0xe0,    1,    1 },    // VK_INSERT
{ /* 0x01b */ kKeyDelete,    0xe0,    1,    1 },    // VK_DELETE
{ /* 0x01c */ kKeyScrollLock,   0,    0,    0 },    // VK_SCRLLOCK
{ /* 0x01d */ kKeyNumLock,      0,    0,    0 },    // VK_NUMLOCK
{ /* 0x01e */ kKeyKP_Enter,  0x0d,    1,    1 },    // VK_ENTER
{ /* 0x01f */ kKeySysReq,       0,    0,    0 },    // VK_SYSRQ
{ /* 0x020 */ kKeyF1,           0,    0,    1 },    // VK_F1
{ /* 0x021 */ kKeyF2,           0,    0,    1 },    // VK_F2
{ /* 0x022 */ kKeyF3,           0,    0,    1 },    // VK_F3
{ /* 0x023 */ kKeyF4,           0,    0,    1 },    // VK_F4
{ /* 0x024 */ kKeyF5,           0,    0,    1 },    // VK_F5
{ /* 0x025 */ kKeyF6,           0,    0,    1 },    // VK_F6
{ /* 0x026 */ kKeyF7,           0,    0,    1 },    // VK_F7
{ /* 0x027 */ kKeyF8,           0,    0,    1 },    // VK_F8
{ /* 0x028 */ kKeyF9,           0,    0,    1 },    // VK_F9
{ /* 0x029 */ kKeyF10,          0,    0,    1 },    // VK_F10
{ /* 0x02a */ kKeyF11,          0,    0,    1 },    // VK_F11
{ /* 0x02b */ kKeyF12,          0,    0,    1 },    // VK_F12
{ /* 0x02c */ kKeyF13,          0,    0,    1 },    // VK_F13
{ /* 0x02d */ kKeyF14,          0,    0,    1 },    // VK_F14
{ /* 0x02e */ kKeyF15,          0,    0,    1 },    // VK_F15
{ /* 0x02f */ kKeyF16,          0,    0,    1 },    // VK_F16
{ /* 0x030 */ kKeyF17,          0,    0,    1 },    // VK_F17
{ /* 0x031 */ kKeyF18,          0,    0,    1 },    // VK_F18
{ /* 0x032 */ kKeyF19,          0,    0,    1 },    // VK_F19
{ /* 0x033 */ kKeyF20,          0,    0,    1 },    // VK_F20
{ /* 0x034 */ kKeyF21,          0,    0,    1 },    // VK_F21
{ /* 0x035 */ kKeyF22,          0,    0,    1 },    // VK_F22
{ /* 0x036 */ kKeyF23,          0,    0,    1 },    // VK_F23
{ /* 0x037 */ kKeyF24,          0,    0,    1 },    // VK_F24
{ /* 0x038 */ kKeyNone,         0,    0,    0 },    // VK_ENDDRAG
{ /* 0x039 */ kKeyNone,         0,    0,    0 },    // VK_CLEAR
{ /* 0x03a */ kKeyNone,         0,    0,    0 },    // VK_EREOF
{ /* 0x03b */ kKeyNone,         0,    0,    0 },    // VK_PA1
{ /* 0x03c */ kKeyNone,         0,    0,    0 },    // VK_ATTN
{ /* 0x03d */ kKeyNone,         0,    0,    0 },    // VK_CRSEL
{ /* 0x03e */ kKeyNone,         0,    0,    0 },    // VK_EXSEL
{ /* 0x03f */ kKeyNone,         0,    0,    0 },    // VK_COPY
{ /* 0x040 */ kKeyNone,         0,    0,    0 },    // VK_BLK1
{ /* 0x041 */ kKeyNone,         0,    0,    0 },    // VK_BLK2
};

struct CWin32Modifiers {
public:
    ULONG               m_vk;
    KeyModifierMask     m_mask;
};

static const CWin32Modifiers s_modifiers[] =
{
    { VK_SHIFT,    KeyModifierShift   },
    { VK_CTRL,     KeyModifierControl },
    { VK_ALT,      KeyModifierAlt     },
    { VK_ALTGRAF,  KeyModifierAltGr     },
};

CPMKeyState::CPMKeyState(void* eventTarget, FakeMsgFunc fakeMsg) :
    m_eventTarget(eventTarget),
    m_fakeMsg(fakeMsg),
        m_lastButton(0),
    m_fixTimer(NULL),
    m_lastDown(kKeyNone),
    m_useSavedModifiers(false),
    m_savedModifiers(0),
    m_originalSavedModifiers(0)
{
    LOG((CLOG_DEBUG2 "CPMKeyState:"));
}

CPMKeyState::~CPMKeyState()
{
    LOG((CLOG_DEBUG2 "~CPMKeyState:"));
    disable();
}

void
CPMKeyState::disable()
{
    LOG((CLOG_DEBUG2 "disable:"));
    if (m_fixTimer != NULL) {
        EVENTQUEUE->removeHandler(CEvent::kTimer, m_fixTimer);
        EVENTQUEUE->deleteTimer(m_fixTimer);
        m_fixTimer = NULL;
    }
    m_lastDown = kKeyNone;
}

KeyButton
CPMKeyState::virtualKeyToButton(ULONG virtualKey) const
{
    LOG((CLOG_DEBUG2 "virtualKeyToButton:"));
    return m_virtualKeyToButton[virtualKey & 0xffu];
}

bool
CPMKeyState::testAutoRepeat(bool press, bool isRepeat, KeyButton button)
{
    LOG((CLOG_DEBUG2 "testAutoRepeat:"));
    if (!isRepeat)
        isRepeat = press && button == m_lastDown && button != kKeyNone;
    m_lastDown = press ? button : kKeyNone;
    return isRepeat;
}

void
CPMKeyState::saveModifiers()
{
    LOG((CLOG_DEBUG2 "saveModifiers:"));
    m_savedModifiers         = getActiveModifiers();
    m_originalSavedModifiers = m_savedModifiers;
}

void
CPMKeyState::useSavedModifiers(bool enable)
{
    LOG((CLOG_DEBUG2 "useSavedModifiers:"));
    if (enable != m_useSavedModifiers) {
        m_useSavedModifiers = enable;
        if (!m_useSavedModifiers) {
            // transfer any modifier state changes to CKeyState's state
            KeyModifierMask mask = m_originalSavedModifiers ^ m_savedModifiers;
            getActiveModifiersRValue() = (getActiveModifiers() & ~mask) | (m_savedModifiers & mask);
        }
    }
}

KeyID
CPMKeyState::mapKeyFromEvent(USHORT fsFlags, UCHAR ucRepeat, UCHAR ucScanCode, USHORT usch, USHORT usvk, KeyModifierMask* maskOut) const
{
    LOG((CLOG_DEBUG2 "mapKeyFromEvent:"));
#if 0 /** @todo */
    static const KeyModifierMask s_controlAlt = KeyModifierControl | KeyModifierAlt;

    // extract character, virtual key, and if we didn't use AltGr
    char c       = (char)((charAndVirtKey & 0xff00u) >> 8);
    ULONG vkCode = (charAndVirtKey & 0xffu);
    bool noAltGr = ((charAndVirtKey & 0xff0000u) != 0);

    // handle some keys via table lookup
    KeyID id     = getKeyID(vkCode, (KeyButton)((info >> 16) & 0x1ffu));

    // check if not in table;  map character to key id
    if (id == kKeyNone && c != 0) {
        if ((c & 0x80u) == 0) {
            // ASCII
            id = static_cast<KeyID>(c) & 0xffu;
        }
        else {
            // character is not really ASCII.  instead it's some
            // character in the current ANSI code page.  try to
            // convert that to a Unicode character.  if we fail
            // then use the single byte character as is.
            char src = c;
            wchar_t unicode;
            if (MultiByteToWideChar(CP_THREAD_ACP, MB_PRECOMPOSED,
                                        &src, 1, &unicode, 1) > 0) {
                id = static_cast<KeyID>(unicode);
            }
            else {
                id = static_cast<KeyID>(c) & 0xffu;
            }
        }
    }

    // set modifier mask
    if (maskOut != NULL) {
        KeyModifierMask active = getActiveModifiers();
        if (!noAltGr && (active & s_controlAlt) == s_controlAlt) {
            // if !noAltGr then we're only interested in matching the
            // key, not the AltGr.  AltGr is down (i.e. control and alt
            // are down) but we don't want the client to have to match
            // that so we clear it.
            active &= ~s_controlAlt;
        }
        *maskOut = active;
    }
    return id;
#else
    return kKeyNone;
#endif
}

ULONG
CPMKeyState::mapKeyToVirtualKey(KeyID key) const
{
    LOG((CLOG_DEBUG2 "mapKeyToVirtualKey:"));
    if (key == kKeyNone)
        return 0;
    KeyToVKMap::const_iterator i = m_keyToVKMap.find(key);
    if (i == m_keyToVKMap.end())
        return 0;
    return i->second;
}

void
CPMKeyState::sendKeyEvent(void* target,
                            bool press, bool isAutoRepeat,
                            KeyID key, KeyModifierMask mask,
                            SInt32 count, KeyButton button)
{
    LOG((CLOG_DEBUG2 "sendKeyEvent:"));
    if (press || isAutoRepeat) {
        // send key
        if (press && !isAutoRepeat) {
            CKeyState::sendKeyEvent(target, true, false, key, mask, 1, button);
            if (count > 0) {
                --count;
            }
        }
        if (count >= 1) {
            CKeyState::sendKeyEvent(target, true, true, key, mask, count, button);
        }
    }
    else {
        // do key up
        CKeyState::sendKeyEvent(target, false, false, key, mask, 1, button);
    }
}

void
CPMKeyState::fakeKeyDown(KeyID id, KeyModifierMask mask, KeyButton button)
{
    LOG((CLOG_DEBUG2 "fakeKeyDown: id=%#x mask=%#x button=%#x", id, mask, button));
    CKeyState::fakeKeyDown(id, mask, button);
}

void
CPMKeyState::fakeKeyRepeat(KeyID id, KeyModifierMask mask, SInt32 count, KeyButton button)
{
    LOG((CLOG_DEBUG2 "fakeKeyRepeat: id=%#x mask=%#x count=%d button=%#x", id, mask, count, button));
    CKeyState::fakeKeyRepeat(id, mask, count, button);
}

bool
CPMKeyState::fakeCtrlAltDel()
{
    LOG((CLOG_DEBUG2 "fakeCtrlAltDel:"));
    fakeKeyDown(kKeyDelete, KeyModifierControl | KeyModifierAlt, virtualKeyToButton(VK_DELETE));
}

KeyModifierMask
CPMKeyState::pollActiveModifiers() const
{
    LOG((CLOG_DEBUG2 "pollActiveModifiers:"));
    KeyModifierMask state = 0;

    // get non-toggle modifiers from our own shadow key state
    for (size_t i = 0; i < sizeof(s_modifiers) / sizeof(s_modifiers[0]); ++i) {
        KeyButton button = virtualKeyToButton(s_modifiers[i].m_vk);
        if (button != 0 && isKeyDown(button)) {
            state |= s_modifiers[i].m_mask;
        }
    }

    // we can get toggle modifiers from the system
    if (WinGetKeyState(HWND_DESKTOP, VK_CAPSLOCK) & 1)
        state |= KeyModifierCapsLock;
    if (WinGetKeyState(HWND_DESKTOP, VK_NUMLOCK) & 1)
        state |= KeyModifierNumLock;
    if (WinGetKeyState(HWND_DESKTOP, VK_SCRLLOCK) & 1)
        state |= KeyModifierScrollLock;
    return state;
}

SInt32
CPMKeyState::pollActiveGroup() const
{
    LOG((CLOG_DEBUG2 "pollActiveGroup:"));
#if 0
    HKL hkl            = GetKeyboardLayout(targetThread);

    // get group
    GroupMap::const_iterator i = m_groupMap.find(hkl);
    if (i == m_groupMap.end()) {
        LOG((CLOG_DEBUG1 "can't find keyboard layout %08x", hkl));
        return 0;
    }

    return i->second;
#else
    return 0;
#endif
}

void
CPMKeyState::pollPressedKeys(KeyButtonSet& pressedKeys) const
{
    LOG((CLOG_DEBUG2 "pollPressedKeys:"));
    BYTE keyState[256];
    if (WinSetKeyboardStateTable(HWND_DESKTOP, keyState, FALSE)) {
        for (KeyButton i = 1; i < 256; ++i) {
            if ((keyState[i] & 0x80) != 0) {
                pressedKeys.insert(i);
            }
        }
    }
}

static USHORT
convertKeyModiferMaskToTCF(const KeyModifierMask mask)
{
    USHORT fShiftState = 0;
    if (mask & KeyModifierShift)    fShiftState |= TCF_SHIFT;
    if (mask & KeyModifierControl)  fShiftState |= TCF_CONTROL;
    if (mask & KeyModifierAlt)      fShiftState |= TCF_ALT;
    if (mask & KeyModifierAltGr)    fShiftState |= TCF_ALTGR;
    if (mask & KeyModifierCapsLock) fShiftState |= TCF_CAPSLOCK;
    if (mask & KeyModifierNumLock)  fShiftState |= TCF_NUMLOCK;
    return fShiftState;
}

static KeyModifierMask
convertTCFToKeyModiferMask(const USHORT fShiftState)
{
    KeyModifierMask mask = 0;
    if (fShiftState & TCF_SHIFT)    mask |= KeyModifierShift;
    if (fShiftState & TCF_CONTROL)  mask |= KeyModifierControl;
    if (fShiftState & TCF_ALT)      mask |= KeyModifierAlt;
    if (fShiftState & TCF_ALTGR)    mask |= KeyModifierAltGr;
    if (fShiftState & TCF_CAPSLOCK) mask |= KeyModifierCapsLock;
    if (fShiftState & TCF_NUMLOCK)  mask |= KeyModifierNumLock;
    return mask;
}

static bool
isASCIIControlChar(const USHORT usChar)
{
    switch (usChar) {
        /* Skip the ones with virtual keys. */
        case 0x08 + 0x40: case 0x08 + 0x60: // backspace
        case 0x09 + 0x40: case 0x09 + 0x60: // horizontal tab
        case 0x1b + 0x40: case 0x1b + 0x60: // escape
        case 0x0d + 0x40: case 0x0d + 0x60: // return
            return false;
        default:
            return (usChar >= 'A' && usChar <= '_')
                || (usChar >= 'a' && usChar <= 'z');
    }
}

void
CPMKeyState::convertScancodes(ClientData *pData)
{
    /*
     * This is a bit tricky / hackish, but what we have to do here is to
     * deal with extended keys and the keypad stuff. The scancode
     * translation tables doesn't seem to be 100% correct for these
     * keys, and these keys are the ones we need them the most. sigh.
     */

    //
    // navigation and insert/delete buttons left of the numpad:
    //      home, up, pgup, left, right, end, down, pgdn, insert, delete
    //
    // The pm and translated scancodes are mixed up in these cases.
    // The keys are all extended and secondary.
    //
    if (    pData->s.xlatScan >= 0x60 /* VK_HOME */
        &&  pData->s.xlatScan <= 0x69 /* VK_DELETE */
        &&  pData->s.virtualKey != 0) {
        pData->s.scan = pData->s.xlatScan;
        pData->s.xlatScan = m_pmScanToOemScanExt[pData->s.xlatScan];
        pData->s.fExtendedKey = true;
        pData->s.fNeedNumUnlockKey = true;
        pData->s.fSecondary = true;
        return;
    }

    //
    // There are some virtual keys we know are extended, mark them so (like the Fxx keys).
    //
    if (    pData->s.virtualKey < sizeof(s_virtualKey) / sizeof(s_virtualKey[0])
        &&  !pData->s.fExtendedKey) {
        if (    s_virtualKey[pData->s.virtualKey].extended == 1
            ||  (   s_virtualKey[pData->s.virtualKey].extended == -1
                 && (   pData->s.xlatChar == 0
                     || pData->s.xlatChar == s_virtualKey[pData->s.virtualKey].ch))) {
            pData->s.fExtendedKey = true;
        }
    }

    //
    // Do the translating.
    //
    if (pData->s.xlatScan == pData->s.scan) {
        pData->s.scan = m_pmScanToOemScan[pData->s.xlatScan];
        //if (pData->s.fExtendedKey) {
        //  pData->s.scan = m_pmScanToOemScanExt[pData->s.xlatScan];
        //} else {
        //  pData->s.scan = m_pmScanToOemScan[pData->s.xlatScan];
        //}
    }
}


void
CPMKeyState::getKeyMap(CKeyMap& keyMap)
{
    LOG((CLOG_DEBUG2 "getKeyMap:"));
#if 0
    // update keyboard groups
    if (getGroups(m_groups)) {
        m_groupMap.clear();
        SInt32 numGroups = (SInt32)m_groups.size();
        for (SInt32 g = 0; g < numGroups; ++g) {
            m_groupMap[m_groups[g]] = g;
        }
    }
    HKL activeLayout = GetKeyboardLayout(0);
#endif

    // clear table
    memset(m_virtualKeyToButton, 0, sizeof(m_virtualKeyToButton));
    m_keyToVKMap.clear();

    CKeyMap::KeyItem item;
    for (SInt32 g = 0; g < 1; ++g) {
        item.m_group = g;

        //
        // Fill the scan code conversion tables.
        //
        for (unsigned i = 0; i < sizeof(m_pmScanToOemScan) / sizeof(m_pmScanToOemScan[0]); i++) {
            USHORT us = i;
            USHORT fShiftState = 0;
            WinTranslateChar2(0, &us, NULL, TC_SCANTOOEMSCAN, &fShiftState);
            m_pmScanToOemScan[i] = us;
        }
        for (unsigned i = 0; i < sizeof(m_pmScanToOemScanExt) / sizeof(m_pmScanToOemScanExt[0]); i++) {
            USHORT us = i;
            USHORT fShiftState = TCF_EXTENDEDKEY;
            WinTranslateChar2(0, &us, NULL, TC_SCANTOOEMSCAN, &fShiftState);
            m_pmScanToOemScanExt[i] = us;
        }
        for (unsigned i = 0; i < sizeof(m_oemScanToPmScan) / sizeof(m_oemScanToPmScan[0]); i++) {
            USHORT us = i;
            USHORT fShiftState = 0;
            WinTranslateChar2(0, &us, NULL, TC_OEMSCANTOSCAN, &fShiftState);
            m_oemScanToPmScan[i] = us;
        }
        for (unsigned i = 0; i < sizeof(m_oemScanToPmScanExt) / sizeof(m_oemScanToPmScanExt[0]); i++) {
            USHORT us = i;
            USHORT fShiftState = TCF_EXTENDEDKEY;
            WinTranslateChar2(0, &us, NULL, TC_OEMSCANTOSCAN, &fShiftState);
            m_oemScanToPmScanExt[i] = us;
        }


        //
        // map buttons (scancodes) to virtual keys
        //
        memset(m_buttonToVK, 0, sizeof(m_buttonToVK));
        for (KeyButton i = 1; i < 256; ++i) {
            USHORT usVirtualKey = i;
            USHORT fShiftState = 0;
            WinTranslateChar2(0, &usVirtualKey, NULL, TC_SCANCODETOVIRTUALKEY, &fShiftState);
            if (    usVirtualKey != 0
                &&  m_buttonToVK[i] == 0) {
                m_buttonToVK[i] = usVirtualKey;
            }
        }

/// @todo the stuff down to the button loop isn't really done yet.

        // now map virtual keys to buttons.  multiple virtual keys may map
        // to a single button.  if the virtual key matches the one in
        // m_buttonToVK then we use the button as is.  if not then it's
        // either a numpad key and we use the button as is or it's an
        // extended button.
        for (ULONG i = 1; i < 255; ++i) {
            // skip virtual keys we don't want
            switch (i) {
            case VK_BUTTON1:
            case VK_BUTTON2:
            case VK_BUTTON3:
            case VK_MENU:
                continue;
            }

            // get the button
            USHORT usButton = i;
            USHORT fShiftState = 0;
            WinTranslateChar2(0, &usButton, NULL, TC_VIRTUALKEYTOSCANCODE, &fShiftState);

            // add extended key if virtual keys don't match
            if (    usButton != 0
                &&  m_buttonToVK[usButton] != i) {
                m_buttonToVK[usButton | 0x100u] = i;
            }
        }

        // set virtual key to button table
//      if (GetKeyboardLayout(0) == m_groups[g]) {
            for (KeyButton i = 0; i < 512; ++i) {
                if (m_buttonToVK[i] != 0) {
                    if (m_virtualKeyToButton[m_buttonToVK[i]] == 0) {
                        m_virtualKeyToButton[m_buttonToVK[i]] = i;
                    }
                }
            }
//      }

        //
        // Add the keys to the map.
        //
        //for (KeyButton i = 0; i < 256; ++i) {
        for (KeyButton i = 0; i < 256; ++i) {
            //
            // Does this translate to a virtual key or character?
            //
            USHORT usChar = i;
            USHORT fShiftState = 0;
            USHORT fCharKCFlags = WinTranslateChar2(0, &usChar, NULL, TC_SCANCODETOCHAR, &fShiftState);
            USHORT usVirtualKey = i;
            USHORT fVirtualKeyKCFlags = WinTranslateChar2(0, &usVirtualKey, NULL, TC_SCANCODETOVIRTUALKEY, &fShiftState);
            if (!usChar && !usVirtualKey) {
                // try with control down.
                usChar = i;
                fShiftState = TCF_CONTROL;
                fCharKCFlags = WinTranslateChar2(0, &usChar, NULL, TC_SCANCODETOCHAR, &fShiftState);
                usVirtualKey = i;
                fShiftState = TCF_CONTROL;
                fVirtualKeyKCFlags = WinTranslateChar2(0, &usVirtualKey, NULL, TC_SCANCODETOVIRTUALKEY, &fShiftState);
            }
            if (!usChar && !usVirtualKey) {
                // try with altgr down.
                usChar = i;
                fShiftState = TCF_ALTGR;
                fCharKCFlags = WinTranslateChar2(0, &usChar, NULL, TC_SCANCODETOCHAR, &fShiftState);
                usVirtualKey = i;
                fShiftState = TCF_ALTGR;
                fVirtualKeyKCFlags = WinTranslateChar2(0, &usVirtualKey, NULL, TC_SCANCODETOVIRTUALKEY, &fShiftState);
            }
            if (!usChar && !usVirtualKey) {
                // try with numlock toggled.
                usChar = i;
                fShiftState = TCF_NUMLOCK;
                fCharKCFlags = WinTranslateChar2(0, &usChar, NULL, TC_SCANCODETOCHAR, &fShiftState);
                usVirtualKey = i;
                fShiftState = TCF_NUMLOCK;
                fVirtualKeyKCFlags = WinTranslateChar2(0, &usVirtualKey, NULL, TC_SCANCODETOVIRTUALKEY, &fShiftState);
            }
            if (    usChar
                ||  usVirtualKey) {
                // initialize the item
                item.m_id        = getKeyID(m_buttonToVK[i], i, false, usChar);
                item.m_button    = i;
                item.m_required  = 0;
                item.m_sensitive = 0;
                item.m_dead      = false;
                item.m_lock      = false;

                // get flags for modifier keys
                CKeyMap::initModifierKey(item);
                if (item.m_generates != 0) {
                    // it's a modifier key.
                    item.m_lock = usVirtualKey == VK_NUMLOCK
                               || usVirtualKey == VK_SCRLLOCK
                               || usVirtualKey == VK_CAPSLOCK;
                    ClientData data;
                    data.u = 0;
                    data.s.scan       = i;
                    data.s.xlatScan   = i;
                    data.s.fShiftKey  = true;
                    data.s.xlatChar   = usChar;
                    data.s.virtualKey = usVirtualKey;
                    convertScancodes(&data);
                    item.m_client = data.u;
                    addKeyEntry(keyMap, item);
                } else {
                    //
                    // Check which keys it might be sensitive to.
                    // We assume (possibly incorrectly) that combinations doesn't matter... :)
                    //
                    static const struct {
                        KeyModifierMask mask;
                        unsigned        fShiftState;
                    } modifiers[] = {
                        { KeyModifierShift, TCF_SHIFT},
                        { KeyModifierControl, TCF_CONTROL },
                        { KeyModifierAlt, TCF_ALT },
                        { KeyModifierAltGr, TCF_ALTGR },
                        { KeyModifierCapsLock, TCF_CAPSLOCK },
                        { KeyModifierNumLock, TCF_NUMLOCK }
                    };
                    usChar = i;
                    fShiftState = 0;
                    fCharKCFlags = WinTranslateChar2(0, &usChar, NULL, TC_SCANCODETOCHAR, &fShiftState);
                    usVirtualKey = i;
                    fShiftState = 0;
                    fVirtualKeyKCFlags = WinTranslateChar2(0, &usVirtualKey, NULL, TC_SCANCODETOVIRTUALKEY, &fShiftState);
                    for (unsigned j = 0; j < sizeof(modifiers) / sizeof(modifiers[0]); j++) {
                        USHORT usCh = i;
                        fShiftState = modifiers[j].fShiftState;
                        if (    WinTranslateChar2(0, &usCh, NULL, TC_SCANCODETOCHAR, &fShiftState) != 0
                            &&  usCh != 0
                            &&  usCh != usChar) {
                            item.m_sensitive |= modifiers[j].mask;
                            continue;
                        }
                        usCh = i;
                        fShiftState = modifiers[j].fShiftState;
                        WinTranslateChar2(0, &usCh, NULL, TC_SCANCODETOVIRTUALKEY, &fShiftState);
                        if (    usCh != 0
                            &&  usCh != usVirtualKey) {
                            item.m_sensitive |= modifiers[j].mask;
                            continue;
                        }
                    }
                    if (isASCIIControlChar(usChar)) {
                        item.m_sensitive |= KeyModifierControl;
                    }

                    //
                    // Check for numpad keys. We need to adjust stuff then.
                    // It think this test should be sufficient.
                    //
                    bool numpad = (item.m_sensitive & KeyModifierNumLock) != 0;

                    //
                    // Unoptimized brute force algorithm for determining the
                    // possible combinations and adding them to the key map.
                    //
                    struct
                    {
                        UCHAR   uchChar;
                        UCHAR   uchVirtualKey;
                    } combinations[1 << TCF_MAX_BITS];

                    for (unsigned j = 0; j < sizeof(combinations) / sizeof(combinations[0]); j++) {
                        const KeyModifierMask mask = convertTCFToKeyModiferMask(j);
                        if ((mask & item.m_sensitive) != mask /*|| j>3*/) {
                            combinations[j].uchChar = combinations[j].uchVirtualKey = 0;
                            continue;
                        }
                        // get any character value
                        USHORT us = i;
                        fShiftState = j;
                        if (WinTranslateChar2(0, &us, NULL, TC_SCANCODETOCHAR, &fShiftState) == 0) {
                            us = 0;
                        }
                        combinations[j].uchChar = us;

                        // get any virtual key value
                        us = i;
                        fShiftState = j;
                        WinTranslateChar2(0, &us, NULL, TC_SCANCODETOVIRTUALKEY, &fShiftState);
                        combinations[j].uchVirtualKey = us;

                        // deal with missing character translations
                        if (    combinations[j].uchChar == 0
                            &&  us != 0
                            &&  us < sizeof(s_virtualKey) / sizeof(s_virtualKey[0])) {
                            combinations[j].uchChar = s_virtualKey[us].ch;
                        }
                    }

                    // 2. Eliminate duplicates.
                    for (unsigned j = 0; j < sizeof(combinations) / sizeof(combinations[0]); j++) {
                        unsigned best = j;
                        KeyModifierMask bestMask = convertTCFToKeyModiferMask(j);
                        for (unsigned k = j + 1; k < sizeof(combinations) / sizeof(combinations[0]); k++) {
                            if (    combinations[best].uchChar == combinations[k].uchChar
                                &&  combinations[best].uchVirtualKey == combinations[k].uchVirtualKey) {
                                const KeyModifierMask mask = convertTCFToKeyModiferMask(k);
                                // drop L/R mixes.
                                if (mask == bestMask) {
                                    combinations[k].uchChar = combinations[k].uchVirtualKey = 0;
                                }
                            }
                        }
                    }

                    // 3. Add the remainders.
                    for (unsigned j = 0; j < sizeof(combinations) / sizeof(combinations[0]); j++) {
                        if (combinations[j].uchChar || combinations[j].uchVirtualKey) {
                            USHORT usChar = combinations[j].uchChar;
                            USHORT usVirtualKey = combinations[j].uchVirtualKey;

                            // update the changing item members
                            item.m_required  = convertTCFToKeyModiferMask(j);
                            if (usVirtualKey) {
                                switch (usVirtualKey) {
                                case VK_BACKTAB:
                                    item.m_required  |= KeyModifierShift;
                                    item.m_sensitive |= KeyModifierShift;
                                    usChar            = 0; // this one is translated incorrectly
                                    break;
                                }
                                item.m_id    = getKeyID(usVirtualKey, i, numpad, usChar);
                            } else {
                                item.m_id    = usChar; /// @todo translate to unicode!
                            }
                            ClientData data;
                            data.u = 0;
                            data.s.scan       = i;
                            data.s.xlatScan   = i;
                            data.s.xlatChar   = usChar;
                            data.s.virtualKey = usVirtualKey;
                            convertScancodes(&data);
                            item.m_client = data.u;
                            addKeyEntry(keyMap, item);
                        }
                    }

                    // 4. ASCII Control Character variants. (ARG! this doesn't work, fakeMsg hacks it.)
                    if (isASCIIControlChar(usChar)) {
                        /* add key */
                        item.m_id        = usChar - (usChar >= 'a' ? 0x60 : 0x40);
                        item.m_required  = KeyModifierControl;
                        ClientData data;
                        data.u = 0;
                        data.s.scan      = i;
                        data.s.xlatScan  = i;
                        data.s.xlatChar  = item.m_id;
                        convertScancodes(&data);
                        item.m_client    = data.u;
                        addKeyEntry(keyMap, item);
                        item.m_id        = usChar;
                        addKeyEntry(keyMap, item);
                    }
                }
            }
        } // for buttons 0 thru 254

        //
        // Various other keys for which we don't expect the above loops to catch.
        //
        item.m_id        = kKeyMenu;
        item.m_button    = 0xee;
        item.m_required  = 0;
        item.m_sensitive = 0;
        item.m_dead      = false;
        item.m_lock      = false;
        ClientData data;
        data.u = 0;
        data.s.scan      = 0x7c;
        data.s.xlatScan  = 0xee;
        data.s.fSecondary = true;
        data.s.fExtendedKey = true;
        data.s.fNeedNumUnlockKey = true;
        item.m_client    = data.u;
        CKeyMap::initModifierKey(item);
        addKeyEntry(keyMap, item);

        item.m_id        = kKeySuper_L;
        item.m_button    = 0xec;
        data.s.xlatScan  = 0xec;
        data.s.scan      = 0x7e;
        item.m_client    = data.u;
        CKeyMap::initModifierKey(item);
        addKeyEntry(keyMap, item);

        item.m_id        = kKeySuper_R;
        item.m_button    = 0xed;
        data.s.xlatScan  = 0xed;
        data.s.scan      = 0x7f;
        item.m_client    = data.u;
        CKeyMap::initModifierKey(item);
        addKeyEntry(keyMap, item);

    }

#if 0
    // restore keyboard layout
    ActivateKeyboardLayout(activeLayout, 0);
#endif
}

void
CPMKeyState::fakeKey(const Keystroke& keystroke)
{
    LOG((CLOG_DEBUG2 "fakeKey:"));
    switch (keystroke.m_type) {
    case Keystroke::kButton: {
        LOG((CLOG_DEBUG2 "  %03x (%08llx) %s%s", keystroke.m_data.m_button.m_button, keystroke.m_data.m_button.m_client,
             keystroke.m_data.m_button.m_press ? "down" : "up", keystroke.m_data.m_button.m_repeat ? " repeate" : ""));
        KeyButton button = keystroke.m_data.m_button.m_button;

        // OS/2 doesn't send key ups for key repeats
        if (keystroke.m_data.m_button.m_repeat &&
            !keystroke.m_data.m_button.m_press) {
            LOG((CLOG_DEBUG "  discard key repeat release"));
            break;
        }

        // unpack the m_client packet.
        ClientData data;
        data.u = keystroke.m_data.m_button.m_client;

        //
        // synthesize message
        // There are two options at this point, WM_CHAR or WM_VIOCHAR. It seems
        // like WM_VIOCHAR is the better choice since that's the form in which
        // keyboard events normally arrive in the system queue.
        //
        // WM_VIOCHAR:
        //      mp1.s1: KC_ flags.
        //      mp1.c3: repeat
        //      mp1.c4: scancode
        //      mp2.c1: translated char
        //      mp2.c2: translated scancode
        //      mp2.s2: KDD_ flags.
        //

        // the standard bits.
        HAB hab = CPMUtil::getHAB();/// @todo fix this
        QMSG qmsg;
        qmsg.hwnd = NULLHANDLE;
        qmsg.msg = WM_VIOCHAR;
        qmsg.ptl.x = 0;
        qmsg.ptl.y = 0;
        qmsg.time = WinGetCurrentTime(hab);
        qmsg.reserved = 0;

        //
        // calc the key code flags.
        // PM deals with: KC_TOGGLE, KC_VIRTUALKEY, KC_CHAR, (WM_CHAR: also KC_CTRL, KC_ALT, KC_SHIFT).
        //
        USHORT fKC = 0;
        if (data.s.scan)
            fKC |= KC_SCANCODE;
        if (!keystroke.m_data.m_button.m_press)
            fKC |= KC_KEYUP;
        /// @todo check that PM does KC_INVALIDCOMP
        if (data.s.fDeadKey)
            fKC |= KC_DEADKEY;
        if (data.s.fComposite)
            fKC |= KC_COMPOSITE;
        if (!keystroke.m_data.m_button.m_press && m_lastButton == data.s.scan)
            fKC |= KC_LONEKEY;

        // we need these for calcing fKDD, so just put them in even if PM can do it for us.
        KeyModifierMask modifierMask = this->getActiveModifiers();
        if (modifierMask & KeyModifierShift)
            fKC |= KC_SHIFT;
        if (modifierMask & KeyModifierControl)
            fKC |= KC_CTRL;
        if (modifierMask & KeyModifierAlt)
            fKC |= KC_ALT;

        //
        // Calc the keyboard device driver flags.
        //
        USHORT fKDD;
        // the action
        if (data.s.fShiftKey)
            fKDD = KDD_SHIFTKEY;
/** @todo Determin the KDD_ACTIONMASK value.
KDD_PREFIXKEY
KDD_PAUSEKEY
KDD_PSEUDOPAUSE
KDD_WAKEUPKEY
KDD_BREAKKEY
KDD_PSEUDOBREAK
KDD_PRTECHOKEY
KDD_PSEUDOPRECH */
        else
            fKDD = KDD_PUTINKIB;
#if 0
        if (fKC & KC_LONEKEY)       fKDD |= KDD_KC_LONEKEY;
        if (fKC & KC_PREVDOWN)      fKDD |= KDD_KC_PREVDOWN;
        if (fKC & KC_KEYUP)         fKDD |= KDD_KC_KEYUP | KDD_BREAK;
        if (fKC & KC_ALT)           fKDD |= KDD_KC_ALT;
        if (fKC & KC_CTRL)          fKDD |= KDD_KC_CTRL;
        if (fKC & KC_SHIFT)         fKDD |= KDD_KC_SHIFT;
#else
        if (fKC & KC_KEYUP)         fKDD |= KDD_BREAK;
#endif
        if (data.s.fSecondary)      fKDD |= KDD_SECONDARY;
        if (data.s.fExtendedKey)    fKDD |= KDD_EXTENDEDKEY;

        //
        // Adjust char for ASCII control keys. (Ctrl+C and stuff)
        //
        if (    (fKC & KC_CTRL)
            &&  isASCIIControlChar(data.s.xlatChar)) {
            data.s.xlatChar -= (data.s.xlatChar >= 'a' ? 0x60 : 0x40);
        }

        //
        // For some keys (navigation and edit keys left to the numpad),
        // any numlock or shift needs to be 'canceled'.
        // (At least this happens with my logitech diNovo keyboards.)
        //
        if (    data.s.fNeedNumUnlockKey
            &&  (   (modifierMask & (KeyModifierShift | KeyModifierNumLock)) == KeyModifierNumLock
                 || (modifierMask & (KeyModifierShift | KeyModifierNumLock)) == KeyModifierShift)) {
            // need to insert a key nullifying the numlock/shift.
            QMSG qmsg0 = qmsg;
            qmsg0.mp1 = MPFROMSH2CH(fKC & KC_KEYUP, 1, 0);
            qmsg0.mp2 = MPFROM2SHORT(MAKESHORT(0, 0x2a), (fKDD & KDD_BREAK) | KDD_UNDEFINED | KDD_SECONDARY | KDD_EXTENDEDKEY);

            // this message comes first on keydown and last on keyup.
            if (fKC & KC_KEYUP) {
                QMSG tmp = qmsg;
                qmsg = qmsg0;
                qmsg0 = tmp;
            }

            LOG((CLOG_DEBUG "WM_VIOCHAR: fKC=%04x rep=%02x scan=%02x xlch=%02x(%c) xlscan=%02x fKDD=%04x ",
                 SHORT1FROMMP(qmsg0.mp1), CHAR3FROMMP(qmsg0.mp1), CHAR4FROMMP(qmsg0.mp1), CHAR1FROMMP(qmsg0.mp2),
                 isprint(CHAR1FROMMP(qmsg0.mp2)) ? CHAR1FROMMP(qmsg0.mp2) : '.', CHAR2FROMMP(qmsg0.mp2), SHORT2FROMMP(qmsg0.mp2)));
            const char *pszError = m_fakeMsg(hab, &qmsg0);
            if (pszError) {
                LOG((CLOG_ERR " fakeMsg failed to inject msg=%#lx mp1=%#lx mp2=%#lx: %s", qmsg0.msg, qmsg0.mp1, qmsg0.mp2, pszError));
            }
        }

        //
        // Inject the (last) message.
        //
        qmsg.mp1 = MPFROMSH2CH(fKC, 1, data.s.scan);
        qmsg.mp2 = MPFROM2SHORT(MAKESHORT(data.s.xlatChar, data.s.xlatScan), fKDD);
        LOG((CLOG_DEBUG "WM_VIOCHAR: fKC=%04x rep=%02x scan=%02x xlch=%02x(%c) xlscan=%02x fKDD=%04x ",
             SHORT1FROMMP(qmsg.mp1), CHAR3FROMMP(qmsg.mp1), CHAR4FROMMP(qmsg.mp1), CHAR1FROMMP(qmsg.mp2),
             isprint(CHAR1FROMMP(qmsg.mp2)) ? CHAR1FROMMP(qmsg.mp2) : '.', CHAR2FROMMP(qmsg.mp2), SHORT2FROMMP(qmsg.mp2)));
        const char *pszError = m_fakeMsg(hab, &qmsg);
        if (pszError) {
            LOG((CLOG_ERR " fakeMsg failed to inject msg=%#lx mp1=%#lx mp2=%#lx: %s", qmsg.msg, qmsg.mp1, qmsg.mp2, pszError));
        }

        // remember the previous key for KC_LONEKEY.
        if (keystroke.m_data.m_button.m_press && !keystroke.m_data.m_button.m_repeat)
            m_lastButton = data.s.scan;
        break;
    }

    case Keystroke::kGroup:
#if 0
        // we don't restore the group.  we'd like to but we can't be
        // sure the restoring group change will be processed after the
        // key events.
        if (!keystroke.m_data.m_group.m_restore) {
            if (keystroke.m_data.m_group.m_absolute) {
                LOG((CLOG_DEBUG1 "  group %d", keystroke.m_data.m_group.m_group));
                setWindowGroup(keystroke.m_data.m_group.m_group);
            }
            else {
                LOG((CLOG_DEBUG1 "  group %+d", keystroke.m_data.m_group.m_group));
                setWindowGroup(getEffectiveGroup(pollActiveGroup(),
                                    keystroke.m_data.m_group.m_group));
            }
        }
#endif
        break;
    }
}

KeyModifierMask&
CPMKeyState::getActiveModifiersRValue()
{
    LOG((CLOG_DEBUG2 "getActiveModifiersRValue:"));
    if (m_useSavedModifiers) {
        return m_savedModifiers;
    }
    return CKeyState::getActiveModifiersRValue();
}

KeyID
CPMKeyState::getKeyID(ULONG virtualKey, KeyButton button, bool numpad, USHORT usChar)
{
    LOG((CLOG_DEBUG2 "getKeyID:"));
    if (virtualKey < sizeof(s_virtualKey) / sizeof(s_virtualKey[0])) {

        //
        // Numpad kludge.
        //
        if (numpad && s_virtualKey[virtualKey].numpad) {
            switch (usChar) {
                case '=':   return kKeyKP_Equal;
                case '*':   return kKeyKP_Multiply;
                case '+':   return kKeyKP_Add;
                //case ',':
                //case '.':   return kKeyKP_Separator ? kKeyKP_Decimal;
                case '-':   return kKeyKP_Subtract;
                case '/':   return kKeyKP_Divide;
                case '0':   return kKeyKP_0;
                case '1':   return kKeyKP_1;
                case '2':   return kKeyKP_2;
                case '3':   return kKeyKP_3;
                case '4':   return kKeyKP_4;
                case '5':   return kKeyKP_5;
                case '6':   return kKeyKP_6;
                case '7':   return kKeyKP_7;
                case '8':   return kKeyKP_8;
                case '9':   return kKeyKP_9;
                case ' ':   return kKeyKP_Space;
            }
            switch (s_virtualKey[virtualKey].key) {
               case kKeyHome:       return kKeyKP_Home;
               case kKeyLeft:       return kKeyKP_Left;
               case kKeyUp:         return kKeyKP_Up;
               case kKeyRight:      return kKeyKP_Right;
               case kKeyDown:       return kKeyKP_Down;
               case kKeyPageUp:     return kKeyKP_PageUp;
               case kKeyPageDown:   return kKeyKP_PageDown;
               case kKeyEnd:        return kKeyKP_End;
               case kKeyBegin:      return kKeyKP_Begin;
               case kKeyInsert:     return kKeyKP_Insert;
               case kKeyDelete:     return kKeyKP_Delete;
            }
        } else {
            //
            // Left/right shift and control kludge.
            //
            switch (virtualKey) {
                case VK_CTRL:
                    if (m_virtualKeyToButton[VK_CTRL] != button) {
                        return kKeyControl_R;
                    }
                    break;
                case VK_SHIFT:
                    if (m_virtualKeyToButton[VK_SHIFT] != button) {
                        return kKeyShift_R;
                    }
                    break;
            }
        }
        return s_virtualKey[virtualKey].key;
    }
    return kKeyNone;
}

KeyID
CPMKeyState::getIDForKey(CKeyMap::KeyItem& item,
                KeyButton button, ULONG virtualKey,
                PBYTE keyState) const
{
    LOG((CLOG_DEBUG2 "getIDForKey:"));
#if 0
    int n;
    KeyID id;
    WORD ascii;
    n  = ToAsciiEx(virtualKey, button, keyState, &ascii, 0, hkl);
    id = static_cast<KeyID>(ascii & 0xffu);
    switch (n) {
    case -1:
        return CKeyMap::getDeadKey(id);

    default:
    case 0:
        // unmapped
        return kKeyNone;

    case 1:
        return id;

    case 2:
        // left over dead key in buffer;  oops.
        return getIDForKey(item, button, virtualKey, keyState, hkl);
    }
#else
    //fixme!
    return 0;
#endif
}

static const char *
getKeyName(KeyID key)
{
    const KeyNameMapEntry *entry = &kKeyNameMap[0];
    while (entry->m_id != kKeyNone) {
        if (entry->m_id == key) {
            return entry->m_name;
        }
        entry++;
    }
    /* ASCII? */
    static char s_buf[2][2];
    static char s_idxBuf;
    if (key >= ' ' && key <= 'z') {
        char *buf = &s_buf[s_idxBuf = (s_idxBuf + 1) % 2][0];
        *buf = key;
        return buf;
    }
    return "";
}

void
CPMKeyState::addKeyEntry(CKeyMap& keyMap, CKeyMap::KeyItem& item)
{
    ClientData data; data.u = item.m_client; assert(sizeof(data.u) == sizeof(data));
    LOG((CLOG_DEBUG1
         "addKeyEntry: scan=%02x+%02x req=%c%c%c%c%c%c[%c%c%c%c%c%c]{%x} id=%04x(%c) xlatChar=%02x(%c) scan=%02x xlatScan=%02x [%s]%s%s%s%s%s%s",
         item.m_button, convertKeyModiferMaskToTCF(item.m_required),
         item.m_required & KeyModifierShift ? 'S' : '-',
         item.m_required & KeyModifierControl ? 'C' : '-',
         item.m_required & KeyModifierAlt ? 'A' : '-',
         item.m_required & KeyModifierAltGr ? 'R' : '-',
         item.m_required & KeyModifierCapsLock ? 'U' : '-',
         item.m_required & KeyModifierNumLock ? 'N' : '-',
         item.m_sensitive & KeyModifierShift ? 'S' : '-',
         item.m_sensitive & KeyModifierControl ? 'C' : '-',
         item.m_sensitive & KeyModifierAlt ? 'A' : '-',
         item.m_sensitive & KeyModifierAltGr ? 'R' : '-',
         item.m_sensitive & KeyModifierCapsLock ? 'U' : '-',
         item.m_sensitive & KeyModifierNumLock ? 'N' : '-',
         item.m_generates,
         item.m_id,             item.m_id < 128 && isprint(item.m_id)       ? item.m_id       : '.',
         data.s.xlatChar, data.s.xlatChar < 128 && isprint(data.s.xlatChar) ? data.s.xlatChar : '.',
         data.s.scan, data.s.xlatScan,
         getKeyName(item.m_id),
         data.s.fExtendedKey ? " extendedkey" : "",
         data.s.fShiftKey    ? " shiftkey" : "",
         data.s.fDeadKey     ? " deadkey" : "",
         data.s.fComposite   ? " composite" : "",
         item.m_lock         ? " lock" : "",
         item.m_dead         ? " dead" : ""
         ));
    if (data.s.scan2 || data.s.xlatScan2) {
        LOG((CLOG_DEBUG1
             "addKeyEntry: xlatChar2=%02x(%c) scan2=%02x xlatScan2=%02x",
             data.s.xlatChar2, data.s.xlatChar2 < 128 && isprint(data.s.xlatChar2) ? data.s.xlatChar2 : '.',
             data.s.scan2, data.s.xlatScan2));
    }

    if (!keyMap.addKeyEntry(item)) {
        LOG((CLOG_DEBUG1 "!not accepted!"));
    }
    if (item.m_group == 0) {
        m_keyToVKMap[item.m_id] = item.m_client;
    }
}

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

