michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim:expandtab:shiftwidth=4:tabstop=4: michael@0: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "prlog.h" michael@0: michael@0: #include "nsGtkKeyUtils.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #if (MOZ_WIDGET_GTK == 3) michael@0: #include michael@0: #endif michael@0: #include michael@0: #include "WidgetUtils.h" michael@0: #include "keysym2ucs.h" michael@0: #include "nsIBidiKeyboard.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo* gKeymapWrapperLog = nullptr; michael@0: #endif // PR_LOGGING michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/MouseEvents.h" michael@0: #include "mozilla/TextEvents.h" michael@0: michael@0: namespace mozilla { michael@0: namespace widget { michael@0: michael@0: #define IS_ASCII_ALPHABETICAL(key) \ michael@0: ((('a' <= key) && (key <= 'z')) || (('A' <= key) && (key <= 'Z'))) michael@0: michael@0: #define MOZ_MODIFIER_KEYS "MozKeymapWrapper" michael@0: michael@0: KeymapWrapper* KeymapWrapper::sInstance = nullptr; michael@0: guint KeymapWrapper::sLastRepeatableHardwareKeyCode = 0; michael@0: KeymapWrapper::RepeatState KeymapWrapper::sRepeatState = michael@0: KeymapWrapper::NOT_PRESSED; michael@0: nsIBidiKeyboard* sBidiKeyboard = nullptr; michael@0: michael@0: #ifdef PR_LOGGING michael@0: michael@0: static const char* GetBoolName(bool aBool) michael@0: { michael@0: return aBool ? "TRUE" : "FALSE"; michael@0: } michael@0: michael@0: /* static */ const char* michael@0: KeymapWrapper::GetModifierName(Modifier aModifier) michael@0: { michael@0: switch (aModifier) { michael@0: case CAPS_LOCK: return "CapsLock"; michael@0: case NUM_LOCK: return "NumLock"; michael@0: case SCROLL_LOCK: return "ScrollLock"; michael@0: case SHIFT: return "Shift"; michael@0: case CTRL: return "Ctrl"; michael@0: case ALT: return "Alt"; michael@0: case SUPER: return "Super"; michael@0: case HYPER: return "Hyper"; michael@0: case META: return "Meta"; michael@0: case LEVEL3: return "Level3"; michael@0: case LEVEL5: return "Level5"; michael@0: case NOT_MODIFIER: return "NotModifier"; michael@0: default: return "InvalidValue"; michael@0: } michael@0: } michael@0: michael@0: #endif // PR_LOGGING michael@0: michael@0: /* static */ KeymapWrapper::Modifier michael@0: KeymapWrapper::GetModifierForGDKKeyval(guint aGdkKeyval) michael@0: { michael@0: switch (aGdkKeyval) { michael@0: case GDK_Caps_Lock: return CAPS_LOCK; michael@0: case GDK_Num_Lock: return NUM_LOCK; michael@0: case GDK_Scroll_Lock: return SCROLL_LOCK; michael@0: case GDK_Shift_Lock: michael@0: case GDK_Shift_L: michael@0: case GDK_Shift_R: return SHIFT; michael@0: case GDK_Control_L: michael@0: case GDK_Control_R: return CTRL; michael@0: case GDK_Alt_L: michael@0: case GDK_Alt_R: return ALT; michael@0: case GDK_Super_L: michael@0: case GDK_Super_R: return SUPER; michael@0: case GDK_Hyper_L: michael@0: case GDK_Hyper_R: return HYPER; michael@0: case GDK_Meta_L: michael@0: case GDK_Meta_R: return META; michael@0: case GDK_ISO_Level3_Shift: michael@0: case GDK_Mode_switch: return LEVEL3; michael@0: case GDK_ISO_Level5_Shift: return LEVEL5; michael@0: default: return NOT_MODIFIER; michael@0: } michael@0: } michael@0: michael@0: guint michael@0: KeymapWrapper::GetModifierMask(Modifier aModifier) const michael@0: { michael@0: switch (aModifier) { michael@0: case CAPS_LOCK: michael@0: return GDK_LOCK_MASK; michael@0: case NUM_LOCK: michael@0: return mModifierMasks[INDEX_NUM_LOCK]; michael@0: case SCROLL_LOCK: michael@0: return mModifierMasks[INDEX_SCROLL_LOCK]; michael@0: case SHIFT: michael@0: return GDK_SHIFT_MASK; michael@0: case CTRL: michael@0: return GDK_CONTROL_MASK; michael@0: case ALT: michael@0: return mModifierMasks[INDEX_ALT]; michael@0: case SUPER: michael@0: return mModifierMasks[INDEX_SUPER]; michael@0: case HYPER: michael@0: return mModifierMasks[INDEX_HYPER]; michael@0: case META: michael@0: return mModifierMasks[INDEX_META]; michael@0: case LEVEL3: michael@0: return mModifierMasks[INDEX_LEVEL3]; michael@0: case LEVEL5: michael@0: return mModifierMasks[INDEX_LEVEL5]; michael@0: default: michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: KeymapWrapper::ModifierKey* michael@0: KeymapWrapper::GetModifierKey(guint aHardwareKeycode) michael@0: { michael@0: for (uint32_t i = 0; i < mModifierKeys.Length(); i++) { michael@0: ModifierKey& key = mModifierKeys[i]; michael@0: if (key.mHardwareKeycode == aHardwareKeycode) { michael@0: return &key; michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: /* static */ KeymapWrapper* michael@0: KeymapWrapper::GetInstance() michael@0: { michael@0: if (sInstance) { michael@0: sInstance->Init(); michael@0: return sInstance; michael@0: } michael@0: michael@0: sInstance = new KeymapWrapper(); michael@0: return sInstance; michael@0: } michael@0: michael@0: KeymapWrapper::KeymapWrapper() : michael@0: mInitialized(false), mGdkKeymap(gdk_keymap_get_default()), michael@0: mXKBBaseEventCode(0) michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (!gKeymapWrapperLog) { michael@0: gKeymapWrapperLog = PR_NewLogModule("KeymapWrapperWidgets"); michael@0: } michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): Constructor, mGdkKeymap=%p", michael@0: this, mGdkKeymap)); michael@0: #endif // PR_LOGGING michael@0: michael@0: g_signal_connect(mGdkKeymap, "keys-changed", michael@0: (GCallback)OnKeysChanged, this); michael@0: michael@0: // This is necessary for catching the destroying timing. michael@0: g_object_weak_ref(G_OBJECT(mGdkKeymap), michael@0: (GWeakNotify)OnDestroyKeymap, this); michael@0: michael@0: InitXKBExtension(); michael@0: michael@0: Init(); michael@0: } michael@0: michael@0: void michael@0: KeymapWrapper::Init() michael@0: { michael@0: if (mInitialized) { michael@0: return; michael@0: } michael@0: mInitialized = true; michael@0: michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): Init, mGdkKeymap=%p", michael@0: this, mGdkKeymap)); michael@0: michael@0: mModifierKeys.Clear(); michael@0: memset(mModifierMasks, 0, sizeof(mModifierMasks)); michael@0: michael@0: InitBySystemSettings(); michael@0: michael@0: gdk_window_add_filter(nullptr, FilterEvents, this); michael@0: michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): Init, CapsLock=0x%X, NumLock=0x%X, " michael@0: "ScrollLock=0x%X, Level3=0x%X, Level5=0x%X, " michael@0: "Shift=0x%X, Ctrl=0x%X, Alt=0x%X, Meta=0x%X, Super=0x%X, Hyper=0x%X", michael@0: this, michael@0: GetModifierMask(CAPS_LOCK), GetModifierMask(NUM_LOCK), michael@0: GetModifierMask(SCROLL_LOCK), GetModifierMask(LEVEL3), michael@0: GetModifierMask(LEVEL5), michael@0: GetModifierMask(SHIFT), GetModifierMask(CTRL), michael@0: GetModifierMask(ALT), GetModifierMask(META), michael@0: GetModifierMask(SUPER), GetModifierMask(HYPER))); michael@0: } michael@0: michael@0: void michael@0: KeymapWrapper::InitXKBExtension() michael@0: { michael@0: PodZero(&mKeyboardState); michael@0: michael@0: int xkbMajorVer = XkbMajorVersion; michael@0: int xkbMinorVer = XkbMinorVersion; michael@0: if (!XkbLibraryVersion(&xkbMajorVer, &xkbMinorVer)) { michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitXKBExtension failed due to failure of " michael@0: "XkbLibraryVersion()", this)); michael@0: return; michael@0: } michael@0: michael@0: Display* display = michael@0: gdk_x11_display_get_xdisplay(gdk_display_get_default()); michael@0: michael@0: // XkbLibraryVersion() set xkbMajorVer and xkbMinorVer to that of the michael@0: // library, which may be newer than what is required of the server in michael@0: // XkbQueryExtension(), so these variables should be reset to michael@0: // XkbMajorVersion and XkbMinorVersion before the XkbQueryExtension call. michael@0: xkbMajorVer = XkbMajorVersion; michael@0: xkbMinorVer = XkbMinorVersion; michael@0: int opcode, baseErrorCode; michael@0: if (!XkbQueryExtension(display, &opcode, &mXKBBaseEventCode, &baseErrorCode, michael@0: &xkbMajorVer, &xkbMinorVer)) { michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitXKBExtension failed due to failure of " michael@0: "XkbQueryExtension(), display=0x%p", this, display)); michael@0: return; michael@0: } michael@0: michael@0: if (!XkbSelectEventDetails(display, XkbUseCoreKbd, XkbStateNotify, michael@0: XkbModifierStateMask, XkbModifierStateMask)) { michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitXKBExtension failed due to failure of " michael@0: "XkbSelectEventDetails() for XModifierStateMask, display=0x%p", michael@0: this, display)); michael@0: return; michael@0: } michael@0: michael@0: if (!XkbSelectEventDetails(display, XkbUseCoreKbd, XkbControlsNotify, michael@0: XkbPerKeyRepeatMask, XkbPerKeyRepeatMask)) { michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitXKBExtension failed due to failure of " michael@0: "XkbSelectEventDetails() for XkbControlsNotify, display=0x%p", michael@0: this, display)); michael@0: return; michael@0: } michael@0: michael@0: if (!XGetKeyboardControl(display, &mKeyboardState)) { michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitXKBExtension failed due to failure of " michael@0: "XGetKeyboardControl(), display=0x%p", michael@0: this, display)); michael@0: return; michael@0: } michael@0: michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitXKBExtension, Succeeded", this)); michael@0: } michael@0: michael@0: void michael@0: KeymapWrapper::InitBySystemSettings() michael@0: { michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitBySystemSettings, mGdkKeymap=%p", michael@0: this, mGdkKeymap)); michael@0: michael@0: Display* display = michael@0: gdk_x11_display_get_xdisplay(gdk_display_get_default()); michael@0: michael@0: int min_keycode = 0; michael@0: int max_keycode = 0; michael@0: XDisplayKeycodes(display, &min_keycode, &max_keycode); michael@0: michael@0: int keysyms_per_keycode = 0; michael@0: KeySym* xkeymap = XGetKeyboardMapping(display, min_keycode, michael@0: max_keycode - min_keycode + 1, michael@0: &keysyms_per_keycode); michael@0: if (!xkeymap) { michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitBySystemSettings, " michael@0: "Failed due to null xkeymap", this)); michael@0: return; michael@0: } michael@0: michael@0: XModifierKeymap* xmodmap = XGetModifierMapping(display); michael@0: if (!xmodmap) { michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitBySystemSettings, " michael@0: "Failed due to null xmodmap", this)); michael@0: XFree(xkeymap); michael@0: return; michael@0: } michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitBySystemSettings, min_keycode=%d, " michael@0: "max_keycode=%d, keysyms_per_keycode=%d, max_keypermod=%d", michael@0: this, min_keycode, max_keycode, keysyms_per_keycode, michael@0: xmodmap->max_keypermod)); michael@0: michael@0: // The modifiermap member of the XModifierKeymap structure contains 8 sets michael@0: // of max_keypermod KeyCodes, one for each modifier in the order Shift, michael@0: // Lock, Control, Mod1, Mod2, Mod3, Mod4, and Mod5. michael@0: // Only nonzero KeyCodes have meaning in each set, and zero KeyCodes are michael@0: // ignored. michael@0: michael@0: // Note that two or more modifiers may use one modifier flag. E.g., michael@0: // on Ubuntu 10.10, Alt and Meta share the Mod1 in default settings. michael@0: // And also Super and Hyper share the Mod4. In such cases, we need to michael@0: // decide which modifier flag means one of DOM modifiers. michael@0: michael@0: // mod[0] is Modifier introduced by Mod1. michael@0: Modifier mod[5]; michael@0: int32_t foundLevel[5]; michael@0: for (uint32_t i = 0; i < ArrayLength(mod); i++) { michael@0: mod[i] = NOT_MODIFIER; michael@0: foundLevel[i] = INT32_MAX; michael@0: } michael@0: const uint32_t map_size = 8 * xmodmap->max_keypermod; michael@0: for (uint32_t i = 0; i < map_size; i++) { michael@0: KeyCode keycode = xmodmap->modifiermap[i]; michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitBySystemSettings, " michael@0: " i=%d, keycode=0x%08X", michael@0: this, i, keycode)); michael@0: if (!keycode || keycode < min_keycode || keycode > max_keycode) { michael@0: continue; michael@0: } michael@0: michael@0: ModifierKey* modifierKey = GetModifierKey(keycode); michael@0: if (!modifierKey) { michael@0: modifierKey = mModifierKeys.AppendElement(ModifierKey(keycode)); michael@0: } michael@0: michael@0: const KeySym* syms = michael@0: xkeymap + (keycode - min_keycode) * keysyms_per_keycode; michael@0: const uint32_t bit = i / xmodmap->max_keypermod; michael@0: modifierKey->mMask |= 1 << bit; michael@0: michael@0: // We need to know the meaning of Mod1, Mod2, Mod3, Mod4 and Mod5. michael@0: // Let's skip if current map is for others. michael@0: if (bit < 3) { michael@0: continue; michael@0: } michael@0: michael@0: const int32_t modIndex = bit - 3; michael@0: for (int32_t j = 0; j < keysyms_per_keycode; j++) { michael@0: Modifier modifier = GetModifierForGDKKeyval(syms[j]); michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitBySystemSettings, " michael@0: " Mod%d, j=%d, syms[j]=%s(0x%X), modifier=%s", michael@0: this, modIndex + 1, j, gdk_keyval_name(syms[j]), syms[j], michael@0: GetModifierName(modifier))); michael@0: michael@0: switch (modifier) { michael@0: case NOT_MODIFIER: michael@0: // Don't overwrite the stored information with michael@0: // NOT_MODIFIER. michael@0: break; michael@0: case CAPS_LOCK: michael@0: case SHIFT: michael@0: case CTRL: michael@0: // Ignore the modifiers defined in GDK spec. They shouldn't michael@0: // be mapped to Mod1-5 because they must not work on native michael@0: // GTK applications. michael@0: break; michael@0: default: michael@0: // If new modifier is found in higher level than stored michael@0: // value, we don't need to overwrite it. michael@0: if (j > foundLevel[modIndex]) { michael@0: break; michael@0: } michael@0: // If new modifier is more important than stored value, michael@0: // we should overwrite it with new modifier. michael@0: if (j == foundLevel[modIndex]) { michael@0: mod[modIndex] = std::min(modifier, mod[modIndex]); michael@0: break; michael@0: } michael@0: foundLevel[modIndex] = j; michael@0: mod[modIndex] = modifier; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < COUNT_OF_MODIFIER_INDEX; i++) { michael@0: Modifier modifier; michael@0: switch (i) { michael@0: case INDEX_NUM_LOCK: michael@0: modifier = NUM_LOCK; michael@0: break; michael@0: case INDEX_SCROLL_LOCK: michael@0: modifier = SCROLL_LOCK; michael@0: break; michael@0: case INDEX_ALT: michael@0: modifier = ALT; michael@0: break; michael@0: case INDEX_META: michael@0: modifier = META; michael@0: break; michael@0: case INDEX_SUPER: michael@0: modifier = SUPER; michael@0: break; michael@0: case INDEX_HYPER: michael@0: modifier = HYPER; michael@0: break; michael@0: case INDEX_LEVEL3: michael@0: modifier = LEVEL3; michael@0: break; michael@0: case INDEX_LEVEL5: michael@0: modifier = LEVEL5; michael@0: break; michael@0: default: michael@0: MOZ_CRASH("All indexes must be handled here"); michael@0: } michael@0: for (uint32_t j = 0; j < ArrayLength(mod); j++) { michael@0: if (modifier == mod[j]) { michael@0: mModifierMasks[i] |= 1 << (j + 3); michael@0: } michael@0: } michael@0: } michael@0: michael@0: XFreeModifiermap(xmodmap); michael@0: XFree(xkeymap); michael@0: } michael@0: michael@0: KeymapWrapper::~KeymapWrapper() michael@0: { michael@0: gdk_window_remove_filter(nullptr, FilterEvents, this); michael@0: NS_IF_RELEASE(sBidiKeyboard); michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): Destructor", this)); michael@0: } michael@0: michael@0: /* static */ GdkFilterReturn michael@0: KeymapWrapper::FilterEvents(GdkXEvent* aXEvent, michael@0: GdkEvent* aGdkEvent, michael@0: gpointer aData) michael@0: { michael@0: XEvent* xEvent = static_cast(aXEvent); michael@0: switch (xEvent->type) { michael@0: case KeyPress: { michael@0: // If the key doesn't support auto repeat, ignore the event because michael@0: // even if such key (e.g., Shift) is pressed during auto repeat of michael@0: // anoter key, it doesn't stop the auto repeat. michael@0: KeymapWrapper* self = static_cast(aData); michael@0: if (!self->IsAutoRepeatableKey(xEvent->xkey.keycode)) { michael@0: break; michael@0: } michael@0: if (sRepeatState == NOT_PRESSED) { michael@0: sRepeatState = FIRST_PRESS; michael@0: } else if (sLastRepeatableHardwareKeyCode == xEvent->xkey.keycode) { michael@0: sRepeatState = REPEATING; michael@0: } else { michael@0: // If a different key is pressed while another key is pressed, michael@0: // auto repeat system repeats only the last pressed key. michael@0: // So, setting new keycode and setting repeat state as first key michael@0: // press should work fine. michael@0: sRepeatState = FIRST_PRESS; michael@0: } michael@0: sLastRepeatableHardwareKeyCode = xEvent->xkey.keycode; michael@0: break; michael@0: } michael@0: case KeyRelease: { michael@0: if (sLastRepeatableHardwareKeyCode != xEvent->xkey.keycode) { michael@0: // This case means the key release event is caused by michael@0: // a non-repeatable key such as Shift or a repeatable key that michael@0: // was pressed before sLastRepeatableHardwareKeyCode was michael@0: // pressed. michael@0: break; michael@0: } michael@0: sRepeatState = NOT_PRESSED; michael@0: break; michael@0: } michael@0: case FocusOut: { michael@0: // At moving focus, we should reset keyboard repeat state. michael@0: // Strictly, this causes incorrect behavior. However, this michael@0: // correctness must be enough for web applications. michael@0: sRepeatState = NOT_PRESSED; michael@0: break; michael@0: } michael@0: default: { michael@0: KeymapWrapper* self = static_cast(aData); michael@0: if (xEvent->type != self->mXKBBaseEventCode) { michael@0: break; michael@0: } michael@0: XkbEvent* xkbEvent = (XkbEvent*)xEvent; michael@0: if (xkbEvent->any.xkb_type != XkbControlsNotify || michael@0: !(xkbEvent->ctrls.changed_ctrls & XkbPerKeyRepeatMask)) { michael@0: break; michael@0: } michael@0: if (!XGetKeyboardControl(xkbEvent->any.display, michael@0: &self->mKeyboardState)) { michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): FilterEvents failed due to failure " michael@0: "of XGetKeyboardControl(), display=0x%p", michael@0: self, xkbEvent->any.display)); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return GDK_FILTER_CONTINUE; michael@0: } michael@0: michael@0: /* static */ void michael@0: KeymapWrapper::OnDestroyKeymap(KeymapWrapper* aKeymapWrapper, michael@0: GdkKeymap *aGdkKeymap) michael@0: { michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper: OnDestroyKeymap, aGdkKeymap=%p, aKeymapWrapper=%p", michael@0: aGdkKeymap, aKeymapWrapper)); michael@0: MOZ_ASSERT(aKeymapWrapper == sInstance, michael@0: "Desroying unexpected instance"); michael@0: delete sInstance; michael@0: sInstance = nullptr; michael@0: } michael@0: michael@0: /* static */ void michael@0: KeymapWrapper::OnKeysChanged(GdkKeymap *aGdkKeymap, michael@0: KeymapWrapper* aKeymapWrapper) michael@0: { michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper: OnKeysChanged, aGdkKeymap=%p, aKeymapWrapper=%p", michael@0: aGdkKeymap, aKeymapWrapper)); michael@0: michael@0: MOZ_ASSERT(sInstance == aKeymapWrapper, michael@0: "This instance must be the singleton instance"); michael@0: michael@0: // We cannot reintialize here becasue we don't have GdkWindow which is using michael@0: // the GdkKeymap. We'll reinitialize it when next GetInstance() is called. michael@0: sInstance->mInitialized = false; michael@0: michael@0: // Reset the bidi keyboard settings for the new GdkKeymap michael@0: if (!sBidiKeyboard) { michael@0: CallGetService("@mozilla.org/widget/bidikeyboard;1", &sBidiKeyboard); michael@0: } michael@0: if (sBidiKeyboard) { michael@0: sBidiKeyboard->Reset(); michael@0: } michael@0: } michael@0: michael@0: /* static */ guint michael@0: KeymapWrapper::GetCurrentModifierState() michael@0: { michael@0: GdkModifierType modifiers; michael@0: gdk_display_get_pointer(gdk_display_get_default(), michael@0: nullptr, nullptr, nullptr, &modifiers); michael@0: return static_cast(modifiers); michael@0: } michael@0: michael@0: /* static */ bool michael@0: KeymapWrapper::AreModifiersCurrentlyActive(Modifiers aModifiers) michael@0: { michael@0: guint modifierState = GetCurrentModifierState(); michael@0: return AreModifiersActive(aModifiers, modifierState); michael@0: } michael@0: michael@0: /* static */ bool michael@0: KeymapWrapper::AreModifiersActive(Modifiers aModifiers, michael@0: guint aModifierState) michael@0: { michael@0: NS_ENSURE_TRUE(aModifiers, false); michael@0: michael@0: KeymapWrapper* keymapWrapper = GetInstance(); michael@0: for (uint32_t i = 0; i < sizeof(Modifier) * 8 && aModifiers; i++) { michael@0: Modifier modifier = static_cast(1 << i); michael@0: if (!(aModifiers & modifier)) { michael@0: continue; michael@0: } michael@0: if (!(aModifierState & keymapWrapper->GetModifierMask(modifier))) { michael@0: return false; michael@0: } michael@0: aModifiers &= ~modifier; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* static */ void michael@0: KeymapWrapper::InitInputEvent(WidgetInputEvent& aInputEvent, michael@0: guint aModifierState) michael@0: { michael@0: KeymapWrapper* keymapWrapper = GetInstance(); michael@0: michael@0: aInputEvent.modifiers = 0; michael@0: // DOM Meta key should be TRUE only on Mac. We need to discuss this michael@0: // issue later. michael@0: if (keymapWrapper->AreModifiersActive(SHIFT, aModifierState)) { michael@0: aInputEvent.modifiers |= MODIFIER_SHIFT; michael@0: } michael@0: if (keymapWrapper->AreModifiersActive(CTRL, aModifierState)) { michael@0: aInputEvent.modifiers |= MODIFIER_CONTROL; michael@0: } michael@0: if (keymapWrapper->AreModifiersActive(ALT, aModifierState)) { michael@0: aInputEvent.modifiers |= MODIFIER_ALT; michael@0: } michael@0: if (keymapWrapper->AreModifiersActive(META, aModifierState)) { michael@0: aInputEvent.modifiers |= MODIFIER_META; michael@0: } michael@0: if (keymapWrapper->AreModifiersActive(SUPER, aModifierState) || michael@0: keymapWrapper->AreModifiersActive(HYPER, aModifierState)) { michael@0: aInputEvent.modifiers |= MODIFIER_OS; michael@0: } michael@0: if (keymapWrapper->AreModifiersActive(LEVEL3, aModifierState) || michael@0: keymapWrapper->AreModifiersActive(LEVEL5, aModifierState)) { michael@0: aInputEvent.modifiers |= MODIFIER_ALTGRAPH; michael@0: } michael@0: if (keymapWrapper->AreModifiersActive(CAPS_LOCK, aModifierState)) { michael@0: aInputEvent.modifiers |= MODIFIER_CAPSLOCK; michael@0: } michael@0: if (keymapWrapper->AreModifiersActive(NUM_LOCK, aModifierState)) { michael@0: aInputEvent.modifiers |= MODIFIER_NUMLOCK; michael@0: } michael@0: if (keymapWrapper->AreModifiersActive(SCROLL_LOCK, aModifierState)) { michael@0: aInputEvent.modifiers |= MODIFIER_SCROLLLOCK; michael@0: } michael@0: michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_DEBUG, michael@0: ("KeymapWrapper(%p): InitInputEvent, aModifierState=0x%08X, " michael@0: "aInputEvent.modifiers=0x%04X (Shift: %s, Control: %s, Alt: %s, " michael@0: "Meta: %s, OS: %s, AltGr: %s, " michael@0: "CapsLock: %s, NumLock: %s, ScrollLock: %s)", michael@0: keymapWrapper, aModifierState, aInputEvent.modifiers, michael@0: GetBoolName(aInputEvent.modifiers & MODIFIER_SHIFT), michael@0: GetBoolName(aInputEvent.modifiers & MODIFIER_CONTROL), michael@0: GetBoolName(aInputEvent.modifiers & MODIFIER_ALT), michael@0: GetBoolName(aInputEvent.modifiers & MODIFIER_META), michael@0: GetBoolName(aInputEvent.modifiers & MODIFIER_OS), michael@0: GetBoolName(aInputEvent.modifiers & MODIFIER_ALTGRAPH), michael@0: GetBoolName(aInputEvent.modifiers & MODIFIER_CAPSLOCK), michael@0: GetBoolName(aInputEvent.modifiers & MODIFIER_NUMLOCK), michael@0: GetBoolName(aInputEvent.modifiers & MODIFIER_SCROLLLOCK))); michael@0: michael@0: switch(aInputEvent.eventStructType) { michael@0: case NS_MOUSE_EVENT: michael@0: case NS_MOUSE_SCROLL_EVENT: michael@0: case NS_WHEEL_EVENT: michael@0: case NS_DRAG_EVENT: michael@0: case NS_SIMPLE_GESTURE_EVENT: michael@0: break; michael@0: default: michael@0: return; michael@0: } michael@0: michael@0: WidgetMouseEventBase& mouseEvent = *aInputEvent.AsMouseEventBase(); michael@0: mouseEvent.buttons = 0; michael@0: if (aModifierState & GDK_BUTTON1_MASK) { michael@0: mouseEvent.buttons |= WidgetMouseEvent::eLeftButtonFlag; michael@0: } michael@0: if (aModifierState & GDK_BUTTON3_MASK) { michael@0: mouseEvent.buttons |= WidgetMouseEvent::eRightButtonFlag; michael@0: } michael@0: if (aModifierState & GDK_BUTTON2_MASK) { michael@0: mouseEvent.buttons |= WidgetMouseEvent::eMiddleButtonFlag; michael@0: } michael@0: michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_DEBUG, michael@0: ("KeymapWrapper(%p): InitInputEvent, aInputEvent has buttons, " michael@0: "aInputEvent.buttons=0x%04X (Left: %s, Right: %s, Middle: %s, " michael@0: "4th (BACK): %s, 5th (FORWARD): %s)", michael@0: keymapWrapper, mouseEvent.buttons, michael@0: GetBoolName(mouseEvent.buttons & WidgetMouseEvent::eLeftButtonFlag), michael@0: GetBoolName(mouseEvent.buttons & WidgetMouseEvent::eRightButtonFlag), michael@0: GetBoolName(mouseEvent.buttons & WidgetMouseEvent::eMiddleButtonFlag), michael@0: GetBoolName(mouseEvent.buttons & WidgetMouseEvent::e4thButtonFlag), michael@0: GetBoolName(mouseEvent.buttons & WidgetMouseEvent::e5thButtonFlag))); michael@0: } michael@0: michael@0: /* static */ uint32_t michael@0: KeymapWrapper::ComputeDOMKeyCode(const GdkEventKey* aGdkKeyEvent) michael@0: { michael@0: // If the keyval indicates it's a modifier key, we should use unshifted michael@0: // key's modifier keyval. michael@0: guint keyval = aGdkKeyEvent->keyval; michael@0: if (GetModifierForGDKKeyval(keyval)) { michael@0: // But if the keyval without modifiers isn't a modifier key, we michael@0: // shouldn't use it. E.g., Japanese keyboard layout's michael@0: // Shift + Eisu-Toggle key is CapsLock. This is an actual rare case, michael@0: // Windows uses different keycode for a physical key for different michael@0: // shift key state. michael@0: guint keyvalWithoutModifier = GetGDKKeyvalWithoutModifier(aGdkKeyEvent); michael@0: if (GetModifierForGDKKeyval(keyvalWithoutModifier)) { michael@0: keyval = keyvalWithoutModifier; michael@0: } michael@0: // Note that the modifier keycode and activating or deactivating michael@0: // modifier flag may be mismatched, but it's okay. If a DOM key michael@0: // event handler is testing a keydown event, it's more likely being michael@0: // used to test which key is being pressed than to test which michael@0: // modifier will become active. So, if we computed DOM keycode michael@0: // from modifier flag which were changing by the physical key, then michael@0: // there would be no other way for the user to generate the original michael@0: // keycode. michael@0: uint32_t DOMKeyCode = GetDOMKeyCodeFromKeyPairs(keyval); michael@0: NS_ASSERTION(DOMKeyCode, "All modifier keys must have a DOM keycode"); michael@0: return DOMKeyCode; michael@0: } michael@0: michael@0: // If the key isn't printable, let's look at the key pairs. michael@0: uint32_t charCode = GetCharCodeFor(aGdkKeyEvent); michael@0: if (!charCode) { michael@0: // Always use unshifted keycode for the non-printable key. michael@0: // XXX It might be better to decide DOM keycode from all keyvals of michael@0: // the hardware keycode. However, I think that it's too excessive. michael@0: guint keyvalWithoutModifier = GetGDKKeyvalWithoutModifier(aGdkKeyEvent); michael@0: uint32_t DOMKeyCode = GetDOMKeyCodeFromKeyPairs(keyvalWithoutModifier); michael@0: if (!DOMKeyCode) { michael@0: // If the unshifted keyval couldn't be mapped to a DOM keycode, michael@0: // we should fallback to legacy logic, so, we should recompute with michael@0: // the keyval with aGdkKeyEvent. michael@0: DOMKeyCode = GetDOMKeyCodeFromKeyPairs(keyval); michael@0: } michael@0: return DOMKeyCode; michael@0: } michael@0: michael@0: // printable numpad keys should be resolved here. michael@0: switch (keyval) { michael@0: case GDK_KP_Multiply: return NS_VK_MULTIPLY; michael@0: case GDK_KP_Add: return NS_VK_ADD; michael@0: case GDK_KP_Separator: return NS_VK_SEPARATOR; michael@0: case GDK_KP_Subtract: return NS_VK_SUBTRACT; michael@0: case GDK_KP_Decimal: return NS_VK_DECIMAL; michael@0: case GDK_KP_Divide: return NS_VK_DIVIDE; michael@0: case GDK_KP_0: return NS_VK_NUMPAD0; michael@0: case GDK_KP_1: return NS_VK_NUMPAD1; michael@0: case GDK_KP_2: return NS_VK_NUMPAD2; michael@0: case GDK_KP_3: return NS_VK_NUMPAD3; michael@0: case GDK_KP_4: return NS_VK_NUMPAD4; michael@0: case GDK_KP_5: return NS_VK_NUMPAD5; michael@0: case GDK_KP_6: return NS_VK_NUMPAD6; michael@0: case GDK_KP_7: return NS_VK_NUMPAD7; michael@0: case GDK_KP_8: return NS_VK_NUMPAD8; michael@0: case GDK_KP_9: return NS_VK_NUMPAD9; michael@0: } michael@0: michael@0: KeymapWrapper* keymapWrapper = GetInstance(); michael@0: michael@0: // Ignore all modifier state except NumLock. michael@0: guint baseState = michael@0: (aGdkKeyEvent->state & keymapWrapper->GetModifierMask(NUM_LOCK)); michael@0: michael@0: // Basically, we should use unmodified character for deciding our keyCode. michael@0: uint32_t unmodifiedChar = michael@0: keymapWrapper->GetCharCodeFor(aGdkKeyEvent, baseState, michael@0: aGdkKeyEvent->group); michael@0: if (IsBasicLatinLetterOrNumeral(unmodifiedChar)) { michael@0: // If the unmodified character is an ASCII alphabet or an ASCII michael@0: // numeric, it's the best hint for deciding our keyCode. michael@0: return WidgetUtils::ComputeKeyCodeFromChar(unmodifiedChar); michael@0: } michael@0: michael@0: // If the unmodified character is not an ASCII character, that means we michael@0: // couldn't find the hint. We should reset it. michael@0: if (unmodifiedChar > 0x7F) { michael@0: unmodifiedChar = 0; michael@0: } michael@0: michael@0: // Retry with shifted keycode. michael@0: guint shiftState = (baseState | keymapWrapper->GetModifierMask(SHIFT)); michael@0: uint32_t shiftedChar = michael@0: keymapWrapper->GetCharCodeFor(aGdkKeyEvent, shiftState, michael@0: aGdkKeyEvent->group); michael@0: if (IsBasicLatinLetterOrNumeral(shiftedChar)) { michael@0: // A shifted character can be an ASCII alphabet on Hebrew keyboard michael@0: // layout. And also shifted character can be an ASCII numeric on michael@0: // AZERTY keyboad layout. Then, it's a good hint for deciding our michael@0: // keyCode. michael@0: return WidgetUtils::ComputeKeyCodeFromChar(shiftedChar); michael@0: } michael@0: michael@0: // If the shifted unmodified character isn't an ASCII character, we should michael@0: // discard it too. michael@0: if (shiftedChar > 0x7F) { michael@0: shiftedChar = 0; michael@0: } michael@0: michael@0: // If current keyboard layout isn't ASCII alphabet inputtable layout, michael@0: // look for ASCII alphabet inputtable keyboard layout. If the key michael@0: // inputs an ASCII alphabet or an ASCII numeric, we should use it michael@0: // for deciding our keyCode. michael@0: // Note that it's important not to use alternative keyboard layout for ASCII michael@0: // alphabet inputabble keyboard layout because the keycode for the key with michael@0: // alternative keyboard layout may conflict with another key on current michael@0: // keyboard layout. michael@0: if (!keymapWrapper->IsLatinGroup(aGdkKeyEvent->group)) { michael@0: gint minGroup = keymapWrapper->GetFirstLatinGroup(); michael@0: if (minGroup >= 0) { michael@0: uint32_t unmodCharLatin = michael@0: keymapWrapper->GetCharCodeFor(aGdkKeyEvent, baseState, michael@0: minGroup); michael@0: if (IsBasicLatinLetterOrNumeral(unmodCharLatin)) { michael@0: // If the unmodified character is an ASCII alphabet or michael@0: // an ASCII numeric, we should use it for the keyCode. michael@0: return WidgetUtils::ComputeKeyCodeFromChar(unmodCharLatin); michael@0: } michael@0: uint32_t shiftedCharLatin = michael@0: keymapWrapper->GetCharCodeFor(aGdkKeyEvent, shiftState, michael@0: minGroup); michael@0: if (IsBasicLatinLetterOrNumeral(shiftedCharLatin)) { michael@0: // If the shifted character is an ASCII alphabet or an ASCII michael@0: // numeric, we should use it for the keyCode. michael@0: return WidgetUtils::ComputeKeyCodeFromChar(shiftedCharLatin); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If unmodified character is in ASCII range, use it. Otherwise, use michael@0: // shifted character. michael@0: if (!unmodifiedChar && !shiftedChar) { michael@0: return 0; michael@0: } michael@0: return WidgetUtils::ComputeKeyCodeFromChar( michael@0: unmodifiedChar ? unmodifiedChar : shiftedChar); michael@0: } michael@0: michael@0: KeyNameIndex michael@0: KeymapWrapper::ComputeDOMKeyNameIndex(const GdkEventKey* aGdkKeyEvent) michael@0: { michael@0: switch (aGdkKeyEvent->keyval) { michael@0: michael@0: #define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \ michael@0: case aNativeKey: return aKeyNameIndex; michael@0: michael@0: #include "NativeKeyToDOMKeyName.h" michael@0: michael@0: #undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX michael@0: michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: return KEY_NAME_INDEX_Unidentified; michael@0: } michael@0: michael@0: /* static */ void michael@0: KeymapWrapper::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent, michael@0: GdkEventKey* aGdkKeyEvent) michael@0: { michael@0: KeymapWrapper* keymapWrapper = GetInstance(); michael@0: michael@0: aKeyEvent.mKeyNameIndex = michael@0: keymapWrapper->ComputeDOMKeyNameIndex(aGdkKeyEvent); michael@0: if (aKeyEvent.mKeyNameIndex == KEY_NAME_INDEX_Unidentified) { michael@0: uint32_t charCode = GetCharCodeFor(aGdkKeyEvent); michael@0: if (!charCode) { michael@0: charCode = keymapWrapper->GetUnmodifiedCharCodeFor(aGdkKeyEvent); michael@0: } michael@0: if (charCode) { michael@0: aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; michael@0: MOZ_ASSERT(aKeyEvent.mKeyValue.IsEmpty(), michael@0: "Uninitialized mKeyValue must be empty"); michael@0: AppendUCS4ToUTF16(charCode, aKeyEvent.mKeyValue); michael@0: } michael@0: } michael@0: aKeyEvent.keyCode = ComputeDOMKeyCode(aGdkKeyEvent); michael@0: michael@0: // NOTE: The state of given key event indicates adjacent state of michael@0: // modifier keys. E.g., even if the event is Shift key press event, michael@0: // the bit for Shift is still false. By the same token, even if the michael@0: // event is Shift key release event, the bit for Shift is still true. michael@0: // Unfortunately, gdk_keyboard_get_modifiers() returns current modifier michael@0: // state. It means if there're some pending modifier key press or michael@0: // key release events, the result isn't what we want. michael@0: guint modifierState = aGdkKeyEvent->state; michael@0: if (aGdkKeyEvent->is_modifier) { michael@0: Display* display = michael@0: gdk_x11_display_get_xdisplay(gdk_display_get_default()); michael@0: if (XEventsQueued(display, QueuedAfterReading)) { michael@0: XEvent nextEvent; michael@0: XPeekEvent(display, &nextEvent); michael@0: if (nextEvent.type == keymapWrapper->mXKBBaseEventCode) { michael@0: XkbEvent* XKBEvent = (XkbEvent*)&nextEvent; michael@0: if (XKBEvent->any.xkb_type == XkbStateNotify) { michael@0: XkbStateNotifyEvent* stateNotifyEvent = michael@0: (XkbStateNotifyEvent*)XKBEvent; michael@0: modifierState &= ~0xFF; michael@0: modifierState |= stateNotifyEvent->lookup_mods; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: InitInputEvent(aKeyEvent, modifierState); michael@0: michael@0: switch (aGdkKeyEvent->keyval) { michael@0: case GDK_Shift_L: michael@0: case GDK_Control_L: michael@0: case GDK_Alt_L: michael@0: case GDK_Super_L: michael@0: case GDK_Hyper_L: michael@0: case GDK_Meta_L: michael@0: aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT; michael@0: break; michael@0: michael@0: case GDK_Shift_R: michael@0: case GDK_Control_R: michael@0: case GDK_Alt_R: michael@0: case GDK_Super_R: michael@0: case GDK_Hyper_R: michael@0: case GDK_Meta_R: michael@0: aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT; michael@0: break; michael@0: michael@0: case GDK_KP_0: michael@0: case GDK_KP_1: michael@0: case GDK_KP_2: michael@0: case GDK_KP_3: michael@0: case GDK_KP_4: michael@0: case GDK_KP_5: michael@0: case GDK_KP_6: michael@0: case GDK_KP_7: michael@0: case GDK_KP_8: michael@0: case GDK_KP_9: michael@0: case GDK_KP_Space: michael@0: case GDK_KP_Tab: michael@0: case GDK_KP_Enter: michael@0: case GDK_KP_F1: michael@0: case GDK_KP_F2: michael@0: case GDK_KP_F3: michael@0: case GDK_KP_F4: michael@0: case GDK_KP_Home: michael@0: case GDK_KP_Left: michael@0: case GDK_KP_Up: michael@0: case GDK_KP_Right: michael@0: case GDK_KP_Down: michael@0: case GDK_KP_Prior: // same as GDK_KP_Page_Up michael@0: case GDK_KP_Next: // same as GDK_KP_Page_Down michael@0: case GDK_KP_End: michael@0: case GDK_KP_Begin: michael@0: case GDK_KP_Insert: michael@0: case GDK_KP_Delete: michael@0: case GDK_KP_Equal: michael@0: case GDK_KP_Multiply: michael@0: case GDK_KP_Add: michael@0: case GDK_KP_Separator: michael@0: case GDK_KP_Subtract: michael@0: case GDK_KP_Decimal: michael@0: case GDK_KP_Divide: michael@0: aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD; michael@0: break; michael@0: michael@0: default: michael@0: aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD; michael@0: break; michael@0: } michael@0: michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitKeyEvent, modifierState=0x%08X " michael@0: "aGdkKeyEvent={ type=%s, keyval=%s(0x%X), state=0x%08X, " michael@0: "hardware_keycode=0x%08X, is_modifier=%s } " michael@0: "aKeyEvent={ message=%s, isShift=%s, isControl=%s, " michael@0: "isAlt=%s, isMeta=%s }", michael@0: keymapWrapper, modifierState, michael@0: ((aGdkKeyEvent->type == GDK_KEY_PRESS) ? michael@0: "GDK_KEY_PRESS" : "GDK_KEY_RELEASE"), michael@0: gdk_keyval_name(aGdkKeyEvent->keyval), michael@0: aGdkKeyEvent->keyval, aGdkKeyEvent->state, michael@0: aGdkKeyEvent->hardware_keycode, michael@0: GetBoolName(aGdkKeyEvent->is_modifier), michael@0: ((aKeyEvent.message == NS_KEY_DOWN) ? "NS_KEY_DOWN" : michael@0: (aKeyEvent.message == NS_KEY_PRESS) ? "NS_KEY_PRESS" : michael@0: "NS_KEY_UP"), michael@0: GetBoolName(aKeyEvent.IsShift()), GetBoolName(aKeyEvent.IsControl()), michael@0: GetBoolName(aKeyEvent.IsAlt()), GetBoolName(aKeyEvent.IsMeta()))); michael@0: michael@0: if (aKeyEvent.message == NS_KEY_PRESS) { michael@0: keymapWrapper->InitKeypressEvent(aKeyEvent, aGdkKeyEvent); michael@0: } michael@0: michael@0: // The transformations above and in gdk for the keyval are not invertible michael@0: // so link to the GdkEvent (which will vanish soon after return from the michael@0: // event callback) to give plugins access to hardware_keycode and state. michael@0: // (An XEvent would be nice but the GdkEvent is good enough.) michael@0: aKeyEvent.pluginEvent = (void *)aGdkKeyEvent; michael@0: aKeyEvent.time = aGdkKeyEvent->time; michael@0: aKeyEvent.mNativeKeyEvent = static_cast(aGdkKeyEvent); michael@0: aKeyEvent.mIsRepeat = sRepeatState == REPEATING && michael@0: aGdkKeyEvent->hardware_keycode == sLastRepeatableHardwareKeyCode; michael@0: } michael@0: michael@0: /* static */ uint32_t michael@0: KeymapWrapper::GetCharCodeFor(const GdkEventKey *aGdkKeyEvent) michael@0: { michael@0: // Anything above 0xf000 is considered a non-printable michael@0: // Exception: directly encoded UCS characters michael@0: if (aGdkKeyEvent->keyval > 0xf000 && michael@0: (aGdkKeyEvent->keyval & 0xff000000) != 0x01000000) { michael@0: // Keypad keys are an exception: they return a value different michael@0: // from their non-keypad equivalents, but mozilla doesn't distinguish. michael@0: switch (aGdkKeyEvent->keyval) { michael@0: case GDK_KP_Space: return ' '; michael@0: case GDK_KP_Equal: return '='; michael@0: case GDK_KP_Multiply: return '*'; michael@0: case GDK_KP_Add: return '+'; michael@0: case GDK_KP_Separator: return ','; michael@0: case GDK_KP_Subtract: return '-'; michael@0: case GDK_KP_Decimal: return '.'; michael@0: case GDK_KP_Divide: return '/'; michael@0: case GDK_KP_0: return '0'; michael@0: case GDK_KP_1: return '1'; michael@0: case GDK_KP_2: return '2'; michael@0: case GDK_KP_3: return '3'; michael@0: case GDK_KP_4: return '4'; michael@0: case GDK_KP_5: return '5'; michael@0: case GDK_KP_6: return '6'; michael@0: case GDK_KP_7: return '7'; michael@0: case GDK_KP_8: return '8'; michael@0: case GDK_KP_9: return '9'; michael@0: default: return 0; // non-printables michael@0: } michael@0: } michael@0: michael@0: static const long MAX_UNICODE = 0x10FFFF; michael@0: michael@0: // we're supposedly printable, let's try to convert michael@0: long ucs = keysym2ucs(aGdkKeyEvent->keyval); michael@0: if ((ucs != -1) && (ucs < MAX_UNICODE)) { michael@0: return ucs; michael@0: } michael@0: michael@0: // I guess we couldn't convert michael@0: return 0; michael@0: } michael@0: michael@0: uint32_t michael@0: KeymapWrapper::GetCharCodeFor(const GdkEventKey *aGdkKeyEvent, michael@0: guint aModifierState, michael@0: gint aGroup) michael@0: { michael@0: guint keyval; michael@0: if (!gdk_keymap_translate_keyboard_state(mGdkKeymap, michael@0: aGdkKeyEvent->hardware_keycode, michael@0: GdkModifierType(aModifierState), michael@0: aGroup, &keyval, nullptr, nullptr, nullptr)) { michael@0: return 0; michael@0: } michael@0: GdkEventKey tmpEvent = *aGdkKeyEvent; michael@0: tmpEvent.state = aModifierState; michael@0: tmpEvent.keyval = keyval; michael@0: tmpEvent.group = aGroup; michael@0: return GetCharCodeFor(&tmpEvent); michael@0: } michael@0: michael@0: uint32_t michael@0: KeymapWrapper::GetUnmodifiedCharCodeFor(const GdkEventKey* aGdkKeyEvent) michael@0: { michael@0: guint state = aGdkKeyEvent->state & michael@0: (GetModifierMask(SHIFT) | GetModifierMask(CAPS_LOCK) | michael@0: GetModifierMask(NUM_LOCK) | GetModifierMask(SCROLL_LOCK) | michael@0: GetModifierMask(LEVEL3) | GetModifierMask(LEVEL5)); michael@0: uint32_t charCode = GetCharCodeFor(aGdkKeyEvent, GdkModifierType(state), michael@0: aGdkKeyEvent->group); michael@0: if (charCode) { michael@0: return charCode; michael@0: } michael@0: // If no character is mapped to the key when Level3 Shift or Level5 Shift michael@0: // is active, let's return a character which is inputted by the key without michael@0: // Level3 nor Level5 Shift. michael@0: guint stateWithoutAltGraph = michael@0: state & ~(GetModifierMask(LEVEL3) | GetModifierMask(LEVEL5)); michael@0: if (state == stateWithoutAltGraph) { michael@0: return 0; michael@0: } michael@0: return GetCharCodeFor(aGdkKeyEvent, GdkModifierType(stateWithoutAltGraph), michael@0: aGdkKeyEvent->group); michael@0: } michael@0: michael@0: gint michael@0: KeymapWrapper::GetKeyLevel(GdkEventKey *aGdkKeyEvent) michael@0: { michael@0: gint level; michael@0: if (!gdk_keymap_translate_keyboard_state(mGdkKeymap, michael@0: aGdkKeyEvent->hardware_keycode, michael@0: GdkModifierType(aGdkKeyEvent->state), michael@0: aGdkKeyEvent->group, nullptr, nullptr, &level, nullptr)) { michael@0: return -1; michael@0: } michael@0: return level; michael@0: } michael@0: michael@0: gint michael@0: KeymapWrapper::GetFirstLatinGroup() michael@0: { michael@0: GdkKeymapKey *keys; michael@0: gint count; michael@0: gint minGroup = -1; michael@0: if (gdk_keymap_get_entries_for_keyval(mGdkKeymap, GDK_a, &keys, &count)) { michael@0: // find the minimum number group for latin inputtable layout michael@0: for (gint i = 0; i < count && minGroup != 0; ++i) { michael@0: if (keys[i].level != 0 && keys[i].level != 1) { michael@0: continue; michael@0: } michael@0: if (minGroup >= 0 && keys[i].group > minGroup) { michael@0: continue; michael@0: } michael@0: minGroup = keys[i].group; michael@0: } michael@0: g_free(keys); michael@0: } michael@0: return minGroup; michael@0: } michael@0: michael@0: bool michael@0: KeymapWrapper::IsLatinGroup(guint8 aGroup) michael@0: { michael@0: GdkKeymapKey *keys; michael@0: gint count; michael@0: bool result = false; michael@0: if (gdk_keymap_get_entries_for_keyval(mGdkKeymap, GDK_a, &keys, &count)) { michael@0: for (gint i = 0; i < count; ++i) { michael@0: if (keys[i].level != 0 && keys[i].level != 1) { michael@0: continue; michael@0: } michael@0: if (keys[i].group == aGroup) { michael@0: result = true; michael@0: break; michael@0: } michael@0: } michael@0: g_free(keys); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: bool michael@0: KeymapWrapper::IsAutoRepeatableKey(guint aHardwareKeyCode) michael@0: { michael@0: uint8_t indexOfArray = aHardwareKeyCode / 8; michael@0: MOZ_ASSERT(indexOfArray < ArrayLength(mKeyboardState.auto_repeats), michael@0: "invalid index"); michael@0: char bitMask = 1 << (aHardwareKeyCode % 8); michael@0: return (mKeyboardState.auto_repeats[indexOfArray] & bitMask) != 0; michael@0: } michael@0: michael@0: /* static */ bool michael@0: KeymapWrapper::IsBasicLatinLetterOrNumeral(uint32_t aCharCode) michael@0: { michael@0: return (aCharCode >= 'a' && aCharCode <= 'z') || michael@0: (aCharCode >= 'A' && aCharCode <= 'Z') || michael@0: (aCharCode >= '0' && aCharCode <= '9'); michael@0: } michael@0: michael@0: /* static */ guint michael@0: KeymapWrapper::GetGDKKeyvalWithoutModifier(const GdkEventKey *aGdkKeyEvent) michael@0: { michael@0: KeymapWrapper* keymapWrapper = GetInstance(); michael@0: guint state = michael@0: (aGdkKeyEvent->state & keymapWrapper->GetModifierMask(NUM_LOCK)); michael@0: guint keyval; michael@0: if (!gdk_keymap_translate_keyboard_state(keymapWrapper->mGdkKeymap, michael@0: aGdkKeyEvent->hardware_keycode, GdkModifierType(state), michael@0: aGdkKeyEvent->group, &keyval, nullptr, nullptr, nullptr)) { michael@0: return 0; michael@0: } michael@0: return keyval; michael@0: } michael@0: michael@0: /* static */ uint32_t michael@0: KeymapWrapper::GetDOMKeyCodeFromKeyPairs(guint aGdkKeyval) michael@0: { michael@0: switch (aGdkKeyval) { michael@0: case GDK_Cancel: return NS_VK_CANCEL; michael@0: case GDK_BackSpace: return NS_VK_BACK; michael@0: case GDK_Tab: michael@0: case GDK_ISO_Left_Tab: return NS_VK_TAB; michael@0: case GDK_Clear: return NS_VK_CLEAR; michael@0: case GDK_Return: return NS_VK_RETURN; michael@0: case GDK_Shift_L: michael@0: case GDK_Shift_R: michael@0: case GDK_Shift_Lock: return NS_VK_SHIFT; michael@0: case GDK_Control_L: michael@0: case GDK_Control_R: return NS_VK_CONTROL; michael@0: case GDK_Alt_L: michael@0: case GDK_Alt_R: return NS_VK_ALT; michael@0: case GDK_Meta_L: michael@0: case GDK_Meta_R: return NS_VK_META; michael@0: michael@0: // Assume that Super or Hyper is always mapped to physical Win key. michael@0: case GDK_Super_L: michael@0: case GDK_Super_R: michael@0: case GDK_Hyper_L: michael@0: case GDK_Hyper_R: return NS_VK_WIN; michael@0: michael@0: // GTK's AltGraph key is similar to Mac's Option (Alt) key. However, michael@0: // unfortunately, browsers on Mac are using NS_VK_ALT for it even though michael@0: // it's really different from Alt key on Windows. michael@0: // On the other hand, GTK's AltGrapsh keys are really different from michael@0: // Alt key. However, there is no AltGrapsh key on Windows. On Windows, michael@0: // both Ctrl and Alt keys are pressed internally when AltGr key is michael@0: // pressed. For some languages' users, AltGraph key is important, so, michael@0: // web applications on such locale may want to know AltGraph key press. michael@0: // Therefore, we should map AltGr keycode for them only on GTK. michael@0: case GDK_ISO_Level3_Shift: michael@0: case GDK_ISO_Level5_Shift: michael@0: // We assume that Mode_switch is always used for level3 shift. michael@0: case GDK_Mode_switch: return NS_VK_ALTGR; michael@0: michael@0: case GDK_Pause: return NS_VK_PAUSE; michael@0: case GDK_Caps_Lock: return NS_VK_CAPS_LOCK; michael@0: case GDK_Kana_Lock: michael@0: case GDK_Kana_Shift: return NS_VK_KANA; michael@0: case GDK_Hangul: return NS_VK_HANGUL; michael@0: // case GDK_XXX: return NS_VK_JUNJA; michael@0: // case GDK_XXX: return NS_VK_FINAL; michael@0: case GDK_Hangul_Hanja: return NS_VK_HANJA; michael@0: case GDK_Kanji: return NS_VK_KANJI; michael@0: case GDK_Escape: return NS_VK_ESCAPE; michael@0: case GDK_Henkan: return NS_VK_CONVERT; michael@0: case GDK_Muhenkan: return NS_VK_NONCONVERT; michael@0: // case GDK_XXX: return NS_VK_ACCEPT; michael@0: // case GDK_XXX: return NS_VK_MODECHANGE; michael@0: case GDK_Page_Up: return NS_VK_PAGE_UP; michael@0: case GDK_Page_Down: return NS_VK_PAGE_DOWN; michael@0: case GDK_End: return NS_VK_END; michael@0: case GDK_Home: return NS_VK_HOME; michael@0: case GDK_Left: return NS_VK_LEFT; michael@0: case GDK_Up: return NS_VK_UP; michael@0: case GDK_Right: return NS_VK_RIGHT; michael@0: case GDK_Down: return NS_VK_DOWN; michael@0: case GDK_Select: return NS_VK_SELECT; michael@0: case GDK_Print: return NS_VK_PRINT; michael@0: case GDK_Execute: return NS_VK_EXECUTE; michael@0: case GDK_Insert: return NS_VK_INSERT; michael@0: case GDK_Delete: return NS_VK_DELETE; michael@0: case GDK_Help: return NS_VK_HELP; michael@0: michael@0: // keypad keys michael@0: case GDK_KP_Left: return NS_VK_LEFT; michael@0: case GDK_KP_Right: return NS_VK_RIGHT; michael@0: case GDK_KP_Up: return NS_VK_UP; michael@0: case GDK_KP_Down: return NS_VK_DOWN; michael@0: case GDK_KP_Page_Up: return NS_VK_PAGE_UP; michael@0: // Not sure what these are michael@0: // case GDK_KP_Prior: return NS_VK_; michael@0: // case GDK_KP_Next: return NS_VK_; michael@0: case GDK_KP_Begin: return NS_VK_CLEAR; // Num-unlocked 5 michael@0: case GDK_KP_Page_Down: return NS_VK_PAGE_DOWN; michael@0: case GDK_KP_Home: return NS_VK_HOME; michael@0: case GDK_KP_End: return NS_VK_END; michael@0: case GDK_KP_Insert: return NS_VK_INSERT; michael@0: case GDK_KP_Delete: return NS_VK_DELETE; michael@0: case GDK_KP_Enter: return NS_VK_RETURN; michael@0: michael@0: case GDK_Num_Lock: return NS_VK_NUM_LOCK; michael@0: case GDK_Scroll_Lock: return NS_VK_SCROLL_LOCK; michael@0: michael@0: // Function keys michael@0: case GDK_F1: return NS_VK_F1; michael@0: case GDK_F2: return NS_VK_F2; michael@0: case GDK_F3: return NS_VK_F3; michael@0: case GDK_F4: return NS_VK_F4; michael@0: case GDK_F5: return NS_VK_F5; michael@0: case GDK_F6: return NS_VK_F6; michael@0: case GDK_F7: return NS_VK_F7; michael@0: case GDK_F8: return NS_VK_F8; michael@0: case GDK_F9: return NS_VK_F9; michael@0: case GDK_F10: return NS_VK_F10; michael@0: case GDK_F11: return NS_VK_F11; michael@0: case GDK_F12: return NS_VK_F12; michael@0: case GDK_F13: return NS_VK_F13; michael@0: case GDK_F14: return NS_VK_F14; michael@0: case GDK_F15: return NS_VK_F15; michael@0: case GDK_F16: return NS_VK_F16; michael@0: case GDK_F17: return NS_VK_F17; michael@0: case GDK_F18: return NS_VK_F18; michael@0: case GDK_F19: return NS_VK_F19; michael@0: case GDK_F20: return NS_VK_F20; michael@0: case GDK_F21: return NS_VK_F21; michael@0: case GDK_F22: return NS_VK_F22; michael@0: case GDK_F23: return NS_VK_F23; michael@0: case GDK_F24: return NS_VK_F24; michael@0: michael@0: // context menu key, keysym 0xff67, typically keycode 117 on 105-key michael@0: // (Microsoft) x86 keyboards, located between right 'Windows' key and michael@0: // right Ctrl key michael@0: case GDK_Menu: return NS_VK_CONTEXT_MENU; michael@0: case GDK_Sleep: return NS_VK_SLEEP; michael@0: michael@0: case GDK_3270_Attn: return NS_VK_ATTN; michael@0: case GDK_3270_CursorSelect: return NS_VK_CRSEL; michael@0: case GDK_3270_ExSelect: return NS_VK_EXSEL; michael@0: case GDK_3270_EraseEOF: return NS_VK_EREOF; michael@0: case GDK_3270_Play: return NS_VK_PLAY; michael@0: // case GDK_XXX: return NS_VK_ZOOM; michael@0: case GDK_3270_PA1: return NS_VK_PA1; michael@0: michael@0: // map Sun Keyboard special keysyms on to NS_VK keys michael@0: michael@0: // Sun F11 key generates SunF36(0x1005ff10) keysym michael@0: case 0x1005ff10: return NS_VK_F11; michael@0: // Sun F12 key generates SunF37(0x1005ff11) keysym michael@0: case 0x1005ff11: return NS_VK_F12; michael@0: default: return 0; michael@0: } michael@0: } michael@0: michael@0: void michael@0: KeymapWrapper::InitKeypressEvent(WidgetKeyboardEvent& aKeyEvent, michael@0: GdkEventKey* aGdkKeyEvent) michael@0: { michael@0: NS_ENSURE_TRUE_VOID(aKeyEvent.message == NS_KEY_PRESS); michael@0: michael@0: aKeyEvent.charCode = GetCharCodeFor(aGdkKeyEvent); michael@0: if (!aKeyEvent.charCode) { michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitKeypressEvent, " michael@0: "keyCode=0x%02X, charCode=0x%08X", michael@0: this, aKeyEvent.keyCode, aKeyEvent.charCode)); michael@0: return; michael@0: } michael@0: michael@0: // If the event causes inputting a character, keyCode must be zero. michael@0: aKeyEvent.keyCode = 0; michael@0: michael@0: // If Ctrl or Alt or Meta or OS is pressed, we need to append the key michael@0: // details for handling shortcut key. Otherwise, we have no additional michael@0: // work. michael@0: if (!aKeyEvent.IsControl() && !aKeyEvent.IsAlt() && michael@0: !aKeyEvent.IsMeta() && !aKeyEvent.IsOS()) { michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitKeypressEvent, " michael@0: "keyCode=0x%02X, charCode=0x%08X", michael@0: this, aKeyEvent.keyCode, aKeyEvent.charCode)); michael@0: return; michael@0: } michael@0: michael@0: gint level = GetKeyLevel(aGdkKeyEvent); michael@0: if (level != 0 && level != 1) { michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitKeypressEvent, " michael@0: "keyCode=0x%02X, charCode=0x%08X, level=%d", michael@0: this, aKeyEvent.keyCode, aKeyEvent.charCode, level)); michael@0: return; michael@0: } michael@0: michael@0: guint baseState = aGdkKeyEvent->state & michael@0: ~(GetModifierMask(SHIFT) | GetModifierMask(CTRL) | michael@0: GetModifierMask(ALT) | GetModifierMask(META) | michael@0: GetModifierMask(SUPER) | GetModifierMask(HYPER)); michael@0: michael@0: // We shold send both shifted char and unshifted char, all keyboard layout michael@0: // users can use all keys. Don't change event.charCode. On some keyboard michael@0: // layouts, Ctrl/Alt/Meta keys are used for inputting some characters. michael@0: AlternativeCharCode altCharCodes(0, 0); michael@0: // unshifted charcode of current keyboard layout. michael@0: altCharCodes.mUnshiftedCharCode = michael@0: GetCharCodeFor(aGdkKeyEvent, baseState, aGdkKeyEvent->group); michael@0: bool isLatin = (altCharCodes.mUnshiftedCharCode <= 0xFF); michael@0: // shifted charcode of current keyboard layout. michael@0: altCharCodes.mShiftedCharCode = michael@0: GetCharCodeFor(aGdkKeyEvent, michael@0: baseState | GetModifierMask(SHIFT), michael@0: aGdkKeyEvent->group); michael@0: isLatin = isLatin && (altCharCodes.mShiftedCharCode <= 0xFF); michael@0: if (altCharCodes.mUnshiftedCharCode || altCharCodes.mShiftedCharCode) { michael@0: aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes); michael@0: } michael@0: michael@0: bool needLatinKeyCodes = !isLatin; michael@0: if (!needLatinKeyCodes) { michael@0: needLatinKeyCodes = michael@0: (IS_ASCII_ALPHABETICAL(altCharCodes.mUnshiftedCharCode) != michael@0: IS_ASCII_ALPHABETICAL(altCharCodes.mShiftedCharCode)); michael@0: } michael@0: michael@0: // If current keyboard layout can input Latin characters, we don't need michael@0: // more information. michael@0: if (!needLatinKeyCodes) { michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitKeypressEvent, keyCode=0x%02X, " michael@0: "charCode=0x%08X, level=%d, altCharCodes={ " michael@0: "mUnshiftedCharCode=0x%08X, mShiftedCharCode=0x%08X }", michael@0: this, aKeyEvent.keyCode, aKeyEvent.charCode, level, michael@0: altCharCodes.mUnshiftedCharCode, altCharCodes.mShiftedCharCode)); michael@0: return; michael@0: } michael@0: michael@0: // Next, find Latin inputtable keyboard layout. michael@0: gint minGroup = GetFirstLatinGroup(); michael@0: if (minGroup < 0) { michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitKeypressEvent, " michael@0: "Latin keyboard layout isn't found: " michael@0: "keyCode=0x%02X, charCode=0x%08X, level=%d, " michael@0: "altCharCodes={ mUnshiftedCharCode=0x%08X, " michael@0: "mShiftedCharCode=0x%08X }", michael@0: this, aKeyEvent.keyCode, aKeyEvent.charCode, level, michael@0: altCharCodes.mUnshiftedCharCode, altCharCodes.mShiftedCharCode)); michael@0: return; michael@0: } michael@0: michael@0: AlternativeCharCode altLatinCharCodes(0, 0); michael@0: uint32_t unmodifiedCh = michael@0: aKeyEvent.IsShift() ? altCharCodes.mShiftedCharCode : michael@0: altCharCodes.mUnshiftedCharCode; michael@0: michael@0: // unshifted charcode of found keyboard layout. michael@0: uint32_t ch = GetCharCodeFor(aGdkKeyEvent, baseState, minGroup); michael@0: altLatinCharCodes.mUnshiftedCharCode = michael@0: IsBasicLatinLetterOrNumeral(ch) ? ch : 0; michael@0: // shifted charcode of found keyboard layout. michael@0: ch = GetCharCodeFor(aGdkKeyEvent, michael@0: baseState | GetModifierMask(SHIFT), michael@0: minGroup); michael@0: altLatinCharCodes.mShiftedCharCode = michael@0: IsBasicLatinLetterOrNumeral(ch) ? ch : 0; michael@0: if (altLatinCharCodes.mUnshiftedCharCode || michael@0: altLatinCharCodes.mShiftedCharCode) { michael@0: aKeyEvent.alternativeCharCodes.AppendElement(altLatinCharCodes); michael@0: } michael@0: // If the charCode is not Latin, and the level is 0 or 1, we should michael@0: // replace the charCode to Latin char if Alt and Meta keys are not michael@0: // pressed. (Alt should be sent the localized char for accesskey michael@0: // like handling of Web Applications.) michael@0: ch = aKeyEvent.IsShift() ? altLatinCharCodes.mShiftedCharCode : michael@0: altLatinCharCodes.mUnshiftedCharCode; michael@0: if (ch && !(aKeyEvent.IsAlt() || aKeyEvent.IsMeta()) && michael@0: aKeyEvent.charCode == unmodifiedCh) { michael@0: aKeyEvent.charCode = ch; michael@0: } michael@0: michael@0: PR_LOG(gKeymapWrapperLog, PR_LOG_ALWAYS, michael@0: ("KeymapWrapper(%p): InitKeypressEvent, " michael@0: "keyCode=0x%02X, charCode=0x%08X, level=%d, minGroup=%d, " michael@0: "altCharCodes={ mUnshiftedCharCode=0x%08X, " michael@0: "mShiftedCharCode=0x%08X } " michael@0: "altLatinCharCodes={ mUnshiftedCharCode=0x%08X, " michael@0: "mShiftedCharCode=0x%08X }", michael@0: this, aKeyEvent.keyCode, aKeyEvent.charCode, level, minGroup, michael@0: altCharCodes.mUnshiftedCharCode, altCharCodes.mShiftedCharCode, michael@0: altLatinCharCodes.mUnshiftedCharCode, michael@0: altLatinCharCodes.mShiftedCharCode)); michael@0: } michael@0: michael@0: /* static */ bool michael@0: KeymapWrapper::IsKeyPressEventNecessary(GdkEventKey* aGdkKeyEvent) michael@0: { michael@0: // If this is a modifier key event, we shouldn't send keypress event. michael@0: switch (ComputeDOMKeyCode(aGdkKeyEvent)) { michael@0: case NS_VK_SHIFT: michael@0: case NS_VK_CONTROL: michael@0: case NS_VK_ALT: michael@0: case NS_VK_ALTGR: michael@0: case NS_VK_WIN: michael@0: case NS_VK_CAPS_LOCK: michael@0: case NS_VK_NUM_LOCK: michael@0: case NS_VK_SCROLL_LOCK: michael@0: return false; michael@0: default: michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: } // namespace widget michael@0: } // namespace mozilla