/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/Logging.h" #include "mozilla/ArrayUtils.h" #include "mozilla/AutoRestore.h" #include "mozilla/DebugOnly.h" #include "mozilla/MouseEvents.h" #include "mozilla/MiscEvents.h" #include "mozilla/Preferences.h" #include "mozilla/TextEvents.h" #include "nsAlgorithm.h" #include "nsExceptionHandler.h" #include "nsGkAtoms.h" #include "nsIUserIdleServiceInternal.h" #include "nsIWindowsRegKey.h" #include "nsPrintfCString.h" #include "nsQuickSort.h" #include "nsReadableUtils.h" #include "nsServiceManagerUtils.h" #include "nsToolkit.h" #include "nsUnicharUtils.h" #include "nsWindowDbg.h" #include "KeyboardLayout.h" #include "WidgetUtils.h" #include "WinUtils.h" #include "npapi.h" #include #include #include #ifndef WINABLEAPI # include #endif // In WinUser.h, MAPVK_VK_TO_VSC_EX is defined only when WINVER >= 0x0600 #ifndef MAPVK_VK_TO_VSC_EX # define MAPVK_VK_TO_VSC_EX (4) #endif // For collecting other people's log, tell them `MOZ_LOG=KeyboardHandler:4,sync` // rather than `MOZ_LOG=KeyboardHandler:5,sync` since using `5` may create too // big file. // Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior. mozilla::LazyLogModule gKeyLog("KeyboardHandler"); namespace mozilla { namespace widget { static const char* const kVirtualKeyName[] = { "NULL", "VK_LBUTTON", "VK_RBUTTON", "VK_CANCEL", "VK_MBUTTON", "VK_XBUTTON1", "VK_XBUTTON2", "0x07", "VK_BACK", "VK_TAB", "0x0A", "0x0B", "VK_CLEAR", "VK_RETURN", "0x0E", "0x0F", "VK_SHIFT", "VK_CONTROL", "VK_MENU", "VK_PAUSE", "VK_CAPITAL", "VK_KANA, VK_HANGUL", "0x16", "VK_JUNJA", "VK_FINAL", "VK_HANJA, VK_KANJI", "0x1A", "VK_ESCAPE", "VK_CONVERT", "VK_NONCONVERT", "VK_ACCEPT", "VK_MODECHANGE", "VK_SPACE", "VK_PRIOR", "VK_NEXT", "VK_END", "VK_HOME", "VK_LEFT", "VK_UP", "VK_RIGHT", "VK_DOWN", "VK_SELECT", "VK_PRINT", "VK_EXECUTE", "VK_SNAPSHOT", "VK_INSERT", "VK_DELETE", "VK_HELP", "VK_0", "VK_1", "VK_2", "VK_3", "VK_4", "VK_5", "VK_6", "VK_7", "VK_8", "VK_9", "0x3A", "0x3B", "0x3C", "0x3D", "0x3E", "0x3F", "0x40", "VK_A", "VK_B", "VK_C", "VK_D", "VK_E", "VK_F", "VK_G", "VK_H", "VK_I", "VK_J", "VK_K", "VK_L", "VK_M", "VK_N", "VK_O", "VK_P", "VK_Q", "VK_R", "VK_S", "VK_T", "VK_U", "VK_V", "VK_W", "VK_X", "VK_Y", "VK_Z", "VK_LWIN", "VK_RWIN", "VK_APPS", "0x5E", "VK_SLEEP", "VK_NUMPAD0", "VK_NUMPAD1", "VK_NUMPAD2", "VK_NUMPAD3", "VK_NUMPAD4", "VK_NUMPAD5", "VK_NUMPAD6", "VK_NUMPAD7", "VK_NUMPAD8", "VK_NUMPAD9", "VK_MULTIPLY", "VK_ADD", "VK_SEPARATOR", "VK_SUBTRACT", "VK_DECIMAL", "VK_DIVIDE", "VK_F1", "VK_F2", "VK_F3", "VK_F4", "VK_F5", "VK_F6", "VK_F7", "VK_F8", "VK_F9", "VK_F10", "VK_F11", "VK_F12", "VK_F13", "VK_F14", "VK_F15", "VK_F16", "VK_F17", "VK_F18", "VK_F19", "VK_F20", "VK_F21", "VK_F22", "VK_F23", "VK_F24", "0x88", "0x89", "0x8A", "0x8B", "0x8C", "0x8D", "0x8E", "0x8F", "VK_NUMLOCK", "VK_SCROLL", "VK_OEM_NEC_EQUAL, VK_OEM_FJ_JISHO", "VK_OEM_FJ_MASSHOU", "VK_OEM_FJ_TOUROKU", "VK_OEM_FJ_LOYA", "VK_OEM_FJ_ROYA", "0x97", "0x98", "0x99", "0x9A", "0x9B", "0x9C", "0x9D", "0x9E", "0x9F", "VK_LSHIFT", "VK_RSHIFT", "VK_LCONTROL", "VK_RCONTROL", "VK_LMENU", "VK_RMENU", "VK_BROWSER_BACK", "VK_BROWSER_FORWARD", "VK_BROWSER_REFRESH", "VK_BROWSER_STOP", "VK_BROWSER_SEARCH", "VK_BROWSER_FAVORITES", "VK_BROWSER_HOME", "VK_VOLUME_MUTE", "VK_VOLUME_DOWN", "VK_VOLUME_UP", "VK_MEDIA_NEXT_TRACK", "VK_MEDIA_PREV_TRACK", "VK_MEDIA_STOP", "VK_MEDIA_PLAY_PAUSE", "VK_LAUNCH_MAIL", "VK_LAUNCH_MEDIA_SELECT", "VK_LAUNCH_APP1", "VK_LAUNCH_APP2", "0xB8", "0xB9", "VK_OEM_1", "VK_OEM_PLUS", "VK_OEM_COMMA", "VK_OEM_MINUS", "VK_OEM_PERIOD", "VK_OEM_2", "VK_OEM_3", "VK_ABNT_C1", "VK_ABNT_C2", "0xC3", "0xC4", "0xC5", "0xC6", "0xC7", "0xC8", "0xC9", "0xCA", "0xCB", "0xCC", "0xCD", "0xCE", "0xCF", "0xD0", "0xD1", "0xD2", "0xD3", "0xD4", "0xD5", "0xD6", "0xD7", "0xD8", "0xD9", "0xDA", "VK_OEM_4", "VK_OEM_5", "VK_OEM_6", "VK_OEM_7", "VK_OEM_8", "0xE0", "VK_OEM_AX", "VK_OEM_102", "VK_ICO_HELP", "VK_ICO_00", "VK_PROCESSKEY", "VK_ICO_CLEAR", "VK_PACKET", "0xE8", "VK_OEM_RESET", "VK_OEM_JUMP", "VK_OEM_PA1", "VK_OEM_PA2", "VK_OEM_PA3", "VK_OEM_WSCTRL", "VK_OEM_CUSEL", "VK_OEM_ATTN", "VK_OEM_FINISH", "VK_OEM_COPY", "VK_OEM_AUTO", "VK_OEM_ENLW", "VK_OEM_BACKTAB", "VK_ATTN", "VK_CRSEL", "VK_EXSEL", "VK_EREOF", "VK_PLAY", "VK_ZOOM", "VK_NONAME", "VK_PA1", "VK_OEM_CLEAR", "0xFF"}; static_assert(sizeof(kVirtualKeyName) / sizeof(const char*) == 0x100, "The virtual key name must be defined just 256 keys"); static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; } static const nsCString GetCharacterCodeName(WPARAM aCharCode) { switch (aCharCode) { case 0x0000: return "NULL (0x0000)"_ns; case 0x0008: return "BACKSPACE (0x0008)"_ns; case 0x0009: return "CHARACTER TABULATION (0x0009)"_ns; case 0x000A: return "LINE FEED (0x000A)"_ns; case 0x000B: return "LINE TABULATION (0x000B)"_ns; case 0x000C: return "FORM FEED (0x000C)"_ns; case 0x000D: return "CARRIAGE RETURN (0x000D)"_ns; case 0x0018: return "CANCEL (0x0018)"_ns; case 0x001B: return "ESCAPE (0x001B)"_ns; case 0x0020: return "SPACE (0x0020)"_ns; case 0x007F: return "DELETE (0x007F)"_ns; case 0x00A0: return "NO-BREAK SPACE (0x00A0)"_ns; case 0x00AD: return "SOFT HYPHEN (0x00AD)"_ns; case 0x2000: return "EN QUAD (0x2000)"_ns; case 0x2001: return "EM QUAD (0x2001)"_ns; case 0x2002: return "EN SPACE (0x2002)"_ns; case 0x2003: return "EM SPACE (0x2003)"_ns; case 0x2004: return "THREE-PER-EM SPACE (0x2004)"_ns; case 0x2005: return "FOUR-PER-EM SPACE (0x2005)"_ns; case 0x2006: return "SIX-PER-EM SPACE (0x2006)"_ns; case 0x2007: return "FIGURE SPACE (0x2007)"_ns; case 0x2008: return "PUNCTUATION SPACE (0x2008)"_ns; case 0x2009: return "THIN SPACE (0x2009)"_ns; case 0x200A: return "HAIR SPACE (0x200A)"_ns; case 0x200B: return "ZERO WIDTH SPACE (0x200B)"_ns; case 0x200C: return "ZERO WIDTH NON-JOINER (0x200C)"_ns; case 0x200D: return "ZERO WIDTH JOINER (0x200D)"_ns; case 0x200E: return "LEFT-TO-RIGHT MARK (0x200E)"_ns; case 0x200F: return "RIGHT-TO-LEFT MARK (0x200F)"_ns; case 0x2029: return "PARAGRAPH SEPARATOR (0x2029)"_ns; case 0x202A: return "LEFT-TO-RIGHT EMBEDDING (0x202A)"_ns; case 0x202B: return "RIGHT-TO-LEFT EMBEDDING (0x202B)"_ns; case 0x202D: return "LEFT-TO-RIGHT OVERRIDE (0x202D)"_ns; case 0x202E: return "RIGHT-TO-LEFT OVERRIDE (0x202E)"_ns; case 0x202F: return "NARROW NO-BREAK SPACE (0x202F)"_ns; case 0x205F: return "MEDIUM MATHEMATICAL SPACE (0x205F)"_ns; case 0x2060: return "WORD JOINER (0x2060)"_ns; case 0x2066: return "LEFT-TO-RIGHT ISOLATE (0x2066)"_ns; case 0x2067: return "RIGHT-TO-LEFT ISOLATE (0x2067)"_ns; case 0x3000: return "IDEOGRAPHIC SPACE (0x3000)"_ns; case 0xFEFF: return "ZERO WIDTH NO-BREAK SPACE (0xFEFF)"_ns; default: { if (aCharCode < ' ' || (aCharCode >= 0x80 && aCharCode < 0xA0)) { return nsPrintfCString("control (0x%04zX)", aCharCode); } if (NS_IS_HIGH_SURROGATE(aCharCode)) { return nsPrintfCString("high surrogate (0x%04zX)", aCharCode); } if (NS_IS_LOW_SURROGATE(aCharCode)) { return nsPrintfCString("low surrogate (0x%04zX)", aCharCode); } return IS_IN_BMP(aCharCode) ? nsPrintfCString( "'%s' (0x%04zX)", NS_ConvertUTF16toUTF8(nsAutoString(aCharCode)).get(), aCharCode) : nsPrintfCString( "'%s' (0x%08zX)", NS_ConvertUTF16toUTF8(nsAutoString(aCharCode)).get(), aCharCode); } } } static const nsCString GetKeyLocationName(uint32_t aLocation) { switch (aLocation) { case eKeyLocationLeft: return "KEY_LOCATION_LEFT"_ns; case eKeyLocationRight: return "KEY_LOCATION_RIGHT"_ns; case eKeyLocationStandard: return "KEY_LOCATION_STANDARD"_ns; case eKeyLocationNumpad: return "KEY_LOCATION_NUMPAD"_ns; default: return nsPrintfCString("Unknown (0x%04X)", aLocation); } } static const nsCString GetCharacterCodeNames(const char16_t* aChars, uint32_t aLength) { if (!aLength) { return ""_ns; } nsCString result; result.AssignLiteral("\""); StringJoinAppend(result, ", "_ns, Span{aChars, aLength}, [](nsACString& dest, const char16_t charValue) { dest.Append(GetCharacterCodeName(charValue)); }); result.AppendLiteral("\""); return result; } static const nsCString GetCharacterCodeNames( const UniCharsAndModifiers& aUniCharsAndModifiers) { if (aUniCharsAndModifiers.IsEmpty()) { return ""_ns; } nsCString result; result.AssignLiteral("\""); StringJoinAppend(result, ", "_ns, Span{aUniCharsAndModifiers.ToString()}, [](nsACString& dest, const char16_t charValue) { dest.Append(GetCharacterCodeName(charValue)); }); result.AppendLiteral("\""); return result; } class MOZ_STACK_CLASS GetShiftStateName final : public nsAutoCString { public: explicit GetShiftStateName(VirtualKey::ShiftState aShiftState) { if (!aShiftState) { AssignLiteral("none"); return; } if (aShiftState & VirtualKey::STATE_SHIFT) { AssignLiteral("Shift"); aShiftState &= ~VirtualKey::STATE_SHIFT; } if (aShiftState & VirtualKey::STATE_CONTROL) { MaybeAppendSeparator(); AssignLiteral("Ctrl"); aShiftState &= ~VirtualKey::STATE_CONTROL; } if (aShiftState & VirtualKey::STATE_ALT) { MaybeAppendSeparator(); AssignLiteral("Alt"); aShiftState &= ~VirtualKey::STATE_ALT; } if (aShiftState & VirtualKey::STATE_CAPSLOCK) { MaybeAppendSeparator(); AssignLiteral("CapsLock"); aShiftState &= ~VirtualKey::STATE_CAPSLOCK; } MOZ_ASSERT(!aShiftState); } private: void MaybeAppendSeparator() { if (!IsEmpty()) { AppendLiteral(" | "); } } }; static const nsCString GetMessageName(UINT aMessage) { switch (aMessage) { case WM_NULL: return "WM_NULL"_ns; case WM_KEYDOWN: return "WM_KEYDOWN"_ns; case WM_KEYUP: return "WM_KEYUP"_ns; case WM_SYSKEYDOWN: return "WM_SYSKEYDOWN"_ns; case WM_SYSKEYUP: return "WM_SYSKEYUP"_ns; case WM_CHAR: return "WM_CHAR"_ns; case WM_UNICHAR: return "WM_UNICHAR"_ns; case WM_SYSCHAR: return "WM_SYSCHAR"_ns; case WM_DEADCHAR: return "WM_DEADCHAR"_ns; case WM_SYSDEADCHAR: return "WM_SYSDEADCHAR"_ns; case WM_APPCOMMAND: return "WM_APPCOMMAND"_ns; case WM_QUIT: return "WM_QUIT"_ns; default: return nsPrintfCString("Unknown Message (0x%04X)", aMessage); } } static const nsCString GetVirtualKeyCodeName(WPARAM aVK) { if (aVK >= ArrayLength(kVirtualKeyName)) { return nsPrintfCString("Invalid (0x%08zX)", aVK); } return nsCString(kVirtualKeyName[aVK]); } static const nsCString GetAppCommandName(WPARAM aCommand) { switch (aCommand) { case APPCOMMAND_BASS_BOOST: return "APPCOMMAND_BASS_BOOST"_ns; case APPCOMMAND_BASS_DOWN: return "APPCOMMAND_BASS_DOWN"_ns; case APPCOMMAND_BASS_UP: return "APPCOMMAND_BASS_UP"_ns; case APPCOMMAND_BROWSER_BACKWARD: return "APPCOMMAND_BROWSER_BACKWARD"_ns; case APPCOMMAND_BROWSER_FAVORITES: return "APPCOMMAND_BROWSER_FAVORITES"_ns; case APPCOMMAND_BROWSER_FORWARD: return "APPCOMMAND_BROWSER_FORWARD"_ns; case APPCOMMAND_BROWSER_HOME: return "APPCOMMAND_BROWSER_HOME"_ns; case APPCOMMAND_BROWSER_REFRESH: return "APPCOMMAND_BROWSER_REFRESH"_ns; case APPCOMMAND_BROWSER_SEARCH: return "APPCOMMAND_BROWSER_SEARCH"_ns; case APPCOMMAND_BROWSER_STOP: return "APPCOMMAND_BROWSER_STOP"_ns; case APPCOMMAND_CLOSE: return "APPCOMMAND_CLOSE"_ns; case APPCOMMAND_COPY: return "APPCOMMAND_COPY"_ns; case APPCOMMAND_CORRECTION_LIST: return "APPCOMMAND_CORRECTION_LIST"_ns; case APPCOMMAND_CUT: return "APPCOMMAND_CUT"_ns; case APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE: return "APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE"_ns; case APPCOMMAND_FIND: return "APPCOMMAND_FIND"_ns; case APPCOMMAND_FORWARD_MAIL: return "APPCOMMAND_FORWARD_MAIL"_ns; case APPCOMMAND_HELP: return "APPCOMMAND_HELP"_ns; case APPCOMMAND_LAUNCH_APP1: return "APPCOMMAND_LAUNCH_APP1"_ns; case APPCOMMAND_LAUNCH_APP2: return "APPCOMMAND_LAUNCH_APP2"_ns; case APPCOMMAND_LAUNCH_MAIL: return "APPCOMMAND_LAUNCH_MAIL"_ns; case APPCOMMAND_LAUNCH_MEDIA_SELECT: return "APPCOMMAND_LAUNCH_MEDIA_SELECT"_ns; case APPCOMMAND_MEDIA_CHANNEL_DOWN: return "APPCOMMAND_MEDIA_CHANNEL_DOWN"_ns; case APPCOMMAND_MEDIA_CHANNEL_UP: return "APPCOMMAND_MEDIA_CHANNEL_UP"_ns; case APPCOMMAND_MEDIA_FAST_FORWARD: return "APPCOMMAND_MEDIA_FAST_FORWARD"_ns; case APPCOMMAND_MEDIA_NEXTTRACK: return "APPCOMMAND_MEDIA_NEXTTRACK"_ns; case APPCOMMAND_MEDIA_PAUSE: return "APPCOMMAND_MEDIA_PAUSE"_ns; case APPCOMMAND_MEDIA_PLAY: return "APPCOMMAND_MEDIA_PLAY"_ns; case APPCOMMAND_MEDIA_PLAY_PAUSE: return "APPCOMMAND_MEDIA_PLAY_PAUSE"_ns; case APPCOMMAND_MEDIA_PREVIOUSTRACK: return "APPCOMMAND_MEDIA_PREVIOUSTRACK"_ns; case APPCOMMAND_MEDIA_RECORD: return "APPCOMMAND_MEDIA_RECORD"_ns; case APPCOMMAND_MEDIA_REWIND: return "APPCOMMAND_MEDIA_REWIND"_ns; case APPCOMMAND_MEDIA_STOP: return "APPCOMMAND_MEDIA_STOP"_ns; case APPCOMMAND_MIC_ON_OFF_TOGGLE: return "APPCOMMAND_MIC_ON_OFF_TOGGLE"_ns; case APPCOMMAND_MICROPHONE_VOLUME_DOWN: return "APPCOMMAND_MICROPHONE_VOLUME_DOWN"_ns; case APPCOMMAND_MICROPHONE_VOLUME_MUTE: return "APPCOMMAND_MICROPHONE_VOLUME_MUTE"_ns; case APPCOMMAND_MICROPHONE_VOLUME_UP: return "APPCOMMAND_MICROPHONE_VOLUME_UP"_ns; case APPCOMMAND_NEW: return "APPCOMMAND_NEW"_ns; case APPCOMMAND_OPEN: return "APPCOMMAND_OPEN"_ns; case APPCOMMAND_PASTE: return "APPCOMMAND_PASTE"_ns; case APPCOMMAND_PRINT: return "APPCOMMAND_PRINT"_ns; case APPCOMMAND_REDO: return "APPCOMMAND_REDO"_ns; case APPCOMMAND_REPLY_TO_MAIL: return "APPCOMMAND_REPLY_TO_MAIL"_ns; case APPCOMMAND_SAVE: return "APPCOMMAND_SAVE"_ns; case APPCOMMAND_SEND_MAIL: return "APPCOMMAND_SEND_MAIL"_ns; case APPCOMMAND_SPELL_CHECK: return "APPCOMMAND_SPELL_CHECK"_ns; case APPCOMMAND_TREBLE_DOWN: return "APPCOMMAND_TREBLE_DOWN"_ns; case APPCOMMAND_TREBLE_UP: return "APPCOMMAND_TREBLE_UP"_ns; case APPCOMMAND_UNDO: return "APPCOMMAND_UNDO"_ns; case APPCOMMAND_VOLUME_DOWN: return "APPCOMMAND_VOLUME_DOWN"_ns; case APPCOMMAND_VOLUME_MUTE: return "APPCOMMAND_VOLUME_MUTE"_ns; case APPCOMMAND_VOLUME_UP: return "APPCOMMAND_VOLUME_UP"_ns; default: return nsPrintfCString("Unknown app command (0x%08zX)", aCommand); } } static const nsCString GetAppCommandDeviceName(LPARAM aDevice) { switch (aDevice) { case FAPPCOMMAND_KEY: return "FAPPCOMMAND_KEY"_ns; case FAPPCOMMAND_MOUSE: return "FAPPCOMMAND_MOUSE"_ns; case FAPPCOMMAND_OEM: return "FAPPCOMMAND_OEM"_ns; default: return nsPrintfCString("Unknown app command device (0x%04" PRIXLPTR ")", aDevice); } }; class MOZ_STACK_CLASS GetAppCommandKeysName final : public nsAutoCString { public: explicit GetAppCommandKeysName(WPARAM aKeys) { if (aKeys & MK_CONTROL) { AppendLiteral("MK_CONTROL"); aKeys &= ~MK_CONTROL; } if (aKeys & MK_LBUTTON) { MaybeAppendSeparator(); AppendLiteral("MK_LBUTTON"); aKeys &= ~MK_LBUTTON; } if (aKeys & MK_MBUTTON) { MaybeAppendSeparator(); AppendLiteral("MK_MBUTTON"); aKeys &= ~MK_MBUTTON; } if (aKeys & MK_RBUTTON) { MaybeAppendSeparator(); AppendLiteral("MK_RBUTTON"); aKeys &= ~MK_RBUTTON; } if (aKeys & MK_SHIFT) { MaybeAppendSeparator(); AppendLiteral("MK_SHIFT"); aKeys &= ~MK_SHIFT; } if (aKeys & MK_XBUTTON1) { MaybeAppendSeparator(); AppendLiteral("MK_XBUTTON1"); aKeys &= ~MK_XBUTTON1; } if (aKeys & MK_XBUTTON2) { MaybeAppendSeparator(); AppendLiteral("MK_XBUTTON2"); aKeys &= ~MK_XBUTTON2; } if (aKeys) { MaybeAppendSeparator(); AppendPrintf("Unknown Flags (0x%04zX)", aKeys); } if (IsEmpty()) { AssignLiteral("none (0x0000)"); } } private: void MaybeAppendSeparator() { if (!IsEmpty()) { AppendLiteral(" | "); } } }; static const nsCString ToString(const MSG& aMSG) { nsCString result; result.AssignLiteral("{ message="); result.Append(GetMessageName(aMSG.message).get()); result.AppendLiteral(", "); switch (aMSG.message) { case WM_KEYDOWN: case WM_KEYUP: case WM_SYSKEYDOWN: case WM_SYSKEYUP: result.AppendPrintf( "virtual keycode=%s, repeat count=%" PRIdLPTR ", " "scancode=0x%02X, extended key=%s, " "context code=%s, previous key state=%s, " "transition state=%s", GetVirtualKeyCodeName(aMSG.wParam).get(), aMSG.lParam & 0xFFFF, WinUtils::GetScanCode(aMSG.lParam), GetBoolName(WinUtils::IsExtendedScanCode(aMSG.lParam)), GetBoolName((aMSG.lParam & (1 << 29)) != 0), GetBoolName((aMSG.lParam & (1 << 30)) != 0), GetBoolName((aMSG.lParam & (1 << 31)) != 0)); break; case WM_CHAR: case WM_DEADCHAR: case WM_SYSCHAR: case WM_SYSDEADCHAR: result.AppendPrintf( "character code=%s, repeat count=%" PRIdLPTR ", " "scancode=0x%02X, extended key=%s, " "context code=%s, previous key state=%s, " "transition state=%s", GetCharacterCodeName(aMSG.wParam).get(), aMSG.lParam & 0xFFFF, WinUtils::GetScanCode(aMSG.lParam), GetBoolName(WinUtils::IsExtendedScanCode(aMSG.lParam)), GetBoolName((aMSG.lParam & (1 << 29)) != 0), GetBoolName((aMSG.lParam & (1 << 30)) != 0), GetBoolName((aMSG.lParam & (1 << 31)) != 0)); break; case WM_APPCOMMAND: result.AppendPrintf( "window handle=0x%zx, app command=%s, device=%s, dwKeys=%s", aMSG.wParam, GetAppCommandName(GET_APPCOMMAND_LPARAM(aMSG.lParam)).get(), GetAppCommandDeviceName(GET_DEVICE_LPARAM(aMSG.lParam)).get(), GetAppCommandKeysName(GET_KEYSTATE_LPARAM(aMSG.lParam)).get()); break; default: result.AppendPrintf("wParam=%zu, lParam=%" PRIdLPTR, aMSG.wParam, aMSG.lParam); break; } result.AppendPrintf(", hwnd=0x%p", aMSG.hwnd); return result; } static const nsCString ToString( const UniCharsAndModifiers& aUniCharsAndModifiers) { if (aUniCharsAndModifiers.IsEmpty()) { return "{}"_ns; } nsCString result; result.AssignLiteral("{ "); result.Append(GetCharacterCodeName(aUniCharsAndModifiers.CharAt(0))); for (size_t i = 1; i < aUniCharsAndModifiers.Length(); ++i) { if (aUniCharsAndModifiers.ModifiersAt(i - 1) != aUniCharsAndModifiers.ModifiersAt(i)) { result.AppendLiteral(" ["); result.Append(GetModifiersName(aUniCharsAndModifiers.ModifiersAt(0))); result.AppendLiteral("]"); } result.AppendLiteral(", "); result.Append(GetCharacterCodeName(aUniCharsAndModifiers.CharAt(i))); } result.AppendLiteral(" ["); uint32_t lastIndex = aUniCharsAndModifiers.Length() - 1; result.Append(GetModifiersName(aUniCharsAndModifiers.ModifiersAt(lastIndex))); result.AppendLiteral("] }"); return result; } const nsCString ToString(const ModifierKeyState& aModifierKeyState) { nsCString result; result.AssignLiteral("{ "); result.Append(GetModifiersName(aModifierKeyState.GetModifiers()).get()); result.AppendLiteral(" }"); return result; } // Unique id counter associated with a keydown / keypress events. Used in // identifing keypress events for removal from async event dispatch queue // in metrofx after preventDefault is called on keydown events. static uint32_t sUniqueKeyEventId = 0; /***************************************************************************** * mozilla::widget::ModifierKeyState *****************************************************************************/ ModifierKeyState::ModifierKeyState() { Update(); } ModifierKeyState::ModifierKeyState(Modifiers aModifiers) : mModifiers(aModifiers) { MOZ_ASSERT(!(mModifiers & MODIFIER_ALTGRAPH) || (!IsControl() && !IsAlt()), "Neither MODIFIER_CONTROL nor MODIFIER_ALT should be set " "if MODIFIER_ALTGRAPH is set"); } void ModifierKeyState::Update() { mModifiers = 0; if (IS_VK_DOWN(VK_SHIFT)) { mModifiers |= MODIFIER_SHIFT; } // If AltGr key (i.e., VK_RMENU on some keyboard layout) is pressed, only // MODIFIER_ALTGRAPH should be set. Otherwise, i.e., if both Ctrl and Alt // keys are pressed to emulate AltGr key, MODIFIER_CONTROL and MODIFIER_ALT // keys should be set separately. if (KeyboardLayout::GetInstance()->HasAltGr() && IS_VK_DOWN(VK_RMENU)) { mModifiers |= MODIFIER_ALTGRAPH; } else { if (IS_VK_DOWN(VK_CONTROL)) { mModifiers |= MODIFIER_CONTROL; } if (IS_VK_DOWN(VK_MENU)) { mModifiers |= MODIFIER_ALT; } } if (IS_VK_DOWN(VK_LWIN) || IS_VK_DOWN(VK_RWIN)) { mModifiers |= MODIFIER_OS; } if (::GetKeyState(VK_CAPITAL) & 1) { mModifiers |= MODIFIER_CAPSLOCK; } if (::GetKeyState(VK_NUMLOCK) & 1) { mModifiers |= MODIFIER_NUMLOCK; } if (::GetKeyState(VK_SCROLL) & 1) { mModifiers |= MODIFIER_SCROLLLOCK; } } void ModifierKeyState::Unset(Modifiers aRemovingModifiers) { mModifiers &= ~aRemovingModifiers; } void ModifierKeyState::Set(Modifiers aAddingModifiers) { mModifiers |= aAddingModifiers; MOZ_ASSERT(!(mModifiers & MODIFIER_ALTGRAPH) || (!IsControl() && !IsAlt()), "Neither MODIFIER_CONTROL nor MODIFIER_ALT should be set " "if MODIFIER_ALTGRAPH is set"); } void ModifierKeyState::InitInputEvent(WidgetInputEvent& aInputEvent) const { aInputEvent.mModifiers = mModifiers; switch (aInputEvent.mClass) { case eMouseEventClass: case eMouseScrollEventClass: case eWheelEventClass: case eDragEventClass: case eSimpleGestureEventClass: InitMouseEvent(aInputEvent); break; default: break; } } void ModifierKeyState::InitMouseEvent(WidgetInputEvent& aMouseEvent) const { NS_ASSERTION(aMouseEvent.mClass == eMouseEventClass || aMouseEvent.mClass == eWheelEventClass || aMouseEvent.mClass == eDragEventClass || aMouseEvent.mClass == eSimpleGestureEventClass, "called with non-mouse event"); WidgetMouseEventBase& mouseEvent = *aMouseEvent.AsMouseEventBase(); mouseEvent.mButtons = 0; if (::GetKeyState(VK_LBUTTON) < 0) { mouseEvent.mButtons |= MouseButtonsFlag::ePrimaryFlag; } if (::GetKeyState(VK_RBUTTON) < 0) { mouseEvent.mButtons |= MouseButtonsFlag::eSecondaryFlag; } if (::GetKeyState(VK_MBUTTON) < 0) { mouseEvent.mButtons |= MouseButtonsFlag::eMiddleFlag; } if (::GetKeyState(VK_XBUTTON1) < 0) { mouseEvent.mButtons |= MouseButtonsFlag::e4thFlag; } if (::GetKeyState(VK_XBUTTON2) < 0) { mouseEvent.mButtons |= MouseButtonsFlag::e5thFlag; } } bool ModifierKeyState::IsShift() const { return (mModifiers & MODIFIER_SHIFT) != 0; } bool ModifierKeyState::IsControl() const { return (mModifiers & MODIFIER_CONTROL) != 0; } bool ModifierKeyState::IsAlt() const { return (mModifiers & MODIFIER_ALT) != 0; } bool ModifierKeyState::IsWin() const { return (mModifiers & MODIFIER_OS) != 0; } bool ModifierKeyState::MaybeMatchShortcutKey() const { // If Windows key is pressed, even if both Ctrl key and Alt key are pressed, // it's possible to match a shortcut key. if (IsWin()) { return true; } // Otherwise, when both Ctrl key and Alt key are pressed, it shouldn't be // a shortcut key for Windows since it means pressing AltGr key on // some keyboard layouts. if (IsControl() ^ IsAlt()) { return true; } // If no modifier key is active except a lockable modifier nor Shift key, // the key shouldn't match any shortcut keys (there are Space and // Shift+Space, though, let's ignore these special case...). return false; } bool ModifierKeyState::IsCapsLocked() const { return (mModifiers & MODIFIER_CAPSLOCK) != 0; } bool ModifierKeyState::IsNumLocked() const { return (mModifiers & MODIFIER_NUMLOCK) != 0; } bool ModifierKeyState::IsScrollLocked() const { return (mModifiers & MODIFIER_SCROLLLOCK) != 0; } /***************************************************************************** * mozilla::widget::UniCharsAndModifiers *****************************************************************************/ void UniCharsAndModifiers::Append(char16_t aUniChar, Modifiers aModifiers) { mChars.Append(aUniChar); mModifiers.AppendElement(aModifiers); } void UniCharsAndModifiers::FillModifiers(Modifiers aModifiers) { for (size_t i = 0; i < Length(); i++) { mModifiers[i] = aModifiers; } } void UniCharsAndModifiers::OverwriteModifiersIfBeginsWith( const UniCharsAndModifiers& aOther) { if (!BeginsWith(aOther)) { return; } for (size_t i = 0; i < aOther.Length(); ++i) { mModifiers[i] = aOther.mModifiers[i]; } } bool UniCharsAndModifiers::UniCharsEqual( const UniCharsAndModifiers& aOther) const { return mChars.Equals(aOther.mChars); } bool UniCharsAndModifiers::UniCharsCaseInsensitiveEqual( const UniCharsAndModifiers& aOther) const { return mChars.Equals(aOther.mChars, nsCaseInsensitiveStringComparator); } bool UniCharsAndModifiers::BeginsWith( const UniCharsAndModifiers& aOther) const { return StringBeginsWith(mChars, aOther.mChars); } UniCharsAndModifiers& UniCharsAndModifiers::operator+=( const UniCharsAndModifiers& aOther) { mChars.Append(aOther.mChars); mModifiers.AppendElements(aOther.mModifiers); return *this; } UniCharsAndModifiers UniCharsAndModifiers::operator+( const UniCharsAndModifiers& aOther) const { UniCharsAndModifiers result(*this); result += aOther; return result; } /***************************************************************************** * mozilla::widget::VirtualKey *****************************************************************************/ // static VirtualKey::ShiftState VirtualKey::ModifiersToShiftState(Modifiers aModifiers) { ShiftState state = 0; if (aModifiers & MODIFIER_SHIFT) { state |= STATE_SHIFT; } if (aModifiers & MODIFIER_ALTGRAPH) { state |= STATE_ALTGRAPH; } else { if (aModifiers & MODIFIER_CONTROL) { state |= STATE_CONTROL; } if (aModifiers & MODIFIER_ALT) { state |= STATE_ALT; } } if (aModifiers & MODIFIER_CAPSLOCK) { state |= STATE_CAPSLOCK; } return state; } // static Modifiers VirtualKey::ShiftStateToModifiers(ShiftState aShiftState) { Modifiers modifiers = 0; if (aShiftState & STATE_SHIFT) { modifiers |= MODIFIER_SHIFT; } if (aShiftState & STATE_ALTGRAPH) { modifiers |= MODIFIER_ALTGRAPH; } else { if (aShiftState & STATE_CONTROL) { modifiers |= MODIFIER_CONTROL; } if (aShiftState & STATE_ALT) { modifiers |= MODIFIER_ALT; } } if (aShiftState & STATE_CAPSLOCK) { modifiers |= MODIFIER_CAPSLOCK; } return modifiers; } const DeadKeyTable* VirtualKey::MatchingDeadKeyTable( const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) const { if (!mIsDeadKey) { return nullptr; } for (ShiftState shiftState = 0; shiftState < 16; shiftState++) { if (!IsDeadKey(shiftState)) { continue; } const DeadKeyTable* dkt = mShiftStates[shiftState].DeadKey.Table; if (dkt && dkt->IsEqual(aDeadKeyArray, aEntries)) { return dkt; } } return nullptr; } void VirtualKey::SetNormalChars(ShiftState aShiftState, const char16_t* aChars, uint32_t aNumOfChars) { MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState)); SetDeadKey(aShiftState, false); for (uint32_t index = 0; index < aNumOfChars; index++) { // Ignore legacy non-printable control characters mShiftStates[aShiftState].Normal.Chars[index] = (aChars[index] >= 0x20) ? aChars[index] : 0; } uint32_t len = ArrayLength(mShiftStates[aShiftState].Normal.Chars); for (uint32_t index = aNumOfChars; index < len; index++) { mShiftStates[aShiftState].Normal.Chars[index] = 0; } } void VirtualKey::SetDeadChar(ShiftState aShiftState, char16_t aDeadChar) { MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState)); SetDeadKey(aShiftState, true); mShiftStates[aShiftState].DeadKey.DeadChar = aDeadChar; mShiftStates[aShiftState].DeadKey.Table = nullptr; } UniCharsAndModifiers VirtualKey::GetUniChars(ShiftState aShiftState) const { UniCharsAndModifiers result = GetNativeUniChars(aShiftState); const uint8_t kShiftStateIndex = ToShiftStateIndex(aShiftState); if (!(kShiftStateIndex & STATE_CONTROL_ALT)) { // If neither Alt nor Ctrl key is pressed, just return stored data // for the key. return result; } if (result.IsEmpty()) { // If Alt and/or Control are pressed and the key produces no // character, return characters which is produced by the key without // Alt and Control, and return given modifiers as is. result = GetNativeUniChars(kShiftStateIndex & ~STATE_CONTROL_ALT); result.FillModifiers(ShiftStateToModifiers(aShiftState)); return result; } if (IsAltGrIndex(kShiftStateIndex)) { // If AltGr or both Ctrl and Alt are pressed and the key produces // character(s), we need to clear MODIFIER_ALT and MODIFIER_CONTROL // since TextEditor won't handle eKeyPress event whose mModifiers // has MODIFIER_ALT or MODIFIER_CONTROL. Additionally, we need to // use MODIFIER_ALTGRAPH when a key produces character(s) with // AltGr or both Ctrl and Alt on Windows. See following spec issue: // Modifiers finalModifiers = ShiftStateToModifiers(aShiftState); finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL); finalModifiers |= MODIFIER_ALTGRAPH; result.FillModifiers(finalModifiers); return result; } // Otherwise, i.e., Alt or Ctrl is pressed and it produces character(s), // check if different character(s) is produced by the key without Alt/Ctrl. // If it produces different character, we need to consume the Alt and // Ctrl modifier for TextEditor. Otherwise, the key does not produces the // character actually. So, keep setting Alt and Ctrl modifiers. UniCharsAndModifiers unmodifiedReslt = GetNativeUniChars(kShiftStateIndex & ~STATE_CONTROL_ALT); if (!result.UniCharsEqual(unmodifiedReslt)) { Modifiers finalModifiers = ShiftStateToModifiers(aShiftState); finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL); result.FillModifiers(finalModifiers); } return result; } UniCharsAndModifiers VirtualKey::GetNativeUniChars( ShiftState aShiftState) const { const uint8_t kShiftStateIndex = ToShiftStateIndex(aShiftState); UniCharsAndModifiers result; Modifiers modifiers = ShiftStateToModifiers(aShiftState); if (IsDeadKey(aShiftState)) { result.Append(mShiftStates[kShiftStateIndex].DeadKey.DeadChar, modifiers); return result; } uint32_t len = ArrayLength(mShiftStates[kShiftStateIndex].Normal.Chars); for (uint32_t i = 0; i < len && mShiftStates[kShiftStateIndex].Normal.Chars[i]; i++) { result.Append(mShiftStates[kShiftStateIndex].Normal.Chars[i], modifiers); } return result; } // static void VirtualKey::FillKbdState(PBYTE aKbdState, const ShiftState aShiftState) { if (aShiftState & STATE_SHIFT) { aKbdState[VK_SHIFT] |= 0x80; } else { aKbdState[VK_SHIFT] &= ~0x80; aKbdState[VK_LSHIFT] &= ~0x80; aKbdState[VK_RSHIFT] &= ~0x80; } if (aShiftState & STATE_ALTGRAPH) { aKbdState[VK_CONTROL] |= 0x80; aKbdState[VK_LCONTROL] |= 0x80; aKbdState[VK_RCONTROL] &= ~0x80; aKbdState[VK_MENU] |= 0x80; aKbdState[VK_LMENU] &= ~0x80; aKbdState[VK_RMENU] |= 0x80; } else { if (aShiftState & STATE_CONTROL) { aKbdState[VK_CONTROL] |= 0x80; } else { aKbdState[VK_CONTROL] &= ~0x80; aKbdState[VK_LCONTROL] &= ~0x80; aKbdState[VK_RCONTROL] &= ~0x80; } if (aShiftState & STATE_ALT) { aKbdState[VK_MENU] |= 0x80; } else { aKbdState[VK_MENU] &= ~0x80; aKbdState[VK_LMENU] &= ~0x80; aKbdState[VK_RMENU] &= ~0x80; } } if (aShiftState & STATE_CAPSLOCK) { aKbdState[VK_CAPITAL] |= 0x01; } else { aKbdState[VK_CAPITAL] &= ~0x01; } } /***************************************************************************** * mozilla::widget::NativeKey *****************************************************************************/ uint8_t NativeKey::sDispatchedKeyOfAppCommand = 0; NativeKey* NativeKey::sLatestInstance = nullptr; const MSG NativeKey::sEmptyMSG = {}; MSG NativeKey::sLastKeyOrCharMSG = {}; MSG NativeKey::sLastKeyMSG = {}; NativeKey::NativeKey(nsWindow* aWidget, const MSG& aMessage, const ModifierKeyState& aModKeyState, HKL aOverrideKeyboardLayout, nsTArray* aFakeCharMsgs) : mLastInstance(sLatestInstance), mRemovingMsg(sEmptyMSG), mReceivedMsg(sEmptyMSG), mWidget(aWidget), mDispatcher(aWidget->GetTextEventDispatcher()), mMsg(aMessage), mFocusedWndBeforeDispatch(::GetFocus()), mDOMKeyCode(0), mKeyNameIndex(KEY_NAME_INDEX_Unidentified), mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN), mModKeyState(aModKeyState), mVirtualKeyCode(0), mOriginalVirtualKeyCode(0), mShiftedLatinChar(0), mUnshiftedLatinChar(0), mScanCode(0), mIsExtended(false), mIsRepeat(false), mIsDeadKey(false), mIsPrintableKey(false), mIsSkippableInRemoteProcess(false), mCharMessageHasGone(false), mCanIgnoreModifierStateAtKeyPress(true), mFakeCharMsgs(aFakeCharMsgs && aFakeCharMsgs->Length() ? aFakeCharMsgs : nullptr) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::NativeKey(aWidget=0x%p { GetWindowHandle()=0x%p }, " "aMessage=%s, aModKeyState=%s), sLatestInstance=0x%p", this, aWidget, aWidget->GetWindowHandle(), ToString(aMessage).get(), ToString(aModKeyState).get(), sLatestInstance)); MOZ_ASSERT(aWidget); MOZ_ASSERT(mDispatcher); sLatestInstance = this; KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance(); mKeyboardLayout = keyboardLayout->GetLayout(); if (aOverrideKeyboardLayout && mKeyboardLayout != aOverrideKeyboardLayout) { keyboardLayout->OverrideLayout(aOverrideKeyboardLayout); mKeyboardLayout = keyboardLayout->GetLayout(); MOZ_ASSERT(mKeyboardLayout == aOverrideKeyboardLayout); mIsOverridingKeyboardLayout = true; } else { mIsOverridingKeyboardLayout = false; sLastKeyOrCharMSG = aMessage; } if (mMsg.message == WM_APPCOMMAND) { InitWithAppCommand(); } else { InitWithKeyOrChar(); } MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::NativeKey(), mKeyboardLayout=0x%p, " "mFocusedWndBeforeDispatch=0x%p, mDOMKeyCode=%s, " "mKeyNameIndex=%s, mCodeNameIndex=%s, mModKeyState=%s, " "mVirtualKeyCode=%s, mOriginalVirtualKeyCode=%s, " "mCommittedCharsAndModifiers=%s, mInputtingStringAndModifiers=%s, " "mShiftedString=%s, mUnshiftedString=%s, mShiftedLatinChar=%s, " "mUnshiftedLatinChar=%s, mScanCode=0x%04X, mIsExtended=%s, " "mIsRepeat=%s, mIsDeadKey=%s, mIsPrintableKey=%s, " "mIsSkippableInRemoteProcess=%s, mCharMessageHasGone=%s, " "mIsOverridingKeyboardLayout=%s", this, mKeyboardLayout, mFocusedWndBeforeDispatch, GetDOMKeyCodeName(mDOMKeyCode).get(), ToString(mKeyNameIndex).get(), ToString(mCodeNameIndex).get(), ToString(mModKeyState).get(), GetVirtualKeyCodeName(mVirtualKeyCode).get(), GetVirtualKeyCodeName(mOriginalVirtualKeyCode).get(), ToString(mCommittedCharsAndModifiers).get(), ToString(mInputtingStringAndModifiers).get(), ToString(mShiftedString).get(), ToString(mUnshiftedString).get(), GetCharacterCodeName(mShiftedLatinChar).get(), GetCharacterCodeName(mUnshiftedLatinChar).get(), mScanCode, GetBoolName(mIsExtended), GetBoolName(mIsRepeat), GetBoolName(mIsDeadKey), GetBoolName(mIsPrintableKey), GetBoolName(mIsSkippableInRemoteProcess), GetBoolName(mCharMessageHasGone), GetBoolName(mIsOverridingKeyboardLayout))); } void NativeKey::InitIsSkippableForKeyOrChar(const MSG& aLastKeyMSG) { mIsSkippableInRemoteProcess = false; if (!mIsRepeat) { // If the message is not repeated key message, the event should be always // handled in remote process even if it's too old. return; } // Keyboard utilities may send us some generated messages and such messages // may be marked as "repeated", e.g., SendInput() calls with // KEYEVENTF_UNICODE but without KEYEVENTF_KEYUP. However, key sequence // comes from such utilities may be really important. For example, utilities // may send WM_KEYDOWN for VK_BACK to remove previous character and send // WM_KEYDOWN for VK_PACKET to insert a composite character. Therefore, we // should check if current message and previous key message are caused by // same physical key. If not, the message may be generated by such // utility. // XXX With this approach, if VK_BACK messages are generated with known // scancode, we cannot distinguish whether coming VK_BACK message is // actually repeated by the auto-repeat feature. Currently, we need // this hack only for "SinhalaTamil IME" and fortunately, it generates // VK_BACK messages with odd scancode. So, we don't need to handle // VK_BACK specially at least for now. if (mCodeNameIndex == CODE_NAME_INDEX_UNKNOWN) { // If current event is not caused by physical key operation, it may be // caused by a keyboard utility. If so, the event shouldn't be ignored by // BrowserChild since it want to insert the character, delete a character or // move caret. return; } if (mOriginalVirtualKeyCode == VK_PACKET) { // If the message is VK_PACKET, that means that a keyboard utility // tries to insert a character. return; } switch (mMsg.message) { case WM_KEYDOWN: case WM_SYSKEYDOWN: case WM_CHAR: case WM_SYSCHAR: case WM_DEADCHAR: case WM_SYSDEADCHAR: // However, some keyboard layouts may send some keyboard messages with // activating the bit. If we dispatch repeated keyboard events, they // may be ignored by BrowserChild due to performance reason. So, we need // to check if actually a physical key is repeated by the auto-repeat // feature. switch (aLastKeyMSG.message) { case WM_KEYDOWN: case WM_SYSKEYDOWN: if (aLastKeyMSG.wParam == VK_PACKET) { // If the last message was VK_PACKET, that means that a keyboard // utility tried to insert a character. So, current message is // not repeated key event of the previous event. return; } // Let's check whether current message and previous message are // caused by same physical key. mIsSkippableInRemoteProcess = mScanCode == WinUtils::GetScanCode(aLastKeyMSG.lParam) && mIsExtended == WinUtils::IsExtendedScanCode(aLastKeyMSG.lParam); return; default: // If previous message is not a keydown, this must not be generated // by the auto-repeat feature. return; } return; case WM_APPCOMMAND: MOZ_ASSERT_UNREACHABLE( "WM_APPCOMMAND should be handled in " "InitWithAppCommand()"); return; default: // keyup message shouldn't be repeated by the auto-repeat feature. return; } } void NativeKey::InitWithKeyOrChar() { MSG lastKeyMSG = sLastKeyMSG; mScanCode = WinUtils::GetScanCode(mMsg.lParam); mIsExtended = WinUtils::IsExtendedScanCode(mMsg.lParam); switch (mMsg.message) { case WM_KEYDOWN: case WM_SYSKEYDOWN: case WM_KEYUP: case WM_SYSKEYUP: { // Modify sLastKeyMSG now since retrieving following char messages may // cause sending another key message if odd tool hooks GetMessage(), // PeekMessage(). sLastKeyMSG = mMsg; // Note that we don't need to compute raw virtual keycode here even when // it's VK_PROCESS (i.e., already handled by IME) because we need to // export it as DOM_VK_PROCESS and KEY_NAME_INDEX_Process. mOriginalVirtualKeyCode = static_cast(mMsg.wParam); // If the key message is sent from other application like a11y tools, the // scancode value might not be set proper value. Then, probably the value // is 0. // NOTE: If the virtual keycode can be caused by both non-extended key // and extended key, the API returns the non-extended key's // scancode. E.g., VK_LEFT causes "4" key on numpad. if (!mScanCode && mOriginalVirtualKeyCode != VK_PACKET) { uint16_t scanCodeEx = ComputeScanCodeExFromVirtualKeyCode(mMsg.wParam); if (scanCodeEx) { mScanCode = static_cast(scanCodeEx & 0xFF); uint8_t extended = static_cast((scanCodeEx & 0xFF00) >> 8); mIsExtended = (extended == 0xE0) || (extended == 0xE1); } } // Most keys are not distinguished as left or right keys. bool isLeftRightDistinguishedKey = false; // mOriginalVirtualKeyCode must not distinguish left or right of // Shift, Control or Alt. switch (mOriginalVirtualKeyCode) { case VK_SHIFT: case VK_CONTROL: case VK_MENU: isLeftRightDistinguishedKey = true; break; case VK_LSHIFT: case VK_RSHIFT: mVirtualKeyCode = mOriginalVirtualKeyCode; mOriginalVirtualKeyCode = VK_SHIFT; isLeftRightDistinguishedKey = true; break; case VK_LCONTROL: case VK_RCONTROL: mVirtualKeyCode = mOriginalVirtualKeyCode; mOriginalVirtualKeyCode = VK_CONTROL; isLeftRightDistinguishedKey = true; break; case VK_LMENU: case VK_RMENU: mVirtualKeyCode = mOriginalVirtualKeyCode; mOriginalVirtualKeyCode = VK_MENU; isLeftRightDistinguishedKey = true; break; } // If virtual keycode (left-right distinguished keycode) is already // computed, we don't need to do anymore. if (mVirtualKeyCode) { break; } // If the keycode doesn't have LR distinguished keycode, we just set // mOriginalVirtualKeyCode to mVirtualKeyCode. Note that don't compute // it from MapVirtualKeyEx() because the scan code might be wrong if // the message is sent/posted by other application. Then, we will compute // unexpected keycode from the scan code. if (!isLeftRightDistinguishedKey) { break; } NS_ASSERTION(!mVirtualKeyCode, "mVirtualKeyCode has been computed already"); // Otherwise, compute the virtual keycode with MapVirtualKeyEx(). mVirtualKeyCode = ComputeVirtualKeyCodeFromScanCodeEx(); // Following code shouldn't be used now because we compute scancode value // if we detect that the sender doesn't set proper scancode. // However, the detection might fail. Therefore, let's keep using this. switch (mOriginalVirtualKeyCode) { case VK_CONTROL: if (mVirtualKeyCode != VK_LCONTROL && mVirtualKeyCode != VK_RCONTROL) { mVirtualKeyCode = mIsExtended ? VK_RCONTROL : VK_LCONTROL; } break; case VK_MENU: if (mVirtualKeyCode != VK_LMENU && mVirtualKeyCode != VK_RMENU) { mVirtualKeyCode = mIsExtended ? VK_RMENU : VK_LMENU; } break; case VK_SHIFT: if (mVirtualKeyCode != VK_LSHIFT && mVirtualKeyCode != VK_RSHIFT) { // Neither left shift nor right shift is an extended key, // let's use VK_LSHIFT for unknown mapping. mVirtualKeyCode = VK_LSHIFT; } break; default: MOZ_CRASH("Unsupported mOriginalVirtualKeyCode"); } break; } case WM_CHAR: case WM_UNICHAR: case WM_SYSCHAR: // If there is another instance and it is trying to remove a char message // from the queue, this message should be handled in the old instance. if (IsAnotherInstanceRemovingCharMessage()) { // XXX Do we need to make mReceivedMsg an array? MOZ_ASSERT(IsEmptyMSG(mLastInstance->mReceivedMsg)); MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::InitWithKeyOrChar(), WARNING, detecting another " "instance is trying to remove a char message, so, this instance " "should do nothing, mLastInstance=0x%p, mRemovingMsg=%s, " "mReceivedMsg=%s", this, mLastInstance, ToString(mLastInstance->mRemovingMsg).get(), ToString(mLastInstance->mReceivedMsg).get())); mLastInstance->mReceivedMsg = mMsg; return; } // NOTE: If other applications like a11y tools sends WM_*CHAR without // scancode, we cannot compute virtual keycode. I.e., with such // applications, we cannot generate proper KeyboardEvent.code value. mVirtualKeyCode = mOriginalVirtualKeyCode = ComputeVirtualKeyCodeFromScanCodeEx(); NS_ASSERTION(mVirtualKeyCode, "Failed to compute virtual keycode"); break; default: { MOZ_CRASH_UNSAFE_PRINTF("Unsupported message: 0x%04X", mMsg.message); break; } } if (!mVirtualKeyCode) { mVirtualKeyCode = mOriginalVirtualKeyCode; } KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance(); mDOMKeyCode = keyboardLayout->ConvertNativeKeyCodeToDOMKeyCode(mVirtualKeyCode); // Be aware, keyboard utilities can change non-printable keys to printable // keys. In such case, we should make the key value as a printable key. // FYI: IsFollowedByPrintableCharMessage() returns true only when it's // handling a keydown message. mKeyNameIndex = IsFollowedByPrintableCharMessage() ? KEY_NAME_INDEX_USE_STRING : keyboardLayout->ConvertNativeKeyCodeToKeyNameIndex(mVirtualKeyCode); mCodeNameIndex = KeyboardLayout::ConvertScanCodeToCodeNameIndex( GetScanCodeWithExtendedFlag()); // If next message of WM_(SYS)KEYDOWN is WM_*CHAR message and the key // combination is not reserved by the system, let's consume it now. // TODO: We cannot initialize mCommittedCharsAndModifiers for VK_PACKET // if the message is WM_KEYUP because we don't have preceding // WM_CHAR message. // TODO: Like Edge, we shouldn't dispatch two sets of keyboard events // for a Unicode character in non-BMP because its key value looks // broken and not good thing for our editor if only one keydown or // keypress event's default is prevented. I guess, we should store // key message information globally and we should wait following // WM_KEYDOWN if following WM_CHAR is a part of a Unicode character. if ((mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN) && !IsReservedBySystem()) { MSG charMsg; while (GetFollowingCharMessage(charMsg)) { // Although, got message shouldn't be WM_NULL in desktop apps, // we should keep checking this. FYI: This was added for Metrofox. if (charMsg.message == WM_NULL) { continue; } MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::InitWithKeyOrChar(), removed char message, %s", this, ToString(charMsg).get())); Unused << NS_WARN_IF(charMsg.hwnd != mMsg.hwnd); mFollowingCharMsgs.AppendElement(charMsg); } } keyboardLayout->InitNativeKey(*this); // Now, we can know if the key produces character(s) or a dead key with // AltGraph modifier. When user emulates AltGr key press with pressing // both Ctrl and Alt and the key produces character(s) or a dead key, we // need to replace Control and Alt state with AltGraph if the keyboard // layout has AltGr key. // Note that if Ctrl and/or Alt are pressed (not to emulate to press AltGr), // we need to set actual modifiers to eKeyDown and eKeyUp. if (MaybeEmulatingAltGraph() && (mCommittedCharsAndModifiers.IsProducingCharsWithAltGr() || mKeyNameIndex == KEY_NAME_INDEX_Dead)) { mModKeyState.Unset(MODIFIER_CONTROL | MODIFIER_ALT); mModKeyState.Set(MODIFIER_ALTGRAPH); } mIsDeadKey = (IsFollowedByDeadCharMessage() || keyboardLayout->IsDeadKey(mOriginalVirtualKeyCode, mModKeyState)); mIsPrintableKey = mKeyNameIndex == KEY_NAME_INDEX_USE_STRING || KeyboardLayout::IsPrintableCharKey(mOriginalVirtualKeyCode); // The repeat count in mMsg.lParam isn't useful to check whether the event // is caused by the auto-repeat feature because it's not incremented even // if it's repeated twice or more (i.e., always 1). Therefore, we need to // check previous key state (31th bit) instead. If it's 1, the key was down // before the message was sent. mIsRepeat = (mMsg.lParam & (1 << 30)) != 0; InitIsSkippableForKeyOrChar(lastKeyMSG); if (IsKeyDownMessage()) { // Compute some strings which may be inputted by the key with various // modifier state if this key event won't cause text input actually. // They will be used for setting mAlternativeCharCodes in the callback // method which will be called by TextEventDispatcher. if (!IsFollowedByPrintableCharMessage()) { ComputeInputtingStringWithKeyboardLayout(); } // Remove odd char messages if there are. RemoveFollowingOddCharMessages(); } } void NativeKey::InitCommittedCharsAndModifiersWithFollowingCharMessages() { mCommittedCharsAndModifiers.Clear(); // This should cause inputting text in focused editor. However, it // ignores keypress events whose altKey or ctrlKey is true. // Therefore, we need to remove these modifier state here. Modifiers modifiers = mModKeyState.GetModifiers(); if (IsFollowedByPrintableCharMessage()) { modifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL); if (MaybeEmulatingAltGraph()) { modifiers |= MODIFIER_ALTGRAPH; } } // NOTE: This method assumes that WM_CHAR and WM_SYSCHAR are never retrieved // at same time. for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) { // Ignore non-printable char messages. if (!IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) { continue; } char16_t ch = static_cast(mFollowingCharMsgs[i].wParam); mCommittedCharsAndModifiers.Append(ch, modifiers); } } NativeKey::~NativeKey() { MOZ_LOG(gKeyLog, LogLevel::Debug, ("%p NativeKey::~NativeKey(), destroyed", this)); if (mIsOverridingKeyboardLayout) { KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance(); keyboardLayout->RestoreLayout(); } sLatestInstance = mLastInstance; } void NativeKey::InitWithAppCommand() { if (GET_DEVICE_LPARAM(mMsg.lParam) != FAPPCOMMAND_KEY) { return; } uint32_t appCommand = GET_APPCOMMAND_LPARAM(mMsg.lParam); switch (GET_APPCOMMAND_LPARAM(mMsg.lParam)) { #undef NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX #define NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX(aAppCommand, aKeyNameIndex) \ case aAppCommand: \ mKeyNameIndex = aKeyNameIndex; \ break; #include "NativeKeyToDOMKeyName.h" #undef NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX default: mKeyNameIndex = KEY_NAME_INDEX_Unidentified; } // Guess the virtual keycode which caused this message. switch (appCommand) { case APPCOMMAND_BROWSER_BACKWARD: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_BACK; break; case APPCOMMAND_BROWSER_FORWARD: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_FORWARD; break; case APPCOMMAND_BROWSER_REFRESH: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_REFRESH; break; case APPCOMMAND_BROWSER_STOP: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_STOP; break; case APPCOMMAND_BROWSER_SEARCH: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_SEARCH; break; case APPCOMMAND_BROWSER_FAVORITES: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_FAVORITES; break; case APPCOMMAND_BROWSER_HOME: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_HOME; break; case APPCOMMAND_VOLUME_MUTE: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_VOLUME_MUTE; break; case APPCOMMAND_VOLUME_DOWN: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_VOLUME_DOWN; break; case APPCOMMAND_VOLUME_UP: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_VOLUME_UP; break; case APPCOMMAND_MEDIA_NEXTTRACK: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_NEXT_TRACK; break; case APPCOMMAND_MEDIA_PREVIOUSTRACK: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_PREV_TRACK; break; case APPCOMMAND_MEDIA_STOP: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_STOP; break; case APPCOMMAND_MEDIA_PLAY_PAUSE: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_PLAY_PAUSE; break; case APPCOMMAND_LAUNCH_MAIL: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_MAIL; break; case APPCOMMAND_LAUNCH_MEDIA_SELECT: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_MEDIA_SELECT; break; case APPCOMMAND_LAUNCH_APP1: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_APP1; break; case APPCOMMAND_LAUNCH_APP2: mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_APP2; break; default: return; } uint16_t scanCodeEx = ComputeScanCodeExFromVirtualKeyCode(mVirtualKeyCode); mScanCode = static_cast(scanCodeEx & 0xFF); uint8_t extended = static_cast((scanCodeEx & 0xFF00) >> 8); mIsExtended = (extended == 0xE0) || (extended == 0xE1); mDOMKeyCode = KeyboardLayout::GetInstance()->ConvertNativeKeyCodeToDOMKeyCode( mOriginalVirtualKeyCode); mCodeNameIndex = KeyboardLayout::ConvertScanCodeToCodeNameIndex( GetScanCodeWithExtendedFlag()); // If we can map the WM_APPCOMMAND to a virtual keycode, we can trust // the result of GetKeyboardState(). Otherwise, we dispatch both // keydown and keyup events from WM_APPCOMMAND handler. Therefore, // even if WM_APPCOMMAND is caused by auto key repeat, web apps receive // a pair of DOM keydown and keyup events. I.e., KeyboardEvent.repeat // should be never true of such keys. // XXX Isn't the key state always true? If the key press caused this // WM_APPCOMMAND, that means it's pressed at that time. if (mVirtualKeyCode) { BYTE kbdState[256]; memset(kbdState, 0, sizeof(kbdState)); ::GetKeyboardState(kbdState); mIsSkippableInRemoteProcess = mIsRepeat = !!kbdState[mVirtualKeyCode]; } } bool NativeKey::MaybeEmulatingAltGraph() const { return IsControl() && IsAlt() && KeyboardLayout::GetInstance()->HasAltGr(); } // static bool NativeKey::IsControlChar(char16_t aChar) { static const char16_t U_SPACE = 0x20; static const char16_t U_DELETE = 0x7F; return aChar < U_SPACE || aChar == U_DELETE; } bool NativeKey::IsFollowedByDeadCharMessage() const { if (mFollowingCharMsgs.IsEmpty()) { return false; } return IsDeadCharMessage(mFollowingCharMsgs[0]); } bool NativeKey::IsFollowedByPrintableCharMessage() const { for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) { if (IsPrintableCharMessage(mFollowingCharMsgs[i])) { return true; } } return false; } bool NativeKey::IsFollowedByPrintableCharOrSysCharMessage() const { for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) { if (IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) { return true; } } return false; } bool NativeKey::IsReservedBySystem() const { // Alt+Space key is handled by OS, we shouldn't touch it. if (mModKeyState.IsAlt() && !mModKeyState.IsControl() && mVirtualKeyCode == VK_SPACE) { return true; } // XXX How about Alt+F4? We receive WM_SYSKEYDOWN for F4 before closing the // window. Although, we don't prevent to close the window but the key // event shouldn't be exposed to the web. return false; } bool NativeKey::IsIMEDoingKakuteiUndo() const { // Following message pattern is caused by "Kakutei-Undo" of ATOK or WXG: // --------------------------------------------------------------------------- // WM_KEYDOWN * n (wParam = VK_BACK, lParam = 0x1) // WM_KEYUP * 1 (wParam = VK_BACK, lParam = 0xC0000001) # ATOK // WM_IME_STARTCOMPOSITION * 1 (wParam = 0x0, lParam = 0x0) // WM_IME_COMPOSITION * 1 (wParam = 0x0, lParam = 0x1BF) // WM_CHAR * n (wParam = VK_BACK, lParam = 0x1) // WM_KEYUP * 1 (wParam = VK_BACK, lParam = 0xC00E0001) // --------------------------------------------------------------------------- // This doesn't match usual key message pattern such as: // WM_KEYDOWN -> WM_CHAR -> WM_KEYDOWN -> WM_CHAR -> ... -> WM_KEYUP // See following bugs for the detail. // https://bugzilla.mozilla.gr.jp/show_bug.cgi?id=2885 (written in Japanese) // https://bugzilla.mozilla.org/show_bug.cgi?id=194559 (written in English) MSG startCompositionMsg, compositionMsg, charMsg; return WinUtils::PeekMessage(&startCompositionMsg, mMsg.hwnd, WM_IME_STARTCOMPOSITION, WM_IME_STARTCOMPOSITION, PM_NOREMOVE | PM_NOYIELD) && WinUtils::PeekMessage(&compositionMsg, mMsg.hwnd, WM_IME_COMPOSITION, WM_IME_COMPOSITION, PM_NOREMOVE | PM_NOYIELD) && WinUtils::PeekMessage(&charMsg, mMsg.hwnd, WM_CHAR, WM_CHAR, PM_NOREMOVE | PM_NOYIELD) && startCompositionMsg.wParam == 0x0 && startCompositionMsg.lParam == 0x0 && compositionMsg.wParam == 0x0 && compositionMsg.lParam == 0x1BF && charMsg.wParam == VK_BACK && charMsg.lParam == 0x1 && startCompositionMsg.time <= compositionMsg.time && compositionMsg.time <= charMsg.time; } void NativeKey::RemoveFollowingOddCharMessages() { MOZ_ASSERT(IsKeyDownMessage()); // If the keydown message is synthesized for automated tests, there is // nothing to do here. if (mFakeCharMsgs) { return; } // If there are some following char messages before another key message, // there is nothing to do here. if (!mFollowingCharMsgs.IsEmpty()) { return; } // If the handling key isn't Backspace, there is nothing to do here. if (mOriginalVirtualKeyCode != VK_BACK) { return; } // If we don't see the odd message pattern, there is nothing to do here. if (!IsIMEDoingKakuteiUndo()) { return; } // Otherwise, we need to remove odd WM_CHAR messages for ATOK or WXG (both // of them are Japanese IME). MSG msg; while (WinUtils::PeekMessage(&msg, mMsg.hwnd, WM_CHAR, WM_CHAR, PM_REMOVE | PM_NOYIELD)) { if (msg.message != WM_CHAR) { MOZ_RELEASE_ASSERT(msg.message == WM_NULL, "Unexpected message was removed"); continue; } MOZ_LOG( gKeyLog, LogLevel::Info, ("%p NativeKey::RemoveFollowingOddCharMessages(), removed odd char " "message, %s", this, ToString(msg).get())); mRemovedOddCharMsgs.AppendElement(msg); } } UINT NativeKey::GetScanCodeWithExtendedFlag() const { if (!mIsExtended) { return mScanCode; } return (0xE000 | mScanCode); } uint32_t NativeKey::GetKeyLocation() const { switch (mVirtualKeyCode) { case VK_LSHIFT: case VK_LCONTROL: case VK_LMENU: case VK_LWIN: return eKeyLocationLeft; case VK_RSHIFT: case VK_RCONTROL: case VK_RMENU: case VK_RWIN: return eKeyLocationRight; case VK_RETURN: // XXX This code assumes that all keyboard drivers use same mapping. return !mIsExtended ? eKeyLocationStandard : eKeyLocationNumpad; case VK_INSERT: case VK_DELETE: case VK_END: case VK_DOWN: case VK_NEXT: case VK_LEFT: case VK_CLEAR: case VK_RIGHT: case VK_HOME: case VK_UP: case VK_PRIOR: // XXX This code assumes that all keyboard drivers use same mapping. return mIsExtended ? eKeyLocationStandard : eKeyLocationNumpad; // NumLock key isn't included due to IE9's behavior. case VK_NUMPAD0: case VK_NUMPAD1: case VK_NUMPAD2: case VK_NUMPAD3: case VK_NUMPAD4: case VK_NUMPAD5: case VK_NUMPAD6: case VK_NUMPAD7: case VK_NUMPAD8: case VK_NUMPAD9: case VK_DECIMAL: case VK_DIVIDE: case VK_MULTIPLY: case VK_SUBTRACT: case VK_ADD: // Separator key of Brazilian keyboard or JIS keyboard for Mac case VK_ABNT_C2: return eKeyLocationNumpad; case VK_SHIFT: case VK_CONTROL: case VK_MENU: NS_WARNING("Failed to decide the key location?"); [[fallthrough]]; default: return eKeyLocationStandard; } } uint8_t NativeKey::ComputeVirtualKeyCodeFromScanCode() const { return static_cast( ::MapVirtualKeyEx(mScanCode, MAPVK_VSC_TO_VK, mKeyboardLayout)); } uint8_t NativeKey::ComputeVirtualKeyCodeFromScanCodeEx() const { // MapVirtualKeyEx() has been improved for supporting extended keys since // Vista. When we call it for mapping a scancode of an extended key and // a virtual keycode, we need to add 0xE000 to the scancode. return static_cast(::MapVirtualKeyEx( GetScanCodeWithExtendedFlag(), MAPVK_VSC_TO_VK_EX, mKeyboardLayout)); } uint16_t NativeKey::ComputeScanCodeExFromVirtualKeyCode( UINT aVirtualKeyCode) const { return static_cast( ::MapVirtualKeyEx(aVirtualKeyCode, MAPVK_VK_TO_VSC_EX, mKeyboardLayout)); } char16_t NativeKey::ComputeUnicharFromScanCode() const { return static_cast(::MapVirtualKeyEx( ComputeVirtualKeyCodeFromScanCode(), MAPVK_VK_TO_CHAR, mKeyboardLayout)); } nsEventStatus NativeKey::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent) const { return InitKeyEvent(aKeyEvent, mModKeyState); } nsEventStatus NativeKey::InitKeyEvent( WidgetKeyboardEvent& aKeyEvent, const ModifierKeyState& aModKeyState) const { if (mWidget->Destroyed()) { MOZ_CRASH("NativeKey tries to dispatch a key event on destroyed widget"); } LayoutDeviceIntPoint point(0, 0); mWidget->InitEvent(aKeyEvent, &point); switch (aKeyEvent.mMessage) { case eKeyDown: // If it was followed by a char message but it was consumed by somebody, // we should mark it as consumed because somebody must have handled it // and we should prevent to do "double action" for the key operation. // However, for compatibility with older version and other browsers, // we should dispatch the events even in the web content. if (mCharMessageHasGone) { aKeyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow); } aKeyEvent.mKeyCode = mDOMKeyCode; // Unique id for this keydown event and its associated keypress. sUniqueKeyEventId++; aKeyEvent.mUniqueId = sUniqueKeyEventId; break; case eKeyUp: aKeyEvent.mKeyCode = mDOMKeyCode; // Set defaultPrevented of the key event if the VK_MENU is not a system // key release, so that the menu bar does not trigger. This helps avoid // triggering the menu bar for ALT key accelerators used in assistive // technologies such as Window-Eyes and ZoomText or for switching open // state of IME. On the other hand, we should dispatch the events even // in the web content for compatibility with older version and other // browsers. if (mOriginalVirtualKeyCode == VK_MENU && mMsg.message != WM_SYSKEYUP) { aKeyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow); } break; case eKeyPress: MOZ_ASSERT(!mCharMessageHasGone, "If following char message was consumed by somebody, " "keydown event should be consumed above"); aKeyEvent.mUniqueId = sUniqueKeyEventId; break; default: MOZ_CRASH("Invalid event message"); } aKeyEvent.mIsRepeat = mIsRepeat; aKeyEvent.mMaybeSkippableInRemoteProcess = mIsSkippableInRemoteProcess; aKeyEvent.mKeyNameIndex = mKeyNameIndex; if (mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) { aKeyEvent.mKeyValue = mCommittedCharsAndModifiers.ToString(); } aKeyEvent.mCodeNameIndex = mCodeNameIndex; MOZ_ASSERT(mCodeNameIndex != CODE_NAME_INDEX_USE_STRING); aKeyEvent.mLocation = GetKeyLocation(); aModKeyState.InitInputEvent(aKeyEvent); KeyboardLayout::NotifyIdleServiceOfUserActivity(); MOZ_LOG( gKeyLog, LogLevel::Info, ("%p NativeKey::InitKeyEvent(), initialized, aKeyEvent={ " "mMessage=%s, mKeyNameIndex=%s, mKeyValue=\"%s\", mCodeNameIndex=%s, " "mKeyCode=%s, mLocation=%s, mModifiers=%s, DefaultPrevented()=%s }", this, ToChar(aKeyEvent.mMessage), ToString(aKeyEvent.mKeyNameIndex).get(), NS_ConvertUTF16toUTF8(aKeyEvent.mKeyValue).get(), ToString(aKeyEvent.mCodeNameIndex).get(), GetDOMKeyCodeName(aKeyEvent.mKeyCode).get(), GetKeyLocationName(aKeyEvent.mLocation).get(), GetModifiersName(aKeyEvent.mModifiers).get(), GetBoolName(aKeyEvent.DefaultPrevented()))); return aKeyEvent.DefaultPrevented() ? nsEventStatus_eConsumeNoDefault : nsEventStatus_eIgnore; } bool NativeKey::DispatchCommandEvent(uint32_t aEventCommand) const { RefPtr command; switch (aEventCommand) { case APPCOMMAND_BROWSER_BACKWARD: command = nsGkAtoms::Back; break; case APPCOMMAND_BROWSER_FORWARD: command = nsGkAtoms::Forward; break; case APPCOMMAND_BROWSER_REFRESH: command = nsGkAtoms::Reload; break; case APPCOMMAND_BROWSER_STOP: command = nsGkAtoms::Stop; break; case APPCOMMAND_BROWSER_SEARCH: command = nsGkAtoms::Search; break; case APPCOMMAND_BROWSER_FAVORITES: command = nsGkAtoms::Bookmarks; break; case APPCOMMAND_BROWSER_HOME: command = nsGkAtoms::Home; break; case APPCOMMAND_CLOSE: command = nsGkAtoms::Close; break; case APPCOMMAND_FIND: command = nsGkAtoms::Find; break; case APPCOMMAND_HELP: command = nsGkAtoms::Help; break; case APPCOMMAND_NEW: command = nsGkAtoms::New; break; case APPCOMMAND_OPEN: command = nsGkAtoms::Open; break; case APPCOMMAND_PRINT: command = nsGkAtoms::Print; break; case APPCOMMAND_SAVE: command = nsGkAtoms::Save; break; case APPCOMMAND_FORWARD_MAIL: command = nsGkAtoms::ForwardMail; break; case APPCOMMAND_REPLY_TO_MAIL: command = nsGkAtoms::ReplyToMail; break; case APPCOMMAND_SEND_MAIL: command = nsGkAtoms::SendMail; break; case APPCOMMAND_MEDIA_NEXTTRACK: command = nsGkAtoms::NextTrack; break; case APPCOMMAND_MEDIA_PREVIOUSTRACK: command = nsGkAtoms::PreviousTrack; break; case APPCOMMAND_MEDIA_STOP: command = nsGkAtoms::MediaStop; break; case APPCOMMAND_MEDIA_PLAY_PAUSE: command = nsGkAtoms::PlayPause; break; default: MOZ_LOG( gKeyLog, LogLevel::Info, ("%p NativeKey::DispatchCommandEvent(), doesn't dispatch command " "event", this)); return false; } WidgetCommandEvent appCommandEvent(true, command, mWidget); mWidget->InitEvent(appCommandEvent); MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::DispatchCommandEvent(), dispatching " "%s app command event...", this, nsAtomCString(command).get())); bool ok = mWidget->DispatchWindowEvent(appCommandEvent) || mWidget->Destroyed(); MOZ_LOG( gKeyLog, LogLevel::Info, ("%p NativeKey::DispatchCommandEvent(), dispatched app command event, " "result=%s, mWidget->Destroyed()=%s", this, GetBoolName(ok), GetBoolName(mWidget->Destroyed()))); return ok; } bool NativeKey::HandleAppCommandMessage() const { // If the widget has gone, we should do nothing. if (mWidget->Destroyed()) { MOZ_LOG(gKeyLog, LogLevel::Warning, ("%p NativeKey::HandleAppCommandMessage(), WARNING, not handled " "due to " "destroyed the widget", this)); return false; } // NOTE: Typical behavior of WM_APPCOMMAND caused by key is, WM_APPCOMMAND // message is _sent_ first. Then, the DefaultWndProc will _post_ // WM_KEYDOWN message and WM_KEYUP message if the keycode for the // command is available (i.e., mVirtualKeyCode is not 0). // NOTE: IntelliType (Microsoft's keyboard utility software) always consumes // WM_KEYDOWN and WM_KEYUP. // Let's dispatch keydown message before our chrome handles the command // when the message is caused by a keypress. This behavior makes handling // WM_APPCOMMAND be a default action of the keydown event. This means that // web applications can handle multimedia keys and prevent our default action. // This allow web applications to provide better UX for multimedia keyboard // users. bool dispatchKeyEvent = (GET_DEVICE_LPARAM(mMsg.lParam) == FAPPCOMMAND_KEY); if (dispatchKeyEvent) { // If a plug-in window has focus but it didn't consume the message, our // window receive WM_APPCOMMAND message. In this case, we shouldn't // dispatch KeyboardEvents because an event handler may access the // plug-in process synchronously. dispatchKeyEvent = WinUtils::IsOurProcessWindow(reinterpret_cast(mMsg.wParam)); } bool consumed = false; if (dispatchKeyEvent) { nsresult rv = mDispatcher->BeginNativeInputTransaction(); if (NS_WARN_IF(NS_FAILED(rv))) { MOZ_LOG(gKeyLog, LogLevel::Error, ("%p NativeKey::HandleAppCommandMessage(), FAILED due to " "BeginNativeInputTransaction() failure", this)); return true; } MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleAppCommandMessage(), initializing keydown " "event...", this)); WidgetKeyboardEvent keydownEvent(true, eKeyDown, mWidget); nsEventStatus status = InitKeyEvent(keydownEvent, mModKeyState); MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleAppCommandMessage(), tries to dispatch " "keydown event...", this)); // NOTE: If the keydown event is consumed by web contents, we shouldn't // continue to handle the command. if (!mDispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status, const_cast(this))) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleAppCommandMessage(), keydown event isn't " "dispatched", this)); // If keyboard event wasn't fired, there must be composition. // So, we don't need to dispatch a command event. return true; } consumed = status == nsEventStatus_eConsumeNoDefault; MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleAppCommandMessage(), keydown event was " "dispatched, consumed=%s", this, GetBoolName(consumed))); sDispatchedKeyOfAppCommand = mVirtualKeyCode; if (mWidget->Destroyed()) { MOZ_LOG( gKeyLog, LogLevel::Info, ("%p NativeKey::HandleAppCommandMessage(), keydown event caused " "destroying the widget", this)); return true; } } // Dispatch a command event or a content command event if the command is // supported. if (!consumed) { uint32_t appCommand = GET_APPCOMMAND_LPARAM(mMsg.lParam); EventMessage contentCommandMessage = eVoidEvent; switch (appCommand) { case APPCOMMAND_BROWSER_BACKWARD: case APPCOMMAND_BROWSER_FORWARD: case APPCOMMAND_BROWSER_REFRESH: case APPCOMMAND_BROWSER_STOP: case APPCOMMAND_BROWSER_SEARCH: case APPCOMMAND_BROWSER_FAVORITES: case APPCOMMAND_BROWSER_HOME: case APPCOMMAND_CLOSE: case APPCOMMAND_FIND: case APPCOMMAND_HELP: case APPCOMMAND_NEW: case APPCOMMAND_OPEN: case APPCOMMAND_PRINT: case APPCOMMAND_SAVE: case APPCOMMAND_FORWARD_MAIL: case APPCOMMAND_REPLY_TO_MAIL: case APPCOMMAND_SEND_MAIL: case APPCOMMAND_MEDIA_NEXTTRACK: case APPCOMMAND_MEDIA_PREVIOUSTRACK: case APPCOMMAND_MEDIA_STOP: case APPCOMMAND_MEDIA_PLAY_PAUSE: // We shouldn't consume the message always because if we don't handle // the message, the sender (typically, utility of keyboard or mouse) // may send other key messages which indicate well known shortcut key. consumed = DispatchCommandEvent(appCommand); break; // Use content command for following commands: case APPCOMMAND_COPY: contentCommandMessage = eContentCommandCopy; break; case APPCOMMAND_CUT: contentCommandMessage = eContentCommandCut; break; case APPCOMMAND_PASTE: contentCommandMessage = eContentCommandPaste; break; case APPCOMMAND_REDO: contentCommandMessage = eContentCommandRedo; break; case APPCOMMAND_UNDO: contentCommandMessage = eContentCommandUndo; break; } if (contentCommandMessage) { MOZ_ASSERT(!mWidget->Destroyed()); WidgetContentCommandEvent contentCommandEvent(true, contentCommandMessage, mWidget); MOZ_LOG( gKeyLog, LogLevel::Info, ("%p NativeKey::HandleAppCommandMessage(), dispatching %s event...", this, ToChar(contentCommandMessage))); mWidget->DispatchWindowEvent(contentCommandEvent); MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleAppCommandMessage(), dispatched %s event", this, ToChar(contentCommandMessage))); consumed = true; if (mWidget->Destroyed()) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleAppCommandMessage(), %s event caused " "destroying the widget", this, ToChar(contentCommandMessage))); return true; } } else { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleAppCommandMessage(), doesn't dispatch " "content " "command event", this)); } } // Dispatch a keyup event if the command is caused by pressing a key and // the key isn't mapped to a virtual keycode. if (dispatchKeyEvent && !mVirtualKeyCode) { MOZ_ASSERT(!mWidget->Destroyed()); nsresult rv = mDispatcher->BeginNativeInputTransaction(); if (NS_WARN_IF(NS_FAILED(rv))) { MOZ_LOG(gKeyLog, LogLevel::Error, ("%p NativeKey::HandleAppCommandMessage(), FAILED due to " "BeginNativeInputTransaction() failure", this)); return true; } MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleAppCommandMessage(), initializing keyup " "event...", this)); WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget); nsEventStatus status = InitKeyEvent(keyupEvent, mModKeyState); MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleAppCommandMessage(), dispatching keyup " "event...", this)); // NOTE: Ignore if the keyup event is consumed because keyup event // represents just a physical key event state change. mDispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status, const_cast(this)); MOZ_LOG( gKeyLog, LogLevel::Info, ("%p NativeKey::HandleAppCommandMessage(), dispatched keyup event", this)); if (mWidget->Destroyed()) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleAppCommandMessage(), keyup event caused " "destroying the widget", this)); return true; } } return consumed; } bool NativeKey::HandleKeyDownMessage(bool* aEventDispatched) const { MOZ_ASSERT(IsKeyDownMessage()); if (aEventDispatched) { *aEventDispatched = false; } if (sDispatchedKeyOfAppCommand && sDispatchedKeyOfAppCommand == mOriginalVirtualKeyCode) { // The multimedia key event has already been dispatch from // HandleAppCommandMessage(). sDispatchedKeyOfAppCommand = 0; MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keydown " "event due to already dispatched from HandleAppCommandMessage(), ", this)); if (RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) { RedirectedKeyDownMessageManager::Forget(); } return true; } if (IsReservedBySystem()) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keydown " "event because the key combination is reserved by the system", this)); if (RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) { RedirectedKeyDownMessageManager::Forget(); } return false; } // If the widget has gone, we should do nothing. if (mWidget->Destroyed()) { MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::HandleKeyDownMessage(), WARNING, not handled due to " "destroyed the widget", this)); if (RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) { RedirectedKeyDownMessageManager::Forget(); } return false; } bool defaultPrevented = false; if (mFakeCharMsgs || !RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) { nsresult rv = mDispatcher->BeginNativeInputTransaction(); if (NS_WARN_IF(NS_FAILED(rv))) { MOZ_LOG(gKeyLog, LogLevel::Error, ("%p NativeKey::HandleKeyDownMessage(), FAILED due to " "BeginNativeInputTransaction() failure", this)); return true; } bool isIMEEnabled = WinUtils::IsIMEEnabled(mWidget->GetInputContext()); MOZ_LOG(gKeyLog, LogLevel::Debug, ("%p NativeKey::HandleKeyDownMessage(), initializing keydown " "event...", this)); WidgetKeyboardEvent keydownEvent(true, eKeyDown, mWidget); nsEventStatus status = InitKeyEvent(keydownEvent, mModKeyState); MOZ_LOG( gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), dispatching keydown event...", this)); bool dispatched = mDispatcher->DispatchKeyboardEvent( eKeyDown, keydownEvent, status, const_cast(this)); if (aEventDispatched) { *aEventDispatched = dispatched; } if (!dispatched) { // If the keydown event wasn't fired, there must be composition. // we don't need to do anything anymore. MOZ_LOG( gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keypress " "event(s) because keydown event isn't dispatched actually", this)); return false; } defaultPrevented = status == nsEventStatus_eConsumeNoDefault; if (mWidget->Destroyed() || IsFocusedWindowChanged()) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), keydown event caused " "destroying the widget", this)); return true; } MOZ_LOG( gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), dispatched keydown event, " "dispatched=%s, defaultPrevented=%s", this, GetBoolName(dispatched), GetBoolName(defaultPrevented))); // If IMC wasn't associated to the window but is associated it now (i.e., // focus is moved from a non-editable editor to an editor by keydown // event handler), WM_CHAR and WM_SYSCHAR shouldn't cause first character // inputting if IME is opened. But then, we should redirect the native // keydown message to IME. // However, note that if focus has been already moved to another // application, we shouldn't redirect the message to it because the keydown // message is processed by us, so, nobody shouldn't process it. HWND focusedWnd = ::GetFocus(); if (!defaultPrevented && !mFakeCharMsgs && focusedWnd && !isIMEEnabled && WinUtils::IsIMEEnabled(mWidget->GetInputContext())) { RedirectedKeyDownMessageManager::RemoveNextCharMessage(focusedWnd); INPUT keyinput; keyinput.type = INPUT_KEYBOARD; keyinput.ki.wVk = mOriginalVirtualKeyCode; keyinput.ki.wScan = mScanCode; keyinput.ki.dwFlags = KEYEVENTF_SCANCODE; if (mIsExtended) { keyinput.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; } keyinput.ki.time = 0; keyinput.ki.dwExtraInfo = 0; RedirectedKeyDownMessageManager::WillRedirect(mMsg, defaultPrevented); MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), redirecting %s...", this, ToString(mMsg).get())); ::SendInput(1, &keyinput, sizeof(keyinput)); MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), redirected %s", this, ToString(mMsg).get())); // Return here. We shouldn't dispatch keypress event for this WM_KEYDOWN. // If it's needed, it will be dispatched after next (redirected) // WM_KEYDOWN. return true; } } else { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), received a redirected %s", this, ToString(mMsg).get())); defaultPrevented = RedirectedKeyDownMessageManager::DefaultPrevented(); // If this is redirected keydown message, we have dispatched the keydown // event already. if (aEventDispatched) { *aEventDispatched = true; } } RedirectedKeyDownMessageManager::Forget(); MOZ_ASSERT(!mWidget->Destroyed()); // If the key was processed by IME and didn't cause WM_(SYS)CHAR messages, we // shouldn't dispatch keypress event. if (mOriginalVirtualKeyCode == VK_PROCESSKEY && !IsFollowedByPrintableCharOrSysCharMessage()) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress " "event because the key was already handled by IME, " "defaultPrevented=%s", this, GetBoolName(defaultPrevented))); return defaultPrevented; } if (defaultPrevented) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress " "event because preceding keydown event was consumed", this)); return true; } MOZ_ASSERT(!mCharMessageHasGone, "If following char message was consumed by somebody, " "keydown event should have been consumed before dispatch"); // If mCommittedCharsAndModifiers was initialized with following char // messages, we should dispatch keypress events with its information. if (IsFollowedByPrintableCharOrSysCharMessage()) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching " "keypress events with retrieved char messages...", this)); return DispatchKeyPressEventsWithRetrievedCharMessages(); } // If we won't be getting a WM_CHAR, WM_SYSCHAR or WM_DEADCHAR, synthesize a // keypress for almost all keys if (NeedsToHandleWithoutFollowingCharMessages()) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching " "keypress events...", this)); return DispatchKeyPressEventsWithoutCharMessage(); } // If WM_KEYDOWN of VK_PACKET isn't followed by WM_CHAR, we don't need to // dispatch keypress events. if (mVirtualKeyCode == VK_PACKET) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress " "event " "because the key is VK_PACKET and there are no char messages", this)); return false; } if (!mModKeyState.IsControl() && !mModKeyState.IsAlt() && !mModKeyState.IsWin() && mIsPrintableKey) { // If this is simple KeyDown event but next message is not WM_CHAR, // this event may not input text, so we should ignore this event. // See bug 314130. MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress " "event " "because the key event is simple printable key's event but not " "followed " "by char messages", this)); return false; } if (mIsDeadKey) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress " "event " "because the key is a dead key and not followed by char messages", this)); return false; } MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching " "keypress events due to no following char messages...", this)); return DispatchKeyPressEventsWithoutCharMessage(); } bool NativeKey::HandleCharMessage(bool* aEventDispatched) const { MOZ_ASSERT(IsCharOrSysCharMessage(mMsg)); return HandleCharMessage(mMsg, aEventDispatched); } bool NativeKey::HandleCharMessage(const MSG& aCharMsg, bool* aEventDispatched) const { MOZ_ASSERT(IsKeyDownMessage() || IsCharOrSysCharMessage(mMsg)); MOZ_ASSERT(IsCharOrSysCharMessage(aCharMsg.message)); if (aEventDispatched) { *aEventDispatched = false; } if ((IsCharOrSysCharMessage(mMsg) || IsEnterKeyPressCharMessage(mMsg)) && IsAnotherInstanceRemovingCharMessage()) { MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::HandleCharMessage(), WARNING, does nothing because " "the message should be handled in another instance removing this " "message", this)); // Consume this for now because it will be handled by another instance. return true; } // If the key combinations is reserved by the system, we shouldn't dispatch // eKeyPress event for it and passes the message to next wndproc. if (IsReservedBySystem()) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleCharMessage(), doesn't dispatch keypress " "event because the key combination is reserved by the system", this)); return false; } // If the widget has gone, we should do nothing. if (mWidget->Destroyed()) { MOZ_LOG(gKeyLog, LogLevel::Warning, ("%p NativeKey::HandleCharMessage(), WARNING, not handled due to " "destroyed the widget", this)); return false; } // When a control key is inputted by a key, it should be handled without // WM_*CHAR messages at receiving WM_*KEYDOWN message. So, when we receive // WM_*CHAR message directly, we see a control character here. // Note that when the char is '\r', it means that the char message should // cause "Enter" keypress event for inserting a line break. if (IsControlCharMessage(aCharMsg) && !IsEnterKeyPressCharMessage(aCharMsg)) { // In this case, we don't need to dispatch eKeyPress event because: // 1. We're the only browser which dispatches "keypress" event for // non-printable characters (Although, both Chrome and Edge dispatch // "keypress" event for some keys accidentally. For example, "IntlRo" // key with Ctrl of Japanese keyboard layout). // 2. Currently, we may handle shortcut keys with "keydown" event if // it's reserved or something. So, we shouldn't dispatch "keypress" // event without it. // Note that this does NOT mean we stop dispatching eKeyPress event for // key presses causes a control character when Ctrl is pressed. In such // case, DispatchKeyPressEventsWithoutCharMessage() dispatches eKeyPress // instead of this method. MOZ_LOG( gKeyLog, LogLevel::Info, ("%p NativeKey::HandleCharMessage(), doesn't dispatch keypress " "event because received a control character input without WM_KEYDOWN", this)); return false; } // XXXmnakano I think that if mMsg is WM_CHAR, i.e., it comes without // preceding WM_KEYDOWN, we should should dispatch composition // events instead of eKeyPress because they are not caused by // actual keyboard operation. // First, handle normal text input or non-printable key case here. WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget); if (IsEnterKeyPressCharMessage(aCharMsg)) { keypressEvent.mKeyCode = NS_VK_RETURN; } else { keypressEvent.mCharCode = static_cast(aCharMsg.wParam); } nsresult rv = mDispatcher->BeginNativeInputTransaction(); if (NS_WARN_IF(NS_FAILED(rv))) { MOZ_LOG(gKeyLog, LogLevel::Error, ("%p NativeKey::HandleCharMessage(), FAILED due to " "BeginNativeInputTransaction() failure", this)); return true; } MOZ_LOG(gKeyLog, LogLevel::Debug, ("%p NativeKey::HandleCharMessage(), initializing keypress " "event...", this)); ModifierKeyState modKeyState(mModKeyState); // When AltGr is pressed, both Alt and Ctrl are active. However, when they // are active, TextEditor won't treat the keypress event as inputting a // character. Therefore, when AltGr is pressed and the key tries to input // a character, let's set them to false. if (modKeyState.IsControl() && modKeyState.IsAlt() && IsPrintableCharMessage(aCharMsg)) { modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL); } nsEventStatus status = InitKeyEvent(keypressEvent, modKeyState); MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleCharMessage(), dispatching keypress event...", this)); bool dispatched = mDispatcher->MaybeDispatchKeypressEvents( keypressEvent, status, const_cast(this)); if (aEventDispatched) { *aEventDispatched = dispatched; } if (mWidget->Destroyed()) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleCharMessage(), keypress event caused " "destroying the widget", this)); return true; } bool consumed = status == nsEventStatus_eConsumeNoDefault; MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleCharMessage(), dispatched keypress event, " "dispatched=%s, consumed=%s", this, GetBoolName(dispatched), GetBoolName(consumed))); return consumed; } bool NativeKey::HandleKeyUpMessage(bool* aEventDispatched) const { MOZ_ASSERT(IsKeyUpMessage()); if (aEventDispatched) { *aEventDispatched = false; } // If the key combinations is reserved by the system, we shouldn't dispatch // eKeyUp event for it and passes the message to next wndproc. if (IsReservedBySystem()) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyUpMessage(), doesn't dispatch keyup " "event because the key combination is reserved by the system", this)); return false; } // If the widget has gone, we should do nothing. if (mWidget->Destroyed()) { MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::HandleKeyUpMessage(), WARNING, not handled due to " "destroyed the widget", this)); return false; } nsresult rv = mDispatcher->BeginNativeInputTransaction(); if (NS_WARN_IF(NS_FAILED(rv))) { MOZ_LOG(gKeyLog, LogLevel::Error, ("%p NativeKey::HandleKeyUpMessage(), FAILED due to " "BeginNativeInputTransaction() failure", this)); return true; } MOZ_LOG(gKeyLog, LogLevel::Debug, ("%p NativeKey::HandleKeyUpMessage(), initializing keyup event...", this)); WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget); nsEventStatus status = InitKeyEvent(keyupEvent, mModKeyState); MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyUpMessage(), dispatching keyup event...", this)); bool dispatched = mDispatcher->DispatchKeyboardEvent( eKeyUp, keyupEvent, status, const_cast(this)); if (aEventDispatched) { *aEventDispatched = dispatched; } if (mWidget->Destroyed()) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyUpMessage(), keyup event caused " "destroying the widget", this)); return true; } bool consumed = status == nsEventStatus_eConsumeNoDefault; MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::HandleKeyUpMessage(), dispatched keyup event, " "dispatched=%s, consumed=%s", this, GetBoolName(dispatched), GetBoolName(consumed))); return consumed; } bool NativeKey::NeedsToHandleWithoutFollowingCharMessages() const { MOZ_ASSERT(IsKeyDownMessage()); // If the key combination is reserved by the system, the caller shouldn't // do anything with following WM_*CHAR messages. So, let's return true here. if (IsReservedBySystem()) { return true; } // If the keydown message is generated for inputting some Unicode characters // via SendInput() API, we need to handle it only with WM_*CHAR messages. if (mVirtualKeyCode == VK_PACKET) { return false; } // If following char message is for a control character, it should be handled // without WM_CHAR message. This is typically Ctrl + [a-z]. if (mFollowingCharMsgs.Length() == 1 && IsControlCharMessage(mFollowingCharMsgs[0])) { return true; } // If keydown message is followed by WM_CHAR or WM_SYSCHAR whose wParam isn't // a control character, we should dispatch keypress event with the char // message even with any modifier state. if (IsFollowedByPrintableCharOrSysCharMessage()) { return false; } // If any modifier keys which may cause printable keys becoming non-printable // are not pressed, we don't need special handling for the key. // Note that if the key does not produce a character with AltGr and when // AltGr key is pressed, we don't need to dispatch eKeyPress event for it // because AltGr shouldn't be used for a modifier for a shortcut without // Ctrl, Alt or Win. That means that we should treat it in same path for // Shift key. if (!mModKeyState.IsControl() && !mModKeyState.IsAlt() && !mModKeyState.IsWin()) { return false; } // If the key event causes dead key event, we don't need to dispatch keypress // event. if (mIsDeadKey && mCommittedCharsAndModifiers.IsEmpty()) { return false; } // Even if the key is a printable key, it might cause non-printable character // input with modifier key(s). return mIsPrintableKey; } static nsCString GetResultOfInSendMessageEx() { DWORD ret = ::InSendMessageEx(nullptr); if (!ret) { return "ISMEX_NOSEND"_ns; } nsCString result; if (ret & ISMEX_CALLBACK) { result = "ISMEX_CALLBACK"; } if (ret & ISMEX_NOTIFY) { if (!result.IsEmpty()) { result += " | "; } result += "ISMEX_NOTIFY"; } if (ret & ISMEX_REPLIED) { if (!result.IsEmpty()) { result += " | "; } result += "ISMEX_REPLIED"; } if (ret & ISMEX_SEND) { if (!result.IsEmpty()) { result += " | "; } result += "ISMEX_SEND"; } return result; } bool NativeKey::MayBeSameCharMessage(const MSG& aCharMsg1, const MSG& aCharMsg2) const { // NOTE: Although, we don't know when this case occurs, the scan code value // in lParam may be changed from 0 to something. The changed value // is different from the scan code of handling keydown message. static const LPARAM kScanCodeMask = 0x00FF0000; return aCharMsg1.message == aCharMsg2.message && aCharMsg1.wParam == aCharMsg2.wParam && (aCharMsg1.lParam & ~kScanCodeMask) == (aCharMsg2.lParam & ~kScanCodeMask); } bool NativeKey::IsSamePhysicalKeyMessage(const MSG& aKeyOrCharMsg1, const MSG& aKeyOrCharMsg2) const { if (NS_WARN_IF(aKeyOrCharMsg1.message < WM_KEYFIRST) || NS_WARN_IF(aKeyOrCharMsg1.message > WM_KEYLAST) || NS_WARN_IF(aKeyOrCharMsg2.message < WM_KEYFIRST) || NS_WARN_IF(aKeyOrCharMsg2.message > WM_KEYLAST)) { return false; } return WinUtils::GetScanCode(aKeyOrCharMsg1.lParam) == WinUtils::GetScanCode(aKeyOrCharMsg2.lParam) && WinUtils::IsExtendedScanCode(aKeyOrCharMsg1.lParam) == WinUtils::IsExtendedScanCode(aKeyOrCharMsg2.lParam); } bool NativeKey::GetFollowingCharMessage(MSG& aCharMsg) { MOZ_ASSERT(IsKeyDownMessage()); aCharMsg.message = WM_NULL; if (mFakeCharMsgs) { for (size_t i = 0; i < mFakeCharMsgs->Length(); i++) { FakeCharMsg& fakeCharMsg = mFakeCharMsgs->ElementAt(i); if (fakeCharMsg.mConsumed) { continue; } MSG charMsg = fakeCharMsg.GetCharMsg(mMsg.hwnd); fakeCharMsg.mConsumed = true; if (!IsCharMessage(charMsg)) { return false; } aCharMsg = charMsg; return true; } return false; } // If next key message is not char message, we should give up to find a // related char message for the handling keydown event for now. // Note that it's possible other applications may send other key message // after we call TranslateMessage(). That may cause PeekMessage() failing // to get char message for the handling keydown message. MSG nextKeyMsg; if (!WinUtils::PeekMessage(&nextKeyMsg, mMsg.hwnd, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD) || !IsCharMessage(nextKeyMsg)) { MOZ_LOG(gKeyLog, LogLevel::Debug, ("%p NativeKey::GetFollowingCharMessage(), there are no char " "messages", this)); return false; } const MSG kFoundCharMsg = nextKeyMsg; AutoRestore saveLastRemovingMsg(mRemovingMsg); mRemovingMsg = nextKeyMsg; mReceivedMsg = sEmptyMSG; AutoRestore ensureToClearRecivedMsg(mReceivedMsg); // On Metrofox, PeekMessage() sometimes returns WM_NULL even if we specify // the message range. So, if it returns WM_NULL, we should retry to get // the following char message it was found above. for (uint32_t i = 0; i < 50; i++) { MSG removedMsg, nextKeyMsgInAllWindows; bool doCrash = false; if (!WinUtils::PeekMessage(&removedMsg, mMsg.hwnd, nextKeyMsg.message, nextKeyMsg.message, PM_REMOVE | PM_NOYIELD)) { // We meets unexpected case. We should collect the message queue state // and crash for reporting the bug. doCrash = true; // If another instance was created for the removing message during trying // to remove a char message, the instance didn't handle it for preventing // recursive handling. So, let's handle it in this instance. if (!IsEmptyMSG(mReceivedMsg)) { // If focus is moved to different window, we shouldn't handle it on // the widget. Let's discard it for now. if (mReceivedMsg.hwnd != nextKeyMsg.hwnd) { MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::GetFollowingCharMessage(), WARNING, received a " "char message during removing it from the queue, but it's for " "different window, mReceivedMsg=%s, nextKeyMsg=%s, " "kFoundCharMsg=%s", this, ToString(mReceivedMsg).get(), ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); // There might still exist char messages, the loop of calling // this method should be continued. aCharMsg.message = WM_NULL; return true; } // Even if the received message is different from what we tried to // remove from the queue, let's take the received message as a part of // the result of this key sequence. if (mReceivedMsg.message != nextKeyMsg.message || mReceivedMsg.wParam != nextKeyMsg.wParam || mReceivedMsg.lParam != nextKeyMsg.lParam) { MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::GetFollowingCharMessage(), WARNING, received a " "char message during removing it from the queue, but it's " "differnt from what trying to remove from the queue, " "aCharMsg=%s, nextKeyMsg=%s, kFoundCharMsg=%s", this, ToString(mReceivedMsg).get(), ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); } else { MOZ_LOG( gKeyLog, LogLevel::Debug, ("%p NativeKey::GetFollowingCharMessage(), succeeded to " "retrieve next char message via another instance, aCharMsg=%s, " "kFoundCharMsg=%s", this, ToString(mReceivedMsg).get(), ToString(kFoundCharMsg).get())); } aCharMsg = mReceivedMsg; return true; } // The char message is redirected to different thread's window by focus // move or something or just cancelled by external application. if (!WinUtils::PeekMessage(&nextKeyMsgInAllWindows, 0, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD)) { MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " "remove a char message, but it's already gone from all message " "queues, nextKeyMsg=%s, kFoundCharMsg=%s", this, ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); return true; // XXX should return false in this case } // The next key message is redirected to different window created by our // thread, we should do nothing because we must not have focus. if (nextKeyMsgInAllWindows.hwnd != mMsg.hwnd) { aCharMsg = nextKeyMsgInAllWindows; MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " "remove a char message, but found in another message queue, " "nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, kFoundCharMsg=%s", this, ToString(nextKeyMsgInAllWindows).get(), ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); return true; } // If next key message becomes non-char message, this key operation // may have already been consumed or canceled. if (!IsCharMessage(nextKeyMsgInAllWindows)) { MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " "remove a char message and next key message becomes non-char " "message, nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, " "kFoundCharMsg=%s", this, ToString(nextKeyMsgInAllWindows).get(), ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); MOZ_ASSERT(!mCharMessageHasGone); mFollowingCharMsgs.Clear(); mCharMessageHasGone = true; return false; } // If next key message is still a char message but different key message, // we should treat current key operation is consumed or canceled and // next char message should be handled as an orphan char message later. if (!IsSamePhysicalKeyMessage(nextKeyMsgInAllWindows, kFoundCharMsg)) { MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " "remove a char message and next key message becomes differnt " "key's " "char message, nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, " "kFoundCharMsg=%s", this, ToString(nextKeyMsgInAllWindows).get(), ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); MOZ_ASSERT(!mCharMessageHasGone); mFollowingCharMsgs.Clear(); mCharMessageHasGone = true; return false; } // If next key message is still a char message but the message is changed, // we should retry to remove the new message with PeekMessage() again. if (nextKeyMsgInAllWindows.message != nextKeyMsg.message) { MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " "remove a char message due to message change, let's retry to " "remove the message with newly found char message, " "nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, kFoundCharMsg=%s", this, ToString(nextKeyMsgInAllWindows).get(), ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); nextKeyMsg = nextKeyMsgInAllWindows; continue; } // If there is still existing a char message caused by same physical key // in the queue but PeekMessage(PM_REMOVE) failed to remove it from the // queue, it might be possible that the odd keyboard layout or utility // hooks only PeekMessage(PM_NOREMOVE) and GetMessage(). So, let's try // remove the char message with GetMessage() again. // FYI: The wParam might be different from the found message, but it's // okay because we assume that odd keyboard layouts return actual // inputting character at removing the char message. if (WinUtils::GetMessage(&removedMsg, mMsg.hwnd, nextKeyMsg.message, nextKeyMsg.message)) { MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " "remove a char message, but succeeded with GetMessage(), " "removedMsg=%s, kFoundCharMsg=%s", this, ToString(removedMsg).get(), ToString(kFoundCharMsg).get())); // Cancel to crash, but we need to check the removed message value. doCrash = false; } // If we've already removed some WM_NULL messages from the queue and // the found message has already gone from the queue, let's treat the key // as inputting no characters and already consumed. else if (i > 0) { MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " "remove a char message, but removed %d WM_NULL messages", this, i)); // If the key is a printable key or a control key but tried to input // a character, mark mCharMessageHasGone true for handling the keydown // event as inputting empty string. MOZ_ASSERT(!mCharMessageHasGone); mFollowingCharMsgs.Clear(); mCharMessageHasGone = true; return false; } MOZ_LOG(gKeyLog, LogLevel::Error, ("%p NativeKey::GetFollowingCharMessage(), FAILED, lost target " "message to remove, nextKeyMsg=%s", this, ToString(nextKeyMsg).get())); } if (doCrash) { nsPrintfCString info( "\nPeekMessage() failed to remove char message! " "\nActive keyboard layout=0x%p (%s), " "\nHandling message: %s, InSendMessageEx()=%s, " "\nFound message: %s, " "\nWM_NULL has been removed: %d, " "\nNext key message in all windows: %s, " "time=%ld, ", KeyboardLayout::GetActiveLayout(), KeyboardLayout::GetActiveLayoutName().get(), ToString(mMsg).get(), GetResultOfInSendMessageEx().get(), ToString(kFoundCharMsg).get(), i, ToString(nextKeyMsgInAllWindows).get(), nextKeyMsgInAllWindows.time); CrashReporter::AppendAppNotesToCrashReport(info); MSG nextMsg; if (WinUtils::PeekMessage(&nextMsg, 0, 0, 0, PM_NOREMOVE | PM_NOYIELD)) { nsPrintfCString info("\nNext message in all windows: %s, time=%ld", ToString(nextMsg).get(), nextMsg.time); CrashReporter::AppendAppNotesToCrashReport(info); } else { CrashReporter::AppendAppNotesToCrashReport( "\nThere is no message in any window"_ns); } MOZ_CRASH("We lost the following char message"); } // We're still not sure why ::PeekMessage() may return WM_NULL even with // its first message and its last message are same message. However, // at developing Metrofox, we met this case even with usual keyboard // layouts. So, it might be possible in desktop application or it really // occurs with some odd keyboard layouts which perhaps hook API. if (removedMsg.message == WM_NULL) { MOZ_LOG(gKeyLog, LogLevel::Warning, ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " "remove a char message, instead, removed WM_NULL message, " "removedMsg=%s", this, ToString(removedMsg).get())); // Check if there is the message which we're trying to remove. MSG newNextKeyMsg; if (!WinUtils::PeekMessage(&newNextKeyMsg, mMsg.hwnd, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD)) { // If there is no key message, we should mark this keydown as consumed // because the key operation may have already been handled or canceled. MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " "remove a char message because it's gone during removing it from " "the queue, nextKeyMsg=%s, kFoundCharMsg=%s", this, ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); MOZ_ASSERT(!mCharMessageHasGone); mFollowingCharMsgs.Clear(); mCharMessageHasGone = true; return false; } if (!IsCharMessage(newNextKeyMsg)) { // If next key message becomes a non-char message, we should mark this // keydown as consumed because the key operation may have already been // handled or canceled. MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " "remove a char message because it's gone during removing it from " "the queue, nextKeyMsg=%s, newNextKeyMsg=%s, kFoundCharMsg=%s", this, ToString(nextKeyMsg).get(), ToString(newNextKeyMsg).get(), ToString(kFoundCharMsg).get())); MOZ_ASSERT(!mCharMessageHasGone); mFollowingCharMsgs.Clear(); mCharMessageHasGone = true; return false; } MOZ_LOG( gKeyLog, LogLevel::Debug, ("%p NativeKey::GetFollowingCharMessage(), there is the message " "which is being tried to be removed from the queue, trying again...", this)); continue; } // Typically, this case occurs with WM_DEADCHAR. If the removed message's // wParam becomes 0, that means that the key event shouldn't cause text // input. So, let's ignore the strange char message. if (removedMsg.message == nextKeyMsg.message && !removedMsg.wParam) { MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::GetFollowingCharMessage(), WARNING, succeeded to " "remove a char message, but the removed message's wParam is 0, " "removedMsg=%s", this, ToString(removedMsg).get())); return false; } // This is normal case. if (MayBeSameCharMessage(removedMsg, nextKeyMsg)) { aCharMsg = removedMsg; MOZ_LOG( gKeyLog, LogLevel::Debug, ("%p NativeKey::GetFollowingCharMessage(), succeeded to retrieve " "next char message, aCharMsg=%s", this, ToString(aCharMsg).get())); return true; } // Even if removed message is different char message from the found char // message, when the scan code is same, we can assume that the message // is overwritten by somebody who hooks API. See bug 1336028 comment 0 for // the possible scenarios. if (IsCharMessage(removedMsg) && IsSamePhysicalKeyMessage(removedMsg, nextKeyMsg)) { aCharMsg = removedMsg; MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::GetFollowingCharMessage(), WARNING, succeeded to " "remove a char message, but the removed message was changed from " "the found message except their scancode, aCharMsg=%s, " "nextKeyMsg=%s, kFoundCharMsg=%s", this, ToString(aCharMsg).get(), ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); return true; } // When found message's wParam is 0 and its scancode is 0xFF, we may remove // usual char message actually. In such case, we should use the removed // char message. if (IsCharMessage(removedMsg) && !nextKeyMsg.wParam && WinUtils::GetScanCode(nextKeyMsg.lParam) == 0xFF) { aCharMsg = removedMsg; MOZ_LOG( gKeyLog, LogLevel::Warning, ("%p NativeKey::GetFollowingCharMessage(), WARNING, succeeded to " "remove a char message, but the removed message was changed from " "the found message but the found message was odd, so, ignoring the " "odd found message and respecting the removed message, aCharMsg=%s, " "nextKeyMsg=%s, kFoundCharMsg=%s", this, ToString(aCharMsg).get(), ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); return true; } // NOTE: Although, we don't know when this case occurs, the scan code value // in lParam may be changed from 0 to something. The changed value // is different from the scan code of handling keydown message. MOZ_LOG( gKeyLog, LogLevel::Error, ("%p NativeKey::GetFollowingCharMessage(), FAILED, removed message " "is really different from what we have already found, removedMsg=%s, " "nextKeyMsg=%s, kFoundCharMsg=%s", this, ToString(removedMsg).get(), ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); nsPrintfCString info( "\nPeekMessage() removed unexpcted char message! " "\nActive keyboard layout=0x%p (%s), " "\nHandling message: %s, InSendMessageEx()=%s, " "\nFound message: %s, " "\nRemoved message: %s, ", KeyboardLayout::GetActiveLayout(), KeyboardLayout::GetActiveLayoutName().get(), ToString(mMsg).get(), GetResultOfInSendMessageEx().get(), ToString(kFoundCharMsg).get(), ToString(removedMsg).get()); CrashReporter::AppendAppNotesToCrashReport(info); // What's the next key message? MSG nextKeyMsgAfter; if (WinUtils::PeekMessage(&nextKeyMsgAfter, mMsg.hwnd, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD)) { nsPrintfCString info( "\nNext key message after unexpected char message " "removed: %s, ", ToString(nextKeyMsgAfter).get()); CrashReporter::AppendAppNotesToCrashReport(info); } else { CrashReporter::AppendAppNotesToCrashReport( nsLiteralCString("\nThere is no key message after unexpected char " "message removed, ")); } // Another window has a key message? if (WinUtils::PeekMessage(&nextKeyMsgInAllWindows, 0, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD)) { nsPrintfCString info("\nNext key message in all windows: %s.", ToString(nextKeyMsgInAllWindows).get()); CrashReporter::AppendAppNotesToCrashReport(info); } else { CrashReporter::AppendAppNotesToCrashReport( "\nThere is no key message in any windows."_ns); } MOZ_CRASH("PeekMessage() removed unexpected message"); } MOZ_LOG( gKeyLog, LogLevel::Error, ("%p NativeKey::GetFollowingCharMessage(), FAILED, removed messages " "are all WM_NULL, nextKeyMsg=%s", this, ToString(nextKeyMsg).get())); nsPrintfCString info( "\nWe lost following char message! " "\nActive keyboard layout=0x%p (%s), " "\nHandling message: %s, InSendMessageEx()=%s, \n" "Found message: %s, removed a lot of WM_NULL", KeyboardLayout::GetActiveLayout(), KeyboardLayout::GetActiveLayoutName().get(), ToString(mMsg).get(), GetResultOfInSendMessageEx().get(), ToString(kFoundCharMsg).get()); CrashReporter::AppendAppNotesToCrashReport(info); MOZ_CRASH("We lost the following char message"); return false; } void NativeKey::ComputeInputtingStringWithKeyboardLayout() { KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance(); if (KeyboardLayout::IsPrintableCharKey(mVirtualKeyCode) || mCharMessageHasGone) { mInputtingStringAndModifiers = mCommittedCharsAndModifiers; } else { mInputtingStringAndModifiers.Clear(); } mShiftedString.Clear(); mUnshiftedString.Clear(); mShiftedLatinChar = mUnshiftedLatinChar = 0; // XXX How about when Win key is pressed? if (mModKeyState.IsControl() == mModKeyState.IsAlt()) { return; } // If user is inputting a Unicode character with typing Alt + Numpad // keys, we shouldn't set alternative key codes because the key event // shouldn't match with a mnemonic. However, we should set only // mUnshiftedString for keeping traditional behavior at WM_SYSKEYDOWN. // FYI: I guess that it's okay that mUnshiftedString stays empty. So, // if its value breaks something, you must be able to just return here. if (MaybeTypingUnicodeScalarValue()) { if (!mCommittedCharsAndModifiers.IsEmpty()) { MOZ_ASSERT(mMsg.message == WM_SYSKEYDOWN); char16_t num = mCommittedCharsAndModifiers.CharAt(0); MOZ_ASSERT(num >= '0' && num <= '9'); mUnshiftedString.Append(num, MODIFIER_NONE); return; } // If user presses a function key without NumLock or 3rd party utility // synthesizes a function key on numpad, we should handle it as-is because // the user's intention may be performing `Alt` + foo. MOZ_ASSERT(!KeyboardLayout::IsPrintableCharKey(mVirtualKeyCode)); return; } ModifierKeyState capsLockState(mModKeyState.GetModifiers() & MODIFIER_CAPSLOCK); mUnshiftedString = keyboardLayout->GetUniCharsAndModifiers(mVirtualKeyCode, capsLockState); capsLockState.Set(MODIFIER_SHIFT); mShiftedString = keyboardLayout->GetUniCharsAndModifiers(mVirtualKeyCode, capsLockState); // The current keyboard cannot input alphabets or numerics, // we should append them for Shortcut/Access keys. // E.g., for Cyrillic keyboard layout. capsLockState.Unset(MODIFIER_SHIFT); WidgetUtils::GetLatinCharCodeForKeyCode( mDOMKeyCode, capsLockState.GetModifiers(), &mUnshiftedLatinChar, &mShiftedLatinChar); // If the mShiftedLatinChar isn't 0, the key code is NS_VK_[A-Z]. if (mShiftedLatinChar) { // If the produced characters of the key on current keyboard layout // are same as computed Latin characters, we shouldn't append the // Latin characters to alternativeCharCode. if (mUnshiftedLatinChar == mUnshiftedString.CharAt(0) && mShiftedLatinChar == mShiftedString.CharAt(0)) { mShiftedLatinChar = mUnshiftedLatinChar = 0; } } else if (mUnshiftedLatinChar) { // If the mShiftedLatinChar is 0, the mKeyCode doesn't produce // alphabet character. At that time, the character may be produced // with Shift key. E.g., on French keyboard layout, NS_VK_PERCENT // key produces LATIN SMALL LETTER U WITH GRAVE (U+00F9) without // Shift key but with Shift key, it produces '%'. // If the mUnshiftedLatinChar is produced by the key on current // keyboard layout, we shouldn't append it to alternativeCharCode. if (mUnshiftedLatinChar == mUnshiftedString.CharAt(0) || mUnshiftedLatinChar == mShiftedString.CharAt(0)) { mUnshiftedLatinChar = 0; } } if (!mModKeyState.IsControl()) { return; } // If the mCharCode is not ASCII character, we should replace the // mCharCode with ASCII character only when Ctrl is pressed. // But don't replace the mCharCode when the mCharCode is not same as // unmodified characters. In such case, Ctrl is sometimes used for a // part of character inputting key combination like Shift. uint32_t ch = mModKeyState.IsShift() ? mShiftedLatinChar : mUnshiftedLatinChar; if (!ch) { return; } if (mInputtingStringAndModifiers.IsEmpty() || mInputtingStringAndModifiers.UniCharsCaseInsensitiveEqual( mModKeyState.IsShift() ? mShiftedString : mUnshiftedString)) { mInputtingStringAndModifiers.Clear(); mInputtingStringAndModifiers.Append(ch, mModKeyState.GetModifiers()); } } bool NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages() const { MOZ_ASSERT(IsKeyDownMessage()); MOZ_ASSERT(IsFollowedByPrintableCharOrSysCharMessage()); MOZ_ASSERT(!mWidget->Destroyed()); nsresult rv = mDispatcher->BeginNativeInputTransaction(); if (NS_WARN_IF(NS_FAILED(rv))) { MOZ_LOG( gKeyLog, LogLevel::Error, ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), " "FAILED due to BeginNativeInputTransaction() failure", this)); return true; } WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget); MOZ_LOG(gKeyLog, LogLevel::Debug, ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), " "initializing keypress event...", this)); ModifierKeyState modKeyState(mModKeyState); if (mCanIgnoreModifierStateAtKeyPress && IsFollowedByPrintableCharMessage()) { // If eKeyPress event should cause inputting text in focused editor, // we need to remove Alt and Ctrl state. modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL); } // We don't need to send char message here if there are two or more retrieved // messages because we need to set each message to each eKeyPress event. bool needsCallback = mFollowingCharMsgs.Length() > 1; nsEventStatus status = InitKeyEvent(keypressEvent, modKeyState); MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), " "dispatching keypress event(s)...", this)); bool dispatched = mDispatcher->MaybeDispatchKeypressEvents( keypressEvent, status, const_cast(this), needsCallback); if (mWidget->Destroyed()) { MOZ_LOG( gKeyLog, LogLevel::Info, ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), " "keypress event(s) caused destroying the widget", this)); return true; } bool consumed = status == nsEventStatus_eConsumeNoDefault; MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), " "dispatched keypress event(s), dispatched=%s, consumed=%s", this, GetBoolName(dispatched), GetBoolName(consumed))); return consumed; } bool NativeKey::DispatchKeyPressEventsWithoutCharMessage() const { MOZ_ASSERT(IsKeyDownMessage()); MOZ_ASSERT(!mIsDeadKey || !mCommittedCharsAndModifiers.IsEmpty()); MOZ_ASSERT(!mWidget->Destroyed()); nsresult rv = mDispatcher->BeginNativeInputTransaction(); if (NS_WARN_IF(NS_FAILED(rv))) { MOZ_LOG(gKeyLog, LogLevel::Error, ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), " "FAILED due " "to BeginNativeInputTransaction() failure", this)); return true; } WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget); if (mInputtingStringAndModifiers.IsEmpty() && mShiftedString.IsEmpty() && mUnshiftedString.IsEmpty()) { keypressEvent.mKeyCode = mDOMKeyCode; } MOZ_LOG(gKeyLog, LogLevel::Debug, ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), " "initializing " "keypress event...", this)); nsEventStatus status = InitKeyEvent(keypressEvent, mModKeyState); MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), " "dispatching " "keypress event(s)...", this)); bool dispatched = mDispatcher->MaybeDispatchKeypressEvents( keypressEvent, status, const_cast(this)); if (mWidget->Destroyed()) { MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), " "keypress event(s) caused destroying the widget", this)); return true; } bool consumed = status == nsEventStatus_eConsumeNoDefault; MOZ_LOG( gKeyLog, LogLevel::Info, ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), dispatched " "keypress event(s), dispatched=%s, consumed=%s", this, GetBoolName(dispatched), GetBoolName(consumed))); return consumed; } void NativeKey::WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndex) { // If it's an eKeyPress event and it's generated from retrieved char message, // we need to set raw message information for plugins. if (aKeyboardEvent.mMessage == eKeyPress && IsFollowedByPrintableCharOrSysCharMessage()) { MOZ_RELEASE_ASSERT(aIndex < mCommittedCharsAndModifiers.Length()); uint32_t foundPrintableCharMessages = 0; for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) { if (!IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) { // XXX Should we dispatch a plugin event for WM_*DEADCHAR messages and // WM_CHAR with a control character here? But we're not sure // how can we create such message queue (i.e., WM_CHAR or // WM_SYSCHAR with a printable character and such message are // generated by a keydown). So, let's ignore such case until // we'd get some bug reports. MOZ_LOG(gKeyLog, LogLevel::Warning, ("%p NativeKey::WillDispatchKeyboardEvent(), WARNING, " "ignoring %zuth message due to non-printable char message, %s", this, i + 1, ToString(mFollowingCharMsgs[i]).get())); continue; } if (foundPrintableCharMessages++ == aIndex) { // Found message which caused the eKeyPress event. break; } } // Set modifier state from mCommittedCharsAndModifiers because some of them // might be different. For example, Shift key was pressed at inputting // dead char but Shift key was released before inputting next character. if (mCanIgnoreModifierStateAtKeyPress) { ModifierKeyState modKeyState(mModKeyState); modKeyState.Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_ALTGRAPH | MODIFIER_CAPSLOCK); modKeyState.Set(mCommittedCharsAndModifiers.ModifiersAt(aIndex)); modKeyState.InitInputEvent(aKeyboardEvent); MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::WillDispatchKeyboardEvent(), " "setting %uth modifier state to %s", this, aIndex + 1, ToString(modKeyState).get())); } } size_t longestLength = std::max(mInputtingStringAndModifiers.Length(), std::max(mShiftedString.Length(), mUnshiftedString.Length())); size_t skipUniChars = longestLength - mInputtingStringAndModifiers.Length(); size_t skipShiftedChars = longestLength - mShiftedString.Length(); size_t skipUnshiftedChars = longestLength - mUnshiftedString.Length(); if (aIndex >= longestLength) { MOZ_LOG( gKeyLog, LogLevel::Info, ("%p NativeKey::WillDispatchKeyboardEvent(), does nothing for %uth " "%s event", this, aIndex + 1, ToChar(aKeyboardEvent.mMessage))); return; } // Check if aKeyboardEvent is the last event for a key press. // So, if it's not an eKeyPress event, it's always the last event. // Otherwise, check if the index is the last character of // mCommittedCharsAndModifiers. bool isLastIndex = aKeyboardEvent.mMessage != eKeyPress || mCommittedCharsAndModifiers.IsEmpty() || mCommittedCharsAndModifiers.Length() - 1 == aIndex; nsTArray& altArray = aKeyboardEvent.mAlternativeCharCodes; // Set charCode and adjust modifier state for every eKeyPress event. // This is not necessary for the other keyboard events because the other // keyboard events shouldn't have non-zero charCode value and should have // current modifier state. if (aKeyboardEvent.mMessage == eKeyPress && skipUniChars <= aIndex) { // XXX Modifying modifier state of aKeyboardEvent is illegal, but no way // to set different modifier state per keypress event except this // hack. Note that ideally, dead key should cause composition events // instead of keypress events, though. if (aIndex - skipUniChars < mInputtingStringAndModifiers.Length()) { ModifierKeyState modKeyState(mModKeyState); // If key in combination with Alt and/or Ctrl produces a different // character than without them then do not report these flags // because it is separate keyboard layout shift state. If dead-key // and base character does not produce a valid composite character // then both produced dead-key character and following base // character may have different modifier flags, too. modKeyState.Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_ALTGRAPH | MODIFIER_CAPSLOCK); modKeyState.Set( mInputtingStringAndModifiers.ModifiersAt(aIndex - skipUniChars)); modKeyState.InitInputEvent(aKeyboardEvent); MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::WillDispatchKeyboardEvent(), " "setting %uth modifier state to %s", this, aIndex + 1, ToString(modKeyState).get())); } uint16_t uniChar = mInputtingStringAndModifiers.CharAt(aIndex - skipUniChars); // The mCharCode was set from mKeyValue. However, for example, when Ctrl key // is pressed, its value should indicate an ASCII character for backward // compatibility rather than inputting character without the modifiers. // Therefore, we need to modify mCharCode value here. aKeyboardEvent.SetCharCode(uniChar); MOZ_LOG(gKeyLog, LogLevel::Info, ("%p NativeKey::WillDispatchKeyboardEvent(), " "setting %uth charCode to %s", this, aIndex + 1, GetCharacterCodeName(uniChar).get())); } // We need to append alterntaive charCode values: // - if the event is eKeyPress, we need to append for the index because // eKeyPress event is dispatched for every character inputted by a // key press. // - if the event is not eKeyPress, we need to append for all characters // inputted by the key press because the other keyboard events (e.g., // eKeyDown are eKeyUp) are fired only once for a key press. size_t count; if (aKeyboardEvent.mMessage == eKeyPress) { // Basically, append alternative charCode values only for the index. count = 1; // However, if it's the last eKeyPress event but different shift state // can input longer string, the last eKeyPress event should have all // remaining alternative charCode values. if (isLastIndex) { count = longestLength - aIndex; } } else { count = longestLength; } for (size_t i = 0; i < count; ++i) { uint16_t shiftedChar = 0, unshiftedChar = 0; if (skipShiftedChars <= aIndex + i) { shiftedChar = mShiftedString.CharAt(aIndex + i - skipShiftedChars); } if (skipUnshiftedChars <= aIndex + i) { unshiftedChar = mUnshiftedString.CharAt(aIndex + i - skipUnshiftedChars); } if (shiftedChar || unshiftedChar) { AlternativeCharCode chars(unshiftedChar, shiftedChar); altArray.AppendElement(chars); } if (!isLastIndex) { continue; } if (mUnshiftedLatinChar || mShiftedLatinChar) { AlternativeCharCode chars(mUnshiftedLatinChar, mShiftedLatinChar); altArray.AppendElement(chars); } // Typically, following virtual keycodes are used for a key which can // input the character. However, these keycodes are also used for // other keys on some keyboard layout. E.g., in spite of Shift+'1' // inputs '+' on Thai keyboard layout, a key which is at '=/+' // key on ANSI keyboard layout is VK_OEM_PLUS. Native applications // handle it as '+' key if Ctrl key is pressed. char16_t charForOEMKeyCode = 0; switch (mVirtualKeyCode) { case VK_OEM_PLUS: charForOEMKeyCode = '+'; break; case VK_OEM_COMMA: charForOEMKeyCode = ','; break; case VK_OEM_MINUS: charForOEMKeyCode = '-'; break; case VK_OEM_PERIOD: charForOEMKeyCode = '.'; break; } if (charForOEMKeyCode && charForOEMKeyCode != mUnshiftedString.CharAt(0) && charForOEMKeyCode != mShiftedString.CharAt(0) && charForOEMKeyCode != mUnshiftedLatinChar && charForOEMKeyCode != mShiftedLatinChar) { AlternativeCharCode OEMChars(charForOEMKeyCode, charForOEMKeyCode); altArray.AppendElement(OEMChars); } } } /***************************************************************************** * mozilla::widget::KeyboardLayout *****************************************************************************/ KeyboardLayout* KeyboardLayout::sInstance = nullptr; nsIUserIdleServiceInternal* KeyboardLayout::sIdleService = nullptr; // static KeyboardLayout* KeyboardLayout::GetInstance() { if (!sInstance) { sInstance = new KeyboardLayout(); nsCOMPtr idleService = do_GetService("@mozilla.org/widget/useridleservice;1"); // The refcount will be decreased at shut down. sIdleService = idleService.forget().take(); } return sInstance; } // static void KeyboardLayout::Shutdown() { delete sInstance; sInstance = nullptr; NS_IF_RELEASE(sIdleService); } // static void KeyboardLayout::NotifyIdleServiceOfUserActivity() { sIdleService->ResetIdleTimeOut(0); } KeyboardLayout::KeyboardLayout() : mKeyboardLayout(0), mIsOverridden(false), mIsPendingToRestoreKeyboardLayout(false), mHasAltGr(false) { mDeadKeyTableListHead = nullptr; // A dead key sequence should be made from up to 5 keys. Therefore, 4 is // enough and makes sense because the item is uint8_t. // (Although, even if it's possible to be 6 keys or more in a sequence, // this array will be re-allocated). mActiveDeadKeys.SetCapacity(4); mDeadKeyShiftStates.SetCapacity(4); // NOTE: LoadLayout() should be called via OnLayoutChange(). } KeyboardLayout::~KeyboardLayout() { ReleaseDeadKeyTables(); } bool KeyboardLayout::IsPrintableCharKey(uint8_t aVirtualKey) { return GetKeyIndex(aVirtualKey) >= 0; } WORD KeyboardLayout::ComputeScanCodeForVirtualKeyCode( uint8_t aVirtualKeyCode) const { return static_cast( ::MapVirtualKeyEx(aVirtualKeyCode, MAPVK_VK_TO_VSC, GetLayout())); } bool KeyboardLayout::IsDeadKey(uint8_t aVirtualKey, const ModifierKeyState& aModKeyState) const { int32_t virtualKeyIndex = GetKeyIndex(aVirtualKey); // XXX KeyboardLayout class doesn't support unusual keyboard layout which // maps some function keys as dead keys. if (virtualKeyIndex < 0) { return false; } return mVirtualKeys[virtualKeyIndex].IsDeadKey( VirtualKey::ModifiersToShiftState(aModKeyState.GetModifiers())); } bool KeyboardLayout::IsSysKey(uint8_t aVirtualKey, const ModifierKeyState& aModKeyState) const { // If Alt key is not pressed, it's never a system key combination. // Additionally, if Ctrl key is pressed, it's never a system key combination // too. // FYI: Windows logo key state won't affect if it's a system key. if (!aModKeyState.IsAlt() || aModKeyState.IsControl()) { return false; } int32_t virtualKeyIndex = GetKeyIndex(aVirtualKey); if (virtualKeyIndex < 0) { return true; } UniCharsAndModifiers inputCharsAndModifiers = GetUniCharsAndModifiers(aVirtualKey, aModKeyState); if (inputCharsAndModifiers.IsEmpty()) { return true; } // If the Alt key state isn't consumed, that means that the key with Alt // doesn't cause text input. So, the combination is a system key. return !!(inputCharsAndModifiers.ModifiersAt(0) & MODIFIER_ALT); } void KeyboardLayout::InitNativeKey(NativeKey& aNativeKey) { if (mIsPendingToRestoreKeyboardLayout) { LoadLayout(::GetKeyboardLayout(0)); } // If the aNativeKey is initialized with WM_CHAR, the key information // should be discarded because mKeyValue should have the string to be // inputted. if (aNativeKey.mMsg.message == WM_CHAR) { char16_t ch = static_cast(aNativeKey.mMsg.wParam); // But don't set key value as printable key if the character is a control // character such as 0x0D at pressing Enter key. if (!NativeKey::IsControlChar(ch)) { aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; Modifiers modifiers = aNativeKey.GetModifiers() & ~(MODIFIER_ALT | MODIFIER_CONTROL); aNativeKey.mCommittedCharsAndModifiers.Append(ch, modifiers); return; } } // If the aNativeKey is in a sequence to input a Unicode character with // Alt + numpad keys, we should just set the number as the inputting charcter. // Note that we should compute the key value from the virtual key code // because they may be mapped to alphabets, but they should be treated as // Alt + [0-9] even by web apps. // However, we shouldn't touch the key value if given virtual key code is // not a printable key because it may be synthesized by 3rd party utility // or just NumLock is unlocked and user tries to use shortcut key. In the // latter case, we cannot solve the conflict issue with Alt+foo shortcut key // and inputting a Unicode scalar value like reported to bug 1606655, though, // I have no better idea. Perhaps, `Alt` shouldn't be used for shortcut key // combination on Windows. if (aNativeKey.MaybeTypingUnicodeScalarValue() && KeyboardLayout::IsPrintableCharKey(aNativeKey.mVirtualKeyCode)) { // If the key code value is mapped to a Numpad key, let's compute the key // value with it. This is same behavior as Chrome. In strictly speaking, // I think that the else block's computation is better because it seems // that Windows does not refer virtual key code value, but we should avoid // web-compat issues. char16_t num = '0'; if (aNativeKey.mVirtualKeyCode >= VK_NUMPAD0 && aNativeKey.mVirtualKeyCode <= VK_NUMPAD9) { num = '0' + aNativeKey.mVirtualKeyCode - VK_NUMPAD0; } // Otherwise, let's use fake key value for making never match with // mnemonic. else { switch (aNativeKey.mScanCode) { case 0x0052: // Numpad0 num = '0'; break; case 0x004F: // Numpad1 num = '1'; break; case 0x0050: // Numpad2 num = '2'; break; case 0x0051: // Numpad3 num = '3'; break; case 0x004B: // Numpad4 num = '4'; break; case 0x004C: // Numpad5 num = '5'; break; case 0x004D: // Numpad6 num = '6'; break; case 0x0047: // Numpad7 num = '7'; break; case 0x0048: // Numpad8 num = '8'; break; case 0x0049: // Numpad9 num = '9'; break; default: MOZ_ASSERT_UNREACHABLE( "IsTypingUnicodeScalarValue() must have returned true for wrong " "scancode"); break; } } aNativeKey.mCommittedCharsAndModifiers.Append(num, aNativeKey.GetModifiers()); aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; return; } // When it's followed by non-dead char message(s) for printable character(s), // aNativeKey should dispatch eKeyPress events for them rather than // information from keyboard layout because respecting WM_(SYS)CHAR messages // guarantees that we can always input characters which is expected by // the user even if the user uses odd keyboard layout. // Or, when it was followed by non-dead char message for a printable character // but it's gone at removing the message from the queue, let's treat it // as a key inputting empty string. if (aNativeKey.IsFollowedByPrintableCharOrSysCharMessage() || aNativeKey.mCharMessageHasGone) { MOZ_ASSERT(!aNativeKey.IsCharMessage(aNativeKey.mMsg)); if (aNativeKey.IsFollowedByPrintableCharOrSysCharMessage()) { // Initialize mCommittedCharsAndModifiers with following char messages. aNativeKey.InitCommittedCharsAndModifiersWithFollowingCharMessages(); MOZ_ASSERT(!aNativeKey.mCommittedCharsAndModifiers.IsEmpty()); // Currently, we are doing a ugly hack to keypress events to cause // inputting character even if Ctrl or Alt key is pressed, that is, we // remove Ctrl and Alt modifier state from keypress event. However, for // example, Ctrl+Space which causes ' ' of WM_CHAR message never causes // keypress event whose ctrlKey is true. For preventing this problem, // we should mark as not removable if Ctrl or Alt key does not cause // changing inputting character. if (IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode) && (aNativeKey.IsControl() ^ aNativeKey.IsAlt())) { ModifierKeyState state = aNativeKey.ModifierKeyStateRef(); state.Unset(MODIFIER_ALT | MODIFIER_CONTROL); UniCharsAndModifiers charsWithoutModifier = GetUniCharsAndModifiers(aNativeKey.GenericVirtualKeyCode(), state); aNativeKey.mCanIgnoreModifierStateAtKeyPress = !charsWithoutModifier.UniCharsEqual( aNativeKey.mCommittedCharsAndModifiers); } } else { aNativeKey.mCommittedCharsAndModifiers.Clear(); } aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; // If it's not in dead key sequence, we don't need to do anymore here. if (!IsInDeadKeySequence()) { return; } // If it's in dead key sequence and dead char is inputted as is, we need to // set the previous modifier state which is stored when preceding dead key // is pressed. UniCharsAndModifiers deadChars = GetDeadUniCharsAndModifiers(); aNativeKey.mCommittedCharsAndModifiers.OverwriteModifiersIfBeginsWith( deadChars); // Finish the dead key sequence. DeactivateDeadKeyState(); return; } // If it's a dead key, aNativeKey will be initialized by // MaybeInitNativeKeyAsDeadKey(). if (MaybeInitNativeKeyAsDeadKey(aNativeKey)) { return; } // If the key is not a usual printable key, KeyboardLayout class assume that // it's not cause dead char nor printable char. Therefore, there are nothing // to do here fore such keys (e.g., function keys). // However, this should keep dead key state even if non-printable key is // pressed during a dead key sequence. if (!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode)) { return; } MOZ_ASSERT(aNativeKey.mOriginalVirtualKeyCode != VK_PACKET, "At handling VK_PACKET, we shouldn't refer keyboard layout"); MOZ_ASSERT( aNativeKey.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING, "Printable key's key name index must be KEY_NAME_INDEX_USE_STRING"); // If it's in dead key handling and the pressed key causes a composite // character, aNativeKey will be initialized by // MaybeInitNativeKeyWithCompositeChar(). if (MaybeInitNativeKeyWithCompositeChar(aNativeKey)) { return; } UniCharsAndModifiers baseChars = GetUniCharsAndModifiers(aNativeKey); // If the key press isn't related to any dead keys, initialize aNativeKey // with the characters which should be caused by the key. if (!IsInDeadKeySequence()) { aNativeKey.mCommittedCharsAndModifiers = baseChars; return; } // If the key doesn't cause a composite character with preceding dead key, // initialize aNativeKey with the dead-key character followed by current // key's character. UniCharsAndModifiers deadChars = GetDeadUniCharsAndModifiers(); aNativeKey.mCommittedCharsAndModifiers = deadChars + baseChars; if (aNativeKey.IsKeyDownMessage()) { DeactivateDeadKeyState(); } } bool KeyboardLayout::MaybeInitNativeKeyAsDeadKey(NativeKey& aNativeKey) { // Only when it's not in dead key sequence, we can trust IsDeadKey() result. if (!IsInDeadKeySequence() && !IsDeadKey(aNativeKey)) { return false; } // When keydown message is followed by a dead char message, it should be // initialized as dead key. bool isDeadKeyDownEvent = aNativeKey.IsKeyDownMessage() && aNativeKey.IsFollowedByDeadCharMessage(); // When keyup message is received, let's check if it's one of preceding // dead keys because keydown message order and keyup message order may be // different. bool isDeadKeyUpEvent = !aNativeKey.IsKeyDownMessage() && mActiveDeadKeys.Contains(aNativeKey.GenericVirtualKeyCode()); if (isDeadKeyDownEvent || isDeadKeyUpEvent) { ActivateDeadKeyState(aNativeKey); // Any dead key events don't generate characters. So, a dead key should // cause only keydown event and keyup event whose KeyboardEvent.key // values are "Dead". aNativeKey.mCommittedCharsAndModifiers.Clear(); aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_Dead; return true; } // At keydown message handling, we need to forget the first dead key // because there is no guarantee coming WM_KEYUP for the second dead // key before next WM_KEYDOWN. E.g., due to auto key repeat or pressing // another dead key before releasing current key. Therefore, we can // set only a character for current key for keyup event. if (!IsInDeadKeySequence()) { aNativeKey.mCommittedCharsAndModifiers = GetUniCharsAndModifiers(aNativeKey); return true; } // When non-printable key event comes during a dead key sequence, that must // be a modifier key event. So, such events shouldn't be handled as a part // of the dead key sequence. if (!IsDeadKey(aNativeKey)) { return false; } // FYI: Following code may run when the user doesn't input text actually // but the key sequence is a dead key sequence. For example, // ` -> Ctrl+` with Spanish keyboard layout. Let's keep using this // complicated code for now because this runs really rarely. // Dead key followed by another dead key may cause a composed character // (e.g., "Russian - Mnemonic" keyboard layout's 's' -> 'c'). if (MaybeInitNativeKeyWithCompositeChar(aNativeKey)) { return true; } // Otherwise, dead key followed by another dead key causes inputting both // character. UniCharsAndModifiers prevDeadChars = GetDeadUniCharsAndModifiers(); UniCharsAndModifiers newChars = GetUniCharsAndModifiers(aNativeKey); // But keypress events should be fired for each committed character. aNativeKey.mCommittedCharsAndModifiers = prevDeadChars + newChars; if (aNativeKey.IsKeyDownMessage()) { DeactivateDeadKeyState(); } return true; } bool KeyboardLayout::MaybeInitNativeKeyWithCompositeChar( NativeKey& aNativeKey) { if (!IsInDeadKeySequence()) { return false; } if (NS_WARN_IF(!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode))) { return false; } UniCharsAndModifiers baseChars = GetUniCharsAndModifiers(aNativeKey); if (baseChars.IsEmpty() || !baseChars.CharAt(0)) { return false; } char16_t compositeChar = GetCompositeChar(baseChars.CharAt(0)); if (!compositeChar) { return false; } // Active dead-key and base character does produce exactly one composite // character. aNativeKey.mCommittedCharsAndModifiers.Append(compositeChar, baseChars.ModifiersAt(0)); if (aNativeKey.IsKeyDownMessage()) { DeactivateDeadKeyState(); } return true; } UniCharsAndModifiers KeyboardLayout::GetUniCharsAndModifiers( uint8_t aVirtualKey, VirtualKey::ShiftState aShiftState) const { UniCharsAndModifiers result; int32_t key = GetKeyIndex(aVirtualKey); if (key < 0) { return result; } return mVirtualKeys[key].GetUniChars(aShiftState); } UniCharsAndModifiers KeyboardLayout::GetDeadUniCharsAndModifiers() const { MOZ_RELEASE_ASSERT(mActiveDeadKeys.Length() == mDeadKeyShiftStates.Length()); if (NS_WARN_IF(mActiveDeadKeys.IsEmpty())) { return UniCharsAndModifiers(); } UniCharsAndModifiers result; for (size_t i = 0; i < mActiveDeadKeys.Length(); ++i) { result += GetUniCharsAndModifiers(mActiveDeadKeys[i], mDeadKeyShiftStates[i]); } return result; } char16_t KeyboardLayout::GetCompositeChar(char16_t aBaseChar) const { if (NS_WARN_IF(mActiveDeadKeys.IsEmpty())) { return 0; } // XXX Currently, we don't support computing a composite character with // two or more dead keys since it needs big table for supporting // long chained dead keys. However, this should be a minor bug // because this runs only when the latest keydown event does not cause // WM_(SYS)CHAR messages. So, when user wants to input a character, // this path never runs. if (mActiveDeadKeys.Length() > 1) { return 0; } int32_t key = GetKeyIndex(mActiveDeadKeys[0]); if (key < 0) { return 0; } return mVirtualKeys[key].GetCompositeChar(mDeadKeyShiftStates[0], aBaseChar); } // static HKL KeyboardLayout::GetActiveLayout() { return GetInstance()->mKeyboardLayout; } // static nsCString KeyboardLayout::GetActiveLayoutName() { return GetInstance()->GetLayoutName(GetActiveLayout()); } static bool IsValidKeyboardLayoutsChild(const nsAString& aChildName) { if (aChildName.Length() != 8) { return false; } for (size_t i = 0; i < aChildName.Length(); i++) { if ((aChildName[i] >= '0' && aChildName[i] <= '9') || (aChildName[i] >= 'a' && aChildName[i] <= 'f') || (aChildName[i] >= 'A' && aChildName[i] <= 'F')) { continue; } return false; } return true; } nsCString KeyboardLayout::GetLayoutName(HKL aLayout) const { const wchar_t kKeyboardLayouts[] = L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\"; uint16_t language = reinterpret_cast(aLayout) & 0xFFFF; uint16_t layout = (reinterpret_cast(aLayout) >> 16) & 0xFFFF; // If the layout is less than 0xA000XXXX (normal keyboard layout for the // language) or 0xEYYYXXXX (IMM-IME), we can retrieve its name simply. if (layout < 0xA000 || (layout & 0xF000) == 0xE000) { nsAutoString key(kKeyboardLayouts); key.AppendPrintf("%08" PRIXPTR, layout < 0xA000 ? layout : reinterpret_cast(aLayout)); wchar_t buf[256]; if (NS_WARN_IF(!WinUtils::GetRegistryKey( HKEY_LOCAL_MACHINE, key.get(), L"Layout Text", buf, sizeof(buf)))) { return "No name or too long name"_ns; } return NS_ConvertUTF16toUTF8(buf); } if (NS_WARN_IF((layout & 0xF000) != 0xF000)) { nsCString result; result.AppendPrintf("Odd HKL: 0x%08" PRIXPTR, reinterpret_cast(aLayout)); return result; } // Otherwise, we need to walk the registry under "Keyboard Layouts". nsCOMPtr regKey = do_CreateInstance("@mozilla.org/windows-registry-key;1"); if (NS_WARN_IF(!regKey)) { return ""_ns; } nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, nsString(kKeyboardLayouts), nsIWindowsRegKey::ACCESS_READ); if (NS_WARN_IF(NS_FAILED(rv))) { return ""_ns; } uint32_t childCount = 0; if (NS_WARN_IF(NS_FAILED(regKey->GetChildCount(&childCount))) || NS_WARN_IF(!childCount)) { return ""_ns; } for (uint32_t i = 0; i < childCount; i++) { nsAutoString childName; if (NS_WARN_IF(NS_FAILED(regKey->GetChildName(i, childName))) || !IsValidKeyboardLayoutsChild(childName)) { continue; } uint32_t childNum = static_cast(childName.ToInteger64(&rv, 16)); if (NS_WARN_IF(NS_FAILED(rv))) { continue; } // Ignore normal keyboard layouts for each language. if (childNum <= 0xFFFF) { continue; } // If it doesn't start with 'A' nor 'a', language should be matched. if ((childNum & 0xFFFF) != language && (childNum & 0xF0000000) != 0xA0000000) { continue; } // Then, the child should have "Layout Id" which is "YYY" of 0xFYYYXXXX. nsAutoString key(kKeyboardLayouts); key += childName; wchar_t buf[256]; if (NS_WARN_IF(!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, key.get(), L"Layout Id", buf, sizeof(buf)))) { continue; } uint16_t layoutId = wcstol(buf, nullptr, 16); if (layoutId != (layout & 0x0FFF)) { continue; } if (NS_WARN_IF(!WinUtils::GetRegistryKey( HKEY_LOCAL_MACHINE, key.get(), L"Layout Text", buf, sizeof(buf)))) { continue; } return NS_ConvertUTF16toUTF8(buf); } return ""_ns; } void KeyboardLayout::LoadLayout(HKL aLayout) { mIsPendingToRestoreKeyboardLayout = false; if (mKeyboardLayout == aLayout) { return; } mKeyboardLayout = aLayout; mHasAltGr = false; MOZ_LOG(gKeyLog, LogLevel::Info, ("KeyboardLayout::LoadLayout(aLayout=0x%p (%s))", aLayout, GetLayoutName(aLayout).get())); BYTE kbdState[256]; memset(kbdState, 0, sizeof(kbdState)); BYTE originalKbdState[256]; // Bitfield with all shift states that have at least one dead-key. uint16_t shiftStatesWithDeadKeys = 0; // Bitfield with all shift states that produce any possible dead-key base // characters. uint16_t shiftStatesWithBaseChars = 0; mActiveDeadKeys.Clear(); mDeadKeyShiftStates.Clear(); ReleaseDeadKeyTables(); ::GetKeyboardState(originalKbdState); // For each shift state gather all printable characters that are produced // for normal case when no any dead-key is active. for (VirtualKey::ShiftState shiftState = 0; shiftState < 16; shiftState++) { VirtualKey::FillKbdState(kbdState, shiftState); bool isAltGr = VirtualKey::IsAltGrIndex(shiftState); for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) { int32_t vki = GetKeyIndex(virtualKey); if (vki < 0) { continue; } NS_ASSERTION(uint32_t(vki) < ArrayLength(mVirtualKeys), "invalid index"); char16_t uniChars[5]; int32_t ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)uniChars, ArrayLength(uniChars), 0, mKeyboardLayout); // dead-key if (ret < 0) { shiftStatesWithDeadKeys |= (1 << shiftState); // Repeat dead-key to deactivate it and get its character // representation. char16_t deadChar[2]; ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)deadChar, ArrayLength(deadChar), 0, mKeyboardLayout); NS_ASSERTION(ret == 2, "Expecting twice repeated dead-key character"); mVirtualKeys[vki].SetDeadChar(shiftState, deadChar[0]); MOZ_LOG(gKeyLog, LogLevel::Verbose, (" %s (%d): DeadChar(%s, %s) (ret=%d)", kVirtualKeyName[virtualKey], vki, GetShiftStateName(shiftState).get(), GetCharacterCodeNames(deadChar, 1).get(), ret)); } else { if (ret == 1) { // dead-key can pair only with exactly one base character. shiftStatesWithBaseChars |= (1 << shiftState); } mVirtualKeys[vki].SetNormalChars(shiftState, uniChars, ret); MOZ_LOG(gKeyLog, LogLevel::Verbose, (" %s (%d): NormalChar(%s, %s) (ret=%d)", kVirtualKeyName[virtualKey], vki, GetShiftStateName(shiftState).get(), GetCharacterCodeNames(uniChars, ret).get(), ret)); } // If the key inputs at least one character with AltGr modifier, // check if AltGr changes inputting character. If it does, mark // this keyboard layout has AltGr modifier actually. if (!mHasAltGr && ret > 0 && isAltGr && mVirtualKeys[vki].IsChangedByAltGr(shiftState)) { mHasAltGr = true; MOZ_LOG(gKeyLog, LogLevel::Info, (" Found a key (%s) changed by AltGr: %s -> %s (%s) (ret=%d)", kVirtualKeyName[virtualKey], GetCharacterCodeNames( mVirtualKeys[vki].GetNativeUniChars( shiftState - VirtualKey::ShiftStateIndex::eAltGr)) .get(), GetCharacterCodeNames( mVirtualKeys[vki].GetNativeUniChars(shiftState)) .get(), GetShiftStateName(shiftState).get(), ret)); } } } // Now process each dead-key to find all its base characters and resulting // composite characters. for (VirtualKey::ShiftState shiftState = 0; shiftState < 16; shiftState++) { if (!(shiftStatesWithDeadKeys & (1 << shiftState))) { continue; } VirtualKey::FillKbdState(kbdState, shiftState); for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) { int32_t vki = GetKeyIndex(virtualKey); if (vki >= 0 && mVirtualKeys[vki].IsDeadKey(shiftState)) { DeadKeyEntry deadKeyArray[256]; int32_t n = GetDeadKeyCombinations( virtualKey, kbdState, shiftStatesWithBaseChars, deadKeyArray, ArrayLength(deadKeyArray)); const DeadKeyTable* dkt = mVirtualKeys[vki].MatchingDeadKeyTable(deadKeyArray, n); if (!dkt) { dkt = AddDeadKeyTable(deadKeyArray, n); } mVirtualKeys[vki].AttachDeadKeyTable(shiftState, dkt); } } } ::SetKeyboardState(originalKbdState); if (MOZ_LOG_TEST(gKeyLog, LogLevel::Verbose)) { static const UINT kExtendedScanCode[] = {0x0000, 0xE000}; static const UINT kMapType = MAPVK_VSC_TO_VK_EX; MOZ_LOG(gKeyLog, LogLevel::Verbose, ("Logging virtual keycode values for scancode (0x%p)...", mKeyboardLayout)); for (uint32_t i = 0; i < ArrayLength(kExtendedScanCode); i++) { for (uint32_t j = 1; j <= 0xFF; j++) { UINT scanCode = kExtendedScanCode[i] + j; UINT virtualKeyCode = ::MapVirtualKeyEx(scanCode, kMapType, mKeyboardLayout); MOZ_LOG(gKeyLog, LogLevel::Verbose, ("0x%04X, %s", scanCode, kVirtualKeyName[virtualKeyCode])); } } } MOZ_LOG(gKeyLog, LogLevel::Info, (" AltGr key is %s in %s", mHasAltGr ? "found" : "not found", GetLayoutName(aLayout).get())); } inline int32_t KeyboardLayout::GetKeyIndex(uint8_t aVirtualKey) { // Currently these 68 (NS_NUM_OF_KEYS) virtual keys are assumed // to produce visible representation: // 0x20 - VK_SPACE ' ' // 0x30..0x39 '0'..'9' // 0x41..0x5A 'A'..'Z' // 0x60..0x69 '0'..'9' on numpad // 0x6A - VK_MULTIPLY '*' on numpad // 0x6B - VK_ADD '+' on numpad // 0x6D - VK_SUBTRACT '-' on numpad // 0x6E - VK_DECIMAL '.' on numpad // 0x6F - VK_DIVIDE '/' on numpad // 0x6E - VK_DECIMAL '.' // 0xBA - VK_OEM_1 ';:' for US // 0xBB - VK_OEM_PLUS '+' any country // 0xBC - VK_OEM_COMMA ',' any country // 0xBD - VK_OEM_MINUS '-' any country // 0xBE - VK_OEM_PERIOD '.' any country // 0xBF - VK_OEM_2 '/?' for US // 0xC0 - VK_OEM_3 '`~' for US // 0xC1 - VK_ABNT_C1 '/?' for Brazilian // 0xC2 - VK_ABNT_C2 separator key on numpad (Brazilian or JIS for Mac) // 0xDB - VK_OEM_4 '[{' for US // 0xDC - VK_OEM_5 '\|' for US // 0xDD - VK_OEM_6 ']}' for US // 0xDE - VK_OEM_7 ''"' for US // 0xDF - VK_OEM_8 // 0xE1 - no name // 0xE2 - VK_OEM_102 '\_' for JIS // 0xE3 - no name // 0xE4 - no name static const int8_t xlat[256] = { // 0 1 2 3 4 5 6 7 8 9 A B C D E F //----------------------------------------------------------------------- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, -1, -1, -1, -1, -1, // 30 -1, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 40 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, -1, -1, // 50 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, -1, 49, 50, 51, // 60 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 70 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 90 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // A0 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 52, 53, 54, 55, 56, 57, // B0 58, 59, 60, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // C0 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 61, 62, 63, 64, 65, // D0 -1, 66, 67, 68, 69, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // E0 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // F0 }; return xlat[aVirtualKey]; } int KeyboardLayout::CompareDeadKeyEntries(const void* aArg1, const void* aArg2, void*) { const DeadKeyEntry* arg1 = static_cast(aArg1); const DeadKeyEntry* arg2 = static_cast(aArg2); return arg1->BaseChar - arg2->BaseChar; } const DeadKeyTable* KeyboardLayout::AddDeadKeyTable( const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) { DeadKeyTableListEntry* next = mDeadKeyTableListHead; const size_t bytes = offsetof(DeadKeyTableListEntry, data) + DeadKeyTable::SizeInBytes(aEntries); uint8_t* p = new uint8_t[bytes]; mDeadKeyTableListHead = reinterpret_cast(p); mDeadKeyTableListHead->next = next; DeadKeyTable* dkt = reinterpret_cast(mDeadKeyTableListHead->data); dkt->Init(aDeadKeyArray, aEntries); return dkt; } void KeyboardLayout::ReleaseDeadKeyTables() { while (mDeadKeyTableListHead) { uint8_t* p = reinterpret_cast(mDeadKeyTableListHead); mDeadKeyTableListHead = mDeadKeyTableListHead->next; delete[] p; } } bool KeyboardLayout::EnsureDeadKeyActive(bool aIsActive, uint8_t aDeadKey, const PBYTE aDeadKeyKbdState) { int32_t ret; do { char16_t dummyChars[5]; ret = ::ToUnicodeEx(aDeadKey, 0, (PBYTE)aDeadKeyKbdState, (LPWSTR)dummyChars, ArrayLength(dummyChars), 0, mKeyboardLayout); // returned values: // <0 - Dead key state is active. The keyboard driver will wait for next // character. // 1 - Previous pressed key was a valid base character that produced // exactly one composite character. // >1 - Previous pressed key does not produce any composite characters. // Return dead-key character followed by base character(s). } while ((ret < 0) != aIsActive); return (ret < 0); } void KeyboardLayout::ActivateDeadKeyState(const NativeKey& aNativeKey) { // Dead-key state should be activated at keydown. if (!aNativeKey.IsKeyDownMessage()) { return; } mActiveDeadKeys.AppendElement(aNativeKey.mOriginalVirtualKeyCode); mDeadKeyShiftStates.AppendElement(aNativeKey.GetShiftState()); } void KeyboardLayout::DeactivateDeadKeyState() { if (mActiveDeadKeys.IsEmpty()) { return; } BYTE kbdState[256]; memset(kbdState, 0, sizeof(kbdState)); // Assume that the last dead key can finish dead key sequence. VirtualKey::FillKbdState(kbdState, mDeadKeyShiftStates.LastElement()); EnsureDeadKeyActive(false, mActiveDeadKeys.LastElement(), kbdState); mActiveDeadKeys.Clear(); mDeadKeyShiftStates.Clear(); } bool KeyboardLayout::AddDeadKeyEntry(char16_t aBaseChar, char16_t aCompositeChar, DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) { for (uint32_t index = 0; index < aEntries; index++) { if (aDeadKeyArray[index].BaseChar == aBaseChar) { return false; } } aDeadKeyArray[aEntries].BaseChar = aBaseChar; aDeadKeyArray[aEntries].CompositeChar = aCompositeChar; return true; } uint32_t KeyboardLayout::GetDeadKeyCombinations( uint8_t aDeadKey, const PBYTE aDeadKeyKbdState, uint16_t aShiftStatesWithBaseChars, DeadKeyEntry* aDeadKeyArray, uint32_t aMaxEntries) { bool deadKeyActive = false; uint32_t entries = 0; BYTE kbdState[256]; memset(kbdState, 0, sizeof(kbdState)); for (uint32_t shiftState = 0; shiftState < 16; shiftState++) { if (!(aShiftStatesWithBaseChars & (1 << shiftState))) { continue; } VirtualKey::FillKbdState(kbdState, shiftState); for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) { int32_t vki = GetKeyIndex(virtualKey); // Dead-key can pair only with such key that produces exactly one base // character. if (vki >= 0 && mVirtualKeys[vki].GetNativeUniChars(shiftState).Length() == 1) { // Ensure dead-key is in active state, when it swallows entered // character and waits for the next pressed key. if (!deadKeyActive) { deadKeyActive = EnsureDeadKeyActive(true, aDeadKey, aDeadKeyKbdState); } // Depending on the character the followed the dead-key, the keyboard // driver can produce one composite character, or a dead-key character // followed by a second character. char16_t compositeChars[5]; int32_t ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)compositeChars, ArrayLength(compositeChars), 0, mKeyboardLayout); switch (ret) { case 0: // This key combination does not produce any characters. The // dead-key is still in active state. break; case 1: { char16_t baseChars[5]; ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)baseChars, ArrayLength(baseChars), 0, mKeyboardLayout); if (entries < aMaxEntries) { switch (ret) { case 1: // Exactly one composite character produced. Now, when // dead-key is not active, repeat the last character one more // time to determine the base character. if (AddDeadKeyEntry(baseChars[0], compositeChars[0], aDeadKeyArray, entries)) { entries++; } deadKeyActive = false; break; case -1: { // If pressing another dead-key produces different character, // we should register the dead-key entry with first character // produced by current key. // First inactivate the dead-key state completely. deadKeyActive = EnsureDeadKeyActive(false, aDeadKey, aDeadKeyKbdState); if (NS_WARN_IF(deadKeyActive)) { MOZ_LOG(gKeyLog, LogLevel::Error, (" failed to deactivating the dead-key state...")); break; } for (int32_t i = 0; i < 5; ++i) { ret = ::ToUnicodeEx( virtualKey, 0, kbdState, (LPWSTR)baseChars, ArrayLength(baseChars), 0, mKeyboardLayout); if (ret >= 0) { break; } } if (ret > 0 && AddDeadKeyEntry(baseChars[0], compositeChars[0], aDeadKeyArray, entries)) { entries++; } // Inactivate dead-key state for current virtual keycode. EnsureDeadKeyActive(false, virtualKey, kbdState); break; } default: NS_WARNING("File a bug for this dead-key handling!"); deadKeyActive = false; break; } } MOZ_LOG( gKeyLog, LogLevel::Verbose, (" %s -> %s (%d): DeadKeyEntry(%s, %s) (ret=%d)", kVirtualKeyName[aDeadKey], kVirtualKeyName[virtualKey], vki, GetCharacterCodeNames(compositeChars, 1).get(), ret <= 0 ? "''" : GetCharacterCodeNames(baseChars, std::min(ret, 5)).get(), ret)); break; } default: // 1. Unexpected dead-key. Dead-key chaining is not supported. // 2. More than one character generated. This is not a valid // dead-key and base character combination. deadKeyActive = false; MOZ_LOG( gKeyLog, LogLevel::Verbose, (" %s -> %s (%d): Unsupport dead key type(%s) (ret=%d)", kVirtualKeyName[aDeadKey], kVirtualKeyName[virtualKey], vki, ret <= 0 ? "''" : GetCharacterCodeNames(compositeChars, std::min(ret, 5)) .get(), ret)); break; } } } } if (deadKeyActive) { deadKeyActive = EnsureDeadKeyActive(false, aDeadKey, aDeadKeyKbdState); } NS_QuickSort(aDeadKeyArray, entries, sizeof(DeadKeyEntry), CompareDeadKeyEntries, nullptr); return entries; } uint32_t KeyboardLayout::ConvertNativeKeyCodeToDOMKeyCode( UINT aNativeKeyCode) const { // Alphabet or Numeric or Numpad or Function keys if ((aNativeKeyCode >= 0x30 && aNativeKeyCode <= 0x39) || (aNativeKeyCode >= 0x41 && aNativeKeyCode <= 0x5A) || (aNativeKeyCode >= 0x60 && aNativeKeyCode <= 0x87)) { return static_cast(aNativeKeyCode); } switch (aNativeKeyCode) { // Following keycodes are same as our DOM keycodes case VK_CANCEL: case VK_BACK: case VK_TAB: case VK_CLEAR: case VK_RETURN: case VK_SHIFT: case VK_CONTROL: case VK_MENU: // Alt case VK_PAUSE: case VK_CAPITAL: // CAPS LOCK case VK_KANA: // same as VK_HANGUL case VK_JUNJA: case VK_FINAL: case VK_HANJA: // same as VK_KANJI case VK_ESCAPE: case VK_CONVERT: case VK_NONCONVERT: case VK_ACCEPT: case VK_MODECHANGE: case VK_SPACE: case VK_PRIOR: // PAGE UP case VK_NEXT: // PAGE DOWN case VK_END: case VK_HOME: case VK_LEFT: case VK_UP: case VK_RIGHT: case VK_DOWN: case VK_SELECT: case VK_PRINT: case VK_EXECUTE: case VK_SNAPSHOT: case VK_INSERT: case VK_DELETE: case VK_APPS: // Context Menu case VK_SLEEP: case VK_NUMLOCK: case VK_SCROLL: // SCROLL LOCK case VK_ATTN: // Attension key of IBM midrange computers, e.g., AS/400 case VK_CRSEL: // Cursor Selection case VK_EXSEL: // Extend Selection case VK_EREOF: // Erase EOF key of IBM 3270 keyboard layout case VK_PLAY: case VK_ZOOM: case VK_PA1: // PA1 key of IBM 3270 keyboard layout return uint32_t(aNativeKeyCode); case VK_HELP: return NS_VK_HELP; // Windows key should be mapped to a Win keycode // They should be able to be distinguished by DOM3 KeyboardEvent.location case VK_LWIN: case VK_RWIN: return NS_VK_WIN; case VK_VOLUME_MUTE: return NS_VK_VOLUME_MUTE; case VK_VOLUME_DOWN: return NS_VK_VOLUME_DOWN; case VK_VOLUME_UP: return NS_VK_VOLUME_UP; case VK_LSHIFT: case VK_RSHIFT: return NS_VK_SHIFT; case VK_LCONTROL: case VK_RCONTROL: return NS_VK_CONTROL; // Note that even if the key is AltGr, we should return NS_VK_ALT for // compatibility with both older Gecko and the other browsers. case VK_LMENU: case VK_RMENU: return NS_VK_ALT; // Following keycodes are not defined in our DOM keycodes. case VK_BROWSER_BACK: case VK_BROWSER_FORWARD: case VK_BROWSER_REFRESH: case VK_BROWSER_STOP: case VK_BROWSER_SEARCH: case VK_BROWSER_FAVORITES: case VK_BROWSER_HOME: case VK_MEDIA_NEXT_TRACK: case VK_MEDIA_PREV_TRACK: case VK_MEDIA_STOP: case VK_MEDIA_PLAY_PAUSE: case VK_LAUNCH_MAIL: case VK_LAUNCH_MEDIA_SELECT: case VK_LAUNCH_APP1: case VK_LAUNCH_APP2: return 0; // Following OEM specific virtual keycodes should pass through DOM keyCode // for compatibility with the other browsers on Windows. // Following OEM specific virtual keycodes are defined for Fujitsu/OASYS. case VK_OEM_FJ_JISHO: case VK_OEM_FJ_MASSHOU: case VK_OEM_FJ_TOUROKU: case VK_OEM_FJ_LOYA: case VK_OEM_FJ_ROYA: // Not sure what means "ICO". case VK_ICO_HELP: case VK_ICO_00: case VK_ICO_CLEAR: // Following OEM specific virtual keycodes are defined for Nokia/Ericsson. case VK_OEM_RESET: case VK_OEM_JUMP: case VK_OEM_PA1: case VK_OEM_PA2: case VK_OEM_PA3: case VK_OEM_WSCTRL: case VK_OEM_CUSEL: case VK_OEM_ATTN: case VK_OEM_FINISH: case VK_OEM_COPY: case VK_OEM_AUTO: case VK_OEM_ENLW: case VK_OEM_BACKTAB: // VK_OEM_CLEAR is defined as not OEM specific, but let's pass though // DOM keyCode like other OEM specific virtual keycodes. case VK_OEM_CLEAR: return uint32_t(aNativeKeyCode); // 0xE1 is an OEM specific virtual keycode. However, the value is already // used in our DOM keyCode for AltGr on Linux. So, this virtual keycode // cannot pass through DOM keyCode. case 0xE1: return 0; // Following keycodes are OEM keys which are keycodes for non-alphabet and // non-numeric keys, we should compute each keycode of them from unshifted // character which is inputted by each key. But if the unshifted character // is not an ASCII character but shifted character is an ASCII character, // we should refer it. case VK_OEM_1: case VK_OEM_PLUS: case VK_OEM_COMMA: case VK_OEM_MINUS: case VK_OEM_PERIOD: case VK_OEM_2: case VK_OEM_3: case VK_OEM_4: case VK_OEM_5: case VK_OEM_6: case VK_OEM_7: case VK_OEM_8: case VK_OEM_102: case VK_ABNT_C1: { NS_ASSERTION(IsPrintableCharKey(aNativeKeyCode), "The key must be printable"); ModifierKeyState modKeyState(0); UniCharsAndModifiers uniChars = GetUniCharsAndModifiers(aNativeKeyCode, modKeyState); if (uniChars.Length() != 1 || uniChars.CharAt(0) < ' ' || uniChars.CharAt(0) > 0x7F) { modKeyState.Set(MODIFIER_SHIFT); uniChars = GetUniCharsAndModifiers(aNativeKeyCode, modKeyState); if (uniChars.Length() != 1 || uniChars.CharAt(0) < ' ' || uniChars.CharAt(0) > 0x7F) { // In this case, we've returned 0 in this case for long time because // we decided that we should avoid setting same keyCode value to 2 or // more keys since active keyboard layout may have a key to input the // punctuation with different key. However, setting keyCode to 0 // makes some web applications which are aware of neither // KeyboardEvent.key nor KeyboardEvent.code not work with Firefox // when user selects non-ASCII capable keyboard layout such as // Russian and Thai layout. So, let's decide keyCode value with // major keyboard layout's key which causes the OEM keycode. // Actually, this maps same keyCode value to 2 keys on Russian // keyboard layout. "Period" key causes VK_OEM_PERIOD but inputs // Yu of Cyrillic and "Slash" key causes VK_OEM_2 (same as US // keyboard layout) but inputs "." (period of ASCII). Therefore, // we return DOM_VK_PERIOD which is same as VK_OEM_PERIOD for // "Period" key. On the other hand, we use same keyCode value for // "Slash" key too because it inputs ".". CodeNameIndex code; switch (aNativeKeyCode) { case VK_OEM_1: code = CODE_NAME_INDEX_Semicolon; break; case VK_OEM_PLUS: code = CODE_NAME_INDEX_Equal; break; case VK_OEM_COMMA: code = CODE_NAME_INDEX_Comma; break; case VK_OEM_MINUS: code = CODE_NAME_INDEX_Minus; break; case VK_OEM_PERIOD: code = CODE_NAME_INDEX_Period; break; case VK_OEM_2: code = CODE_NAME_INDEX_Slash; break; case VK_OEM_3: code = CODE_NAME_INDEX_Backquote; break; case VK_OEM_4: code = CODE_NAME_INDEX_BracketLeft; break; case VK_OEM_5: code = CODE_NAME_INDEX_Backslash; break; case VK_OEM_6: code = CODE_NAME_INDEX_BracketRight; break; case VK_OEM_7: code = CODE_NAME_INDEX_Quote; break; case VK_OEM_8: // Use keyCode value for "Backquote" key on UK keyboard layout. code = CODE_NAME_INDEX_Backquote; break; case VK_OEM_102: // Use keyCode value for "IntlBackslash" key. code = CODE_NAME_INDEX_IntlBackslash; break; case VK_ABNT_C1: // "/" of ABNT. // Use keyCode value for "IntlBackslash" key on ABNT keyboard // layout. code = CODE_NAME_INDEX_IntlBackslash; break; default: MOZ_ASSERT_UNREACHABLE("Handle all OEM keycode values"); return 0; } return WidgetKeyboardEvent::GetFallbackKeyCodeOfPunctuationKey(code); } } return WidgetUtils::ComputeKeyCodeFromChar(uniChars.CharAt(0)); } // IE sets 0xC2 to the DOM keyCode for VK_ABNT_C2. However, we're already // using NS_VK_SEPARATOR for the separator key on Mac and Linux. Therefore, // We should keep consistency between Gecko on all platforms rather than // with other browsers since a lot of keyCode values are already different // between browsers. case VK_ABNT_C2: return NS_VK_SEPARATOR; // VK_PROCESSKEY means IME already consumed the key event. case VK_PROCESSKEY: return NS_VK_PROCESSKEY; // VK_PACKET is generated by SendInput() API, we don't need to // care this message as key event. case VK_PACKET: return 0; // If a key is not mapped to a virtual keycode, 0xFF is used. case 0xFF: NS_WARNING("The key is failed to be converted to a virtual keycode"); return 0; } #ifdef DEBUG nsPrintfCString warning( "Unknown virtual keycode (0x%08X), please check the " "latest MSDN document, there may be some new " "keycodes we've never known.", aNativeKeyCode); NS_WARNING(warning.get()); #endif return 0; } KeyNameIndex KeyboardLayout::ConvertNativeKeyCodeToKeyNameIndex( uint8_t aVirtualKey) const { if (IsPrintableCharKey(aVirtualKey) || aVirtualKey == VK_PACKET) { return KEY_NAME_INDEX_USE_STRING; } // If the keyboard layout has AltGr and AltRight key is pressed, // return AltGraph. if (aVirtualKey == VK_RMENU && HasAltGr()) { return KEY_NAME_INDEX_AltGraph; } switch (aVirtualKey) { #undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX #define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \ case aNativeKey: \ return aKeyNameIndex; #include "NativeKeyToDOMKeyName.h" #undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX default: break; } HKL layout = GetLayout(); WORD langID = LOWORD(static_cast(layout)); WORD primaryLangID = PRIMARYLANGID(langID); if (primaryLangID == LANG_JAPANESE) { switch (aVirtualKey) { #undef NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX #define NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, \ aKeyNameIndex) \ case aNativeKey: \ return aKeyNameIndex; #include "NativeKeyToDOMKeyName.h" #undef NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX default: break; } } else if (primaryLangID == LANG_KOREAN) { switch (aVirtualKey) { #undef NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX #define NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \ case aNativeKey: \ return aKeyNameIndex; #include "NativeKeyToDOMKeyName.h" #undef NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX default: return KEY_NAME_INDEX_Unidentified; } } switch (aVirtualKey) { #undef NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX #define NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \ case aNativeKey: \ return aKeyNameIndex; #include "NativeKeyToDOMKeyName.h" #undef NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX default: return KEY_NAME_INDEX_Unidentified; } } // static CodeNameIndex KeyboardLayout::ConvertScanCodeToCodeNameIndex(UINT aScanCode) { switch (aScanCode) { #define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \ case aNativeKey: \ return aCodeNameIndex; #include "NativeKeyToDOMCodeName.h" #undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX default: return CODE_NAME_INDEX_UNKNOWN; } } nsresult KeyboardLayout::SynthesizeNativeKeyEvent( nsWindow* aWidget, int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode, uint32_t aModifierFlags, const nsAString& aCharacters, const nsAString& aUnmodifiedCharacters) { UINT keyboardLayoutListCount = ::GetKeyboardLayoutList(0, nullptr); NS_ASSERTION(keyboardLayoutListCount > 0, "One keyboard layout must be installed at least"); HKL keyboardLayoutListBuff[50]; HKL* keyboardLayoutList = keyboardLayoutListCount < 50 ? keyboardLayoutListBuff : new HKL[keyboardLayoutListCount]; keyboardLayoutListCount = ::GetKeyboardLayoutList(keyboardLayoutListCount, keyboardLayoutList); NS_ASSERTION(keyboardLayoutListCount > 0, "Failed to get all keyboard layouts installed on the system"); nsPrintfCString layoutName("%08x", aNativeKeyboardLayout); HKL loadedLayout = LoadKeyboardLayoutA(layoutName.get(), KLF_NOTELLSHELL); if (loadedLayout == nullptr) { if (keyboardLayoutListBuff != keyboardLayoutList) { delete[] keyboardLayoutList; } return NS_ERROR_NOT_AVAILABLE; } // Setup clean key state and load desired layout BYTE originalKbdState[256]; ::GetKeyboardState(originalKbdState); BYTE kbdState[256]; memset(kbdState, 0, sizeof(kbdState)); // This changes the state of the keyboard for the current thread only, // and we'll restore it soon, so this should be OK. ::SetKeyboardState(kbdState); OverrideLayout(loadedLayout); bool isAltGrKeyPress = false; if (aModifierFlags & nsIWidget::ALTGRAPH) { if (!HasAltGr()) { return NS_ERROR_INVALID_ARG; } // AltGr emulates ControlLeft key press and AltRight key press. // So, we should remove those flags from aModifierFlags before // calling WinUtils::SetupKeyModifiersSequence() to create correct // key sequence. // FYI: We don't support both ControlLeft and AltRight (AltGr) are // pressed at the same time unless synthesizing key is // VK_LCONTROL. aModifierFlags &= ~(nsIWidget::CTRL_L | nsIWidget::ALT_R); } uint8_t argumentKeySpecific = 0; switch (aNativeKeyCode & 0xFF) { case VK_SHIFT: aModifierFlags &= ~(nsIWidget::SHIFT_L | nsIWidget::SHIFT_R); argumentKeySpecific = VK_LSHIFT; break; case VK_LSHIFT: aModifierFlags &= ~nsIWidget::SHIFT_L; argumentKeySpecific = aNativeKeyCode & 0xFF; aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_SHIFT; break; case VK_RSHIFT: aModifierFlags &= ~nsIWidget::SHIFT_R; argumentKeySpecific = aNativeKeyCode & 0xFF; aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_SHIFT; break; case VK_CONTROL: aModifierFlags &= ~(nsIWidget::CTRL_L | nsIWidget::CTRL_R); argumentKeySpecific = VK_LCONTROL; break; case VK_LCONTROL: aModifierFlags &= ~nsIWidget::CTRL_L; argumentKeySpecific = aNativeKeyCode & 0xFF; aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_CONTROL; break; case VK_RCONTROL: aModifierFlags &= ~nsIWidget::CTRL_R; argumentKeySpecific = aNativeKeyCode & 0xFF; aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_CONTROL; break; case VK_MENU: aModifierFlags &= ~(nsIWidget::ALT_L | nsIWidget::ALT_R); argumentKeySpecific = VK_LMENU; break; case VK_LMENU: aModifierFlags &= ~nsIWidget::ALT_L; argumentKeySpecific = aNativeKeyCode & 0xFF; aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_MENU; break; case VK_RMENU: aModifierFlags &= ~(nsIWidget::ALT_R | nsIWidget::ALTGRAPH); argumentKeySpecific = aNativeKeyCode & 0xFF; aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_MENU; // If AltRight key is AltGr in the keyboard layout, let's use // SetupKeyModifiersSequence() to emulate the native behavior // since the same event order between keydown and keyup makes // the following code complicated. if (HasAltGr()) { isAltGrKeyPress = true; aModifierFlags &= ~nsIWidget::CTRL_L; aModifierFlags |= nsIWidget::ALTGRAPH; } break; case VK_CAPITAL: aModifierFlags &= ~nsIWidget::CAPS_LOCK; argumentKeySpecific = VK_CAPITAL; break; case VK_NUMLOCK: aModifierFlags &= ~nsIWidget::NUM_LOCK; argumentKeySpecific = VK_NUMLOCK; break; } AutoTArray keySequence; WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags, WM_KEYDOWN); if (!isAltGrKeyPress) { keySequence.AppendElement(KeyPair(aNativeKeyCode, argumentKeySpecific)); } // Simulate the pressing of each modifier key and then the real key // FYI: Each NativeKey instance here doesn't need to override keyboard layout // since this method overrides and restores the keyboard layout. for (uint32_t i = 0; i < keySequence.Length(); ++i) { uint8_t key = keySequence[i].mGeneral; uint8_t keySpecific = keySequence[i].mSpecific; uint16_t scanCode = keySequence[i].mScanCode; kbdState[key] = 0x81; // key is down and toggled on if appropriate if (keySpecific) { kbdState[keySpecific] = 0x81; } ::SetKeyboardState(kbdState); ModifierKeyState modKeyState; // If scan code isn't specified explicitly, let's compute it with current // keyboard layout. if (!scanCode) { scanCode = ComputeScanCodeForVirtualKeyCode(keySpecific ? keySpecific : key); } LPARAM lParam = static_cast(scanCode << 16); // If the scan code is for an extended key, set extended key flag. if ((scanCode & 0xFF00) == 0xE000) { lParam |= 0x1000000; } // When AltGr key is pressed, both ControlLeft and AltRight cause // WM_KEYDOWN messages. bool makeSysKeyMsg = !(aModifierFlags & nsIWidget::ALTGRAPH) && IsSysKey(key, modKeyState); MSG keyDownMsg = WinUtils::InitMSG(makeSysKeyMsg ? WM_SYSKEYDOWN : WM_KEYDOWN, key, lParam, aWidget->GetWindowHandle()); if (i == keySequence.Length() - 1) { bool makeDeadCharMsg = (IsDeadKey(key, modKeyState) && aCharacters.IsEmpty()); nsAutoString chars(aCharacters); if (makeDeadCharMsg) { UniCharsAndModifiers deadChars = GetUniCharsAndModifiers(key, modKeyState); chars = deadChars.ToString(); NS_ASSERTION(chars.Length() == 1, "Dead char must be only one character"); } if (chars.IsEmpty()) { NativeKey nativeKey(aWidget, keyDownMsg, modKeyState); nativeKey.HandleKeyDownMessage(); } else { AutoTArray fakeCharMsgs; for (uint32_t j = 0; j < chars.Length(); j++) { NativeKey::FakeCharMsg* fakeCharMsg = fakeCharMsgs.AppendElement(); fakeCharMsg->mCharCode = chars.CharAt(j); fakeCharMsg->mScanCode = scanCode; fakeCharMsg->mIsSysKey = makeSysKeyMsg; fakeCharMsg->mIsDeadKey = makeDeadCharMsg; } NativeKey nativeKey(aWidget, keyDownMsg, modKeyState, 0, &fakeCharMsgs); bool dispatched; nativeKey.HandleKeyDownMessage(&dispatched); // If some char messages are not consumed, let's emulate the widget // receiving the message directly. for (uint32_t j = 1; j < fakeCharMsgs.Length(); j++) { if (fakeCharMsgs[j].mConsumed) { continue; } MSG charMsg = fakeCharMsgs[j].GetCharMsg(aWidget->GetWindowHandle()); NativeKey nativeKey(aWidget, charMsg, modKeyState); nativeKey.HandleCharMessage(charMsg); } } } else { NativeKey nativeKey(aWidget, keyDownMsg, modKeyState); nativeKey.HandleKeyDownMessage(); } } keySequence.Clear(); if (!isAltGrKeyPress) { keySequence.AppendElement(KeyPair(aNativeKeyCode, argumentKeySpecific)); } WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags, WM_KEYUP); for (uint32_t i = 0; i < keySequence.Length(); ++i) { uint8_t key = keySequence[i].mGeneral; uint8_t keySpecific = keySequence[i].mSpecific; uint16_t scanCode = keySequence[i].mScanCode; kbdState[key] = 0; // key is up and toggled off if appropriate if (keySpecific) { kbdState[keySpecific] = 0; } ::SetKeyboardState(kbdState); ModifierKeyState modKeyState; // If scan code isn't specified explicitly, let's compute it with current // keyboard layout. if (!scanCode) { scanCode = ComputeScanCodeForVirtualKeyCode(keySpecific ? keySpecific : key); } LPARAM lParam = static_cast(scanCode << 16); // If the scan code is for an extended key, set extended key flag. if ((scanCode & 0xFF00) == 0xE000) { lParam |= 0x1000000; } // Don't use WM_SYSKEYUP for Alt keyup. // NOTE: When AltGr was pressed, ControlLeft causes WM_SYSKEYUP normally. bool makeSysKeyMsg = IsSysKey(key, modKeyState) && key != VK_MENU; MSG keyUpMsg = WinUtils::InitMSG(makeSysKeyMsg ? WM_SYSKEYUP : WM_KEYUP, key, lParam, aWidget->GetWindowHandle()); NativeKey nativeKey(aWidget, keyUpMsg, modKeyState); nativeKey.HandleKeyUpMessage(); } // Restore old key state and layout ::SetKeyboardState(originalKbdState); RestoreLayout(); // Don't unload the layout if it's installed actually. for (uint32_t i = 0; i < keyboardLayoutListCount; i++) { if (keyboardLayoutList[i] == loadedLayout) { loadedLayout = 0; break; } } if (keyboardLayoutListBuff != keyboardLayoutList) { delete[] keyboardLayoutList; } if (loadedLayout) { ::UnloadKeyboardLayout(loadedLayout); } return NS_OK; } /***************************************************************************** * mozilla::widget::DeadKeyTable *****************************************************************************/ char16_t DeadKeyTable::GetCompositeChar(char16_t aBaseChar) const { // Dead-key table is sorted by BaseChar in ascending order. // Usually they are too small to use binary search. for (uint32_t index = 0; index < mEntries; index++) { if (mTable[index].BaseChar == aBaseChar) { return mTable[index].CompositeChar; } if (mTable[index].BaseChar > aBaseChar) { break; } } return 0; } /***************************************************************************** * mozilla::widget::RedirectedKeyDownMessage *****************************************************************************/ MSG RedirectedKeyDownMessageManager::sRedirectedKeyDownMsg; bool RedirectedKeyDownMessageManager::sDefaultPreventedOfRedirectedMsg = false; // static bool RedirectedKeyDownMessageManager::IsRedirectedMessage(const MSG& aMsg) { return (aMsg.message == WM_KEYDOWN || aMsg.message == WM_SYSKEYDOWN) && (sRedirectedKeyDownMsg.message == aMsg.message && WinUtils::GetScanCode(sRedirectedKeyDownMsg.lParam) == WinUtils::GetScanCode(aMsg.lParam)); } // static void RedirectedKeyDownMessageManager::RemoveNextCharMessage(HWND aWnd) { MSG msg; if (WinUtils::PeekMessage(&msg, aWnd, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD) && (msg.message == WM_CHAR || msg.message == WM_SYSCHAR)) { WinUtils::PeekMessage(&msg, aWnd, msg.message, msg.message, PM_REMOVE | PM_NOYIELD); } } } // namespace widget } // namespace mozilla