WindowsKeyNames.cpp   [plain text]


// Copyright 2016 The Chromium Authors. All rights reserved.
// Copyright (C) 2019 Sony Interactive Entertainment Inc.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "config.h"
#include "WindowsKeyNames.h"

namespace WebCore {

enum class WindowsKeyNames::KeyModifier : uint8_t {
    Shift = 1 << 0,
    Control = 1 << 1,
    Alt = 1 << 2,
    CapsLock = 1 << 3,
};

static void setModifierState(BYTE* keyboardState, WindowsKeyNames::KeyModifierSet modifiers)
{
    // According to MSDN GetKeyState():
    // 1. If the high-order bit is 1, the key is down; otherwise, it is up.
    // 2. If the low-order bit is 1, the key is toggled. A key, such as the
    //    CAPS LOCK key, is toggled if it is turned on. The key is off and
    //    untoggled if the low-order bit is 0.
    // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646301.aspx
    if (modifiers.contains(WindowsKeyNames::KeyModifier::Shift))
        keyboardState[VK_SHIFT] |= 0x80;

    if (modifiers.contains(WindowsKeyNames::KeyModifier::Control))
        keyboardState[VK_CONTROL] |= 0x80;

    if (modifiers.contains(WindowsKeyNames::KeyModifier::Alt))
        keyboardState[VK_MENU] |= 0x80;

    if (modifiers.contains(WindowsKeyNames::KeyModifier::CapsLock))
        keyboardState[VK_CAPITAL] |= 0x01;
}

static bool hasControlAndAlt(WindowsKeyNames::KeyModifierSet modifiers)
{
    return modifiers.containsAll({ WindowsKeyNames::KeyModifier::Control, WindowsKeyNames::KeyModifier::Alt });
}

// This table must be sorted by 'virtualKey' for binary search.
constexpr struct NonPrintableKeyEntry {
    int virtualKey;
    ASCIILiteral domKey;
} cNonPrintableKeyMap[] = {
    { VK_CANCEL, "Cancel"_s },
    { VK_BACK, "Backspace"_s },
    { VK_TAB, "Tab"_s },
    { VK_CLEAR, "Clear"_s },
    { VK_RETURN, "Enter"_s },
    { VK_SHIFT, "Shift"_s },
    { VK_CONTROL, "Control"_s },
    { VK_MENU, "Alt"_s },
    { VK_PAUSE, "Pause"_s },
    { VK_CAPITAL, "CapsLock"_s },
    // VK_KANA == VK_HANGUL
    { VK_JUNJA, "JunjaMode"_s },
    { VK_FINAL, "FINAL_MODE"_s },
    // VK_HANJA == VK_KANJI
    { VK_ESCAPE, "Escape"_s },
    { VK_CONVERT, "Convert"_s },
    { VK_NONCONVERT, "NonConvert"_s },
    { VK_ACCEPT, "Accept"_s },
    { VK_MODECHANGE, "ModeChange"_s },
    // VK_SPACE
    { VK_PRIOR, "PageUp"_s },
    { VK_NEXT, "PageDown"_s },
    { VK_END, "End"_s },
    { VK_HOME, "Home"_s },
    { VK_LEFT, "ArrowLeft"_s },
    { VK_UP, "ArrowUp"_s },
    { VK_RIGHT, "ArrowRight"_s },
    { VK_DOWN, "ArrowDown"_s },
    { VK_SELECT, "Select"_s },
    { VK_PRINT, "Print"_s },
    { VK_EXECUTE, "Execute"_s },
    { VK_SNAPSHOT, "PrintScreen"_s },
    { VK_INSERT, "Insert"_s },
    { VK_DELETE, "Delete"_s },
    { VK_HELP, "Help"_s },
    // VK_0..9
    // VK_A..Z
    { VK_LWIN, "Meta"_s },
    // VK_COMMAND == VK_LWIN
    { VK_RWIN, "Meta"_s },
    { VK_APPS, "ContextMenu"_s },
    { VK_SLEEP, "Standby"_s },
    // VK_NUMPAD0..9
    // VK_MULTIPLY, VK_ADD, VK_SEPARATOR, VK_SUBTRACT, VK_DECIMAL,
    // VK_DIVIDE
    { VK_F1, "F1"_s },
    { VK_F2, "F2"_s },
    { VK_F3, "F3"_s },
    { VK_F4, "F4"_s },
    { VK_F5, "F5"_s },
    { VK_F6, "F6"_s },
    { VK_F7, "F7"_s },
    { VK_F8, "F8"_s },
    { VK_F9, "F9"_s },
    { VK_F10, "F10"_s },
    { VK_F11, "F11"_s },
    { VK_F12, "F12"_s },
    { VK_F13, "F13"_s },
    { VK_F14, "F14"_s },
    { VK_F15, "F15"_s },
    { VK_F16, "F16"_s },
    { VK_F17, "F17"_s },
    { VK_F18, "F18"_s },
    { VK_F19, "F19"_s },
    { VK_F20, "F20"_s },
    { VK_F21, "F21"_s },
    { VK_F22, "F22"_s },
    { VK_F23, "F23"_s },
    { VK_F24, "F24"_s },
    { VK_NUMLOCK, "NumLock"_s },
    { VK_SCROLL, "ScrollLock"_s },
    { VK_LSHIFT, "Shift"_s },
    { VK_RSHIFT, "Shift"_s },
    { VK_LCONTROL, "Control"_s },
    { VK_RCONTROL, "Control"_s },
    { VK_LMENU, "Alt"_s },
    { VK_RMENU, "Alt"_s },
    { VK_BROWSER_BACK, "BrowserBack"_s },
    { VK_BROWSER_FORWARD, "BrowserForward"_s },
    { VK_BROWSER_REFRESH, "BrowserRefresh"_s },
    { VK_BROWSER_STOP, "BrowserStop"_s },
    { VK_BROWSER_SEARCH, "BrowserSearch"_s },
    { VK_BROWSER_FAVORITES, "BrowserFavorites"_s },
    { VK_BROWSER_HOME, "BrowserHome"_s },
    { VK_VOLUME_MUTE, "AudioVolume"_s },
    { VK_VOLUME_DOWN, "AudioVolumeDown"_s },
    { VK_VOLUME_UP, "AudioVolumeUp"_s },
    { VK_MEDIA_NEXT_TRACK, "MediaTrackNext"_s },
    { VK_MEDIA_PREV_TRACK, "MediaTrackPrevious"_s },
    { VK_MEDIA_STOP, "MediaStop"_s },
    { VK_MEDIA_PLAY_PAUSE, "MediaPlayPause"_s },
    // VK_OEM_1..8, 102, PLUS, COMMA, MINUS, PERIOD
    { VK_PROCESSKEY, "Process"_s },
    // VK_PACKET - Used to pass Unicode char, considered as printable key.
    { VK_ATTN, "Attn"_s },
    { VK_CRSEL, "CrSel"_s },
    { VK_EXSEL, "ExSel"_s },
    { VK_EREOF, "EraseEof"_s },
    { VK_PLAY, "Play"_s },
    { VK_ZOOM, "ZoomToggle"_s },
    { VK_OEM_CLEAR, "Clear"_s },
};

// Disambiguates the meaning of certain non-printable keys which have different
// meanings under different languages, but use the same VKEY code.
static String languageSpecificOemVirtualKeyToDomKey(int virtualKey, HKL layout)
{
    WORD language = LOWORD(layout);
    WORD primaryLanguage = PRIMARYLANGID(language);
    if (primaryLanguage == LANG_KOREAN) {
        switch (virtualKey) {
        case VK_HANGUL:
            return "HangulMode"_s;
        case VK_HANJA:
            return "HanjaMode"_s;
        default:
            return String();
        }
    } else if (primaryLanguage == LANG_JAPANESE) {
        switch (virtualKey) {
        // VK_KANA isn't generated by any modern layouts but is a listed value
        // that third-party apps might synthesize, so we handle it anyway.
        case VK_KANA:
        case VK_ATTN:
            return "KanaMode"_s;
        case VK_KANJI:
            return "KanjiMode"_s;
        case VK_OEM_ATTN:
            return "Alphanumeric"_s;
        case VK_OEM_FINISH:
            return "Katakana"_s;
        case VK_OEM_COPY:
            return "Hiragana"_s;
        case VK_OEM_BACKTAB:
            return "Romaji"_s;
        case VK_OEM_AUTO:
            return "Hankaku"_s;
        case VK_OEM_ENLW:
            return "Zenkaku"_s;
        default:
            return String();
        }
    }
    return String();
}

static String nonPrintableVirtualKeyToDomKey(int virtualKey, HKL layout)
{
    // 1. Check if |virtualKey| has a |layout|-specific meaning.
    const String key = languageSpecificOemVirtualKeyToDomKey(virtualKey, layout);
    if (!key.isNull())
        return key;

    // 2. Most |virtualKeys| have the same meaning regardless of |layout|.
    const NonPrintableKeyEntry* result = std::lower_bound(
        std::begin(cNonPrintableKeyMap), std::end(cNonPrintableKeyMap), virtualKey,
        [](const NonPrintableKeyEntry& entry, int needle) {
            return entry.virtualKey < needle;
        });
    if (result != std::end(cNonPrintableKeyMap) && result->virtualKey == virtualKey)
        return result->domKey;

    return String();
}

WindowsKeyNames::WindowsKeyNames()
{
    updateLayout();
}

String WindowsKeyNames::domKeyFromLParam(LPARAM lParam)
{
    unsigned scanCode = (lParam >> 16) & 0xff;
    bool extended = lParam & 0x01000000;
    unsigned virtualKey = MapVirtualKey(scanCode, MAPVK_VSC_TO_VK);
    KeyModifierSet modifiers;
    if (GetKeyState(VK_SHIFT) < 0)
        modifiers.add(KeyModifier::Shift);
    if (GetKeyState(VK_CONTROL) < 0)
        modifiers.add(KeyModifier::Control);
    if (GetKeyState(VK_MENU ) < 0)
        modifiers.add(KeyModifier::Alt);
    if (GetKeyState(VK_CAPITAL) & 1)
        modifiers.add(KeyModifier::CapsLock);

    updateLayout();
    // Windows expresses right-Alt as VK_MENU with the extended flag set.
    // This key should generate AltGraph under layouts which use that modifier.
    if (virtualKey == VK_MENU && extended && m_hasAltGraph)
        return "AltGraph"_s;

    String key = nonPrintableVirtualKeyToDomKey(virtualKey, m_keyboardLayout);
    if (!key.isNull())
        return key;

    const KeyModifierSet modifiersToTry[] = {
        // Trying to match Firefox's behavior and UIEvents DomKey guidelines.
        // If the combination doesn't produce a printable character, the key value
        // should be the key with no modifiers except for Shift and AltGr.
        // See https://w3c.github.io/uievents/#keys-guidelines
        modifiers,
        KeyModifierSet({ KeyModifier::Shift, KeyModifier::CapsLock }) & modifiers,
    };

    for (auto tryModifiers : modifiersToTry) {
        const auto& it = m_printableKeyCodeToKey.find(std::make_pair(virtualKey, tryModifiers));
        if (it != m_printableKeyCodeToKey.end()) {
            key = it->value;
            if (!key.isNull())
                return key;
        }
    }

    return "Unidentified"_s;
}

String WindowsKeyNames::domKeyFromChar(UChar c)
{
    switch (c) {
    case '\x1b':
        return "Escape"_s;
    case '\t':
        return "Tab"_s;
    case '\r':
        return "Enter"_s;
    default:
        break;
    }
    return makeString(c);
}

// This table must be sorted by 'scanCode' for binary search.
constexpr struct {
    unsigned scanCode;
    ASCIILiteral domCode;
} cDomCodeMap[] = {
    { 0x0001, "Escape"_s },
    { 0x0002, "Digit1"_s },
    { 0x0003, "Digit2"_s },
    { 0x0004, "Digit3"_s },
    { 0x0005, "Digit4"_s },
    { 0x0006, "Digit5"_s },
    { 0x0007, "Digit6"_s },
    { 0x0008, "Digit7"_s },
    { 0x0009, "Digit8"_s },
    { 0x000a, "Digit9"_s },
    { 0x000b, "Digit0"_s },
    { 0x000c, "Minus"_s },
    { 0x000d, "Equal"_s },
    { 0x000e, "Backspace"_s },
    { 0x000f, "Tab"_s },
    { 0x0010, "KeyQ"_s },
    { 0x0011, "KeyW"_s },
    { 0x0012, "KeyE"_s },
    { 0x0013, "KeyR"_s },
    { 0x0014, "KeyT"_s },
    { 0x0015, "KeyY"_s },
    { 0x0016, "KeyU"_s },
    { 0x0017, "KeyI"_s },
    { 0x0018, "KeyO"_s },
    { 0x0019, "KeyP"_s },
    { 0x001a, "BracketLeft"_s },
    { 0x001b, "BracketRight"_s },
    { 0x001c, "Enter"_s },
    { 0x001d, "ControlLeft"_s },
    { 0x001e, "KeyA"_s },
    { 0x001f, "KeyS"_s },
    { 0x0020, "KeyD"_s },
    { 0x0021, "KeyF"_s },
    { 0x0022, "KeyG"_s },
    { 0x0023, "KeyH"_s },
    { 0x0024, "KeyJ"_s },
    { 0x0025, "KeyK"_s },
    { 0x0026, "KeyL"_s },
    { 0x0027, "Semicolon"_s },
    { 0x0028, "Quote"_s },
    { 0x0029, "Backquote"_s },
    { 0x002a, "ShiftLeft"_s },
    { 0x002b, "Backslash"_s },
    { 0x002c, "KeyZ"_s },
    { 0x002d, "KeyX"_s },
    { 0x002e, "KeyC"_s },
    { 0x002f, "KeyV"_s },
    { 0x0030, "KeyB"_s },
    { 0x0031, "KeyN"_s },
    { 0x0032, "KeyM"_s },
    { 0x0033, "Comma"_s },
    { 0x0034, "Period"_s },
    { 0x0035, "Slash"_s },
    { 0x0036, "ShiftRight"_s },
    { 0x0037, "NumpadMultiply"_s },
    { 0x0038, "AltLeft"_s },
    { 0x0039, "Space"_s },
    { 0x003a, "CapsLock"_s },
    { 0x003b, "F1"_s },
    { 0x003c, "F2"_s },
    { 0x003d, "F3"_s },
    { 0x003e, "F4"_s },
    { 0x003f, "F5"_s },
    { 0x0040, "F6"_s },
    { 0x0041, "F7"_s },
    { 0x0042, "F8"_s },
    { 0x0043, "F9"_s },
    { 0x0044, "F10"_s },
    { 0x0045, "Pause"_s },
    { 0x0046, "ScrollLock"_s },
    { 0x0047, "Numpad7"_s },
    { 0x0048, "Numpad8"_s },
    { 0x0049, "Numpad9"_s },
    { 0x004a, "NumpadSubtract"_s },
    { 0x004b, "Numpad4"_s },
    { 0x004c, "Numpad5"_s },
    { 0x004d, "Numpad6"_s },
    { 0x004e, "NumpadAdd"_s },
    { 0x004f, "Numpad1"_s },
    { 0x0050, "Numpad2"_s },
    { 0x0051, "Numpad3"_s },
    { 0x0052, "Numpad0"_s },
    { 0x0053, "NumpadDecimal"_s },
    { 0x0056, "IntlBackslash"_s },
    { 0x0057, "F11"_s },
    { 0x0058, "F12"_s },
    { 0x0059, "NumpadEqual"_s },
    { 0x0064, "F13"_s },
    { 0x0065, "F14"_s },
    { 0x0066, "F15"_s },
    { 0x0067, "F16"_s },
    { 0x0068, "F17"_s },
    { 0x0069, "F18"_s },
    { 0x006a, "F19"_s },
    { 0x006b, "F20"_s },
    { 0x006c, "F21"_s },
    { 0x006d, "F22"_s },
    { 0x006e, "F23"_s },
    { 0x0070, "KanaMode"_s },
    { 0x0071, "Lang2"_s },
    { 0x0072, "Lang1"_s },
    { 0x0073, "IntlRo"_s },
    { 0x0076, "F24"_s },
    { 0x0077, "Lang4"_s },
    { 0x0078, "Lang3"_s },
    { 0x0079, "Convert"_s },
    { 0x007b, "NonConvert"_s },
    { 0x007d, "IntlYen"_s },
    { 0x007e, "NumpadComma"_s },
    { 0x0108, "Undo"_s },
    { 0x010a, "Paste"_s },
    { 0x0110, "MediaTrackPrevious"_s },
    { 0x0117, "Cut"_s },
    { 0x0118, "Copy"_s },
    { 0x0119, "MediaTrackNext"_s },
    { 0x011c, "NumpadEnter"_s },
    { 0x011d, "ControlRight"_s },
    { 0x0120, "AudioVolumeMute"_s },
    { 0x0121, "LaunchApp2"_s },
    { 0x0122, "MediaPlayPause"_s },
    { 0x0124, "MediaStop"_s },
    { 0x012c, "Eject"_s },
    { 0x012e, "AudioVolumeDown"_s },
    { 0x0130, "AudioVolumeUp"_s },
    { 0x0132, "BrowserHome"_s },
    { 0x0135, "NumpadDivide"_s },
    { 0x0137, "PrintScreen"_s },
    { 0x0138, "AltRight"_s },
    { 0x013b, "Help"_s },
    { 0x0145, "NumLock"_s },
    { 0x0147, "Home"_s },
    { 0x0148, "ArrowUp"_s },
    { 0x0149, "PageUp"_s },
    { 0x014b, "ArrowLeft"_s },
    { 0x014d, "ArrowRight"_s },
    { 0x014f, "End"_s },
    { 0x0150, "ArrowDown"_s },
    { 0x0151, "PageDown"_s },
    { 0x0152, "Insert"_s },
    { 0x0153, "Delete"_s },
    { 0x015b, "MetaLeft"_s },
    { 0x015c, "MetaRight"_s },
    { 0x015d, "ContextMenu"_s },
    { 0x015e, "Power"_s },
    { 0x015f, "Sleep"_s },
    { 0x0163, "WakeUp"_s },
    { 0x0165, "BrowserSearch"_s },
    { 0x0166, "BrowserFavorites"_s },
    { 0x0167, "BrowserRefresh"_s },
    { 0x0168, "BrowserStop"_s },
    { 0x0169, "BrowserForward"_s },
    { 0x016a, "BrowserBack"_s },
    { 0x016b, "LaunchApp1"_s },
    { 0x016c, "LaunchMail"_s },
    { 0x016d, "MediaSelect"_s },
};

String WindowsKeyNames::domCodeFromLParam(LPARAM lParam)
{
    unsigned extendedScanCode = (lParam >> 16) & 0x1ff;
    const auto* result = std::lower_bound(
        std::begin(cDomCodeMap), std::end(cDomCodeMap), extendedScanCode,
        [](const auto& entry, int needle) {
            return entry.scanCode < needle;
        });
    if (result != std::end(cDomCodeMap) && result->scanCode == extendedScanCode)
        return result->domCode;

    return "Unidentified"_s;
}

void WindowsKeyNames::updateLayout()
{
    HKL currentLayout = GetKeyboardLayout(0);
    if (currentLayout == m_keyboardLayout)
        return;

    BYTE keyboardStateToRestore[256];
    if (!GetKeyboardState(keyboardStateToRestore))
        return;

    m_keyboardLayout = currentLayout;
    m_printableKeyCodeToKey.clear();
    m_hasAltGraph = false;

    // Map size for some sample keyboard layouts:
    // US: 476, French: 602, Persian: 482, Vietnamese: 1436
    m_printableKeyCodeToKey.reserveInitialCapacity(1500);

    KeyModifierSet allCombination { KeyModifier::Shift, KeyModifier::Control, KeyModifier::Alt, KeyModifier::CapsLock };

    for (int combination = 0; combination <= allCombination.toRaw(); ++combination) {
        BYTE keyboardState[256] = { };

        // Setting up keyboard state for modifiers.
        auto modifiers = KeyModifierSet::fromRaw(combination);
        setModifierState(keyboardState, modifiers);

        for (unsigned virtualKey = 0; virtualKey <= 0xFF; ++virtualKey) {
            wchar_t translatedChars[5];
            int rv = ToUnicodeEx(virtualKey, 0, keyboardState, translatedChars,
                WTF_ARRAY_LENGTH(translatedChars), 0, m_keyboardLayout);

            if (rv == -1) {
                // Dead key, injecting VK_SPACE to get character representation.
                BYTE emptyState[256] = { };
                rv = ToUnicodeEx(VK_SPACE, 0, emptyState, translatedChars,
                    WTF_ARRAY_LENGTH(translatedChars), 0, m_keyboardLayout);
                // Expecting a dead key character (not followed by a space).
                if (rv == 1)
                    m_printableKeyCodeToKey.set(std::make_pair(virtualKey, modifiers), "Dead"_s);
            } else if (rv == 1) {
                if (translatedChars[0] >= 0x20) {
                    m_printableKeyCodeToKey.set(std::make_pair(virtualKey, modifiers), String(translatedChars, 1));

                    // Detect whether the layout makes use of AltGraph.
                    if (hasControlAndAlt(modifiers))
                        m_hasAltGraph = true;
                }
            }
        }
    }
    SetKeyboardState(keyboardStateToRestore);
}

} // namespace WebCore