From 84dd62f55388076d5f857fc014a42b782d69d040 Mon Sep 17 00:00:00 2001 From: "neil%parkwaycc.co.uk" Date: Thu, 16 Mar 2006 21:02:22 +0000 Subject: [PATCH] Relanding fix for bug 287179 unmodified charCode is generated for keypress event when both ctrl and shift are held down, patch by Dainis Jonitis r=emaijala sr=me for changes since previous attempts (see previous checkins for superreviewers) --- widget/src/windows/Makefile.in | 7 +- widget/src/windows/nsKeyboardLayout.cpp | 81 ++++++++------- widget/src/windows/nsKeyboardLayout.h | 23 ++--- widget/src/windows/nsWindow.cpp | 131 ++++++++++++++++-------- 4 files changed, 148 insertions(+), 94 deletions(-) diff --git a/widget/src/windows/Makefile.in b/widget/src/windows/Makefile.in index d2957a12b04..cc8de942451 100644 --- a/widget/src/windows/Makefile.in +++ b/widget/src/windows/Makefile.in @@ -67,9 +67,9 @@ REQUIRES = xpcom \ xuldoc \ view \ imglib2 \ - uriloader \ - webbrowserpersist \ - unicharutil \ + uriloader \ + webbrowserpersist \ + unicharutil \ $(NULL) ifdef MOZ_ENABLE_CAIRO_GFX @@ -82,6 +82,7 @@ CPPSRCS = \ nsAppShell.cpp \ nsLookAndFeel.cpp \ nsToolkit.cpp \ + nsKeyboardLayout.cpp \ nsDataObj.cpp \ nsDataObjCollection.cpp \ nsClipboard.cpp \ diff --git a/widget/src/windows/nsKeyboardLayout.cpp b/widget/src/windows/nsKeyboardLayout.cpp index 3d5d6cba59a..9345afc9bfe 100644 --- a/widget/src/windows/nsKeyboardLayout.cpp +++ b/widget/src/windows/nsKeyboardLayout.cpp @@ -225,6 +225,22 @@ PRUint32 VirtualKey::GetNativeUniChars (PRUint8 aShiftState, PRUint16* aUniChars +KeyboardLayout::KeyboardLayout () +{ +#ifndef WINCE + mDeadKeyTableListHead = nsnull; +#endif + + LoadLayout (); +} + +KeyboardLayout::~KeyboardLayout () +{ +#ifndef WINCE + ReleaseDeadKeyTables (); +#endif +} + PRBool KeyboardLayout::IsPrintableCharKey (PRUint8 aVirtualKey) { #ifndef WINCE @@ -361,37 +377,39 @@ void KeyboardLayout::LoadLayout () NS_ASSERTION (vki < NS_ARRAY_LENGTH (mVirtualKeys), "invalid index"); PRUint16 uniChars [5]; - WORD ascii; + WORD ascii [2]; // On Win9x ToAsciiEx returns WORD per character, on Win2k BYTE per character. PRInt32 rv; if (nsToolkit::mIsNT) rv = ::ToUnicode (virtualKey, 0, kbdState, (LPWSTR)uniChars, NS_ARRAY_LENGTH (uniChars), 0); else - rv = ::ToAsciiEx (virtualKey, 0, kbdState, &ascii, 0, mKeyboardLayout); + rv = ::ToAsciiEx (virtualKey, 0, kbdState, ascii, 0, mKeyboardLayout); - if (rv == -1) // dead-key + if (rv < 0) // dead-key { shiftStatesWithDeadKeys |= 1 << shiftState; // Repeat dead-key to deactivate it and get its character representation. - PRUint16 deadChar; + PRUint16 deadChar [2]; if (nsToolkit::mIsNT) - ::ToUnicode (virtualKey, 0, kbdState, (LPWSTR)&deadChar, 1, 0); + rv = ::ToUnicode (virtualKey, 0, kbdState, (LPWSTR)deadChar, NS_ARRAY_LENGTH (deadChar), 0); else { - rv = ::ToAsciiEx (virtualKey, 0, kbdState, &ascii, 0, mKeyboardLayout); - ::MultiByteToWideChar (mCodePage, 0, (LPCSTR)&ascii, rv, (WCHAR*)&deadChar, 1); + rv = ::ToAsciiEx (virtualKey, 0, kbdState, ascii, 0, mKeyboardLayout); + ::MultiByteToWideChar (mCodePage, 0, (LPCSTR)ascii, 1, (WCHAR*)deadChar, 1); } - mVirtualKeys [vki].SetDeadChar (shiftState, deadChar); + NS_ASSERTION (rv == 2, "Expecting twice repeated dead-key character"); + + mVirtualKeys [vki].SetDeadChar (shiftState, deadChar [0]); } else { if (rv == 1) // dead-key can pair only with exactly one base character. shiftStatesWithBaseChars |= 1 << shiftState; if (!nsToolkit::mIsNT) - rv = ::MultiByteToWideChar (mCodePage, 0, (LPCSTR)&ascii, rv, (WCHAR*)uniChars, NS_ARRAY_LENGTH (uniChars)); + rv = ::MultiByteToWideChar (mCodePage, 0, (LPCSTR)ascii, rv, (WCHAR*)uniChars, NS_ARRAY_LENGTH (uniChars)); mVirtualKeys [vki].SetNormalChars (shiftState, uniChars, rv); } @@ -485,11 +503,12 @@ void KeyboardLayout::SetShiftState (PBYTE aKbdState, PRUint8 aShiftState) inline PRInt32 KeyboardLayout::GetKeyIndex (PRUint8 aVirtualKey) { -// Currently these 49 (NUM_OF_KEYS) virtual keys are assumed +// Currently these 50 (NUM_OF_KEYS) virtual keys are assumed // to produce visible representation: // 0x20 - VK_SPACE ' ' // 0x30..0x39 '0'..'9' // 0x41..0x5A 'A'..'Z' +// 0x6E - VK_DECIMAL '.' // 0xBA - VK_OEM_1 ';:' for US // 0xBB - VK_OEM_PLUS '+' any country // 0xBC - VK_OEM_COMMA ',' any country @@ -513,14 +532,14 @@ inline PRInt32 KeyboardLayout::GetKeyIndex (PRUint8 aVirtualKey) 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 - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 60 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 37, -1, // 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, 37, 38, 39, 40, 41, 42, // B0 - 43, -1, -1, -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, 44, 45, 46, 47, 48, // D0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 38, 39, 40, 41, 42, 43, // B0 + 44, -1, -1, -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, 45, 46, 47, 48, 49, // D0 -1, -1, -1, -1, -1, -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 }; @@ -577,13 +596,13 @@ PRBool KeyboardLayout::EnsureDeadKeyActive (PRBool aIsActive, PRUint8 aDeadKey, else rv = ::ToAsciiEx (aDeadKey, 0, (PBYTE)aDeadKeyKbdState, dummyChars, 0, mKeyboardLayout); // returned values: - // -1 - Dead key state is active. The keyboard driver will wait for next character. + // <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 ((rv == -1) != aIsActive); + } while ((rv < 0) != aIsActive); - return (rv == -1); + return (rv < 0); } void KeyboardLayout::DeactivateDeadKeyState () @@ -644,26 +663,16 @@ PRUint32 KeyboardLayout::GetDeadKeyCombinations (PRUint8 aDeadKey, const PBYTE a // 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. PRUint16 compositeChars [5]; + WORD ascii [2]; // On Win9x ToAsciiEx returns WORD per character, on Win2k BYTE per character. PRInt32 rv; if (nsToolkit::mIsNT) rv = ::ToUnicode (virtualKey, 0, kbdState, (LPWSTR)compositeChars, NS_ARRAY_LENGTH (compositeChars), 0); else - { - WORD ascii; - rv = ::ToAsciiEx (virtualKey, 0, kbdState, &ascii, 0, mKeyboardLayout); - - if (rv == 1) - ::MultiByteToWideChar (mCodePage, 0, (LPCSTR)&ascii, 1, (WCHAR*)compositeChars, NS_ARRAY_LENGTH (compositeChars)); - } + rv = ::ToAsciiEx (virtualKey, 0, kbdState, ascii, 0, mKeyboardLayout); switch (rv) { - case -1: - // Unexpected dead-key - deadKeyActive = PR_FALSE; - break; - case 0: // This key combination does not produce any characters. The dead-key is still in active state. break; @@ -678,14 +687,15 @@ PRUint32 KeyboardLayout::GetDeadKeyCombinations (PRUint8 aDeadKey, const PBYTE a rv = ::ToUnicode (virtualKey, 0, kbdState, (LPWSTR)baseChars, NS_ARRAY_LENGTH (baseChars), 0); else { - WORD ascii; - rv = ::ToAsciiEx (virtualKey, 0, kbdState, &ascii, 0, mKeyboardLayout); + ::MultiByteToWideChar (mCodePage, 0, (LPCSTR)ascii, 1, (WCHAR*)compositeChars, NS_ARRAY_LENGTH (compositeChars)); - if (rv == 1) - ::MultiByteToWideChar (mCodePage, 0, (LPCSTR)&ascii, 1, (WCHAR*)baseChars, NS_ARRAY_LENGTH (baseChars)); + rv = ::ToAsciiEx (virtualKey, 0, kbdState, ascii, 0, mKeyboardLayout); + rv = ::MultiByteToWideChar (mCodePage, 0, (LPCSTR)ascii, rv, (WCHAR*)baseChars, NS_ARRAY_LENGTH (baseChars)); } - if (entries < aMaxEntries) + NS_ASSERTION (rv == 1, "One base character expected"); + + if (rv == 1 && entries < aMaxEntries) if (AddDeadKeyEntry (baseChars [0], compositeChars [0], aDeadKeyArray, entries)) entries++; @@ -694,7 +704,8 @@ PRUint32 KeyboardLayout::GetDeadKeyCombinations (PRUint8 aDeadKey, const PBYTE a } default: - // More than one character generated. This is not a valid dead-key and base character combination. + // 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 = PR_FALSE; break; } diff --git a/widget/src/windows/nsKeyboardLayout.h b/widget/src/windows/nsKeyboardLayout.h index 6060fab4fcd..5a82baab321 100644 --- a/widget/src/windows/nsKeyboardLayout.h +++ b/widget/src/windows/nsKeyboardLayout.h @@ -49,10 +49,10 @@ #define VK_OEM_PERIOD 0xBE // '.' any country #define VK_OEM_2 0xBF // '/?' for US #define VK_OEM_3 0xC0 // '`~' for US -#define VK_OEM_4 0xDB // '[{' for US -#define VK_OEM_5 0xDC // '\|' for US -#define VK_OEM_6 0xDD // ']}' for US -#define VK_OEM_7 0xDE // ''"' for US +#define VK_OEM_4 0xDB // '[{' for US +#define VK_OEM_5 0xDC // '\|' for US +#define VK_OEM_6 0xDD // ']}' for US +#define VK_OEM_7 0xDE // ''"' for US #define VK_OEM_8 0xDF @@ -143,7 +143,7 @@ class KeyboardLayout PRUint8 data [1]; }; - #define NUM_OF_KEYS 49 + #define NUM_OF_KEYS 50 UINT mCodePage; // Used for Win9x only HKL mKeyboardLayout; @@ -172,17 +172,8 @@ class KeyboardLayout #endif public: - KeyboardLayout () - { - LoadLayout (); - } - - ~KeyboardLayout () - { -#ifndef WINCE - ReleaseDeadKeyTables (); -#endif - } + KeyboardLayout (); + ~KeyboardLayout (); static PRBool IsPrintableCharKey (PRUint8 aVirtualKey); diff --git a/widget/src/windows/nsWindow.cpp b/widget/src/windows/nsWindow.cpp index d4ae7512fa9..7dc5f1247dc 100644 --- a/widget/src/windows/nsWindow.cpp +++ b/widget/src/windows/nsWindow.cpp @@ -75,6 +75,7 @@ #include "imgIContainer.h" #include "gfxIImageFrame.h" #include "nsNativeCharsetUtils.h" +#include "nsKeyboardLayout.h" #include #include @@ -784,6 +785,7 @@ nsWindow::nsWindow() : nsBaseWidget() HKL nsWindow::gKeyboardLayout = 0; UINT nsWindow::gCurrentKeyboardCP = 0; PRBool nsWindow::gSwitchKeyboardLayout = PR_FALSE; +static KeyboardLayout gKbdLayout; //------------------------------------------------------------------------- // @@ -3183,9 +3185,9 @@ NS_METHOD nsWindow::EnableDragDrop(PRBool aEnable) UINT nsWindow::MapFromNativeToDOM(UINT aNativeKeyCode) { switch (aNativeKeyCode) { - case 0xBA: return NS_VK_SEMICOLON; - case 0xBB: return NS_VK_EQUALS; - case 0xBD: return NS_VK_SUBTRACT; + case VK_OEM_1: return NS_VK_SEMICOLON; // 0xBA, For the US standard keyboard, the ';:' key + case VK_OEM_PLUS: return NS_VK_EQUALS; // 0xBB, For any country/region, the '+' key + case VK_OEM_MINUS: return NS_VK_SUBTRACT; // 0xBD, For any country/region, the '-' key } return aNativeKeyCode; @@ -3265,6 +3267,8 @@ PRBool nsWindow::DispatchKeyEvent(PRUint32 aEventType, WORD aCharCode, UINT aVir //------------------------------------------------------------------------- BOOL nsWindow::OnKeyDown(UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData) { + gKbdLayout.OnKeyDown (aVirtualKeyCode); + UINT virtualKeyCode = sIMEIsComposing ? aVirtualKeyCode : MapFromNativeToDOM(aVirtualKeyCode); #ifdef DEBUG @@ -3296,12 +3300,25 @@ BOOL nsWindow::OnKeyDown(UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData) if (virtualKeyCode == NS_VK_RETURN || virtualKeyCode == NS_VK_BACK || (mIsControlDown && !mIsAltDown && !mIsShiftDown && (virtualKeyCode == NS_VK_ADD || virtualKeyCode == NS_VK_SUBTRACT || - virtualKeyCode == NS_VK_EQUALS))) + virtualKeyCode == NS_VK_EQUALS)) || + ((mIsControlDown || mIsAltDown) && KeyboardLayout::IsPrintableCharKey (aVirtualKeyCode))) { - // Remove a possible WM_CHAR or WM_SYSCHAR from the message queue - if (gotMsg && (msg.message == WM_CHAR || msg.message == WM_SYSCHAR)) { + // Remove a possible WM_CHAR or WM_SYSCHAR messages from the message queue. + // They can be more than one because of: + // * Dead-keys not pairing with base character + // * Some keyboard layouts may map up to 4 characters to the single key + + PRBool anyCharMessagesRemoved = PR_FALSE; + + while (gotMsg && (msg.message == WM_CHAR || msg.message == WM_SYSCHAR)) + { nsToolkit::mGetMessage(&msg, mWnd, WM_KEYFIRST, WM_KEYLAST); - } else if (virtualKeyCode == NS_VK_BACK) { + anyCharMessagesRemoved = PR_TRUE; + + gotMsg = nsToolkit::mPeekMessage (&msg, mWnd, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD); + } + + if (!anyCharMessagesRemoved && virtualKeyCode == NS_VK_BACK) { MSG imeStartCompositionMsg, imeCompositionMsg; if (nsToolkit::mPeekMessage(&imeStartCompositionMsg, mWnd, WM_IME_STARTCOMPOSITION, WM_IME_STARTCOMPOSITION, PM_NOREMOVE | PM_NOYIELD) && nsToolkit::mPeekMessage(&imeCompositionMsg, mWnd, WM_IME_COMPOSITION, WM_IME_COMPOSITION, PM_NOREMOVE | PM_NOYIELD) @@ -3336,8 +3353,10 @@ BOOL nsWindow::OnKeyDown(UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData) (msg.message == WM_CHAR || msg.message == WM_SYSCHAR || msg.message == WM_DEADCHAR)) { // If prevent default set for keydown, do same for keypress nsToolkit::mGetMessage(&msg, mWnd, msg.message, msg.message); + if (msg.message == WM_DEADCHAR) return PR_FALSE; + #ifdef KE_DEBUG printf("%s\tchar=%c\twp=%4x\tlp=%8x\n", (msg.message == WM_SYSCHAR) ? "WM_SYSCHAR" : "WM_CHAR", @@ -3346,47 +3365,77 @@ BOOL nsWindow::OnKeyDown(UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData) return OnChar(msg.wParam, extraFlags); } - WORD asciiKey = 0; + if (gKbdLayout.IsDeadKey ()) + return PR_FALSE; + + PRUint8 shiftStates [5]; + PRUint16 uniChars [5]; + PRUint32 numOfUniChars = 0; + PRUint32 numOfShiftStates = 0; switch (virtualKeyCode) { // keys to be sent as characters - case NS_VK_ADD : asciiKey = '+'; break; - case NS_VK_SUBTRACT : asciiKey = '-'; break; - case NS_VK_SEMICOLON : asciiKey = ';'; break; - case NS_VK_EQUALS : asciiKey = '='; break; - case NS_VK_COMMA : asciiKey = ','; break; - case NS_VK_PERIOD : asciiKey = '.'; break; - case NS_VK_QUOTE : asciiKey = '\''; break; - case NS_VK_BACK_QUOTE: asciiKey = '`'; break; - case NS_VK_DIVIDE : - case NS_VK_SLASH : asciiKey = '/'; break; - case NS_VK_MULTIPLY : asciiKey = '*'; break; - case NS_VK_NUMPAD0 : asciiKey = '0'; break; - case NS_VK_NUMPAD1 : asciiKey = '1'; break; - case NS_VK_NUMPAD2 : asciiKey = '2'; break; - case NS_VK_NUMPAD3 : asciiKey = '3'; break; - case NS_VK_NUMPAD4 : asciiKey = '4'; break; - case NS_VK_NUMPAD5 : asciiKey = '5'; break; - case NS_VK_NUMPAD6 : asciiKey = '6'; break; - case NS_VK_NUMPAD7 : asciiKey = '7'; break; - case NS_VK_NUMPAD8 : asciiKey = '8'; break; - case NS_VK_NUMPAD9 : asciiKey = '9'; break; + case NS_VK_ADD: uniChars [0] = '+'; numOfUniChars = 1; break; + case NS_VK_SUBTRACT: uniChars [0] = '-'; numOfUniChars = 1; break; + case NS_VK_DIVIDE: uniChars [0] = '/'; numOfUniChars = 1; break; + case NS_VK_MULTIPLY: uniChars [0] = '*'; numOfUniChars = 1; break; + case NS_VK_NUMPAD0: + case NS_VK_NUMPAD1: + case NS_VK_NUMPAD2: + case NS_VK_NUMPAD3: + case NS_VK_NUMPAD4: + case NS_VK_NUMPAD5: + case NS_VK_NUMPAD6: + case NS_VK_NUMPAD7: + case NS_VK_NUMPAD8: + case NS_VK_NUMPAD9: uniChars [0] = virtualKeyCode - NS_VK_NUMPAD0 + '0'; numOfUniChars = 1; break; + default: - // NS_VK_0 - NS_VK_9 and NS_VK_A - NS_VK_Z match their ascii values - if ((NS_VK_0 <= virtualKeyCode && virtualKeyCode <= NS_VK_9) || - (NS_VK_A <= virtualKeyCode && virtualKeyCode <= NS_VK_Z)) { - asciiKey = virtualKeyCode; - // Take the Shift state into account - if (!mIsShiftDown - && NS_VK_A <= virtualKeyCode && virtualKeyCode <= NS_VK_Z) { - asciiKey += 0x20; + if (KeyboardLayout::IsPrintableCharKey (aVirtualKeyCode)) + numOfUniChars = numOfShiftStates = gKbdLayout.GetUniChars (uniChars, shiftStates, NS_ARRAY_LENGTH (uniChars)); + + if (mIsControlDown ^ mIsAltDown) + { + // XXX + // For both Alt+key and Ctrl+key combinations we return the latin characters A..Z and + // numbers 0..9, ignoring the real characters returned by active keyboard layout. + // This is required to make sure that all shortcut keys (e.g. Ctrl+c, Ctrl+1, Alt+f) + // work the same way no matter what keyboard layout you are using. + // Currently it is impossible to use non-latin characters for keyboard shortcuts. + + if ((NS_VK_0 <= virtualKeyCode && virtualKeyCode <= NS_VK_9) || + (NS_VK_A <= virtualKeyCode && virtualKeyCode <= NS_VK_Z)) + { + uniChars [0] = virtualKeyCode; + numOfUniChars = 1; + numOfShiftStates = 0; + + // For letters take the Shift state into account + if (!mIsShiftDown && + NS_VK_A <= virtualKeyCode && virtualKeyCode <= NS_VK_Z) + uniChars [0] += 0x20; } } } - if (asciiKey) - DispatchKeyEvent(NS_KEY_PRESS, asciiKey, 0, aKeyData, extraFlags); - else + if (numOfUniChars) + { + for (PRUint32 cnt = 0; cnt < numOfUniChars; cnt++) + { + if (cnt < numOfShiftStates) + { + // 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. + mIsShiftDown = (shiftStates [cnt] & eShift) != 0; + mIsControlDown = (shiftStates [cnt] & eCtrl) != 0; + mIsAltDown = (shiftStates [cnt] & eAlt) != 0; + } + + DispatchKeyEvent(NS_KEY_PRESS, uniChars [cnt], 0, aKeyData, extraFlags); + } + } else DispatchKeyEvent(NS_KEY_PRESS, 0, virtualKeyCode, aKeyData, extraFlags); return noDefault; @@ -6613,6 +6662,8 @@ BOOL nsWindow::OnInputLangChange(HKL aHKL, LRESULT *oRetValue) NS_IMM_GETPROPERTY(gKeyboardLayout, IGP_PROPERTY, imeProp); nsToolkit::mUseImeApiW = (imeProp & IME_PROP_UNICODE) ? PR_TRUE : PR_FALSE; } + + gKbdLayout.LoadLayout(); } ResetInputState();