Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=80: */
/* 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 "TextInputHandler.h"
#include "mozilla/Logging.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/StaticPrefs_intl.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TextEventDispatcher.h"
#include "mozilla/TextEvents.h"
#include "mozilla/ToString.h"
#include "nsChildView.h"
#include "nsCocoaFeatures.h"
#include "nsObjCExceptions.h"
#include "nsBidiUtils.h"
#include "nsToolkit.h"
#include "nsCocoaUtils.h"
#include "WidgetUtils.h"
#include "nsPrintfCString.h"
using namespace mozilla;
using namespace mozilla::widget;
LazyLogModule gLog("TextInputHandlerWidgets");
static const char* OnOrOff(bool aBool) { return aBool ? "ON" : "off"; }
static const char* TrueOrFalse(bool aBool) { return aBool ? "TRUE" : "FALSE"; }
static const char* GetKeyNameForNativeKeyCode(unsigned short aNativeKeyCode) {
switch (aNativeKeyCode) {
case kVK_Escape:
return "Escape";
case kVK_RightCommand:
return "Right-Command";
case kVK_Command:
return "Command";
case kVK_Shift:
return "Shift";
case kVK_CapsLock:
return "CapsLock";
case kVK_Option:
return "Option";
case kVK_Control:
return "Control";
case kVK_RightShift:
return "Right-Shift";
case kVK_RightOption:
return "Right-Option";
case kVK_RightControl:
return "Right-Control";
case kVK_ANSI_KeypadClear:
return "Clear";
case kVK_F1:
return "F1";
case kVK_F2:
return "F2";
case kVK_F3:
return "F3";
case kVK_F4:
return "F4";
case kVK_F5:
return "F5";
case kVK_F6:
return "F6";
case kVK_F7:
return "F7";
case kVK_F8:
return "F8";
case kVK_F9:
return "F9";
case kVK_F10:
return "F10";
case kVK_F11:
return "F11";
case kVK_F12:
return "F12";
case kVK_F13:
return "F13/PrintScreen";
case kVK_F14:
return "F14/ScrollLock";
case kVK_F15:
return "F15/Pause";
case kVK_ANSI_Keypad0:
return "NumPad-0";
case kVK_ANSI_Keypad1:
return "NumPad-1";
case kVK_ANSI_Keypad2:
return "NumPad-2";
case kVK_ANSI_Keypad3:
return "NumPad-3";
case kVK_ANSI_Keypad4:
return "NumPad-4";
case kVK_ANSI_Keypad5:
return "NumPad-5";
case kVK_ANSI_Keypad6:
return "NumPad-6";
case kVK_ANSI_Keypad7:
return "NumPad-7";
case kVK_ANSI_Keypad8:
return "NumPad-8";
case kVK_ANSI_Keypad9:
return "NumPad-9";
case kVK_ANSI_KeypadMultiply:
return "NumPad-*";
case kVK_ANSI_KeypadPlus:
return "NumPad-+";
case kVK_ANSI_KeypadMinus:
return "NumPad--";
case kVK_ANSI_KeypadDecimal:
return "NumPad-.";
case kVK_ANSI_KeypadDivide:
return "NumPad-/";
case kVK_ANSI_KeypadEquals:
return "NumPad-=";
case kVK_ANSI_KeypadEnter:
return "NumPad-Enter";
case kVK_Return:
return "Return";
case kVK_Powerbook_KeypadEnter:
return "NumPad-EnterOnPowerBook";
case kVK_PC_Insert:
return "Insert/Help";
case kVK_PC_Delete:
return "Delete";
case kVK_Tab:
return "Tab";
case kVK_PC_Backspace:
return "Backspace";
case kVK_Home:
return "Home";
case kVK_End:
return "End";
case kVK_PageUp:
return "PageUp";
case kVK_PageDown:
return "PageDown";
case kVK_LeftArrow:
return "LeftArrow";
case kVK_RightArrow:
return "RightArrow";
case kVK_UpArrow:
return "UpArrow";
case kVK_DownArrow:
return "DownArrow";
case kVK_PC_ContextMenu:
return "ContextMenu";
case kVK_Function:
return "Function";
case kVK_VolumeUp:
return "VolumeUp";
case kVK_VolumeDown:
return "VolumeDown";
case kVK_Mute:
return "Mute";
case kVK_ISO_Section:
return "ISO_Section";
case kVK_JIS_Yen:
return "JIS_Yen";
case kVK_JIS_Underscore:
return "JIS_Underscore";
case kVK_JIS_KeypadComma:
return "JIS_KeypadComma";
case kVK_JIS_Eisu:
return "JIS_Eisu";
case kVK_JIS_Kana:
return "JIS_Kana";
case kVK_ANSI_A:
return "A";
case kVK_ANSI_B:
return "B";
case kVK_ANSI_C:
return "C";
case kVK_ANSI_D:
return "D";
case kVK_ANSI_E:
return "E";
case kVK_ANSI_F:
return "F";
case kVK_ANSI_G:
return "G";
case kVK_ANSI_H:
return "H";
case kVK_ANSI_I:
return "I";
case kVK_ANSI_J:
return "J";
case kVK_ANSI_K:
return "K";
case kVK_ANSI_L:
return "L";
case kVK_ANSI_M:
return "M";
case kVK_ANSI_N:
return "N";
case kVK_ANSI_O:
return "O";
case kVK_ANSI_P:
return "P";
case kVK_ANSI_Q:
return "Q";
case kVK_ANSI_R:
return "R";
case kVK_ANSI_S:
return "S";
case kVK_ANSI_T:
return "T";
case kVK_ANSI_U:
return "U";
case kVK_ANSI_V:
return "V";
case kVK_ANSI_W:
return "W";
case kVK_ANSI_X:
return "X";
case kVK_ANSI_Y:
return "Y";
case kVK_ANSI_Z:
return "Z";
case kVK_ANSI_1:
return "1";
case kVK_ANSI_2:
return "2";
case kVK_ANSI_3:
return "3";
case kVK_ANSI_4:
return "4";
case kVK_ANSI_5:
return "5";
case kVK_ANSI_6:
return "6";
case kVK_ANSI_7:
return "7";
case kVK_ANSI_8:
return "8";
case kVK_ANSI_9:
return "9";
case kVK_ANSI_0:
return "0";
case kVK_ANSI_Equal:
return "Equal";
case kVK_ANSI_Minus:
return "Minus";
case kVK_ANSI_RightBracket:
return "RightBracket";
case kVK_ANSI_LeftBracket:
return "LeftBracket";
case kVK_ANSI_Quote:
return "Quote";
case kVK_ANSI_Semicolon:
return "Semicolon";
case kVK_ANSI_Backslash:
return "Backslash";
case kVK_ANSI_Comma:
return "Comma";
case kVK_ANSI_Slash:
return "Slash";
case kVK_ANSI_Period:
return "Period";
case kVK_ANSI_Grave:
return "Grave";
default:
return "undefined";
}
}
static const char* GetCharacters(const nsAString& aString) {
if (aString.IsEmpty()) {
return "";
}
nsAutoString escapedStr;
for (uint32_t i = 0; i < aString.Length(); i++) {
char16_t ch = aString.CharAt(i);
if (ch < 0x20) {
nsPrintfCString utf8str("(U+%04X)", ch);
escapedStr += NS_ConvertUTF8toUTF16(utf8str);
} else if (ch <= 0x7E) {
escapedStr += ch;
} else {
nsPrintfCString utf8str("(U+%04X)", ch);
escapedStr += ch;
escapedStr += NS_ConvertUTF8toUTF16(utf8str);
}
}
// the result will be freed automatically by cocoa.
NSString* result = nsCocoaUtils::ToNSString(escapedStr);
return [result UTF8String];
}
static const char* GetCharacters(const NSString* aString) {
nsAutoString str;
nsCocoaUtils::GetStringForNSString(aString, str);
return GetCharacters(str);
}
static const char* GetCharacters(const CFStringRef aString) {
const NSString* str = reinterpret_cast<const NSString*>(aString);
return GetCharacters(str);
}
static const char* GetNativeKeyEventType(NSEvent* aNativeEvent) {
switch ([aNativeEvent type]) {
case NSEventTypeKeyDown:
return "NSEventTypeKeyDown";
case NSEventTypeKeyUp:
return "NSEventTypeKeyUp";
case NSEventTypeFlagsChanged:
return "NSEventTypeFlagsChanged";
default:
return "not key event";
}
}
static const char* GetGeckoKeyEventType(const WidgetEvent& aEvent) {
switch (aEvent.mMessage) {
case eKeyDown:
return "eKeyDown";
case eKeyUp:
return "eKeyUp";
case eKeyPress:
return "eKeyPress";
default:
return "not key event";
}
}
static const char* GetWindowLevelName(NSInteger aWindowLevel) {
switch (aWindowLevel) {
case kCGBaseWindowLevelKey:
return "kCGBaseWindowLevelKey (NSNormalWindowLevel)";
case kCGMinimumWindowLevelKey:
return "kCGMinimumWindowLevelKey";
case kCGDesktopWindowLevelKey:
return "kCGDesktopWindowLevelKey";
case kCGBackstopMenuLevelKey:
return "kCGBackstopMenuLevelKey";
case kCGNormalWindowLevelKey:
return "kCGNormalWindowLevelKey";
case kCGFloatingWindowLevelKey:
return "kCGFloatingWindowLevelKey (NSFloatingWindowLevel)";
case kCGTornOffMenuWindowLevelKey:
return "kCGTornOffMenuWindowLevelKey (NSSubmenuWindowLevel, NSTornOffMenuWindowLevel)";
case kCGDockWindowLevelKey:
return "kCGDockWindowLevelKey (NSDockWindowLevel)";
case kCGMainMenuWindowLevelKey:
return "kCGMainMenuWindowLevelKey (NSMainMenuWindowLevel)";
case kCGStatusWindowLevelKey:
return "kCGStatusWindowLevelKey (NSStatusWindowLevel)";
case kCGModalPanelWindowLevelKey:
return "kCGModalPanelWindowLevelKey (NSModalPanelWindowLevel)";
case kCGPopUpMenuWindowLevelKey:
return "kCGPopUpMenuWindowLevelKey (NSPopUpMenuWindowLevel)";
case kCGDraggingWindowLevelKey:
return "kCGDraggingWindowLevelKey";
case kCGScreenSaverWindowLevelKey:
return "kCGScreenSaverWindowLevelKey (NSScreenSaverWindowLevel)";
case kCGMaximumWindowLevelKey:
return "kCGMaximumWindowLevelKey";
case kCGOverlayWindowLevelKey:
return "kCGOverlayWindowLevelKey";
case kCGHelpWindowLevelKey:
return "kCGHelpWindowLevelKey";
case kCGUtilityWindowLevelKey:
return "kCGUtilityWindowLevelKey";
case kCGDesktopIconWindowLevelKey:
return "kCGDesktopIconWindowLevelKey";
case kCGCursorWindowLevelKey:
return "kCGCursorWindowLevelKey";
case kCGNumberOfWindowLevelKeys:
return "kCGNumberOfWindowLevelKeys";
default:
return "unknown window level";
}
}
static bool IsControlChar(uint32_t aCharCode) { return aCharCode < ' ' || aCharCode == 0x7F; }
static uint32_t gHandlerInstanceCount = 0;
static void EnsureToLogAllKeyboardLayoutsAndIMEs() {
static bool sDone = false;
if (!sDone) {
sDone = true;
TextInputHandler::DebugPrintAllKeyboardLayouts();
IMEInputHandler::DebugPrintAllIMEModes();
}
}
inline NSRange MakeNSRangeFrom(const Maybe<OffsetAndData<uint32_t>>& aOffsetAndData) {
if (aOffsetAndData.isNothing()) {
return NSMakeRange(NSNotFound, 0);
}
return NSMakeRange(aOffsetAndData->StartOffset(), aOffsetAndData->Length());
}
#pragma mark -
/******************************************************************************
*
* TISInputSourceWrapper implementation
*
******************************************************************************/
TISInputSourceWrapper* TISInputSourceWrapper::sCurrentInputSource = nullptr;
// static
TISInputSourceWrapper& TISInputSourceWrapper::CurrentInputSource() {
if (!sCurrentInputSource) {
sCurrentInputSource = new TISInputSourceWrapper();
}
if (!sCurrentInputSource->IsInitializedByCurrentInputSource()) {
sCurrentInputSource->InitByCurrentInputSource();
}
return *sCurrentInputSource;
}
// static
void TISInputSourceWrapper::Shutdown() {
if (!sCurrentInputSource) {
return;
}
sCurrentInputSource->Clear();
delete sCurrentInputSource;
sCurrentInputSource = nullptr;
}
bool TISInputSourceWrapper::TranslateToString(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType,
nsAString& aStr) {
aStr.Truncate();
const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
MOZ_LOG(gLog, LogLevel::Info,
("%p TISInputSourceWrapper::TranslateToString, aKeyCode=0x%X, "
"aModifiers=0x%X, aKbType=0x%X UCKey=%p\n "
"Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
this, static_cast<unsigned int>(aKeyCode), static_cast<unsigned int>(aModifiers),
static_cast<unsigned int>(aKbType), UCKey, OnOrOff(aModifiers & shiftKey),
OnOrOff(aModifiers & controlKey), OnOrOff(aModifiers & optionKey),
OnOrOff(aModifiers & cmdKey), OnOrOff(aModifiers & alphaLock),
OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));
NS_ENSURE_TRUE(UCKey, false);
UInt32 deadKeyState = 0;
UniCharCount len;
UniChar chars[5];
OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode, kUCKeyActionDown, aModifiers >> 8, aKbType,
kUCKeyTranslateNoDeadKeysMask, &deadKeyState, 5, &len, chars);
MOZ_LOG(gLog, LogLevel::Info,
("%p TISInputSourceWrapper::TranslateToString, err=0x%X, len=%zu", this,
static_cast<int>(err), len));
NS_ENSURE_TRUE(err == noErr, false);
if (len == 0) {
return true;
}
if (!aStr.SetLength(len, fallible)) {
return false;
}
NS_ASSERTION(sizeof(char16_t) == sizeof(UniChar),
"size of char16_t and size of UniChar are different");
memcpy(aStr.BeginWriting(), chars, len * sizeof(char16_t));
MOZ_LOG(gLog, LogLevel::Info,
("%p TISInputSourceWrapper::TranslateToString, aStr=\"%s\"", this,
NS_ConvertUTF16toUTF8(aStr).get()));
return true;
}
uint32_t TISInputSourceWrapper::TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers,
UInt32 aKbType) {
nsAutoString str;
if (!TranslateToString(aKeyCode, aModifiers, aKbType, str) || str.Length() != 1) {
return 0;
}
return static_cast<uint32_t>(str.CharAt(0));
}
bool TISInputSourceWrapper::IsDeadKey(NSEvent* aNativeKeyEvent) {
if ([[aNativeKeyEvent characters] length]) {
return false;
}
// Assmue that if control key or command key is pressed, it's not a dead key.
NSUInteger cocoaState = [aNativeKeyEvent modifierFlags];
if (cocoaState & (NSEventModifierFlagControl | NSEventModifierFlagCommand)) {
return false;
}
UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
switch (nativeKeyCode) {
case kVK_ANSI_A:
case kVK_ANSI_B:
case kVK_ANSI_C:
case kVK_ANSI_D:
case kVK_ANSI_E:
case kVK_ANSI_F:
case kVK_ANSI_G:
case kVK_ANSI_H:
case kVK_ANSI_I:
case kVK_ANSI_J:
case kVK_ANSI_K:
case kVK_ANSI_L:
case kVK_ANSI_M:
case kVK_ANSI_N:
case kVK_ANSI_O:
case kVK_ANSI_P:
case kVK_ANSI_Q:
case kVK_ANSI_R:
case kVK_ANSI_S:
case kVK_ANSI_T:
case kVK_ANSI_U:
case kVK_ANSI_V:
case kVK_ANSI_W:
case kVK_ANSI_X:
case kVK_ANSI_Y:
case kVK_ANSI_Z:
case kVK_ANSI_1:
case kVK_ANSI_2:
case kVK_ANSI_3:
case kVK_ANSI_4:
case kVK_ANSI_5:
case kVK_ANSI_6:
case kVK_ANSI_7:
case kVK_ANSI_8:
case kVK_ANSI_9:
case kVK_ANSI_0:
case kVK_ANSI_Equal:
case kVK_ANSI_Minus:
case kVK_ANSI_RightBracket:
case kVK_ANSI_LeftBracket:
case kVK_ANSI_Quote:
case kVK_ANSI_Semicolon:
case kVK_ANSI_Backslash:
case kVK_ANSI_Comma:
case kVK_ANSI_Slash:
case kVK_ANSI_Period:
case kVK_ANSI_Grave:
case kVK_JIS_Yen:
case kVK_JIS_Underscore:
break;
default:
// Let's assume that dead key can be only a printable key in standard
// position.
return false;
}
// If TranslateToChar() returns non-zero value, that means that
// the key may input a character with different dead key state.
UInt32 kbType = GetKbdType();
UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
return IsDeadKey(nativeKeyCode, carbonState, kbType);
}
bool TISInputSourceWrapper::IsDeadKey(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType) {
const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
MOZ_LOG(gLog, LogLevel::Info,
("%p TISInputSourceWrapper::IsDeadKey, aKeyCode=0x%X, "
"aModifiers=0x%X, aKbType=0x%X UCKey=%p\n "
"Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
this, static_cast<unsigned int>(aKeyCode), static_cast<unsigned int>(aModifiers),
static_cast<unsigned int>(aKbType), UCKey, OnOrOff(aModifiers & shiftKey),
OnOrOff(aModifiers & controlKey), OnOrOff(aModifiers & optionKey),
OnOrOff(aModifiers & cmdKey), OnOrOff(aModifiers & alphaLock),
OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));
if (NS_WARN_IF(!UCKey)) {
return false;
}
UInt32 deadKeyState = 0;
UniCharCount len;
UniChar chars[5];
OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode, kUCKeyActionDown, aModifiers >> 8, aKbType, 0,
&deadKeyState, 5, &len, chars);
MOZ_LOG(gLog, LogLevel::Info,
("%p TISInputSourceWrapper::IsDeadKey, err=0x%X, "
"len=%zu, deadKeyState=%u",
this, static_cast<int>(err), len, deadKeyState));
if (NS_WARN_IF(err != noErr)) {
return false;
}
return deadKeyState != 0;
}
void TISInputSourceWrapper::InitByInputSourceID(const char* aID) {
Clear();
if (!aID) return;
CFStringRef idstr = ::CFStringCreateWithCString(kCFAllocatorDefault, aID, kCFStringEncodingASCII);
InitByInputSourceID(idstr);
::CFRelease(idstr);
}
void TISInputSourceWrapper::InitByInputSourceID(const nsString& aID) {
Clear();
if (aID.IsEmpty()) return;
CFStringRef idstr = ::CFStringCreateWithCharacters(
kCFAllocatorDefault, reinterpret_cast<const UniChar*>(aID.get()), aID.Length());
InitByInputSourceID(idstr);
::CFRelease(idstr);
}
void TISInputSourceWrapper::InitByInputSourceID(const CFStringRef aID) {
Clear();
if (!aID) return;
const void* keys[] = {kTISPropertyInputSourceID};
const void* values[] = {aID};
CFDictionaryRef filter = ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
NS_ASSERTION(filter, "failed to create the filter");
mInputSourceList = ::TISCreateInputSourceList(filter, true);
::CFRelease(filter);
if (::CFArrayGetCount(mInputSourceList) > 0) {
mInputSource = static_cast<TISInputSourceRef>(
const_cast<void*>(::CFArrayGetValueAtIndex(mInputSourceList, 0)));
if (IsKeyboardLayout()) {
mKeyboardLayout = mInputSource;
}
}
}
void TISInputSourceWrapper::InitByLayoutID(SInt32 aLayoutID, bool aOverrideKeyboard) {
// NOTE: Doument new layout IDs in TextInputHandler.h when you add ones.
switch (aLayoutID) {
case 0:
InitByInputSourceID("com.apple.keylayout.US");
break;
case 1:
InitByInputSourceID("com.apple.keylayout.Greek");
break;
case 2:
InitByInputSourceID("com.apple.keylayout.German");
break;
case 3:
InitByInputSourceID("com.apple.keylayout.Swedish-Pro");
break;
case 4:
InitByInputSourceID("com.apple.keylayout.DVORAK-QWERTYCMD");
break;
case 5:
InitByInputSourceID("com.apple.keylayout.Thai");
break;
case 6:
InitByInputSourceID("com.apple.keylayout.Arabic");
break;
case 7:
InitByInputSourceID("com.apple.keylayout.ArabicPC");
break;
case 8:
InitByInputSourceID("com.apple.keylayout.French");
break;
case 9:
InitByInputSourceID("com.apple.keylayout.Hebrew");
break;
case 10:
InitByInputSourceID("com.apple.keylayout.Lithuanian");
break;
case 11:
InitByInputSourceID("com.apple.keylayout.Norwegian");
break;
case 12:
InitByInputSourceID("com.apple.keylayout.Spanish");
break;
default:
Clear();
break;
}
mOverrideKeyboard = aOverrideKeyboard;
}
void TISInputSourceWrapper::InitByCurrentInputSource() {
Clear();
mInputSource = ::TISCopyCurrentKeyboardInputSource();
mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
if (!mKeyboardLayout) {
mKeyboardLayout = ::TISCopyCurrentKeyboardLayoutInputSource();
}
// If this causes composition, the current keyboard layout may input non-ASCII
// characters such as Japanese Kana characters or Hangul characters.
// However, we need to set ASCII characters to DOM key events for consistency
// with other platforms.
if (IsOpenedIMEMode()) {
TISInputSourceWrapper tis(mKeyboardLayout);
if (!tis.IsASCIICapable()) {
mKeyboardLayout = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
}
}
}
void TISInputSourceWrapper::InitByCurrentKeyboardLayout() {
Clear();
mInputSource = ::TISCopyCurrentKeyboardLayoutInputSource();
mKeyboardLayout = mInputSource;
}
void TISInputSourceWrapper::InitByCurrentASCIICapableInputSource() {
Clear();
mInputSource = ::TISCopyCurrentASCIICapableKeyboardInputSource();
mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
if (mKeyboardLayout) {
TISInputSourceWrapper tis(mKeyboardLayout);
if (!tis.IsASCIICapable()) {
mKeyboardLayout = nullptr;
}
}
if (!mKeyboardLayout) {
mKeyboardLayout = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
}
}
void TISInputSourceWrapper::InitByCurrentASCIICapableKeyboardLayout() {
Clear();
mInputSource = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
mKeyboardLayout = mInputSource;
}
void TISInputSourceWrapper::InitByCurrentInputMethodKeyboardLayoutOverride() {
Clear();
mInputSource = ::TISCopyInputMethodKeyboardLayoutOverride();
mKeyboardLayout = mInputSource;
}
void TISInputSourceWrapper::InitByTISInputSourceRef(TISInputSourceRef aInputSource) {
Clear();
mInputSource = aInputSource;
if (IsKeyboardLayout()) {
mKeyboardLayout = mInputSource;
}
}
void TISInputSourceWrapper::InitByLanguage(CFStringRef aLanguage) {
Clear();
mInputSource = ::TISCopyInputSourceForLanguage(aLanguage);
if (IsKeyboardLayout()) {
mKeyboardLayout = mInputSource;
}
}
const UCKeyboardLayout* TISInputSourceWrapper::GetUCKeyboardLayout() {
NS_ENSURE_TRUE(mKeyboardLayout, nullptr);
if (mUCKeyboardLayout) {
return mUCKeyboardLayout;
}
CFDataRef uchr = static_cast<CFDataRef>(
::TISGetInputSourceProperty(mKeyboardLayout, kTISPropertyUnicodeKeyLayoutData));
// We should be always able to get the layout here.
NS_ENSURE_TRUE(uchr, nullptr);
mUCKeyboardLayout = reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(uchr));
return mUCKeyboardLayout;
}
bool TISInputSourceWrapper::GetBoolProperty(const CFStringRef aKey) {
CFBooleanRef ret = static_cast<CFBooleanRef>(::TISGetInputSourceProperty(mInputSource, aKey));
return ::CFBooleanGetValue(ret);
}
bool TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey, CFStringRef& aStr) {
aStr = static_cast<CFStringRef>(::TISGetInputSourceProperty(mInputSource, aKey));
return aStr != nullptr;
}
bool TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey, nsAString& aStr) {
CFStringRef str;
GetStringProperty(aKey, str);
nsCocoaUtils::GetStringForNSString((const NSString*)str, aStr);
return !aStr.IsEmpty();
}
bool TISInputSourceWrapper::IsOpenedIMEMode() {
NS_ENSURE_TRUE(mInputSource, false);
if (!IsIMEMode()) return false;
return !IsASCIICapable();
}
bool TISInputSourceWrapper::IsIMEMode() {
NS_ENSURE_TRUE(mInputSource, false);
CFStringRef str;
GetInputSourceType(str);
NS_ENSURE_TRUE(str, false);
return ::CFStringCompare(kTISTypeKeyboardInputMode, str, 0) == kCFCompareEqualTo;
}
bool TISInputSourceWrapper::IsKeyboardLayout() {
NS_ENSURE_TRUE(mInputSource, false);
CFStringRef str;
GetInputSourceType(str);
NS_ENSURE_TRUE(str, false);
return ::CFStringCompare(kTISTypeKeyboardLayout, str, 0) == kCFCompareEqualTo;
}
bool TISInputSourceWrapper::GetLanguageList(CFArrayRef& aLanguageList) {
NS_ENSURE_TRUE(mInputSource, false);
aLanguageList = static_cast<CFArrayRef>(
::TISGetInputSourceProperty(mInputSource, kTISPropertyInputSourceLanguages));
return aLanguageList != nullptr;
}
bool TISInputSourceWrapper::GetPrimaryLanguage(CFStringRef& aPrimaryLanguage) {
NS_ENSURE_TRUE(mInputSource, false);
CFArrayRef langList;
NS_ENSURE_TRUE(GetLanguageList(langList), false);
if (::CFArrayGetCount(langList) == 0) return false;
aPrimaryLanguage = static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, 0));
return aPrimaryLanguage != nullptr;
}
bool TISInputSourceWrapper::GetPrimaryLanguage(nsAString& aPrimaryLanguage) {
NS_ENSURE_TRUE(mInputSource, false);
CFStringRef primaryLanguage;
NS_ENSURE_TRUE(GetPrimaryLanguage(primaryLanguage), false);
nsCocoaUtils::GetStringForNSString((const NSString*)primaryLanguage, aPrimaryLanguage);
return !aPrimaryLanguage.IsEmpty();
}
bool TISInputSourceWrapper::IsForRTLLanguage() {
if (mIsRTL < 0) {
// Get the input character of the 'A' key of ANSI keyboard layout.
nsAutoString str;
bool ret = TranslateToString(kVK_ANSI_A, 0, eKbdType_ANSI, str);
NS_ENSURE_TRUE(ret, ret);
char16_t ch = str.IsEmpty() ? char16_t(0) : str.CharAt(0);
mIsRTL = UTF16_CODE_UNIT_IS_BIDI(ch);
}
return mIsRTL != 0;
}
bool TISInputSourceWrapper::IsForJapaneseLanguage() {
nsAutoString lang;
GetPrimaryLanguage(lang);
return lang.EqualsLiteral("ja");
}
bool TISInputSourceWrapper::IsInitializedByCurrentInputSource() {
return mInputSource == ::TISCopyCurrentKeyboardInputSource();
}
void TISInputSourceWrapper::Select() {
if (!mInputSource) return;
::TISSelectInputSource(mInputSource);
}
void TISInputSourceWrapper::Clear() {
// Clear() is always called when TISInputSourceWrappper is created.
EnsureToLogAllKeyboardLayoutsAndIMEs();
if (mInputSourceList) {
::CFRelease(mInputSourceList);
}
mInputSourceList = nullptr;
mInputSource = nullptr;
mKeyboardLayout = nullptr;
mIsRTL = -1;
mUCKeyboardLayout = nullptr;
mOverrideKeyboard = false;
}
bool TISInputSourceWrapper::IsPrintableKeyEvent(NSEvent* aNativeKeyEvent) const {
UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
bool isPrintableKey = !TextInputHandler::IsSpecialGeckoKey(nativeKeyCode);
if (isPrintableKey && [aNativeKeyEvent type] != NSEventTypeKeyDown &&
[aNativeKeyEvent type] != NSEventTypeKeyUp) {
NS_WARNING("Why would a printable key not be an NSEventTypeKeyDown or NSEventTypeKeyUp event?");
isPrintableKey = false;
}
return isPrintableKey;
}
UInt32 TISInputSourceWrapper::GetKbdType() const {
// If a keyboard layout override is set, we also need to force the keyboard
// type to something ANSI to avoid test failures on machines with JIS
// keyboards (since the pair of keyboard layout and physical keyboard type
// form the actual key layout). This assumes that the test setting the
// override was written assuming an ANSI keyboard.
return mOverrideKeyboard ? eKbdType_ANSI : ::LMGetKbdType();
}
void TISInputSourceWrapper::ComputeInsertStringForCharCode(NSEvent* aNativeKeyEvent,
const WidgetKeyboardEvent& aKeyEvent,
const nsAString* aInsertString,
nsAString& aResult) {
if (aInsertString) {
// If the caller expects that the aInsertString will be input, we shouldn't
// change it.
aResult = *aInsertString;
} else if (IsPrintableKeyEvent(aNativeKeyEvent)) {
// If IME is open, [aNativeKeyEvent characters] may be a character
// which will be appended to the composition string. However, especially,
// while IME is disabled, most users and developers expect the key event
// works as IME closed. So, we should compute the aResult with
// the ASCII capable keyboard layout.
// NOTE: Such keyboard layouts typically change the layout to its ASCII
// capable layout when Command key is pressed. And we don't worry
// when Control key is pressed too because it causes inputting
// control characters.
// Additionally, if the key event doesn't input any text, the event may be
// dead key event. In this case, the charCode value should be the dead
// character.
UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
if ((!aKeyEvent.IsMeta() && !aKeyEvent.IsControl() && IsOpenedIMEMode()) ||
![[aNativeKeyEvent characters] length]) {
UInt32 state = nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
uint32_t ch = TranslateToChar(nativeKeyCode, state, GetKbdType());
if (ch) {
aResult = ch;
}
} else {
// If the caller isn't sure what string will be input, let's use
// characters of NSEvent.
nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], aResult);
}
// If control key is pressed and the eventChars is a non-printable control
// character, we should convert it to ASCII alphabet.
if (aKeyEvent.IsControl() && !aResult.IsEmpty() && aResult[0] <= char16_t(26)) {
aResult = (aKeyEvent.IsShift() ^ aKeyEvent.IsCapsLocked())
? static_cast<char16_t>(aResult[0] + ('A' - 1))
: static_cast<char16_t>(aResult[0] + ('a' - 1));
}
// If Meta key is pressed, it may cause to switch the keyboard layout like
// Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
else if (aKeyEvent.IsMeta() && !(aKeyEvent.IsControl() || aKeyEvent.IsAlt())) {
UInt32 kbType = GetKbdType();
UInt32 numLockState = aKeyEvent.IsNumLocked() ? kEventKeyModifierNumLockMask : 0;
UInt32 capsLockState = aKeyEvent.IsCapsLocked() ? alphaLock : 0;
UInt32 shiftState = aKeyEvent.IsShift() ? shiftKey : 0;
uint32_t uncmdedChar = TranslateToChar(nativeKeyCode, numLockState, kbType);
uint32_t cmdedChar = TranslateToChar(nativeKeyCode, cmdKey | numLockState, kbType);
// If we can make a good guess at the characters that the user would
// expect this key combination to produce (with and without Shift) then
// use those characters. This also corrects for CapsLock.
uint32_t ch = 0;
if (uncmdedChar == cmdedChar) {
// The characters produced with Command seem similar to those without
// Command.
ch = TranslateToChar(nativeKeyCode, shiftState | capsLockState | numLockState, kbType);
} else {
TISInputSourceWrapper USLayout("com.apple.keylayout.US");
uint32_t uncmdedUSChar = USLayout.TranslateToChar(nativeKeyCode, numLockState, kbType);
// If it looks like characters from US keyboard layout when Command key
// is pressed, we should compute a character in the layout.
if (uncmdedUSChar == cmdedChar) {
ch = USLayout.TranslateToChar(nativeKeyCode, shiftState | capsLockState | numLockState,
kbType);
}
}
// If there is a more preferred character for the commanded key event,
// we should use it.
if (ch) {
aResult = ch;
}
}
}
// Remove control characters which shouldn't be inputted on editor.
// XXX Currently, we don't find any cases inserting control characters with
// printable character. So, just checking first character is enough.
if (!aResult.IsEmpty() && IsControlChar(aResult[0])) {
aResult.Truncate();
}
}
void TISInputSourceWrapper::InitKeyEvent(NSEvent* aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
bool aIsProcessedByIME, const nsAString* aInsertString) {
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
MOZ_ASSERT(!aIsProcessedByIME || aKeyEvent.mMessage != eKeyPress,
"eKeyPress event should not be marked as proccessed by IME");
MOZ_LOG(gLog, LogLevel::Info,
("%p TISInputSourceWrapper::InitKeyEvent, aNativeKeyEvent=%p, "
"aKeyEvent.mMessage=%s, aProcessedByIME=%s, aInsertString=%p, "
"IsOpenedIMEMode()=%s",
this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent), TrueOrFalse(aIsProcessedByIME),
aInsertString, TrueOrFalse(IsOpenedIMEMode())));
if (NS_WARN_IF(!aNativeKeyEvent)) {
return;
}
nsCocoaUtils::InitInputEvent(aKeyEvent, aNativeKeyEvent);
// This is used only while dispatching the event (which is a synchronous
// call), so there is no need to retain and release this data.
aKeyEvent.mNativeKeyEvent = aNativeKeyEvent;
aKeyEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
UInt32 kbType = GetKbdType();
UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
// macOS handles dead key as IME. If the key is first key press of dead
// key, we should use KEY_NAME_INDEX_Dead for first (dead) key event.
// So, if aIsProcessedByIME is true, it may be dead key. Let's check
// if current key event is a dead key's keydown event.
bool isProcessedByIME =
aIsProcessedByIME && !TISInputSourceWrapper::CurrentInputSource().IsDeadKey(aNativeKeyEvent);
aKeyEvent.mKeyCode = isProcessedByIME
? NS_VK_PROCESSKEY
: ComputeGeckoKeyCode(nativeKeyCode, kbType, aKeyEvent.IsMeta());
switch (nativeKeyCode) {
case kVK_Command:
case kVK_Shift:
case kVK_Option:
case kVK_Control:
aKeyEvent.mLocation = eKeyLocationLeft;
break;
case kVK_RightCommand:
case kVK_RightShift:
case kVK_RightOption:
case kVK_RightControl:
aKeyEvent.mLocation = eKeyLocationRight;
break;
case kVK_ANSI_Keypad0:
case kVK_ANSI_Keypad1:
case kVK_ANSI_Keypad2:
case kVK_ANSI_Keypad3:
case kVK_ANSI_Keypad4:
case kVK_ANSI_Keypad5:
case kVK_ANSI_Keypad6:
case kVK_ANSI_Keypad7:
case kVK_ANSI_Keypad8:
case kVK_ANSI_Keypad9:
case kVK_ANSI_KeypadMultiply:
case kVK_ANSI_KeypadPlus:
case kVK_ANSI_KeypadMinus:
case kVK_ANSI_KeypadDecimal:
case kVK_ANSI_KeypadDivide:
case kVK_ANSI_KeypadEquals:
case kVK_ANSI_KeypadEnter:
case kVK_JIS_KeypadComma:
case kVK_Powerbook_KeypadEnter:
aKeyEvent.mLocation = eKeyLocationNumpad;
break;
default:
aKeyEvent.mLocation = eKeyLocationStandard;
break;
}
aKeyEvent.mIsRepeat =
([aNativeKeyEvent type] == NSEventTypeKeyDown) ? [aNativeKeyEvent isARepeat] : false;
MOZ_LOG(gLog, LogLevel::Info,
("%p TISInputSourceWrapper::InitKeyEvent, "
"shift=%s, ctrl=%s, alt=%s, meta=%s",
this, OnOrOff(aKeyEvent.IsShift()), OnOrOff(aKeyEvent.IsControl()),
OnOrOff(aKeyEvent.IsAlt()), OnOrOff(aKeyEvent.IsMeta())));
if (isProcessedByIME) {
aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
} else if (IsPrintableKeyEvent(aNativeKeyEvent)) {
aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
// If insertText calls this method, let's use the string.
if (aInsertString && !aInsertString->IsEmpty() && !IsControlChar((*aInsertString)[0])) {
aKeyEvent.mKeyValue = *aInsertString;
}
// If meta key is pressed, the printable key layout may be switched from
// non-ASCII capable layout to ASCII capable, or from Dvorak to QWERTY.
// KeyboardEvent.key value should be the switched layout's character.
else if (aKeyEvent.IsMeta()) {
nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], aKeyEvent.mKeyValue);
}
// If control key is pressed, some keys may produce printable character via
// [aNativeKeyEvent characters]. Otherwise, translate input character of
// the key without control key.
else if (aKeyEvent.IsControl()) {
NSUInteger cocoaState = [aNativeKeyEvent modifierFlags] & ~NSEventModifierFlagControl;
UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
if (IsDeadKey(nativeKeyCode, carbonState, kbType)) {
aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Dead;
} else {
aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, carbonState, kbType);
if (!aKeyEvent.mKeyValue.IsEmpty() && IsControlChar(aKeyEvent.mKeyValue[0])) {
// Don't expose control character to the web.
aKeyEvent.mKeyValue.Truncate();
}
}
}
// Otherwise, KeyboardEvent.key expose
// [aNativeKeyEvent characters] value. However, if IME is open and the
// keyboard layout isn't ASCII capable, exposing the non-ASCII character
// doesn't match with other platform's behavior. For the compatibility
// with other platform's Gecko, we need to set a translated character.
else if (IsOpenedIMEMode()) {
UInt32 state = nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, state, kbType);
} else {
nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], aKeyEvent.mKeyValue);
// If the key value is empty, the event may be a dead key event.
// If TranslateToChar() returns non-zero value, that means that
// the key may input a character with different dead key state.
if (aKeyEvent.mKeyValue.IsEmpty()) {
NSUInteger cocoaState = [aNativeKeyEvent modifierFlags];
UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
if (TranslateToChar(nativeKeyCode, carbonState, kbType)) {
aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Dead;
}
}
}
// Last resort. If .key value becomes empty string, we should use
// charactersIgnoringModifiers, if it's available.
if (aKeyEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
(aKeyEvent.mKeyValue.IsEmpty() || IsControlChar(aKeyEvent.mKeyValue[0]))) {
nsCocoaUtils::GetStringForNSString([aNativeKeyEvent charactersIgnoringModifiers],
aKeyEvent.mKeyValue);
// But don't expose it if it's a control character.
if (!aKeyEvent.mKeyValue.IsEmpty() && IsControlChar(aKeyEvent.mKeyValue[0])) {
aKeyEvent.mKeyValue.Truncate();
}
}
} else {
// Compute the key for non-printable keys and some special printable keys.
aKeyEvent.mKeyNameIndex = ComputeGeckoKeyNameIndex(nativeKeyCode);
}
aKeyEvent.mCodeNameIndex = ComputeGeckoCodeNameIndex(nativeKeyCode, kbType);
MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
NS_OBJC_END_TRY_IGNORE_BLOCK
}
void TISInputSourceWrapper::WillDispatchKeyboardEvent(NSEvent* aNativeKeyEvent,
const nsAString* aInsertString,
uint32_t aIndexOfKeypress,
WidgetKeyboardEvent& aKeyEvent) {
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
// Nothing to do here if the native key event is neither NSEventTypeKeyDown nor
// NSEventTypeKeyUp because accessing [aNativeKeyEvent characters] causes throwing
// an exception.
if ([aNativeKeyEvent type] != NSEventTypeKeyDown && [aNativeKeyEvent type] != NSEventTypeKeyUp) {
return;
}
UInt32 kbType = GetKbdType();
if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
nsAutoString chars;
nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], chars);
NS_ConvertUTF16toUTF8 utf8Chars(chars);
char16_t uniChar = static_cast<char16_t>(aKeyEvent.mCharCode);
MOZ_LOG(gLog, LogLevel::Info,
("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
"aNativeKeyEvent=%p, aInsertString=%p (\"%s\"), "
"aIndexOfKeypress=%u, [aNativeKeyEvent characters]=\"%s\", "
"aKeyEvent={ mMessage=%s, mCharCode=0x%X(%s) }, kbType=0x%X, "
"IsOpenedIMEMode()=%s",
this, aNativeKeyEvent, aInsertString,
aInsertString ? GetCharacters(*aInsertString) : "", aIndexOfKeypress,
GetCharacters([aNativeKeyEvent characters]), GetGeckoKeyEventType(aKeyEvent),
aKeyEvent.mCharCode, uniChar ? NS_ConvertUTF16toUTF8(&uniChar, 1).get() : "",
static_cast<unsigned int>(kbType), TrueOrFalse(IsOpenedIMEMode())));
}
nsAutoString insertStringForCharCode;
ComputeInsertStringForCharCode(aNativeKeyEvent, aKeyEvent, aInsertString,
insertStringForCharCode);
// 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.
uint32_t charCode = 0;
if (aIndexOfKeypress < insertStringForCharCode.Length()) {
charCode = insertStringForCharCode[aIndexOfKeypress];
}
aKeyEvent.SetCharCode(charCode);
MOZ_LOG(gLog, LogLevel::Info,
("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
"aKeyEvent.mKeyCode=0x%X, aKeyEvent.mCharCode=0x%X",
this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode));
// If aInsertString is not nullptr (it means InsertText() is called)
// and it acutally inputs a character, we don't need to append alternative
// charCode values since such keyboard event shouldn't be handled as
// a shortcut key.
if (aInsertString && charCode) {
return;
}
TISInputSourceWrapper USLayout("com.apple.keylayout.US");
bool isRomanKeyboardLayout = IsASCIICapable();
UInt32 key = [aNativeKeyEvent keyCode];
// Caps lock and num lock modifier state:
UInt32 lockState = 0;
if ([aNativeKeyEvent modifierFlags] & NSEventModifierFlagCapsLock) {
lockState |= alphaLock;
}
if ([aNativeKeyEvent modifierFlags] & NSEventModifierFlagNumericPad) {
lockState |= kEventKeyModifierNumLockMask;
}
MOZ_LOG(gLog, LogLevel::Info,
("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
"isRomanKeyboardLayout=%s, kbType=0x%X, key=0x%X",
this, TrueOrFalse(isRomanKeyboardLayout), static_cast<unsigned int>(kbType),
static_cast<unsigned int>(key)));
nsString str;
// normal chars
uint32_t unshiftedChar = TranslateToChar(key, lockState, kbType);
UInt32 shiftLockMod = shiftKey | lockState;
uint32_t shiftedChar = TranslateToChar(key, shiftLockMod, kbType);
// characters generated with Cmd key
// XXX we should remove CapsLock state, which changes characters from
// Latin to Cyrillic with Russian layout on 10.4 only when Cmd key
// is pressed.
UInt32 numState = (lockState & ~alphaLock); // only num lock state
uint32_t uncmdedChar = TranslateToChar(key, numState, kbType);
UInt32 shiftNumMod = numState | shiftKey;
uint32_t uncmdedShiftChar = TranslateToChar(key, shiftNumMod, kbType);
uint32_t uncmdedUSChar = USLayout.TranslateToChar(key, numState, kbType);
UInt32 cmdNumMod = cmdKey | numState;
uint32_t cmdedChar = TranslateToChar(key, cmdNumMod, kbType);
UInt32 cmdShiftNumMod = shiftKey | cmdNumMod;
uint32_t cmdedShiftChar = TranslateToChar(key, cmdShiftNumMod, kbType);
// Is the keyboard layout changed by Cmd key?
// E.g., Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
bool isCmdSwitchLayout = uncmdedChar != cmdedChar;
// Is the keyboard layout for Latin, but Cmd key switches the layout?
// I.e., Dvorak-QWERTY
bool isDvorakQWERTY = isCmdSwitchLayout && isRomanKeyboardLayout;
// If the current keyboard is not Dvorak-QWERTY or Cmd is not pressed,
// we should append unshiftedChar and shiftedChar for handling the
// normal characters. These are the characters that the user is most
// likely to associate with this key.
if ((unshiftedChar || shiftedChar) && (!aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
AlternativeCharCode altCharCodes(unshiftedChar, shiftedChar);
aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
}
MOZ_LOG(
gLog, LogLevel::Info,
("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
"aKeyEvent.isMeta=%s, isDvorakQWERTY=%s, "
"unshiftedChar=U+%X, shiftedChar=U+%X",
this, OnOrOff(aKeyEvent.IsMeta()), TrueOrFalse(isDvorakQWERTY), unshiftedChar, shiftedChar));
// Most keyboard layouts provide the same characters in the NSEvents
// with Command+Shift as with Command. However, with Command+Shift we
// want the character on the second level. e.g. With a US QWERTY
// layout, we want "?" when the "/","?" key is pressed with
// Command+Shift.
// On a German layout, the OS gives us '/' with Cmd+Shift+SS(eszett)
// even though Cmd+SS is 'SS' and Shift+'SS' is '?'. This '/' seems
// like a hack to make the Cmd+"?" event look the same as the Cmd+"?"
// event on a US keyboard. The user thinks they are typing Cmd+"?", so
// we'll prefer the "?" character, replacing mCharCode with shiftedChar
// when Shift is pressed. However, in case there is a layout where the
// character unique to Cmd+Shift is the character that the user expects,
// we'll send it as an alternative char.
bool hasCmdShiftOnlyChar = cmdedChar != cmdedShiftChar && uncmdedShiftChar != cmdedShiftChar;
uint32_t originalCmdedShiftChar = cmdedShiftChar;
// If we can make a good guess at the characters that the user would
// expect this key combination to produce (with and without Shift) then
// use those characters. This also corrects for CapsLock, which was
// ignored above.
if (!isCmdSwitchLayout) {
// The characters produced with Command seem similar to those without
// Command.
if (unshiftedChar) {
cmdedChar = unshiftedChar;
}
if (shiftedChar) {
cmdedShiftChar = shiftedChar;
}
} else if (uncmdedUSChar == cmdedChar) {
// It looks like characters from a US layout are provided when Command
// is down.
uint32_t ch = USLayout.TranslateToChar(key, lockState, kbType);
if (ch) {
cmdedChar = ch;
}
ch = USLayout.TranslateToChar(key, shiftLockMod, kbType);
if (ch) {
cmdedShiftChar = ch;
}
}
// If the current keyboard layout is switched by the Cmd key,
// we should append cmdedChar and shiftedCmdChar that are
// Latin char for the key.
// If the keyboard layout is Dvorak-QWERTY, we should append them only when
// command key is pressed because when command key isn't pressed, uncmded
// chars have been appended already.
if ((cmdedChar || cmdedShiftChar) && isCmdSwitchLayout &&
(aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
AlternativeCharCode altCharCodes(cmdedChar, cmdedShiftChar);
aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
}
MOZ_LOG(gLog, LogLevel::Info,
("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
"hasCmdShiftOnlyChar=%s, isCmdSwitchLayout=%s, isDvorakQWERTY=%s, "
"cmdedChar=U+%X, cmdedShiftChar=U+%X",
this, TrueOrFalse(hasCmdShiftOnlyChar), TrueOrFalse(isDvorakQWERTY),
TrueOrFalse(isDvorakQWERTY), cmdedChar, cmdedShiftChar));
// Special case for 'SS' key of German layout. See the comment of
// hasCmdShiftOnlyChar definition for the detail.
if (hasCmdShiftOnlyChar && originalCmdedShiftChar) {
AlternativeCharCode altCharCodes(0, originalCmdedShiftChar);
aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
}
MOZ_LOG(gLog, LogLevel::Info,
("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
"hasCmdShiftOnlyChar=%s, originalCmdedShiftChar=U+%X",
this, TrueOrFalse(hasCmdShiftOnlyChar), originalCmdedShiftChar));
NS_OBJC_END_TRY_IGNORE_BLOCK
}
uint32_t TISInputSourceWrapper::ComputeGeckoKeyCode(UInt32 aNativeKeyCode, UInt32 aKbType,
bool aCmdIsPressed) {
MOZ_LOG(
gLog, LogLevel::Info,
("%p TISInputSourceWrapper::ComputeGeckoKeyCode, aNativeKeyCode=0x%X, "
"aKbType=0x%X, aCmdIsPressed=%s, IsOpenedIMEMode()=%s, "
"IsASCIICapable()=%s",
this, static_cast<unsigned int>(aNativeKeyCode), static_cast<unsigned int>(aKbType),
TrueOrFalse(aCmdIsPressed), TrueOrFalse(IsOpenedIMEMode()), TrueOrFalse(IsASCIICapable())));
switch (aNativeKeyCode) {
case kVK_Space:
return NS_VK_SPACE;
case kVK_Escape:
return NS_VK_ESCAPE;
// modifiers
case kVK_RightCommand:
case kVK_Command:
return NS_VK_META;
case kVK_RightShift:
case kVK_Shift:
return NS_VK_SHIFT;
case kVK_CapsLock:
return NS_VK_CAPS_LOCK;
case kVK_RightControl:
case kVK_Control:
return NS_VK_CONTROL;
case kVK_RightOption:
case kVK_Option:
return NS_VK_ALT;
case kVK_ANSI_KeypadClear:
return NS_VK_CLEAR;
// function keys
case kVK_F1:
return NS_VK_F1;
case kVK_F2:
return NS_VK_F2;
case kVK_F3:
return NS_VK_F3;
case kVK_F4:
return NS_VK_F4;
case kVK_F5:
return NS_VK_F5;
case kVK_F6:
return NS_VK_F6;
case kVK_F7:
return NS_VK_F7;
case kVK_F8:
return NS_VK_F8;
case kVK_F9:
return NS_VK_F9;
case kVK_F10:
return NS_VK_F10;
case kVK_F11:
return NS_VK_F11;
case kVK_F12:
return NS_VK_F12;
// case kVK_F13: return NS_VK_F13; // clash with the 3 below
// case kVK_F14: return NS_VK_F14;
// case kVK_F15: return NS_VK_F15;
case kVK_F16:
return NS_VK_F16;
case kVK_F17:
return NS_VK_F17;
case kVK_F18:
return NS_VK_F18;
case kVK_F19:
return NS_VK_F19;
case kVK_PC_Pause:
return NS_VK_PAUSE;
case kVK_PC_ScrollLock:
return NS_VK_SCROLL_LOCK;
case kVK_PC_PrintScreen:
return NS_VK_PRINTSCREEN;
// keypad
case kVK_ANSI_Keypad0:
return NS_VK_NUMPAD0;
case kVK_ANSI_Keypad1:
return NS_VK_NUMPAD1;
case kVK_ANSI_Keypad2:
return NS_VK_NUMPAD2;
case kVK_ANSI_Keypad3:
return NS_VK_NUMPAD3;
case kVK_ANSI_Keypad4:
return NS_VK_NUMPAD4;
case kVK_ANSI_Keypad5:
return NS_VK_NUMPAD5;
case kVK_ANSI_Keypad6:
return NS_VK_NUMPAD6;
case kVK_ANSI_Keypad7:
return NS_VK_NUMPAD7;
case kVK_ANSI_Keypad8:
return NS_VK_NUMPAD8;
case kVK_ANSI_Keypad9:
return NS_VK_NUMPAD9;
case kVK_ANSI_KeypadMultiply:
return NS_VK_MULTIPLY;
case kVK_ANSI_KeypadPlus:
return NS_VK_ADD;
case kVK_ANSI_KeypadMinus:
return NS_VK_SUBTRACT;
case kVK_ANSI_KeypadDecimal:
return NS_VK_DECIMAL;
case kVK_ANSI_KeypadDivide:
return NS_VK_DIVIDE;
case kVK_JIS_KeypadComma:
return NS_VK_SEPARATOR;
// IME keys
case kVK_JIS_Eisu:
return NS_VK_EISU;
case kVK_JIS_Kana:
return NS_VK_KANA;
// these may clash with forward delete and help
case kVK_PC_Insert:
return NS_VK_INSERT;
case kVK_PC_Delete:
return NS_VK_DELETE;
case kVK_PC_Backspace:
return NS_VK_BACK;
case kVK_Tab:
return NS_VK_TAB;
case kVK_Home:
return NS_VK_HOME;
case kVK_End:
return NS_VK_END;
case kVK_PageUp:
return NS_VK_PAGE_UP;
case kVK_PageDown:
return NS_VK_PAGE_DOWN;
case kVK_LeftArrow:
return NS_VK_LEFT;
case kVK_RightArrow:
return NS_VK_RIGHT;
case kVK_UpArrow:
return NS_VK_UP;
case kVK_DownArrow:
return NS_VK_DOWN;
case kVK_PC_ContextMenu:
return NS_VK_CONTEXT_MENU;
case kVK_ANSI_1:
return NS_VK_1;
case kVK_ANSI_2:
return NS_VK_2;
case kVK_ANSI_3:
return NS_VK_3;
case kVK_ANSI_4:
return NS_VK_4;
case kVK_ANSI_5:
return NS_VK_5;
case kVK_ANSI_6:
return NS_VK_6;
case kVK_ANSI_7:
return NS_VK_7;
case kVK_ANSI_8:
return NS_VK_8;
case kVK_ANSI_9:
return NS_VK_9;
case kVK_ANSI_0:
return NS_VK_0;
case kVK_ANSI_KeypadEnter:
case kVK_Return:
case kVK_Powerbook_KeypadEnter:
return NS_VK_RETURN;
}
// If Cmd key is pressed, that causes switching keyboard layout temporarily.
// E.g., Dvorak-QWERTY. Therefore, if Cmd key is pressed, we should honor it.
UInt32 modifiers = aCmdIsPressed ? cmdKey : 0;
uint32_t charCode = TranslateToChar(aNativeKeyCode, modifiers, aKbType);
// Special case for Mac. Mac inputs Yen sign (U+00A5) directly instead of
// Back slash (U+005C). We should return NS_VK_BACK_SLASH for compatibility
// with other platforms.
// XXX How about Won sign (U+20A9) which has same problem as Yen sign?
if (charCode == 0x00A5) {
return NS_VK_BACK_SLASH;
}
uint32_t keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
if (keyCode) {
return keyCode;
}
// If the unshifed char isn't an ASCII character, use shifted char.
charCode = TranslateToChar(aNativeKeyCode, modifiers | shiftKey, aKbType);
keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
if (keyCode) {
return keyCode;
}
if (!IsASCIICapable()) {
// Retry with ASCII capable keyboard layout.
TISInputSourceWrapper currentKeyboardLayout;
currentKeyboardLayout.InitByCurrentASCIICapableKeyboardLayout();
NS_ENSURE_TRUE(mInputSource != currentKeyboardLayout.mInputSource, 0);
keyCode = currentKeyboardLayout.ComputeGeckoKeyCode(aNativeKeyCode, aKbType, aCmdIsPressed);
// We've returned 0 for long time if keyCode isn't for an alphabet keys or
// a numeric key even in alternative ASCII capable keyboard layout 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. So, if alternative
// ASCII capable keyboard layout has keyCode value for the key, we should
// use it. In other words, this behavior does that non-ASCII capable
// keyboard layout overrides some keys' keyCode value only if the key
// produces ASCII character by itself or with Shift key.
if (keyCode) {
return keyCode;
}
}
// Otherwise, let's decide keyCode value from the native virtual keycode
// value on major keyboard layout.
CodeNameIndex code = ComputeGeckoCodeNameIndex(aNativeKeyCode, aKbType);
return WidgetKeyboardEvent::GetFallbackKeyCodeOfPunctuationKey(code);
}
// static
KeyNameIndex TISInputSourceWrapper::ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode) {
// NOTE:
// When unsupported keys like Convert, Nonconvert of Japanese keyboard is
// pressed:
// on 10.6.x, 'A' key event is fired (and also actually 'a' is inserted).
// on 10.7.x, Nothing happens.
// on 10.8.x, Nothing happens.
// on 10.9.x, FlagsChanged event is fired with keyCode 0xFF.
switch (aNativeKeyCode) {
#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:
return KEY_NAME_INDEX_Unidentified;
}
}
// static
CodeNameIndex TISInputSourceWrapper::ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode,
UInt32 aKbType) {
// macOS swaps native key code between Backquote key and IntlBackslash key
// only when the keyboard type is ISO. Let's treat the key code after
// swapping them here because Chromium does so only when computing .code
// value.
if (::KBGetLayoutType(aKbType) == kKeyboardISO) {
if (aNativeKeyCode == kVK_ISO_Section) {
aNativeKeyCode = kVK_ANSI_Grave;
} else if (aNativeKeyCode == kVK_ANSI_Grave) {
aNativeKeyCode = kVK_ISO_Section;
}
}
switch (aNativeKeyCode) {
#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;
}
}
#pragma mark -
/******************************************************************************
*
* TextInputHandler implementation (static methods)
*
******************************************************************************/
NSUInteger TextInputHandler::sLastModifierState = 0;
// static
CFArrayRef TextInputHandler::CreateAllKeyboardLayoutList() {
const void* keys[] = {kTISPropertyInputSourceType};
const void* values[] = {kTISTypeKeyboardLayout};
CFDictionaryRef filter = ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
NS_ASSERTION(filter, "failed to create the filter");
CFArrayRef list = ::TISCreateInputSourceList(filter, true);
::CFRelease(filter);
return list;
}
// static
void TextInputHandler::DebugPrintAllKeyboardLayouts() {
if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
CFArrayRef list = CreateAllKeyboardLayoutList();
MOZ_LOG(gLog, LogLevel::Info, ("Keyboard layout configuration:"));