forked from mirrors/gecko-dev
`GlobalKeyListener` try to match shortcut keys with exactly checking `Shift` state, but for the cases which do not match localized language and active keyboard layout location, it scans another shortcut keys with ignoring `Shift` state if no handler is available. `<command>` elements and `<key>` elements may be disabled conditionally. E.g., Zoom-in/Zoom-out are disabled when current zoom level is max/min value. In this case, it's odd that another shortcut key which does not exactly match the modifiers works. Therefore, this patch makes `GlobalKeyListener` does not try to scan handlers with ignoring `Shift` state if it has already found a disabled handler which exactly matches with the modifiers. Differential Revision: https://phabricator.services.mozilla.com/D184789
172 lines
5.5 KiB
C++
172 lines
5.5 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 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 "mozilla/layers/KeyboardMap.h"
|
|
|
|
#include "mozilla/TextEvents.h" // for IgnoreModifierState, ShortcutKeyCandidate
|
|
|
|
namespace mozilla {
|
|
namespace layers {
|
|
|
|
KeyboardShortcut::KeyboardShortcut()
|
|
: mKeyCode(0),
|
|
mCharCode(0),
|
|
mModifiers(0),
|
|
mModifiersMask(0),
|
|
mEventType(KeyboardInput::KeyboardEventType::KEY_OTHER),
|
|
mDispatchToContent(false) {}
|
|
|
|
KeyboardShortcut::KeyboardShortcut(KeyboardInput::KeyboardEventType aEventType,
|
|
uint32_t aKeyCode, uint32_t aCharCode,
|
|
Modifiers aModifiers,
|
|
Modifiers aModifiersMask,
|
|
const KeyboardScrollAction& aAction)
|
|
: mAction(aAction),
|
|
mKeyCode(aKeyCode),
|
|
mCharCode(aCharCode),
|
|
mModifiers(aModifiers),
|
|
mModifiersMask(aModifiersMask),
|
|
mEventType(aEventType),
|
|
mDispatchToContent(false) {}
|
|
|
|
KeyboardShortcut::KeyboardShortcut(KeyboardInput::KeyboardEventType aEventType,
|
|
uint32_t aKeyCode, uint32_t aCharCode,
|
|
Modifiers aModifiers,
|
|
Modifiers aModifiersMask)
|
|
: mKeyCode(aKeyCode),
|
|
mCharCode(aCharCode),
|
|
mModifiers(aModifiers),
|
|
mModifiersMask(aModifiersMask),
|
|
mEventType(aEventType),
|
|
mDispatchToContent(true) {}
|
|
|
|
/* static */
|
|
void KeyboardShortcut::AppendHardcodedShortcuts(
|
|
nsTArray<KeyboardShortcut>& aShortcuts) {
|
|
// Tab
|
|
KeyboardShortcut tab1;
|
|
tab1.mDispatchToContent = true;
|
|
tab1.mKeyCode = NS_VK_TAB;
|
|
tab1.mCharCode = 0;
|
|
tab1.mModifiers = 0;
|
|
tab1.mModifiersMask = 0;
|
|
tab1.mEventType = KeyboardInput::KEY_PRESS;
|
|
aShortcuts.AppendElement(tab1);
|
|
|
|
// F6
|
|
KeyboardShortcut tab2;
|
|
tab2.mDispatchToContent = true;
|
|
tab2.mKeyCode = NS_VK_F6;
|
|
tab2.mCharCode = 0;
|
|
tab2.mModifiers = 0;
|
|
tab2.mModifiersMask = 0;
|
|
tab2.mEventType = KeyboardInput::KEY_PRESS;
|
|
aShortcuts.AppendElement(tab2);
|
|
}
|
|
|
|
bool KeyboardShortcut::Matches(const KeyboardInput& aInput,
|
|
const IgnoreModifierState& aIgnore,
|
|
uint32_t aOverrideCharCode) const {
|
|
return mEventType == aInput.mType && MatchesKey(aInput, aOverrideCharCode) &&
|
|
MatchesModifiers(aInput, aIgnore);
|
|
}
|
|
|
|
bool KeyboardShortcut::MatchesKey(const KeyboardInput& aInput,
|
|
uint32_t aOverrideCharCode) const {
|
|
// Compare by the key code if we have one
|
|
if (!mCharCode) {
|
|
return mKeyCode == aInput.mKeyCode;
|
|
}
|
|
|
|
// We are comparing by char code
|
|
uint32_t charCode;
|
|
|
|
// If we are comparing against a shortcut candidate then we might
|
|
// have an override char code
|
|
if (aOverrideCharCode) {
|
|
charCode = aOverrideCharCode;
|
|
} else {
|
|
charCode = aInput.mCharCode;
|
|
}
|
|
|
|
// Both char codes must be in lowercase to compare correctly
|
|
if (IS_IN_BMP(charCode)) {
|
|
charCode = ToLowerCase(static_cast<char16_t>(charCode));
|
|
}
|
|
|
|
return mCharCode == charCode;
|
|
}
|
|
|
|
bool KeyboardShortcut::MatchesModifiers(
|
|
const KeyboardInput& aInput, const IgnoreModifierState& aIgnore) const {
|
|
Modifiers modifiersMask = mModifiersMask;
|
|
|
|
// If we are ignoring Shift or Meta (Windows key), then unset that part of the
|
|
// mask
|
|
if (aIgnore.mMeta) {
|
|
modifiersMask &= ~MODIFIER_META;
|
|
}
|
|
if (aIgnore.mShift) {
|
|
modifiersMask &= ~MODIFIER_SHIFT;
|
|
}
|
|
|
|
// Mask off the modifiers we are ignoring from the keyboard input
|
|
return (aInput.modifiers & modifiersMask) == mModifiers;
|
|
}
|
|
|
|
KeyboardMap::KeyboardMap(nsTArray<KeyboardShortcut>&& aShortcuts)
|
|
: mShortcuts(aShortcuts) {}
|
|
|
|
KeyboardMap::KeyboardMap() = default;
|
|
|
|
Maybe<KeyboardShortcut> KeyboardMap::FindMatch(
|
|
const KeyboardInput& aEvent) const {
|
|
// If there are no shortcut candidates, then just search with with the
|
|
// keyboard input
|
|
if (aEvent.mShortcutCandidates.IsEmpty()) {
|
|
return FindMatchInternal(aEvent, IgnoreModifierState());
|
|
}
|
|
|
|
// Otherwise do a search with each shortcut candidate in order
|
|
for (const auto& key : aEvent.mShortcutCandidates) {
|
|
IgnoreModifierState ignoreModifierState;
|
|
ignoreModifierState.mShift =
|
|
key.mShiftState == ShortcutKeyCandidate::ShiftState::Ignorable;
|
|
|
|
auto match = FindMatchInternal(aEvent, ignoreModifierState, key.mCharCode);
|
|
if (match) {
|
|
return match;
|
|
}
|
|
}
|
|
return Nothing();
|
|
}
|
|
|
|
Maybe<KeyboardShortcut> KeyboardMap::FindMatchInternal(
|
|
const KeyboardInput& aEvent, const IgnoreModifierState& aIgnore,
|
|
uint32_t aOverrideCharCode) const {
|
|
for (auto& shortcut : mShortcuts) {
|
|
if (shortcut.Matches(aEvent, aIgnore, aOverrideCharCode)) {
|
|
return Some(shortcut);
|
|
}
|
|
}
|
|
|
|
#ifdef XP_WIN
|
|
// Windows native applications ignore Windows-Logo key state when checking
|
|
// shortcut keys even if the key is pressed. Therefore, if there is no
|
|
// shortcut key which exactly matches current modifier state, we should
|
|
// retry to look for a shortcut key without the Windows-Logo key press.
|
|
if (!aIgnore.mMeta && (aEvent.modifiers & MODIFIER_META)) {
|
|
IgnoreModifierState ignoreModifierState(aIgnore);
|
|
ignoreModifierState.mMeta = true;
|
|
return FindMatchInternal(aEvent, ignoreModifierState, aOverrideCharCode);
|
|
}
|
|
#endif
|
|
|
|
return Nothing();
|
|
}
|
|
|
|
} // namespace layers
|
|
} // namespace mozilla
|