diff --git a/widget/tests/test_keycodes.xul b/widget/tests/test_keycodes.xul index 64a0580052e..fb49bef369b 100644 --- a/widget/tests/test_keycodes.xul +++ b/widget/tests/test_keycodes.xul @@ -91,6 +91,7 @@ if (IS_MAC) { "US":0x409, "German":0x407, "Greek":0x408, + "Spanish":0x40a, "French":0x40c, "Swedish":0x41d, "Arabic":0x401, @@ -248,13 +249,18 @@ function runKeyEventTests() var expectEventTypeList = []; if (aShouldDelivedEvent & SHOULD_DELIVER_KEYDOWN) expectEventTypeList.push("keydown"); - if (aShouldDelivedEvent & SHOULD_DELIVER_KEYPRESS) + if (aShouldDelivedEvent & SHOULD_DELIVER_KEYPRESS) { expectEventTypeList.push("keypress"); + for (var i = 1; i < aExpectGeckoChar.length; i++) { + expectEventTypeList.push("keypress"); + } + } if (aShouldDelivedEvent & SHOULD_DELIVER_KEYUP) expectEventTypeList.push("keyup"); is(eventList.length, expectEventTypeList.length, name + ", wrong number of key events"); var longerLength = Math.max(eventList.length, expectEventTypeList.length); + var keypressCount = 0; for (var i = 0; i < longerLength; i++) { var firedEventType = i < eventList.length ? eventList[i].type : ""; var expectEventType = i < expectEventTypeList.length ? expectEventTypeList[i] : ""; @@ -271,7 +277,7 @@ function runKeyEventTests() is(e.shiftKey, aEvent.shift || aEvent.shiftRight || 0, name + ", Shift mismatch"); if (aExpectGeckoChar.length > 0 && e.type == "keypress") { - is(e.charCode, aExpectGeckoChar.charCodeAt(0), name + ", charcode"); + is(e.charCode, aExpectGeckoChar.charCodeAt(keypressCount++), name + ", charcode"); if (aExpectedGeckoKeyCode >= 0) { if (aExpectGeckoChar) { is(e.keyCode, 0, name + ", wrong keycode"); @@ -1761,9 +1767,9 @@ function runKeyEventTests() testKey({layout:"French", keyCode:187, shift:1, chars:"+"}, nsIDOMKeyEvent.DOM_VK_EQUALS, "+", SHOULD_DELIVER_ALL); //testKey({layout:"French", keyCode:221, chars:""}, - // 0, "", SHOULD_DELIVER_KEYDOWN_KEYUP); // Dead-key + // nsIDOMKeyEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP); // Dead-key //testKey({layout:"French", keyCode:221, shift:1, chars:""}, - // 0, "", SHOULD_DELIVER_KEYDOWN_KEYUP); // Dead-key + // nsIDOMKeyEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_ALL); // Dead-key testKey({layout:"French", keyCode:186, chars:"$"}, nsIDOMKeyEvent.DOM_VK_DOLLAR, "$", SHOULD_DELIVER_ALL); testKey({layout:"French", keyCode:186, shift:1, chars:"\u00A3"}, @@ -1884,6 +1890,127 @@ function runKeyEventTests() nsIDOMKeyEvent.DOM_VK_PIPE, "|", SHOULD_DELIVER_ALL); testKey({layout:"Norwegian", keyCode:220, shift:1, chars:"\u00A7"}, nsIDOMKeyEvent.DOM_VK_PIPE, "\u00A7", SHOULD_DELIVER_ALL); + + // Dead keys on any layouts + testKey({layout:"French", keyCode:221, chars:""}, + nsIDOMKeyEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"French", keyCode:221, chars:"^^"}, + nsIDOMKeyEvent.DOM_VK_CIRCUMFLEX, "^^", SHOULD_DELIVER_ALL); + + testKey({layout:"French", keyCode:221, chars:""}, + nsIDOMKeyEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"French", keyCode:65, chars:"\u00E2"}, + nsIDOMKeyEvent.DOM_VK_A, "\u00E2", SHOULD_DELIVER_ALL); + + testKey({layout:"French", keyCode:221, chars:""}, + nsIDOMKeyEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"French", keyCode:65, shift:1, chars:"\u00C2"}, + nsIDOMKeyEvent.DOM_VK_A, "\u00C2", SHOULD_DELIVER_ALL); + + testKey({layout:"French", keyCode:221, chars:""}, + nsIDOMKeyEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"French", keyCode:81, chars:"^q"}, + nsIDOMKeyEvent.DOM_VK_Q, "^q", SHOULD_DELIVER_ALL); + + testKey({layout:"French", keyCode:221, shift:1, chars:""}, + nsIDOMKeyEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"French", keyCode:221, shift:1, chars:"\u00A8\u00A8"}, + nsIDOMKeyEvent.DOM_VK_CIRCUMFLEX, "\u00A8\u00A8", SHOULD_DELIVER_ALL); + + testKey({layout:"French", keyCode:221, shift:1, chars:""}, + nsIDOMKeyEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"French", keyCode:65, shift:1, chars:"\u00C4"}, + nsIDOMKeyEvent.DOM_VK_A, "\u00C4", SHOULD_DELIVER_ALL); + + testKey({layout:"French", keyCode:221, shift:1, chars:""}, + nsIDOMKeyEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"French", keyCode:65, chars:"\u00E4"}, + nsIDOMKeyEvent.DOM_VK_A, "\u00E4", SHOULD_DELIVER_ALL); + + testKey({layout:"French", keyCode:221, shift:1, chars:""}, + nsIDOMKeyEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"French", keyCode:81, shift:1, chars:"\u00A8Q"}, + nsIDOMKeyEvent.DOM_VK_Q, "\u00A8Q", SHOULD_DELIVER_ALL); + + testKey({layout:"Spanish", keyCode:186, chars:""}, + nsIDOMKeyEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"Spanish", keyCode:186, chars:"``"}, + nsIDOMKeyEvent.DOM_VK_BACK_QUOTE, "``", SHOULD_DELIVER_ALL); + + testKey({layout:"Spanish", keyCode:186, chars:""}, + nsIDOMKeyEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"Spanish", keyCode:65, chars:"\u00E0"}, + nsIDOMKeyEvent.DOM_VK_A, "\u00E0", SHOULD_DELIVER_ALL); + + testKey({layout:"Spanish", keyCode:186, chars:""}, + nsIDOMKeyEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"Spanish", keyCode:65, shift:1, chars:"\u00C0"}, + nsIDOMKeyEvent.DOM_VK_A, "\u00C0", SHOULD_DELIVER_ALL); + + testKey({layout:"Spanish", keyCode:186, chars:""}, + nsIDOMKeyEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"Spanish", keyCode:81, chars:"`q"}, + nsIDOMKeyEvent.DOM_VK_Q, "`q", SHOULD_DELIVER_ALL); + + testKey({layout:"Spanish", keyCode:186, shift:1, chars:""}, + nsIDOMKeyEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"Spanish", keyCode:186, shift:1, chars:"^^"}, + nsIDOMKeyEvent.DOM_VK_BACK_QUOTE, "^^", SHOULD_DELIVER_ALL); + + testKey({layout:"Spanish", keyCode:186, shift:1, chars:""}, + nsIDOMKeyEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"Spanish", keyCode:65, shift:1, chars:"\u00C2"}, + nsIDOMKeyEvent.DOM_VK_A, "\u00C2", SHOULD_DELIVER_ALL); + + testKey({layout:"Spanish", keyCode:186, shift:1, chars:""}, + nsIDOMKeyEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"Spanish", keyCode:65, chars:"\u00E2"}, + nsIDOMKeyEvent.DOM_VK_A, "\u00E2", SHOULD_DELIVER_ALL); + + testKey({layout:"Spanish", keyCode:186, shift:1, chars:""}, + nsIDOMKeyEvent.DOM_VK_BACK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"Spanish", keyCode:81, shift:1, chars:"^Q"}, + nsIDOMKeyEvent.DOM_VK_Q, "^Q", SHOULD_DELIVER_ALL); + + testKey({layout:"Spanish", keyCode:222, chars:""}, + 0, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"Spanish", keyCode:222, chars:"\u00B4\u00B4"}, + 0, "\u00B4\u00B4", SHOULD_DELIVER_ALL); + + testKey({layout:"Spanish", keyCode:222, chars:""}, + 0, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"Spanish", keyCode:65, chars:"\u00E1"}, + nsIDOMKeyEvent.DOM_VK_A, "\u00E1", SHOULD_DELIVER_ALL); + + testKey({layout:"Spanish", keyCode:222, chars:""}, + 0, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"Spanish", keyCode:65, shift:1, chars:"\u00C1"}, + nsIDOMKeyEvent.DOM_VK_A, "\u00C1", SHOULD_DELIVER_ALL); + + testKey({layout:"Spanish", keyCode:222, chars:""}, + 0, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"Spanish", keyCode:81, chars:"\u00B4q"}, + nsIDOMKeyEvent.DOM_VK_Q, "\u00B4q", SHOULD_DELIVER_ALL); + + testKey({layout:"Spanish", keyCode:222, shift:1, chars:""}, + 0, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"Spanish", keyCode:222, shift:1, chars:"\u00A8\u00A8"}, + 0, "\u00A8\u00A8", SHOULD_DELIVER_ALL); + + testKey({layout:"Spanish", keyCode:222, shift:1, chars:""}, + 0, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"Spanish", keyCode:65, shift:1, chars:"\u00C4"}, + nsIDOMKeyEvent.DOM_VK_A, "\u00C4", SHOULD_DELIVER_ALL); + + testKey({layout:"Spanish", keyCode:222, shift:1, chars:""}, + 0, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"Spanish", keyCode:65, chars:"\u00E4"}, + nsIDOMKeyEvent.DOM_VK_A, "\u00E4", SHOULD_DELIVER_ALL); + + testKey({layout:"Spanish", keyCode:222, shift:1, chars:""}, + 0, "", SHOULD_DELIVER_KEYDOWN_KEYUP); + testKey({layout:"Spanish", keyCode:81, shift:1, chars:"\u00A8Q"}, + nsIDOMKeyEvent.DOM_VK_Q, "\u00A8Q", SHOULD_DELIVER_ALL); } document.removeEventListener("keydown", onKeyEvent, false); diff --git a/widget/windows/KeyboardLayout.cpp b/widget/windows/KeyboardLayout.cpp index d595364a0d0..9a0b594f75a 100644 --- a/widget/windows/KeyboardLayout.cpp +++ b/widget/windows/KeyboardLayout.cpp @@ -513,7 +513,7 @@ NativeKey::GetKeyLocation() const *****************************************************************************/ KeyboardLayout::KeyboardLayout() : - mKeyboardLayout(0) + mKeyboardLayout(0), mPendingKeyboardLayout(0) { mDeadKeyTableListHead = nsnull; @@ -550,6 +550,10 @@ UniCharsAndModifiers KeyboardLayout::OnKeyDown(PRUint8 aVirtualKey, const ModifierKeyState& aModKeyState) { + if (mPendingKeyboardLayout) { + LoadLayout(mPendingKeyboardLayout); + } + PRInt32 virtualKeyIndex = GetKeyIndex(aVirtualKey); if (virtualKeyIndex < 0) { @@ -626,8 +630,15 @@ KeyboardLayout::GetUniCharsAndModifiers( } void -KeyboardLayout::LoadLayout(HKL aLayout) +KeyboardLayout::LoadLayout(HKL aLayout, bool aLoadLater) { + if (aLoadLater) { + mPendingKeyboardLayout = aLayout; + return; + } + + mPendingKeyboardLayout = 0; + if (mKeyboardLayout == aLayout) { return; } diff --git a/widget/windows/KeyboardLayout.h b/widget/windows/KeyboardLayout.h index 4746412f6b4..701d64d766f 100644 --- a/widget/windows/KeyboardLayout.h +++ b/widget/windows/KeyboardLayout.h @@ -8,6 +8,7 @@ #include "nscore.h" #include "nsEvent.h" +#include "nsString.h" #include #define NS_NUM_OF_KEYS 68 @@ -135,6 +136,8 @@ struct UniCharsAndModifiers bool UniCharsEqual(const UniCharsAndModifiers& aOther) const; bool UniCharsCaseInsensitiveEqual(const UniCharsAndModifiers& aOther) const; + + nsString ToString() const { return nsString(mChars, mLength); } }; struct DeadKeyEntry; @@ -305,6 +308,7 @@ class KeyboardLayout }; HKL mKeyboardLayout; + HKL mPendingKeyboardLayout; VirtualKey mVirtualKeys[NS_NUM_OF_KEYS]; DeadKeyTableListEntry* mDeadKeyTableListHead; @@ -357,11 +361,18 @@ public: UniCharsAndModifiers OnKeyDown(PRUint8 aVirtualKey, const ModifierKeyState& aModKeyState); - void LoadLayout(HKL aLayout); + /** + * LoadLayout() loads the keyboard layout. If aLoadLater is true, + * it will be done when OnKeyDown() is called. + */ + void LoadLayout(HKL aLayout, bool aLoadLater = false); PRUint32 ConvertNativeKeyCodeToDOMKeyCode(UINT aNativeKeyCode) const; - HKL GetLayout() const { return mKeyboardLayout; } + HKL GetLayout() const + { + return mPendingKeyboardLayout ? mPendingKeyboardLayout : mKeyboardLayout; + } }; } // namespace widget diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp index ac4698dbfc5..53e05ff7c1d 100644 --- a/widget/windows/nsWindow.cpp +++ b/widget/windows/nsWindow.cpp @@ -5653,10 +5653,26 @@ nsWindow::SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout, const nsAString& aCharacters, const nsAString& aUnmodifiedCharacters) { + UINT keyboardLayoutListCount = ::GetKeyboardLayoutList(0, NULL); + 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 == NULL) + if (loadedLayout == NULL) { + if (keyboardLayoutListBuff != keyboardLayoutList) { + delete [] keyboardLayoutList; + } return NS_ERROR_NOT_AVAILABLE; + } // Setup clean key state and load desired layout BYTE originalKbdState[256]; @@ -5694,9 +5710,30 @@ nsWindow::SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout, lParam |= 0x1000000; } MSG msg = WinUtils::InitMSG(WM_KEYDOWN, key, lParam); - if (i == keySequence.Length() - 1 && aCharacters.Length() > 0) { - nsFakeCharMessage fakeMsg = { aCharacters.CharAt(0), scanCode }; - OnKeyDown(msg, modKeyState, nsnull, &fakeMsg); + if (i == keySequence.Length() - 1) { + bool makeDeadCharMessage = + gKbdLayout.IsDeadKey(key, modKeyState) && aCharacters.IsEmpty(); + nsAutoString chars(aCharacters); + if (makeDeadCharMessage) { + UniCharsAndModifiers deadChars = + gKbdLayout.GetUniCharsAndModifiers(key, modKeyState); + chars = deadChars.ToString(); + NS_ASSERTION(chars.Length() == 1, + "Dead char must be only one character"); + } + if (chars.IsEmpty()) { + OnKeyDown(msg, modKeyState, nsnull, nsnull); + } else { + nsFakeCharMessage fakeMsg = { chars.CharAt(0), scanCode, + makeDeadCharMessage }; + OnKeyDown(msg, modKeyState, nsnull, &fakeMsg); + for (PRUint32 j = 1; j < chars.Length(); j++) { + nsFakeCharMessage fakeMsg = { chars.CharAt(j), scanCode, false }; + MSG msg = fakeMsg.GetCharMessage(mWnd); + NativeKey nativeKey(gKbdLayout, this, msg); + OnChar(msg, nativeKey, modKeyState, nsnull); + } + } } else { OnKeyDown(msg, modKeyState, nsnull, nsnull); } @@ -5724,9 +5761,21 @@ nsWindow::SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout, // Restore old key state and layout ::SetKeyboardState(originalKbdState); - gKbdLayout.LoadLayout(oldLayout); + gKbdLayout.LoadLayout(oldLayout, true); - UnloadKeyboardLayout(loadedLayout); + // Don't unload the layout if it's installed actually. + for (PRUint32 i = 0; i < keyboardLayoutListCount; i++) { + if (keyboardLayoutList[i] == loadedLayout) { + loadedLayout = 0; + break; + } + } + if (keyboardLayoutListBuff != keyboardLayoutList) { + delete [] keyboardLayoutList; + } + if (loadedLayout) { + ::UnloadKeyboardLayout(loadedLayout); + } return NS_OK; } @@ -6358,6 +6407,9 @@ LRESULT nsWindow::OnKeyDown(const MSG &aMsg, msg.message == WM_CHAR || msg.message == WM_SYSCHAR || msg.message == WM_DEADCHAR)) { if (aFakeCharMessage) { MSG msg = aFakeCharMessage->GetCharMessage(mWnd); + if (msg.message == WM_DEADCHAR) { + return false; + } return OnChar(msg, nativeKey, aModKeyState, nsnull, extraFlags); } diff --git a/widget/windows/nsWindowDefs.h b/widget/windows/nsWindowDefs.h index bfa3e59088d..7d57cd1a1ab 100644 --- a/widget/windows/nsWindowDefs.h +++ b/widget/windows/nsWindowDefs.h @@ -211,12 +211,13 @@ struct nsAlternativeCharCode; // defined in nsGUIEvent.h struct nsFakeCharMessage { UINT mCharCode; UINT mScanCode; + bool mIsDeadKey; MSG GetCharMessage(HWND aWnd) { MSG msg; msg.hwnd = aWnd; - msg.message = WM_CHAR; + msg.message = mIsDeadKey ? WM_DEADCHAR : WM_CHAR; msg.wParam = static_cast(mCharCode); msg.lParam = static_cast(mScanCode); msg.time = 0;