mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 15:23:51 +00:00
13e51277df
Differential Revision: https://phabricator.services.mozilla.com/D173969
5383 lines
194 KiB
C++
5383 lines
194 KiB
C++
/* -*- 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 <windows.h>
|
|
#include <winuser.h>
|
|
#include <algorithm>
|
|
|
|
#ifndef WINABLEAPI
|
|
# include <winable.h>
|
|
#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:
|
|
// <https://github.com/w3c/uievents/issues/147>
|
|
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<FakeCharMsg>* 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;
|
|
}
|
|
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<uint8_t>(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<uint8_t>(scanCodeEx & 0xFF);
|
|
uint8_t extended = static_cast<uint8_t>((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<char16_t>(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<uint8_t>(scanCodeEx & 0xFF);
|
|
uint8_t extended = static_cast<uint8_t>((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<uint8_t>(
|
|
::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<uint8_t>(::MapVirtualKeyEx(
|
|
GetScanCodeWithExtendedFlag(), MAPVK_VSC_TO_VK_EX, mKeyboardLayout));
|
|
}
|
|
|
|
uint16_t NativeKey::ComputeScanCodeExFromVirtualKeyCode(
|
|
UINT aVirtualKeyCode) const {
|
|
return static_cast<uint16_t>(
|
|
::MapVirtualKeyEx(aVirtualKeyCode, MAPVK_VK_TO_VSC_EX, mKeyboardLayout));
|
|
}
|
|
|
|
char16_t NativeKey::ComputeUnicharFromScanCode() const {
|
|
return static_cast<char16_t>(::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<nsAtom> 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<HWND>(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<NativeKey*>(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<NativeKey*>(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<NativeKey*>(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<uint32_t>(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<NativeKey*>(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<NativeKey*>(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<MSG> saveLastRemovingMsg(mRemovingMsg);
|
|
mRemovingMsg = nextKeyMsg;
|
|
|
|
mReceivedMsg = sEmptyMSG;
|
|
AutoRestore<MSG> 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<NativeKey*>(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<NativeKey*>(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<AlternativeCharCode>& 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<nsIUserIdleServiceInternal> 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<WORD>(
|
|
::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<char16_t>(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<uintptr_t>(aLayout) & 0xFFFF;
|
|
uint16_t layout = (reinterpret_cast<uintptr_t>(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<uintptr_t>(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<uintptr_t>(aLayout));
|
|
return result;
|
|
}
|
|
|
|
// Otherwise, we need to walk the registry under "Keyboard Layouts".
|
|
nsCOMPtr<nsIWindowsRegKey> 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<uint32_t>(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<const DeadKeyEntry*>(aArg1);
|
|
const DeadKeyEntry* arg2 = static_cast<const DeadKeyEntry*>(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<DeadKeyTableListEntry*>(p);
|
|
mDeadKeyTableListHead->next = next;
|
|
|
|
DeadKeyTable* dkt =
|
|
reinterpret_cast<DeadKeyTable*>(mDeadKeyTableListHead->data);
|
|
|
|
dkt->Init(aDeadKeyArray, aEntries);
|
|
|
|
return dkt;
|
|
}
|
|
|
|
void KeyboardLayout::ReleaseDeadKeyTables() {
|
|
while (mDeadKeyTableListHead) {
|
|
uint8_t* p = reinterpret_cast<uint8_t*>(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<uint32_t>(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<HKL>(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<KeyPair, 10> 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<LPARAM>(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<NativeKey::FakeCharMsg, 10> 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<LPARAM>(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
|