diff options
Diffstat (limited to 'widget/windows/KeyboardLayout.h')
-rw-r--r-- | widget/windows/KeyboardLayout.h | 1120 |
1 files changed, 1120 insertions, 0 deletions
diff --git a/widget/windows/KeyboardLayout.h b/widget/windows/KeyboardLayout.h new file mode 100644 index 0000000000..c7c3bcc56d --- /dev/null +++ b/widget/windows/KeyboardLayout.h @@ -0,0 +1,1120 @@ +/* -*- 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/. */ + +#ifndef KeyboardLayout_h__ +#define KeyboardLayout_h__ + +#include "mozilla/RefPtr.h" +#include "nscore.h" +#include "nsString.h" +#include "nsWindow.h" +#include "nsWindowDefs.h" +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/widget/WinMessages.h" +#include "mozilla/widget/WinModifierKeyState.h" +#include <windows.h> + +#define NS_NUM_OF_KEYS 70 + +#define VK_OEM_1 0xBA // ';:' for US +#define VK_OEM_PLUS 0xBB // '+' any country +#define VK_OEM_COMMA 0xBC +#define VK_OEM_MINUS 0xBD // '-' any country +#define VK_OEM_PERIOD 0xBE +#define VK_OEM_2 0xBF +#define VK_OEM_3 0xC0 +// '/?' for Brazilian (ABNT) +#define VK_ABNT_C1 0xC1 +// Separator in Numpad for Brazilian (ABNT) or JIS keyboard for Mac. +#define VK_ABNT_C2 0xC2 +#define VK_OEM_4 0xDB +#define VK_OEM_5 0xDC +#define VK_OEM_6 0xDD +#define VK_OEM_7 0xDE +#define VK_OEM_8 0xDF +#define VK_OEM_102 0xE2 +#define VK_OEM_CLEAR 0xFE + +class nsIUserIdleServiceInternal; + +namespace mozilla { +namespace widget { + +enum ScanCode : uint16_t { + eCapsLock = 0x003A, + eNumLock = 0xE045, + eShiftLeft = 0x002A, + eShiftRight = 0x0036, + eControlLeft = 0x001D, + eControlRight = 0xE01D, + eAltLeft = 0x0038, + eAltRight = 0xE038, +}; + +// 0: nsIWidget's native modifier flag +// 1: Virtual keycode which does not distinguish whether left or right location. +// 2: Virtual keycode which distinguishes whether left or right location. +// 3: Scan code. +static const uint32_t sModifierKeyMap[][4] = { + {nsIWidget::CAPS_LOCK, VK_CAPITAL, 0, ScanCode::eCapsLock}, + {nsIWidget::NUM_LOCK, VK_NUMLOCK, 0, ScanCode::eNumLock}, + {nsIWidget::SHIFT_L, VK_SHIFT, VK_LSHIFT, ScanCode::eShiftLeft}, + {nsIWidget::SHIFT_R, VK_SHIFT, VK_RSHIFT, ScanCode::eShiftRight}, + {nsIWidget::CTRL_L, VK_CONTROL, VK_LCONTROL, ScanCode::eControlLeft}, + {nsIWidget::CTRL_R, VK_CONTROL, VK_RCONTROL, ScanCode::eControlRight}, + {nsIWidget::ALT_L, VK_MENU, VK_LMENU, ScanCode::eAltLeft}, + {nsIWidget::ALT_R, VK_MENU, VK_RMENU, ScanCode::eAltRight}}; + +class KeyboardLayout; + +class MOZ_STACK_CLASS UniCharsAndModifiers final { + public: + UniCharsAndModifiers() {} + UniCharsAndModifiers operator+(const UniCharsAndModifiers& aOther) const; + UniCharsAndModifiers& operator+=(const UniCharsAndModifiers& aOther); + + /** + * Append a pair of unicode character and the final modifier. + */ + void Append(char16_t aUniChar, Modifiers aModifiers); + void Clear() { + mChars.Truncate(); + mModifiers.Clear(); + } + bool IsEmpty() const { + MOZ_ASSERT(mChars.Length() == mModifiers.Length()); + return mChars.IsEmpty(); + } + + char16_t CharAt(size_t aIndex) const { + MOZ_ASSERT(aIndex < Length()); + return mChars[aIndex]; + } + Modifiers ModifiersAt(size_t aIndex) const { + MOZ_ASSERT(aIndex < Length()); + return mModifiers[aIndex]; + } + size_t Length() const { + MOZ_ASSERT(mChars.Length() == mModifiers.Length()); + return mChars.Length(); + } + + bool IsProducingCharsWithAltGr() const { + return !IsEmpty() && (ModifiersAt(0) & MODIFIER_ALTGRAPH) != 0; + } + + void FillModifiers(Modifiers aModifiers); + /** + * OverwriteModifiersIfBeginsWith() assigns mModifiers with aOther between + * [0] and [aOther.mLength - 1] only when mChars begins with aOther.mChars. + */ + void OverwriteModifiersIfBeginsWith(const UniCharsAndModifiers& aOther); + + bool UniCharsEqual(const UniCharsAndModifiers& aOther) const; + bool UniCharsCaseInsensitiveEqual(const UniCharsAndModifiers& aOther) const; + bool BeginsWith(const UniCharsAndModifiers& aOther) const; + + const nsString& ToString() const { return mChars; } + + private: + nsAutoString mChars; + // 5 is enough number for normal keyboard layout handling. On Windows, + // a dead key sequence may cause inputting up to 5 characters per key press. + CopyableAutoTArray<Modifiers, 5> mModifiers; +}; + +struct DeadKeyEntry { + char16_t BaseChar; + char16_t CompositeChar; +}; + +class DeadKeyTable { + friend class KeyboardLayout; + + uint16_t mEntries; + // KeyboardLayout::AddDeadKeyTable() will allocate as many entries as + // required. It is the only way to create new DeadKeyTable instances. + DeadKeyEntry mTable[1]; + + void Init(const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) { + mEntries = aEntries; + memcpy(mTable, aDeadKeyArray, aEntries * sizeof(DeadKeyEntry)); + } + + static uint32_t SizeInBytes(uint32_t aEntries) { + return offsetof(DeadKeyTable, mTable) + aEntries * sizeof(DeadKeyEntry); + } + + public: + uint32_t Entries() const { return mEntries; } + + bool IsEqual(const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) const { + return (mEntries == aEntries && + !memcmp(mTable, aDeadKeyArray, aEntries * sizeof(DeadKeyEntry))); + } + + char16_t GetCompositeChar(char16_t aBaseChar) const; +}; + +class VirtualKey { + public: + enum ShiftStateIndex : uint8_t { + // 0 - Normal + eNormal = 0, + // 1 - Shift + eShift, + // 2 - Control + eControl, + // 3 - Control + Shift + eControlShift, + // 4 - Alt + eAlt, + // 5 - Alt + Shift + eAltShift, + // 6 - Alt + Control (AltGr) + eAltGr, + // 7 - Alt + Control + Shift (AltGr + Shift) + eAltGrShift, + // 8 - CapsLock + eWithCapsLock, + // 9 - CapsLock + Shift + eShiftWithCapsLock, + // 10 - CapsLock + Control + eControlWithCapsLock, + // 11 - CapsLock + Control + Shift + eControlShiftWithCapsLock, + // 12 - CapsLock + Alt + eAltWithCapsLock, + // 13 - CapsLock + Alt + Shift + eAltShiftWithCapsLock, + // 14 - CapsLock + Alt + Control (CapsLock + AltGr) + eAltGrWithCapsLock, + // 15 - CapsLock + Alt + Control + Shift (CapsLock + AltGr + Shift) + eAltGrShiftWithCapsLock, + }; + + enum ShiftStateFlag { + STATE_SHIFT = 0x01, + STATE_CONTROL = 0x02, + STATE_ALT = 0x04, + STATE_CAPSLOCK = 0x08, + // ShiftState needs to have AltGr state separately since this is necessary + // for lossless conversion with Modifiers. + STATE_ALTGRAPH = 0x80, + // Useful to remove or check Ctrl and Alt flags. + STATE_CONTROL_ALT = STATE_CONTROL | STATE_ALT, + }; + + typedef uint8_t ShiftState; + + static ShiftState ModifiersToShiftState(Modifiers aModifiers); + static ShiftState ModifierKeyStateToShiftState( + const ModifierKeyState& aModKeyState) { + return ModifiersToShiftState(aModKeyState.GetModifiers()); + } + static Modifiers ShiftStateToModifiers(ShiftState aShiftState); + static bool IsAltGrIndex(uint8_t aIndex) { + return (aIndex & STATE_CONTROL_ALT) == STATE_CONTROL_ALT; + } + + private: + union KeyShiftState { + struct { + char16_t Chars[4]; + } Normal; + struct { + const DeadKeyTable* Table; + char16_t DeadChar; + } DeadKey; + }; + + KeyShiftState mShiftStates[16]; + uint16_t mIsDeadKey; + + static uint8_t ToShiftStateIndex(ShiftState aShiftState) { + if (!(aShiftState & STATE_ALTGRAPH)) { + MOZ_ASSERT(aShiftState <= eAltGrShiftWithCapsLock); + return static_cast<uint8_t>(aShiftState); + } + uint8_t index = aShiftState & ~STATE_ALTGRAPH; + index |= (STATE_ALT | STATE_CONTROL); + MOZ_ASSERT(index <= eAltGrShiftWithCapsLock); + return index; + } + + void SetDeadKey(ShiftState aShiftState, bool aIsDeadKey) { + if (aIsDeadKey) { + mIsDeadKey |= 1 << ToShiftStateIndex(aShiftState); + } else { + mIsDeadKey &= ~(1 << ToShiftStateIndex(aShiftState)); + } + } + + public: + static void FillKbdState(PBYTE aKbdState, const ShiftState aShiftState); + + bool IsDeadKey(ShiftState aShiftState) const { + return (mIsDeadKey & (1 << ToShiftStateIndex(aShiftState))) != 0; + } + + /** + * IsChangedByAltGr() is useful to check if a key with AltGr produces + * different character(s) from the key without AltGr. + * Note that this is designed for checking if a keyboard layout has AltGr + * key. So, this result may not exactly correct for the key since it's + * okay to fails in some edge cases when we check all keys which produce + * character(s) in a layout. + */ + bool IsChangedByAltGr(ShiftState aShiftState) const { + MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState)); + MOZ_ASSERT(IsAltGrIndex(aShiftState)); + MOZ_ASSERT(IsDeadKey(aShiftState) || + mShiftStates[aShiftState].Normal.Chars[0]); + const ShiftState kShiftStateWithoutAltGr = + aShiftState - ShiftStateIndex::eAltGr; + if (IsDeadKey(aShiftState) != IsDeadKey(kShiftStateWithoutAltGr)) { + return false; + } + if (IsDeadKey(aShiftState)) { + return mShiftStates[aShiftState].DeadKey.DeadChar != + mShiftStates[kShiftStateWithoutAltGr].DeadKey.DeadChar; + } + for (size_t i = 0; i < 4; i++) { + if (mShiftStates[aShiftState].Normal.Chars[i] != + mShiftStates[kShiftStateWithoutAltGr].Normal.Chars[i]) { + return true; + } + if (!mShiftStates[aShiftState].Normal.Chars[i] && + !mShiftStates[kShiftStateWithoutAltGr].Normal.Chars[i]) { + return false; + } + } + return false; + } + + void AttachDeadKeyTable(ShiftState aShiftState, + const DeadKeyTable* aDeadKeyTable) { + MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState)); + mShiftStates[aShiftState].DeadKey.Table = aDeadKeyTable; + } + + void SetNormalChars(ShiftState aShiftState, const char16_t* aChars, + uint32_t aNumOfChars); + void SetDeadChar(ShiftState aShiftState, char16_t aDeadChar); + const DeadKeyTable* MatchingDeadKeyTable(const DeadKeyEntry* aDeadKeyArray, + uint32_t aEntries) const; + inline char16_t GetCompositeChar(ShiftState aShiftState, + char16_t aBaseChar) const { + return mShiftStates[ToShiftStateIndex(aShiftState)] + .DeadKey.Table->GetCompositeChar(aBaseChar); + } + + char16_t GetCompositeChar(const ModifierKeyState& aModKeyState, + char16_t aBaseChar) const { + return GetCompositeChar(ModifierKeyStateToShiftState(aModKeyState), + aBaseChar); + } + + /** + * GetNativeUniChars() returns character(s) which is produced by the + * key with given modifiers. This does NOT return proper MODIFIER_ALTGRAPH + * state because this is raw accessor of the database of this key. + */ + UniCharsAndModifiers GetNativeUniChars(ShiftState aShiftState) const; + UniCharsAndModifiers GetNativeUniChars( + const ModifierKeyState& aModKeyState) const { + return GetNativeUniChars(ModifierKeyStateToShiftState(aModKeyState)); + } + + /** + * GetUniChars() returns characters and modifiers which are not consumed + * to input the character. + * For example, if you specify Ctrl key but the key produces no character + * with Ctrl, this returns character(s) which is produced by the key + * without Ctrl. So, the result is useful to decide KeyboardEvent.key + * value. + * Another example is, if you specify Ctrl key and the key produces + * different character(s) from the case without Ctrl key, this returns + * the character(s) *without* MODIFIER_CONTROL. This modifier information + * is useful for eKeyPress since TextEditor does not treat eKeyPress events + * whose modifier includes MODIFIER_ALT and/or MODIFIER_CONTROL. + * + * @param aShiftState Modifiers which you want to retrieve + * KeyboardEvent.key value for the key with. + * If AltGr key is pressed, this should include + * STATE_ALTGRAPH and should NOT include + * STATE_ALT nor STATE_CONTROL. + * If both Alt and Ctrl are pressed to emulate + * AltGr, this should include both STATE_ALT and + * STATE_CONTROL but should NOT include + * MODIFIER_ALTGRAPH. + * Then, this returns proper modifiers when + * this key produces no character with AltGr. + */ + UniCharsAndModifiers GetUniChars(ShiftState aShiftState) const; + UniCharsAndModifiers GetUniChars(const ModifierKeyState& aModKeyState) const { + return GetUniChars(ModifierKeyStateToShiftState(aModKeyState)); + } +}; + +class MOZ_STACK_CLASS NativeKey final { + friend class KeyboardLayout; + + public: + struct FakeCharMsg { + UINT mCharCode; + UINT mScanCode; + bool mIsSysKey; + bool mIsDeadKey; + bool mConsumed; + + FakeCharMsg() + : mCharCode(0), + mScanCode(0), + mIsSysKey(false), + mIsDeadKey(false), + mConsumed(false) {} + + MSG GetCharMsg(HWND aWnd) const { + MSG msg; + msg.hwnd = aWnd; + msg.message = mIsDeadKey && mIsSysKey ? WM_SYSDEADCHAR + : mIsDeadKey ? WM_DEADCHAR + : mIsSysKey ? WM_SYSCHAR + : WM_CHAR; + msg.wParam = static_cast<WPARAM>(mCharCode); + msg.lParam = static_cast<LPARAM>(mScanCode << 16); + msg.time = 0; + msg.pt.x = msg.pt.y = 0; + return msg; + } + }; + + NativeKey(nsWindow* aWidget, const MSG& aMessage, + const ModifierKeyState& aModKeyState, + HKL aOverrideKeyboardLayout = 0, + nsTArray<FakeCharMsg>* aFakeCharMsgs = nullptr); + + ~NativeKey(); + + /** + * Handle WM_KEYDOWN message or WM_SYSKEYDOWN message. The instance must be + * initialized with WM_KEYDOWN or WM_SYSKEYDOWN. + * Returns true if dispatched keydown event or keypress event is consumed. + * Otherwise, false. + */ + bool HandleKeyDownMessage(bool* aEventDispatched = nullptr) const; + + /** + * Handles WM_CHAR message or WM_SYSCHAR message. The instance must be + * initialized with them. + * Returns true if dispatched keypress event is consumed. Otherwise, false. + */ + bool HandleCharMessage(bool* aEventDispatched = nullptr) const; + + /** + * Handles keyup message. Returns true if the event is consumed. + * Otherwise, false. + */ + bool HandleKeyUpMessage(bool* aEventDispatched = nullptr) const; + + /** + * Handles WM_APPCOMMAND message. Returns true if the event is consumed. + * Otherwise, false. + */ + bool HandleAppCommandMessage() const; + + /** + * Callback of TextEventDispatcherListener::WillDispatchKeyboardEvent(). + * This method sets alternative char codes of aKeyboardEvent. + */ + void WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyboardEvent, + uint32_t aIndex); + + /** + * Returns true if aChar is a control character which shouldn't be inputted + * into focused text editor. + */ + static bool IsControlChar(char16_t aChar); + + bool IsShift() const { return mModKeyState.IsShift(); } + bool IsControl() const { return mModKeyState.IsControl(); } + bool IsAlt() const { return mModKeyState.IsAlt(); } + bool MaybeEmulatingAltGraph() const; + Modifiers GetModifiers() const { return mModKeyState.GetModifiers(); } + const ModifierKeyState& ModifierKeyStateRef() const { return mModKeyState; } + VirtualKey::ShiftState GetShiftState() const { + return VirtualKey::ModifierKeyStateToShiftState(mModKeyState); + } + + /** + * GenericVirtualKeyCode() returns virtual keycode which cannot distinguish + * position of modifier keys. E.g., VK_CONTROL for both ControlLeft and + * ControlRight. + */ + uint8_t GenericVirtualKeyCode() const { return mOriginalVirtualKeyCode; } + + /** + * SpecificVirtualKeyCode() returns virtual keycode which can distinguish + * position of modifier keys. E.g., returns VK_LCONTROL or VK_RCONTROL + * instead of VK_CONTROL. If the key message is synthesized with not + * enough information, this prefers left position's keycode. + */ + uint8_t SpecificVirtualKeyCode() const { return mVirtualKeyCode; } + + private: + NativeKey* mLastInstance; + // mRemovingMsg is set at removing a char message from + // GetFollowingCharMessage(). + MSG mRemovingMsg; + // mReceivedMsg is set when another instance starts to handle the message + // unexpectedly. + MSG mReceivedMsg; + RefPtr<nsWindow> mWidget; + RefPtr<TextEventDispatcher> mDispatcher; + HKL mKeyboardLayout; + MSG mMsg; + // mFollowingCharMsgs stores WM_CHAR, WM_SYSCHAR, WM_DEADCHAR or + // WM_SYSDEADCHAR message which follows WM_KEYDOWN. + // Note that the stored messaged are already removed from the queue. + // FYI: 5 is enough number for usual keyboard layout handling. On Windows, + // a dead key sequence may cause inputting up to 5 characters per key press. + AutoTArray<MSG, 5> mFollowingCharMsgs; + // mRemovedOddCharMsgs stores WM_CHAR messages which are caused by ATOK or + // WXG (they are Japanese IME) when the user tries to do "Kakutei-undo" + // (it means "undo the last commit"). + nsTArray<MSG> mRemovedOddCharMsgs; + // If dispatching eKeyDown or eKeyPress event causes focus change, + // the instance shouldn't handle remaning char messages. For checking it, + // this should store first focused window. + HWND mFocusedWndBeforeDispatch; + + uint32_t mDOMKeyCode; + KeyNameIndex mKeyNameIndex; + CodeNameIndex mCodeNameIndex; + + ModifierKeyState mModKeyState; + + // mVirtualKeyCode distinguishes left key or right key of modifier key. + uint8_t mVirtualKeyCode; + // mOriginalVirtualKeyCode doesn't distinguish left key or right key of + // modifier key. However, if the given keycode is VK_PROCESS, it's resolved + // to a keycode before it's handled by IME. + uint8_t mOriginalVirtualKeyCode; + + // mCommittedChars indicates the inputted characters which is committed by + // the key. If dead key fail to composite a character, mCommittedChars + // indicates both the dead characters and the base characters. + UniCharsAndModifiers mCommittedCharsAndModifiers; + + // Following strings are computed by + // ComputeInputtingStringWithKeyboardLayout() which is typically called + // before dispatching keydown event. + // mInputtingStringAndModifiers's string is the string to be + // inputted into the focused editor and its modifier state is proper + // modifier state for inputting the string into the editor. + UniCharsAndModifiers mInputtingStringAndModifiers; + // mShiftedString is the string to be inputted into the editor with + // current modifier state with active shift state. + UniCharsAndModifiers mShiftedString; + // mUnshiftedString is the string to be inputted into the editor with + // current modifier state without shift state. + UniCharsAndModifiers mUnshiftedString; + // Following integers are computed by + // ComputeInputtingStringWithKeyboardLayout() which is typically called + // before dispatching keydown event. The meaning of these values is same + // as charCode. + uint32_t mShiftedLatinChar; + uint32_t mUnshiftedLatinChar; + + WORD mScanCode; + bool mIsExtended; + // mIsRepeat is true if the key message is caused by the auto-repeat + // feature. + bool mIsRepeat; + bool mIsDeadKey; + // mIsPrintableKey is true if the key may be a printable key without + // any modifier keys. Otherwise, false. + // Please note that the event may not cause any text input even if this + // is true. E.g., it might be dead key state or Ctrl key may be pressed. + bool mIsPrintableKey; + // mIsSkippableInRemoteProcess is false if the key event shouldn't be + // skipped in the remote process even if it's too old event. + bool mIsSkippableInRemoteProcess; + // mCharMessageHasGone is true if the message is a keydown message and + // it's followed by at least one char message but it's gone at removing + // from the queue. This could occur if PeekMessage() or something is + // hooked by odd tool. + bool mCharMessageHasGone; + // mIsOverridingKeyboardLayout is true if the instance temporarily overriding + // keyboard layout with specified by the constructor. + bool mIsOverridingKeyboardLayout; + // mCanIgnoreModifierStateAtKeyPress is true if it's allowed to remove + // Ctrl or Alt modifier state at dispatching eKeyPress. + bool mCanIgnoreModifierStateAtKeyPress; + + nsTArray<FakeCharMsg>* mFakeCharMsgs; + + // When a keydown event is dispatched at handling WM_APPCOMMAND, the computed + // virtual keycode is set to this. Even if we consume WM_APPCOMMAND message, + // Windows may send WM_KEYDOWN and WM_KEYUP message for them. + // At that time, we should not dispatch key events for them. + static uint8_t sDispatchedKeyOfAppCommand; + + NativeKey() { + MOZ_CRASH("The default constructor of NativeKey isn't available"); + } + + void InitWithAppCommand(); + void InitWithKeyOrChar(); + + /** + * InitIsSkippableForKeyOrChar() initializes mIsSkippableInRemoteProcess with + * mIsRepeat and previous key message information. So, this must be called + * after mIsRepeat is initialized. + */ + void InitIsSkippableForKeyOrChar(const MSG& aLastKeyMSG); + + /** + * InitCommittedCharsAndModifiersWithFollowingCharMessages() initializes + * mCommittedCharsAndModifiers with mFollowingCharMsgs and mModKeyState. + * If mFollowingCharMsgs includes non-printable char messages, they are + * ignored (skipped). + */ + void InitCommittedCharsAndModifiersWithFollowingCharMessages(); + + UINT GetScanCodeWithExtendedFlag() const; + + // The result is one of eKeyLocation*. + uint32_t GetKeyLocation() const; + + /** + * RemoveFollowingOddCharMessages() removes odd WM_CHAR messages from the + * queue when IsIMEDoingKakuteiUndo() returns true. + */ + void RemoveFollowingOddCharMessages(); + + /** + * "Kakutei-Undo" of ATOK or WXG (both of them are Japanese IME) causes + * strange WM_KEYDOWN/WM_KEYUP/WM_CHAR message pattern. So, when this + * returns true, the caller needs to be careful for processing the messages. + */ + bool IsIMEDoingKakuteiUndo() const; + + /** + * This returns true if user types a number key in numpad with Alt key + * to input a Unicode character from its scalar value. + * Note that inputting Unicode scalar value is available without NumLock. + * Therefore, this returns true even if user presses a function key on + * numpad without NumLock, but that may be intended to perform a shortcut + * key like Alt + Home. + */ + bool MaybeTypingUnicodeScalarValue() const { + return !mIsExtended && IsSysKeyDownOrKeyUpMessage() && IsAlt() && + !IsControl() && !IsShift() && + ((mScanCode >= 0x004F && mScanCode <= 0x0052) || // Numpad0-3 + (mScanCode >= 0x004B && mScanCode <= 0x004D) || // Numpad4-6 + (mScanCode >= 0x0047 && mScanCode <= 0x0049)); // Numpad7-9 + } + + bool IsKeyDownMessage() const { + return mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN; + } + bool IsKeyUpMessage() const { + return mMsg.message == WM_KEYUP || mMsg.message == WM_SYSKEYUP; + } + bool IsSysKeyDownOrKeyUpMessage() const { + return mMsg.message == WM_SYSKEYDOWN || mMsg.message == WM_SYSKEYUP; + } + bool IsCharOrSysCharMessage(const MSG& aMSG) const { + return IsCharOrSysCharMessage(aMSG.message); + } + bool IsCharOrSysCharMessage(UINT aMessage) const { + return (aMessage == WM_CHAR || aMessage == WM_SYSCHAR); + } + bool IsCharMessage(const MSG& aMSG) const { + return IsCharMessage(aMSG.message); + } + bool IsCharMessage(UINT aMessage) const { + return (IsCharOrSysCharMessage(aMessage) || IsDeadCharMessage(aMessage)); + } + bool IsDeadCharMessage(const MSG& aMSG) const { + return IsDeadCharMessage(aMSG.message); + } + bool IsDeadCharMessage(UINT aMessage) const { + return (aMessage == WM_DEADCHAR || aMessage == WM_SYSDEADCHAR); + } + bool IsSysCharMessage(const MSG& aMSG) const { + return IsSysCharMessage(aMSG.message); + } + bool IsSysCharMessage(UINT aMessage) const { + return (aMessage == WM_SYSCHAR || aMessage == WM_SYSDEADCHAR); + } + bool MayBeSameCharMessage(const MSG& aCharMsg1, const MSG& aCharMsg2) const; + bool IsSamePhysicalKeyMessage(const MSG& aKeyOrCharMsg1, + const MSG& aKeyOrCharMsg2) const; + bool IsFollowedByPrintableCharMessage() const; + bool IsFollowedByPrintableCharOrSysCharMessage() const; + bool IsFollowedByDeadCharMessage() const; + bool IsPrintableCharMessage(const MSG& aMSG) const { + return aMSG.message == WM_CHAR && + !IsControlChar(static_cast<char16_t>(aMSG.wParam)); + } + bool IsEnterKeyPressCharMessage(const MSG& aMSG) const { + return aMSG.message == WM_CHAR && aMSG.wParam == '\r'; + } + bool IsPrintableCharOrSysCharMessage(const MSG& aMSG) const { + return IsCharOrSysCharMessage(aMSG) && + !IsControlChar(static_cast<char16_t>(aMSG.wParam)); + } + bool IsControlCharMessage(const MSG& aMSG) const { + return IsCharMessage(aMSG.message) && + IsControlChar(static_cast<char16_t>(aMSG.wParam)); + } + + /** + * IsReservedBySystem() returns true if the key combination is reserved by + * the system. Even if it's consumed by web apps, the message should be + * sent to next wndproc. + */ + bool IsReservedBySystem() const; + + /** + * GetFollowingCharMessage() returns following char message of handling + * keydown event. If the message is found, this method returns true. + * Otherwise, returns false. + * + * WARNING: Even if this returns true, aCharMsg may be WM_NULL or its + * hwnd may be different window. + */ + bool GetFollowingCharMessage(MSG& aCharMsg); + + /** + * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK. + */ + uint8_t ComputeVirtualKeyCodeFromScanCode() const; + + /** + * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK_EX. + */ + uint8_t ComputeVirtualKeyCodeFromScanCodeEx() const; + + /** + * Wraps MapVirtualKeyEx() with MAPVK_VK_TO_VSC_EX or MAPVK_VK_TO_VSC. + */ + uint16_t ComputeScanCodeExFromVirtualKeyCode(UINT aVirtualKeyCode) const; + + /** + * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK and MAPVK_VK_TO_CHAR. + */ + char16_t ComputeUnicharFromScanCode() const; + + /** + * Initializes the aKeyEvent with the information stored in the instance. + */ + nsEventStatus InitKeyEvent(WidgetKeyboardEvent& aKeyEvent, + const ModifierKeyState& aModKeyState) const; + nsEventStatus InitKeyEvent(WidgetKeyboardEvent& aKeyEvent) const; + + /** + * Dispatches a command event for aEventCommand. + * Returns true if the event is consumed. Otherwise, false. + */ + bool DispatchCommandEvent(uint32_t aEventCommand) const; + + /** + * DispatchKeyPressEventsWithRetrievedCharMessages() dispatches keypress + * event(s) with retrieved char messages. + */ + bool DispatchKeyPressEventsWithRetrievedCharMessages() const; + + /** + * DispatchKeyPressEventsWithoutCharMessage() dispatches keypress event(s) + * without char messages. So, this should be used only when there are no + * following char messages. + */ + bool DispatchKeyPressEventsWithoutCharMessage() const; + + /** + * Checkes whether the key event down message is handled without following + * WM_CHAR messages. For example, if following WM_CHAR message indicates + * control character input, the WM_CHAR message is unclear whether it's + * caused by a printable key with Ctrl or just a function key such as Enter + * or Backspace. + */ + bool NeedsToHandleWithoutFollowingCharMessages() const; + + /** + * ComputeInputtingStringWithKeyboardLayout() computes string to be inputted + * with the key and the modifier state, without shift state and with shift + * state. + */ + void ComputeInputtingStringWithKeyboardLayout(); + + /** + * IsFocusedWindowChanged() returns true if focused window is changed + * after the instance is created. + */ + bool IsFocusedWindowChanged() const { + return mFocusedWndBeforeDispatch != ::GetFocus(); + } + + /** + * Handles WM_CHAR message or WM_SYSCHAR message. The instance must be + * initialized with WM_KEYDOWN, WM_SYSKEYDOWN or them. + * Returns true if dispatched keypress event is consumed. Otherwise, false. + */ + bool HandleCharMessage(const MSG& aCharMsg, + bool* aEventDispatched = nullptr) const; + + // Calls of PeekMessage() from NativeKey might cause nested message handling + // due to (perhaps) odd API hook. NativeKey should do nothing if given + // message is tried to be retrieved by another instance. + + /** + * sLatestInstacne is a pointer to the newest instance of NativeKey which is + * handling a key or char message(s). + */ + static NativeKey* sLatestInstance; + + static const MSG sEmptyMSG; + + static MSG sLastKeyOrCharMSG; + + static MSG sLastKeyMSG; + + static bool IsEmptyMSG(const MSG& aMSG) { + return !memcmp(&aMSG, &sEmptyMSG, sizeof(MSG)); + } + + bool IsAnotherInstanceRemovingCharMessage() const { + return mLastInstance && !IsEmptyMSG(mLastInstance->mRemovingMsg); + } + + public: + /** + * Returns last key or char MSG. If no MSG has been received yet, the result + * is empty MSG (i.e., .message is WM_NULL). + */ + static const MSG& LastKeyOrCharMSG() { return sLastKeyOrCharMSG; } +}; + +class KeyboardLayout { + public: + static KeyboardLayout* GetInstance(); + static void Shutdown(); + static HKL GetActiveLayout(); + static nsCString GetActiveLayoutName(); + static void NotifyIdleServiceOfUserActivity(); + + static bool IsPrintableCharKey(uint8_t aVirtualKey); + + /** + * HasAltGr() returns true if the keyboard layout's AltRight key is AltGr + * key. + */ + bool HasAltGr() const { return mHasAltGr; } + + /** + * IsDeadKey() returns true if aVirtualKey is a dead key with aModKeyState. + * This method isn't stateful. + */ + bool IsDeadKey(uint8_t aVirtualKey, + const ModifierKeyState& aModKeyState) const; + bool IsDeadKey(const NativeKey& aNativeKey) const { + return IsDeadKey(aNativeKey.GenericVirtualKeyCode(), + aNativeKey.ModifierKeyStateRef()); + } + + /** + * IsInDeadKeySequence() returns true when it's in a dead key sequence. + * It starts when a dead key is down and ends when another key down causes + * inactivating the dead key state. + */ + bool IsInDeadKeySequence() const { return !mActiveDeadKeys.IsEmpty(); } + + /** + * IsSysKey() returns true if aVirtualKey with aModKeyState causes WM_SYSKEY* + * or WM_SYS*CHAR messages. + */ + bool IsSysKey(uint8_t aVirtualKey, + const ModifierKeyState& aModKeyState) const; + bool IsSysKey(const NativeKey& aNativeKey) const { + return IsSysKey(aNativeKey.GenericVirtualKeyCode(), + aNativeKey.ModifierKeyStateRef()); + } + + /** + * GetUniCharsAndModifiers() returns characters which are inputted by + * aVirtualKey with aModKeyState. This method isn't stateful. + * Note that if the combination causes text input, the result's Ctrl and + * Alt key state are never active. + */ + UniCharsAndModifiers GetUniCharsAndModifiers( + uint8_t aVirtualKey, const ModifierKeyState& aModKeyState) const { + VirtualKey::ShiftState shiftState = + VirtualKey::ModifierKeyStateToShiftState(aModKeyState); + return GetUniCharsAndModifiers(aVirtualKey, shiftState); + } + UniCharsAndModifiers GetUniCharsAndModifiers( + const NativeKey& aNativeKey) const { + return GetUniCharsAndModifiers(aNativeKey.GenericVirtualKeyCode(), + aNativeKey.GetShiftState()); + } + + /** + * OnLayoutChange() must be called before the first keydown message is + * received. LoadLayout() changes the keyboard state, that causes breaking + * dead key state. Therefore, we need to load the layout before the first + * keydown message. + */ + void OnLayoutChange(HKL aKeyboardLayout) { + MOZ_ASSERT(!mIsOverridden); + LoadLayout(aKeyboardLayout); + } + + /** + * OverrideLayout() loads the specified keyboard layout. + */ + void OverrideLayout(HKL aLayout) { + mIsOverridden = true; + LoadLayout(aLayout); + } + + /** + * RestoreLayout() loads the current keyboard layout of the thread. + */ + void RestoreLayout() { + mIsOverridden = false; + mIsPendingToRestoreKeyboardLayout = true; + } + + uint32_t ConvertNativeKeyCodeToDOMKeyCode(UINT aNativeKeyCode) const; + + /** + * ConvertNativeKeyCodeToKeyNameIndex() returns KeyNameIndex value for + * non-printable keys (except some special keys like space key). + */ + KeyNameIndex ConvertNativeKeyCodeToKeyNameIndex(uint8_t aVirtualKey) const; + + /** + * ConvertScanCodeToCodeNameIndex() returns CodeNameIndex value for + * the given scan code. aScanCode can be over 0xE000 since this method + * doesn't use Windows API. + */ + static CodeNameIndex ConvertScanCodeToCodeNameIndex(UINT aScanCode); + + HKL GetLayout() const { + return mIsPendingToRestoreKeyboardLayout ? ::GetKeyboardLayout(0) + : mKeyboardLayout; + } + + /** + * This wraps MapVirtualKeyEx() API with MAPVK_VK_TO_VSC. + */ + WORD ComputeScanCodeForVirtualKeyCode(uint8_t aVirtualKeyCode) const; + + /** + * Implementation of nsIWidget::SynthesizeNativeKeyEvent(). + */ + nsresult SynthesizeNativeKeyEvent(nsWindow* aWidget, + int32_t aNativeKeyboardLayout, + int32_t aNativeKeyCode, + uint32_t aModifierFlags, + const nsAString& aCharacters, + const nsAString& aUnmodifiedCharacters); + + private: + KeyboardLayout(); + ~KeyboardLayout(); + + static KeyboardLayout* sInstance; + static nsIUserIdleServiceInternal* sIdleService; + + struct DeadKeyTableListEntry { + DeadKeyTableListEntry* next; + uint8_t data[1]; + }; + + HKL mKeyboardLayout; + + VirtualKey mVirtualKeys[NS_NUM_OF_KEYS]; + DeadKeyTableListEntry* mDeadKeyTableListHead; + // When mActiveDeadKeys is empty, it's not in dead key sequence. + // Otherwise, it contains virtual keycodes which are pressed in current + // dead key sequence. + nsTArray<uint8_t> mActiveDeadKeys; + // mDeadKeyShiftStates is always same length as mActiveDeadKeys. + // This stores shift states at pressing each dead key stored in + // mActiveDeadKeys. + nsTArray<VirtualKey::ShiftState> mDeadKeyShiftStates; + + bool mIsOverridden; + bool mIsPendingToRestoreKeyboardLayout; + bool mHasAltGr; + + static inline int32_t GetKeyIndex(uint8_t aVirtualKey); + static int CompareDeadKeyEntries(const void* aArg1, const void* aArg2, + void* aData); + static bool AddDeadKeyEntry(char16_t aBaseChar, char16_t aCompositeChar, + DeadKeyEntry* aDeadKeyArray, uint32_t aEntries); + bool EnsureDeadKeyActive(bool aIsActive, uint8_t aDeadKey, + const PBYTE aDeadKeyKbdState); + uint32_t GetDeadKeyCombinations(uint8_t aDeadKey, + const PBYTE aDeadKeyKbdState, + uint16_t aShiftStatesWithBaseChars, + DeadKeyEntry* aDeadKeyArray, + uint32_t aMaxEntries); + /** + * Activates or deactivates dead key state. + */ + void ActivateDeadKeyState(const NativeKey& aNativeKey); + void DeactivateDeadKeyState(); + + const DeadKeyTable* AddDeadKeyTable(const DeadKeyEntry* aDeadKeyArray, + uint32_t aEntries); + void ReleaseDeadKeyTables(); + + /** + * Loads the specified keyboard layout. This method always clear the dead key + * state. + */ + void LoadLayout(HKL aLayout); + + /** + * Gets the keyboard layout name of aLayout. Be careful, this may be too + * slow to call at handling user input. + */ + nsCString GetLayoutName(HKL aLayout) const; + + /** + * InitNativeKey() must be called when actually widget receives WM_KEYDOWN or + * WM_KEYUP. This method is stateful. This saves current dead key state at + * WM_KEYDOWN. Additionally, computes current inputted character(s) and set + * them to the aNativeKey. + */ + void InitNativeKey(NativeKey& aNativeKey); + + /** + * MaybeInitNativeKeyAsDeadKey() initializes aNativeKey only when aNativeKey + * is a dead key's event. + * When it's not in a dead key sequence, this activates the dead key state. + * When it's in a dead key sequence, this initializes aNativeKey with a + * composite character or a preceding dead char and a dead char which should + * be caused by aNativeKey. + * Returns true when this initializes aNativeKey. Otherwise, false. + */ + bool MaybeInitNativeKeyAsDeadKey(NativeKey& aNativeKey); + + /** + * MaybeInitNativeKeyWithCompositeChar() may initialize aNativeKey with + * proper composite character when dead key produces a composite character. + * Otherwise, just returns false. + */ + bool MaybeInitNativeKeyWithCompositeChar(NativeKey& aNativeKey); + + /** + * See the comment of GetUniCharsAndModifiers() below. + */ + UniCharsAndModifiers GetUniCharsAndModifiers( + uint8_t aVirtualKey, VirtualKey::ShiftState aShiftState) const; + + /** + * GetDeadUniCharsAndModifiers() returns dead chars which are stored in + * current dead key sequence. So, this is stateful. + */ + UniCharsAndModifiers GetDeadUniCharsAndModifiers() const; + + /** + * GetCompositeChar() returns a composite character with dead character + * caused by mActiveDeadKeys, mDeadKeyShiftStates and a base character + * (aBaseChar). + * If the combination of the dead character and the base character doesn't + * cause a composite character, this returns 0. + */ + char16_t GetCompositeChar(char16_t aBaseChar) const; + + // NativeKey class should access InitNativeKey() directly, but it shouldn't + // be available outside of NativeKey. So, let's make NativeKey a friend + // class of this. + friend class NativeKey; +}; + +class RedirectedKeyDownMessageManager { + public: + /* + * If a window receives WM_KEYDOWN message or WM_SYSKEYDOWM message which is + * a redirected message, NativeKey::DispatchKeyDownAndKeyPressEvent() + * prevents to dispatch eKeyDown event because it has been dispatched + * before the message was redirected. However, in some cases, WM_*KEYDOWN + * message handler may not handle actually. Then, the message handler needs + * to forget the redirected message and remove WM_CHAR message or WM_SYSCHAR + * message for the redirected keydown message. AutoFlusher class is a helper + * class for doing it. This must be created in the stack. + */ + class MOZ_STACK_CLASS AutoFlusher final { + public: + AutoFlusher(nsWindow* aWidget, const MSG& aMsg) + : mCancel(!RedirectedKeyDownMessageManager::IsRedirectedMessage(aMsg)), + mWidget(aWidget), + mMsg(aMsg) {} + + ~AutoFlusher() { + if (mCancel) { + return; + } + // Prevent unnecessary keypress event + if (!mWidget->Destroyed()) { + RedirectedKeyDownMessageManager::RemoveNextCharMessage(mMsg.hwnd); + } + // Foreget the redirected message + RedirectedKeyDownMessageManager::Forget(); + } + + void Cancel() { mCancel = true; } + + private: + bool mCancel; + RefPtr<nsWindow> mWidget; + const MSG& mMsg; + }; + + static void WillRedirect(const MSG& aMsg, bool aDefualtPrevented) { + sRedirectedKeyDownMsg = aMsg; + sDefaultPreventedOfRedirectedMsg = aDefualtPrevented; + } + + static void Forget() { sRedirectedKeyDownMsg.message = WM_NULL; } + + static void PreventDefault() { sDefaultPreventedOfRedirectedMsg = true; } + static bool DefaultPrevented() { return sDefaultPreventedOfRedirectedMsg; } + + static bool IsRedirectedMessage(const MSG& aMsg); + + /** + * RemoveNextCharMessage() should be called by WM_KEYDOWN or WM_SYSKEYDOWM + * message handler. If there is no WM_(SYS)CHAR message for it, this + * method does nothing. + * NOTE: WM_(SYS)CHAR message is posted by TranslateMessage() API which is + * called in message loop. So, WM_(SYS)KEYDOWN message should have + * WM_(SYS)CHAR message in the queue if the keydown event causes character + * input. + */ + static void RemoveNextCharMessage(HWND aWnd); + + private: + // sRedirectedKeyDownMsg is WM_KEYDOWN message or WM_SYSKEYDOWN message which + // is reirected with SendInput() API by + // widget::NativeKey::DispatchKeyDownAndKeyPressEvent() + static MSG sRedirectedKeyDownMsg; + static bool sDefaultPreventedOfRedirectedMsg; +}; + +} // namespace widget +} // namespace mozilla + +#endif |