| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/events/event_rewriter.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <vector> |
| |
| #include "ash/accelerators/accelerator_controller_impl.h" |
| #include "ash/accessibility/sticky_keys/sticky_keys_controller.h" |
| #include "ash/accessibility/sticky_keys/sticky_keys_overlay.h" |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/public/cpp/input_device_settings_controller.h" |
| #include "ash/public/cpp/test/mock_input_device_settings_controller.h" |
| #include "ash/public/mojom/input_device_settings.mojom.h" |
| #include "ash/shell.h" |
| #include "ash/system/input_device_settings/input_device_settings_notification_controller.h" |
| #include "ash/system/input_device_settings/input_device_settings_pref_names.h" |
| #include "ash/system/toast/anchored_nudge_manager_impl.h" |
| #include "base/command_line.h" |
| #include "base/containers/adapters.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_set.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/ash/events/event_rewriter_delegate_impl.h" |
| #include "chrome/browser/ash/input_method/input_method_configuration.h" |
| #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h" |
| #include "chrome/browser/ash/notifications/deprecation_notification_controller.h" |
| #include "chrome/browser/ash/preferences/preferences.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/chrome_ash_test_base.h" |
| #include "components/prefs/pref_member.h" |
| #include "components/sync_preferences/testing_pref_service_syncable.h" |
| #include "components/user_manager/scoped_user_manager.h" |
| #include "device/udev_linux/fake_udev_loader.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/base/accelerators/accelerator.h" |
| #include "ui/base/ime/ash/fake_ime_keyboard.h" |
| #include "ui/base/ime/ash/input_method_manager.h" |
| #include "ui/base/ime/ash/mock_input_method_manager.h" |
| #include "ui/base/ime/ash/mock_input_method_manager_impl.h" |
| #include "ui/base/shortcut_mapping_pref_delegate.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/events/ash/caps_lock_event_rewriter.h" |
| #include "ui/events/ash/discard_key_event_rewriter.h" |
| #include "ui/events/ash/event_rewriter_ash.h" |
| #include "ui/events/ash/event_rewriter_metrics.h" |
| #include "ui/events/ash/keyboard_capability.h" |
| #include "ui/events/ash/keyboard_device_id_event_rewriter.h" |
| #include "ui/events/ash/keyboard_modifier_event_rewriter.h" |
| #include "ui/events/ash/mojom/extended_fkeys_modifier.mojom-shared.h" |
| #include "ui/events/ash/mojom/modifier_key.mojom-shared.h" |
| #include "ui/events/ash/mojom/modifier_key.mojom.h" |
| #include "ui/events/ash/mojom/simulate_right_click_modifier.mojom-shared.h" |
| #include "ui/events/ash/mojom/six_pack_shortcut_modifier.mojom-shared.h" |
| #include "ui/events/ash/pref_names.h" |
| #include "ui/events/devices/device_data_manager.h" |
| #include "ui/events/devices/device_data_manager_test_api.h" |
| #include "ui/events/devices/input_device.h" |
| #include "ui/events/devices/keyboard_device.h" |
| #include "ui/events/devices/touchpad_device.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/events/keycodes/dom/dom_code.h" |
| #include "ui/events/keycodes/dom/dom_key.h" |
| #include "ui/events/keycodes/dom/keycode_converter.h" |
| #include "ui/events/keycodes/keyboard_codes_posix.h" |
| #include "ui/events/ozone/layout/stub/stub_keyboard_layout_engine.h" |
| #include "ui/events/test/events_test_utils.h" |
| #include "ui/events/test/test_event_processor.h" |
| #include "ui/events/test/test_event_rewriter_continuation.h" |
| #include "ui/events/test/test_event_source.h" |
| #include "ui/events/types/event_type.h" |
| #include "ui/message_center/fake_message_center.h" |
| #include "ui/wm/core/window_util.h" |
| |
| namespace { |
| |
| constexpr int kKeyboardDeviceId = 123; |
| constexpr uint32_t kNoScanCode = 0; |
| constexpr char kKbdSysPath[] = "/devices/platform/i8042/serio2/input/input1"; |
| constexpr char kKbdTopRowPropertyName[] = "CROS_KEYBOARD_TOP_ROW_LAYOUT"; |
| constexpr char kKbdTopRowLayoutAttributeName[] = "function_row_physmap"; |
| constexpr char kSixPackKeyNoMatchNudgeId[] = "six-patch-key-no-match-nudge-id"; |
| constexpr char kTopRowKeyNoMatchNudgeId[] = "top-row-key-no-match-nudge-id"; |
| |
| constexpr char kKbdTopRowLayoutUnspecified[] = ""; |
| constexpr char kKbdTopRowLayout1Tag[] = "1"; |
| constexpr char kKbdTopRowLayout2Tag[] = "2"; |
| constexpr char kKbdTopRowLayoutWilcoTag[] = "3"; |
| constexpr char kKbdTopRowLayoutDrallionTag[] = "4"; |
| |
| constexpr int kTouchpadId1 = 10; |
| constexpr int kTouchpadId2 = 11; |
| |
| constexpr int kMouseDeviceId = 456; |
| |
| // A default example of the layout string read from the function_row_physmap |
| // sysfs attribute. The values represent the scan codes for each position |
| // in the top row, which maps to F-Keys. |
| constexpr char kKbdDefaultCustomTopRowLayout[] = |
| "01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f"; |
| |
| // Tag used to mark events as being for quick insert. |
| constexpr std::pair<std::string, std::vector<uint8_t>> kPropertyQuickInsert = { |
| "quick_insert_event", |
| {}}; |
| |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| constexpr char kCros1pInputMethodIdPrefix[] = |
| "_comp_ime_jkghodnilhceideoidjikpgommlajknk"; |
| #endif |
| |
| class TestEventSink : public ui::EventSink { |
| public: |
| TestEventSink() = default; |
| TestEventSink(const TestEventSink&) = delete; |
| TestEventSink& operator=(const TestEventSink&) = delete; |
| ~TestEventSink() override = default; |
| |
| // Returns the recorded events. |
| std::vector<std::unique_ptr<ui::Event>> TakeEvents() { |
| return std::move(events_); |
| } |
| |
| // ui::EventSink: |
| ui::EventDispatchDetails OnEventFromSource(ui::Event* event) override { |
| auto cloned_event = event->Clone(); |
| ui::EventTestApi(cloned_event.get()) |
| .set_native_event(event->native_event()); |
| events_.emplace_back(std::move(cloned_event)); |
| return ui::EventDispatchDetails(); |
| } |
| |
| private: |
| std::vector<std::unique_ptr<ui::Event>> events_; |
| }; |
| |
| class TestKeyboardModifierEventRewriterDelegate |
| : public ui::KeyboardModifierEventRewriter::Delegate { |
| public: |
| explicit TestKeyboardModifierEventRewriterDelegate( |
| ui::EventRewriterAsh::Delegate* ash_delegate) |
| : ash_delegate_(ash_delegate) {} |
| |
| std::optional<ui::mojom::ModifierKey> GetKeyboardRemappedModifierValue( |
| int device_id, |
| ui::mojom::ModifierKey modifier_key, |
| const std::string& pref_name) const override { |
| return ash_delegate_->GetKeyboardRemappedModifierValue( |
| device_id, modifier_key, pref_name); |
| } |
| |
| bool RewriteModifierKeys() override { |
| return ash_delegate_->RewriteModifierKeys(); |
| } |
| |
| private: |
| raw_ptr<ui::EventRewriterAsh::Delegate> ash_delegate_; |
| }; |
| |
| class TestEventRewriterContinuation |
| : public ui::test::TestEventRewriterContinuation { |
| public: |
| TestEventRewriterContinuation() = default; |
| ~TestEventRewriterContinuation() override = default; |
| TestEventRewriterContinuation(const TestEventRewriterContinuation&) = delete; |
| TestEventRewriterContinuation& operator=( |
| const TestEventRewriterContinuation&) = delete; |
| |
| ui::EventDispatchDetails SendEvent(const ui::Event* event) override { |
| passthrough_events.push_back(event->Clone()); |
| return ui::EventDispatchDetails(); |
| } |
| |
| ui::EventDispatchDetails SendEventFinally(const ui::Event* event) override { |
| rewritten_events.push_back(event->Clone()); |
| return ui::EventDispatchDetails(); |
| } |
| |
| ui::EventDispatchDetails DiscardEvent() override { |
| return ui::EventDispatchDetails(); |
| } |
| |
| std::vector<std::unique_ptr<ui::Event>> rewritten_events; |
| std::vector<std::unique_ptr<ui::Event>> passthrough_events; |
| |
| base::WeakPtrFactory<TestEventRewriterContinuation> weak_ptr_factory_{this}; |
| }; |
| |
| // Key representation in test cases. |
| struct TestKeyEvent { |
| ui::EventType type; |
| ui::DomCode code; |
| ui::DomKey key; |
| ui::KeyboardCode keycode; |
| ui::EventFlags flags = ui::EF_NONE; |
| uint32_t scan_code = kNoScanCode; |
| std::vector<std::pair<std::string, std::vector<uint8_t>>> properties; |
| |
| std::string ToString() const; |
| }; |
| |
| std::string TestKeyEvent::ToString() const { |
| std::string type_name(ui::EventTypeName(type)); |
| std::string flags_name = base::JoinString(ui::EventFlagsNames(flags), "|"); |
| |
| std::string property_dump; |
| for (const auto& property : properties) { |
| property_dump += (property_dump.empty() ? "" : "|") + property.first; |
| property_dump += "=" + base::HexEncode(property.second); |
| } |
| |
| return base::StringPrintf( |
| "type=%s(%d) " |
| "code=%s(0x%06X) " |
| "key=%s(0x%08X) " |
| "keycode=0x%02X " |
| "flags=%s(0x%X) " |
| "scan_code=0x%08X " |
| "properties=%s", |
| type_name.c_str(), type, |
| ui::KeycodeConverter::DomCodeToCodeString(code).c_str(), |
| static_cast<uint32_t>(code), |
| ui::KeycodeConverter::DomKeyToKeyString(key).c_str(), |
| static_cast<uint32_t>(key), keycode, flags_name.c_str(), flags, scan_code, |
| property_dump.data()); |
| } |
| |
| inline std::ostream& operator<<(std::ostream& os, const TestKeyEvent& event) { |
| return os << event.ToString(); |
| } |
| |
| inline bool operator==(const TestKeyEvent& e1, const TestKeyEvent& e2) { |
| return e1.type == e2.type && e1.code == e2.code && e1.key == e2.key && |
| e1.keycode == e2.keycode && e1.flags == e2.flags && |
| e1.scan_code == e2.scan_code && e1.properties == e2.properties; |
| } |
| |
| // Factory template of TestKeyEvents just to reduce a lot of code/data |
| // duplication. |
| template <ui::DomCode code, |
| ui::DomKey::Base key, |
| ui::KeyboardCode keycode, |
| ui::EventFlags modifier_flag = ui::EF_NONE, |
| ui::DomKey::Base shifted_key = key> |
| struct TestKey { |
| // Returns press key event. |
| static constexpr TestKeyEvent Pressed( |
| ui::EventFlags flags = ui::EF_NONE, |
| std::vector<std::pair<std::string, std::vector<uint8_t>>> properties = |
| {}) { |
| return {ui::EventType::kKeyPressed, |
| code, |
| (flags & ui::EF_SHIFT_DOWN) ? shifted_key : key, |
| keycode, |
| flags | modifier_flag, |
| kNoScanCode, |
| std::move(properties)}; |
| } |
| |
| // Returns release key event. |
| static constexpr TestKeyEvent Released( |
| ui::EventFlags flags = ui::EF_NONE, |
| std::vector<std::pair<std::string, std::vector<uint8_t>>> properties = |
| {}) { |
| // Note: modifier flag should not be present on release events. |
| return {ui::EventType::kKeyReleased, |
| code, |
| (flags & ui::EF_SHIFT_DOWN) ? shifted_key : key, |
| keycode, |
| flags, |
| kNoScanCode, |
| std::move(properties)}; |
| } |
| |
| // Returns press then release key events. |
| static std::vector<TestKeyEvent> Typed( |
| ui::EventFlags flags = ui::EF_NONE, |
| std::vector<std::pair<std::string, std::vector<uint8_t>>> properties = |
| {}) { |
| return {Pressed(flags, properties), Released(flags, std::move(properties))}; |
| } |
| }; |
| |
| // Short cut of TestKey construction for Character keys. |
| template <ui::DomCode code, |
| char key, |
| ui::KeyboardCode keycode, |
| char shifted_key = key> |
| using TestCharKey = TestKey<code, |
| ui::DomKey::FromCharacter(key), |
| keycode, |
| ui::EF_NONE, |
| ui::DomKey::FromCharacter(shifted_key)>; |
| |
| using KeyUnknown = |
| TestKey<ui::DomCode::NONE, ui::DomKey::UNIDENTIFIED, ui::VKEY_UNKNOWN>; |
| |
| // Character keys. Shift chars are based on US layout. |
| using KeyA = TestCharKey<ui::DomCode::US_A, 'a', ui::VKEY_A, 'A'>; |
| using KeyB = TestCharKey<ui::DomCode::US_B, 'b', ui::VKEY_B, 'B'>; |
| using KeyC = TestCharKey<ui::DomCode::US_C, 'c', ui::VKEY_C, 'C'>; |
| using KeyD = TestCharKey<ui::DomCode::US_D, 'd', ui::VKEY_D, 'D'>; |
| using KeyN = TestCharKey<ui::DomCode::US_N, 'n', ui::VKEY_N, 'N'>; |
| using KeyT = TestCharKey<ui::DomCode::US_T, 't', ui::VKEY_T, 'T'>; |
| using KeyComma = TestCharKey<ui::DomCode::COMMA, ',', ui::VKEY_OEM_COMMA, '<'>; |
| using KeyPeriod = |
| TestCharKey<ui::DomCode::PERIOD, '.', ui::VKEY_OEM_PERIOD, '>'>; |
| using KeyDigit1 = TestCharKey<ui::DomCode::DIGIT1, '1', ui::VKEY_1, '!'>; |
| using KeyDigit2 = TestCharKey<ui::DomCode::DIGIT2, '2', ui::VKEY_2, '@'>; |
| using KeyDigit3 = TestCharKey<ui::DomCode::DIGIT3, '3', ui::VKEY_3, '#'>; |
| using KeyDigit4 = TestCharKey<ui::DomCode::DIGIT4, '4', ui::VKEY_4, '$'>; |
| using KeyDigit5 = TestCharKey<ui::DomCode::DIGIT5, '5', ui::VKEY_5, '%'>; |
| using KeyDigit6 = TestCharKey<ui::DomCode::DIGIT6, '6', ui::VKEY_6, '^'>; |
| using KeyDigit7 = TestCharKey<ui::DomCode::DIGIT7, '7', ui::VKEY_7, '&'>; |
| using KeyDigit8 = TestCharKey<ui::DomCode::DIGIT8, '8', ui::VKEY_8, '*'>; |
| using KeyDigit9 = TestCharKey<ui::DomCode::DIGIT9, '9', ui::VKEY_9, '('>; |
| using KeyDigit0 = TestCharKey<ui::DomCode::DIGIT0, '0', ui::VKEY_0, ')'>; |
| using KeyMinus = TestCharKey<ui::DomCode::MINUS, '-', ui::VKEY_OEM_MINUS, '_'>; |
| using KeyEqual = TestCharKey<ui::DomCode::EQUAL, '=', ui::VKEY_OEM_PLUS, '+'>; |
| |
| // Modifier keys. |
| using KeyLShift = TestKey<ui::DomCode::SHIFT_LEFT, |
| ui::DomKey::SHIFT, |
| ui::VKEY_SHIFT, |
| ui::EF_SHIFT_DOWN>; |
| using KeyRShift = TestKey<ui::DomCode::SHIFT_RIGHT, |
| ui::DomKey::SHIFT, |
| ui::VKEY_SHIFT, |
| ui::EF_SHIFT_DOWN>; |
| using KeyLMeta = TestKey<ui::DomCode::META_LEFT, |
| ui::DomKey::META, |
| ui::VKEY_LWIN, |
| ui::EF_COMMAND_DOWN>; |
| using KeyRMeta = TestKey<ui::DomCode::META_RIGHT, |
| ui::DomKey::META, |
| ui::VKEY_RWIN, |
| ui::EF_COMMAND_DOWN>; |
| using KeyLControl = TestKey<ui::DomCode::CONTROL_LEFT, |
| ui::DomKey::CONTROL, |
| ui::VKEY_CONTROL, |
| ui::EF_CONTROL_DOWN>; |
| using KeyRControl = TestKey<ui::DomCode::CONTROL_RIGHT, |
| ui::DomKey::CONTROL, |
| ui::VKEY_CONTROL, |
| ui::EF_CONTROL_DOWN>; |
| using KeyLAlt = TestKey<ui::DomCode::ALT_LEFT, |
| ui::DomKey::ALT, |
| ui::VKEY_MENU, |
| ui::EF_ALT_DOWN>; |
| using KeyRAlt = TestKey<ui::DomCode::ALT_RIGHT, |
| ui::DomKey::ALT, |
| ui::VKEY_MENU, |
| ui::EF_ALT_DOWN>; |
| using KeyCapsLock = TestKey<ui::DomCode::CAPS_LOCK, |
| ui::DomKey::CAPS_LOCK, |
| ui::VKEY_CAPITAL, |
| ui::EF_MOD3_DOWN>; |
| using KeyJpnAlphanumeric = TestKey<ui::DomCode::LAUNCH_ASSISTANT, |
| ui::DomKey::ALPHANUMERIC, |
| ui::VKEY_CAPITAL, |
| ui::EF_MOD3_DOWN>; |
| using KeyFunction = TestKey<ui::DomCode::FN, |
| ui::DomKey::FN, |
| ui::VKEY_FUNCTION, |
| ui::EF_FUNCTION_DOWN>; |
| |
| // Function keys. |
| using KeyEscape = |
| TestKey<ui::DomCode::ESCAPE, ui::DomKey::ESCAPE, ui::VKEY_ESCAPE>; |
| using KeyF1 = TestKey<ui::DomCode::F1, ui::DomKey::F1, ui::VKEY_F1>; |
| using KeyF2 = TestKey<ui::DomCode::F2, ui::DomKey::F2, ui::VKEY_F2>; |
| using KeyF3 = TestKey<ui::DomCode::F3, ui::DomKey::F3, ui::VKEY_F3>; |
| using KeyF4 = TestKey<ui::DomCode::F4, ui::DomKey::F4, ui::VKEY_F4>; |
| using KeyF5 = TestKey<ui::DomCode::F5, ui::DomKey::F5, ui::VKEY_F5>; |
| using KeyF6 = TestKey<ui::DomCode::F6, ui::DomKey::F6, ui::VKEY_F6>; |
| using KeyF7 = TestKey<ui::DomCode::F7, ui::DomKey::F7, ui::VKEY_F7>; |
| using KeyF8 = TestKey<ui::DomCode::F8, ui::DomKey::F8, ui::VKEY_F8>; |
| using KeyF9 = TestKey<ui::DomCode::F9, ui::DomKey::F9, ui::VKEY_F9>; |
| using KeyF10 = TestKey<ui::DomCode::F10, ui::DomKey::F10, ui::VKEY_F10>; |
| using KeyF11 = TestKey<ui::DomCode::F11, ui::DomKey::F11, ui::VKEY_F11>; |
| using KeyF12 = TestKey<ui::DomCode::F12, ui::DomKey::F12, ui::VKEY_F12>; |
| using KeyF13 = TestKey<ui::DomCode::F13, ui::DomKey::F13, ui::VKEY_F13>; |
| using KeyF14 = TestKey<ui::DomCode::F14, ui::DomKey::F14, ui::VKEY_F14>; |
| using KeyF15 = TestKey<ui::DomCode::F15, ui::DomKey::F15, ui::VKEY_F15>; |
| using KeyBackspace = |
| TestKey<ui::DomCode::BACKSPACE, ui::DomKey::BACKSPACE, ui::VKEY_BACK>; |
| |
| // Chrome OS Special keys. |
| using KeyBrowserBack = TestKey<ui::DomCode::BROWSER_BACK, |
| ui::DomKey::BROWSER_BACK, |
| ui::VKEY_BROWSER_BACK>; |
| using KeyBrowserForward = TestKey<ui::DomCode::BROWSER_FORWARD, |
| ui::DomKey::BROWSER_FORWARD, |
| ui::VKEY_BROWSER_FORWARD>; |
| using KeyBrowserRefresh = TestKey<ui::DomCode::BROWSER_REFRESH, |
| ui::DomKey::BROWSER_REFRESH, |
| ui::VKEY_BROWSER_REFRESH>; |
| using KeyZoomToggle = |
| TestKey<ui::DomCode::ZOOM_TOGGLE, ui::DomKey::ZOOM_TOGGLE, ui::VKEY_ZOOM>; |
| using KeySelectTask = TestKey<ui::DomCode::SELECT_TASK, |
| ui::DomKey::LAUNCH_MY_COMPUTER, |
| ui::VKEY_MEDIA_LAUNCH_APP1>; |
| using KeyBrightnessDown = TestKey<ui::DomCode::BRIGHTNESS_DOWN, |
| ui::DomKey::BRIGHTNESS_DOWN, |
| ui::VKEY_BRIGHTNESS_DOWN>; |
| using KeyBrightnessUp = TestKey<ui::DomCode::BRIGHTNESS_UP, |
| ui::DomKey::BRIGHTNESS_UP, |
| ui::VKEY_BRIGHTNESS_UP>; |
| using KeyMediaPlayPause = TestKey<ui::DomCode::MEDIA_PLAY_PAUSE, |
| ui::DomKey::MEDIA_PLAY_PAUSE, |
| ui::VKEY_MEDIA_PLAY_PAUSE>; |
| using KeyVolumeMute = TestKey<ui::DomCode::VOLUME_MUTE, |
| ui::DomKey::AUDIO_VOLUME_MUTE, |
| ui::VKEY_VOLUME_MUTE>; |
| using KeyVolumeDown = TestKey<ui::DomCode::VOLUME_DOWN, |
| ui::DomKey::AUDIO_VOLUME_DOWN, |
| ui::VKEY_VOLUME_DOWN>; |
| using KeyVolumeUp = TestKey<ui::DomCode::VOLUME_UP, |
| ui::DomKey::AUDIO_VOLUME_UP, |
| ui::VKEY_VOLUME_UP>; |
| using KeyPrivacyScreenToggle = |
| TestKey<ui::DomCode::PRIVACY_SCREEN_TOGGLE, |
| ui::DomKey::F12, // no DomKey for PRIVACY_SCREEN_TOGGLE> |
| ui::VKEY_PRIVACY_SCREEN_TOGGLE>; |
| using KeyLaunchAssistant = TestKey<ui::DomCode::LAUNCH_ASSISTANT, |
| ui::DomKey::LAUNCH_ASSISTANT, |
| ui::VKEY_ASSISTANT>; |
| using KeyQuickInsert = TestKey<ui::DomCode::LAUNCH_ASSISTANT, |
| ui::DomKey::LAUNCH_ASSISTANT, |
| ui::VKEY_ASSISTANT, |
| ui::EF_NONE, |
| ui::DomKey::LAUNCH_ASSISTANT>; |
| |
| using KeyHangulMode = |
| TestKey<ui::DomCode::ALT_RIGHT, ui::DomKey::HANGUL_MODE, ui::VKEY_HANGUL>; |
| |
| // 6-pack keys. |
| using KeyInsert = |
| TestKey<ui::DomCode::INSERT, ui::DomKey::INSERT, ui::VKEY_INSERT>; |
| using KeyDelete = TestKey<ui::DomCode::DEL, ui::DomKey::DEL, ui::VKEY_DELETE>; |
| using KeyHome = TestKey<ui::DomCode::HOME, ui::DomKey::HOME, ui::VKEY_HOME>; |
| using KeyEnd = TestKey<ui::DomCode::END, ui::DomKey::END, ui::VKEY_END>; |
| using KeyPageUp = |
| TestKey<ui::DomCode::PAGE_UP, ui::DomKey::PAGE_UP, ui::VKEY_PRIOR>; |
| using KeyPageDown = |
| TestKey<ui::DomCode::PAGE_DOWN, ui::DomKey::PAGE_DOWN, ui::VKEY_NEXT>; |
| |
| // Arrow keys. |
| using KeyArrowUp = |
| TestKey<ui::DomCode::ARROW_UP, ui::DomKey::ARROW_UP, ui::VKEY_UP>; |
| using KeyArrowDown = |
| TestKey<ui::DomCode::ARROW_DOWN, ui::DomKey::ARROW_DOWN, ui::VKEY_DOWN>; |
| using KeyArrowLeft = |
| TestKey<ui::DomCode::ARROW_LEFT, ui::DomKey::ARROW_LEFT, ui::VKEY_LEFT>; |
| using KeyArrowRight = |
| TestKey<ui::DomCode::ARROW_RIGHT, ui::DomKey::ARROW_RIGHT, ui::VKEY_RIGHT>; |
| |
| // Numpad keys. |
| using KeyNumpad0 = TestCharKey<ui::DomCode::NUMPAD0, '0', ui::VKEY_NUMPAD0>; |
| using KeyNumpadDecimal = |
| TestCharKey<ui::DomCode::NUMPAD_DECIMAL, '.', ui::VKEY_DECIMAL>; |
| using KeyNumpad1 = TestCharKey<ui::DomCode::NUMPAD1, '1', ui::VKEY_NUMPAD1>; |
| using KeyNumpad2 = TestCharKey<ui::DomCode::NUMPAD2, '2', ui::VKEY_NUMPAD2>; |
| using KeyNumpad3 = TestCharKey<ui::DomCode::NUMPAD3, '3', ui::VKEY_NUMPAD3>; |
| using KeyNumpad4 = TestCharKey<ui::DomCode::NUMPAD4, '4', ui::VKEY_NUMPAD4>; |
| using KeyNumpad5 = TestCharKey<ui::DomCode::NUMPAD5, '5', ui::VKEY_NUMPAD5>; |
| using KeyNumpad6 = TestCharKey<ui::DomCode::NUMPAD6, '6', ui::VKEY_NUMPAD6>; |
| using KeyNumpad7 = TestCharKey<ui::DomCode::NUMPAD7, '7', ui::VKEY_NUMPAD7>; |
| using KeyNumpad8 = TestCharKey<ui::DomCode::NUMPAD8, '8', ui::VKEY_NUMPAD8>; |
| using KeyNumpad9 = TestCharKey<ui::DomCode::NUMPAD9, '9', ui::VKEY_NUMPAD9>; |
| |
| // Numpad keys without NumLock key. |
| using KeyNumpadInsert = |
| TestKey<ui::DomCode::NUMPAD0, ui::DomKey::INSERT, ui::VKEY_INSERT>; |
| using KeyNumpadDelete = |
| TestKey<ui::DomCode::NUMPAD_DECIMAL, ui::DomKey::DEL, ui::VKEY_DELETE>; |
| using KeyNumpadEnd = |
| TestKey<ui::DomCode::NUMPAD1, ui::DomKey::END, ui::VKEY_END>; |
| using KeyNumpadArrowDown = |
| TestKey<ui::DomCode::NUMPAD2, ui::DomKey::ARROW_DOWN, ui::VKEY_DOWN>; |
| using KeyNumpadPageDown = |
| TestKey<ui::DomCode::NUMPAD3, ui::DomKey::PAGE_DOWN, ui::VKEY_NEXT>; |
| using KeyNumpadArrowLeft = |
| TestKey<ui::DomCode::NUMPAD4, ui::DomKey::ARROW_LEFT, ui::VKEY_LEFT>; |
| using KeyNumpadClear = |
| TestKey<ui::DomCode::NUMPAD5, ui::DomKey::CLEAR, ui::VKEY_CLEAR>; |
| using KeyNumpadArrowRight = |
| TestKey<ui::DomCode::NUMPAD6, ui::DomKey::ARROW_RIGHT, ui::VKEY_RIGHT>; |
| using KeyNumpadHome = |
| TestKey<ui::DomCode::NUMPAD7, ui::DomKey::HOME, ui::VKEY_HOME>; |
| using KeyNumpadArrowUp = |
| TestKey<ui::DomCode::NUMPAD8, ui::DomKey::ARROW_UP, ui::VKEY_UP>; |
| using KeyNumpadPageUp = |
| TestKey<ui::DomCode::NUMPAD9, ui::DomKey::PAGE_UP, ui::VKEY_PRIOR>; |
| |
| // Keyboard representation in tests. |
| struct TestKeyboard { |
| const char* name; |
| const char* layout; |
| ui::InputDeviceType type; |
| bool has_custom_top_row; |
| bool has_assistant_key = false; |
| bool has_function_key = false; |
| }; |
| constexpr TestKeyboard kInternalChromeKeyboard = { |
| "Internal Keyboard", |
| kKbdTopRowLayoutUnspecified, |
| ui::INPUT_DEVICE_INTERNAL, |
| /*has_custom_top_row=*/false, |
| }; |
| constexpr TestKeyboard kInternalChromeCustomLayoutKeyboard = { |
| "Internal Custom Layout Keyboard", |
| kKbdDefaultCustomTopRowLayout, |
| ui::INPUT_DEVICE_INTERNAL, |
| /*has_custom_top_row=*/true, |
| }; |
| constexpr TestKeyboard kInternalChromeSplitModifierLayoutKeyboard = { |
| "Internal Custom Layout Keyboard", kKbdDefaultCustomTopRowLayout, |
| ui::INPUT_DEVICE_INTERNAL, |
| /*has_custom_top_row=*/true, |
| /*has_assistant_key=*/true, |
| /*has_function_key=*/true, |
| }; |
| constexpr TestKeyboard kExternalChromeKeyboard = { |
| "External Chrome Keyboard", |
| kKbdTopRowLayout1Tag, |
| ui::INPUT_DEVICE_UNKNOWN, |
| /*has_custom_top_row=*/false, |
| }; |
| constexpr TestKeyboard kExternalChromeCustomLayoutKeyboard = { |
| "External Chrome Custom Layout Keyboard", |
| kKbdDefaultCustomTopRowLayout, |
| ui::INPUT_DEVICE_UNKNOWN, |
| /*has_custom_top_row=*/true, |
| }; |
| constexpr TestKeyboard kExternalGenericKeyboard = { |
| "PC Keyboard", |
| kKbdTopRowLayoutUnspecified, |
| ui::INPUT_DEVICE_UNKNOWN, |
| /*has_custom_top_row=*/false, |
| }; |
| constexpr TestKeyboard kExternalAppleKeyboard = { |
| "Apple Keyboard", |
| kKbdTopRowLayoutUnspecified, |
| ui::INPUT_DEVICE_UNKNOWN, |
| /*has_custom_top_row=*/false, |
| }; |
| |
| constexpr TestKeyboard kChromeKeyboardVariants[] = { |
| kInternalChromeKeyboard, |
| kExternalChromeKeyboard, |
| }; |
| constexpr TestKeyboard kChromeCustomKeyboardVariants[] = { |
| kInternalChromeCustomLayoutKeyboard, |
| kExternalChromeCustomLayoutKeyboard, |
| }; |
| constexpr TestKeyboard kNonAppleKeyboardVariants[] = { |
| kInternalChromeKeyboard, kInternalChromeCustomLayoutKeyboard, |
| kExternalChromeKeyboard, kExternalChromeCustomLayoutKeyboard, |
| kExternalGenericKeyboard, |
| }; |
| constexpr TestKeyboard kNonAppleNonCustomLayoutKeyboardVariants[] = { |
| kInternalChromeKeyboard, |
| kExternalChromeKeyboard, |
| kExternalGenericKeyboard, |
| }; |
| constexpr TestKeyboard kAllKeyboardVariants[] = { |
| kInternalChromeKeyboard, |
| kInternalChromeCustomLayoutKeyboard, |
| kInternalChromeSplitModifierLayoutKeyboard, |
| kExternalChromeKeyboard, |
| kExternalChromeCustomLayoutKeyboard, |
| kExternalGenericKeyboard, |
| kExternalAppleKeyboard, |
| }; |
| |
| // Wilco keyboard configs |
| |
| constexpr TestKeyboard kWilco1_0Keyboard{ |
| "Wilco Keyboard", |
| kKbdTopRowLayoutWilcoTag, |
| ui::INPUT_DEVICE_INTERNAL, |
| /*has_custom_top_row=*/false, |
| }; |
| |
| constexpr TestKeyboard kWilco1_5Keyboard{ |
| "Drallion Keyboard", |
| kKbdTopRowLayoutDrallionTag, |
| ui::INPUT_DEVICE_INTERNAL, |
| /*has_custom_top_row=*/false, |
| }; |
| |
| constexpr TestKeyboard kWilcoKeyboardVariants[] = { |
| kWilco1_0Keyboard, |
| kWilco1_5Keyboard, |
| }; |
| |
| // Use to emulate `ImprovedKeyboardShortcuts` is disabled by the policy on |
| // enrolled device. |
| class TestShortcutMappingPrefDelegate : public ui::ShortcutMappingPrefDelegate { |
| public: |
| TestShortcutMappingPrefDelegate() = default; |
| TestShortcutMappingPrefDelegate(const TestShortcutMappingPrefDelegate&) = |
| delete; |
| TestShortcutMappingPrefDelegate& operator=( |
| const TestShortcutMappingPrefDelegate&) = delete; |
| ~TestShortcutMappingPrefDelegate() override = default; |
| |
| // ui::ShortcutMappingPrefDelegate: |
| bool IsDeviceEnterpriseManaged() const override { return true; } |
| bool IsI18nShortcutPrefEnabled() const override { return false; } |
| }; |
| |
| } // namespace |
| |
| namespace ash { |
| |
| class EventRewriterTestBase : public ChromeAshTestBase { |
| public: |
| EventRewriterTestBase() |
| : fake_user_manager_(new FakeChromeUserManager), |
| user_manager_enabler_(base::WrapUnique(fake_user_manager_.get())) {} |
| ~EventRewriterTestBase() override = default; |
| |
| void SetUp() override { |
| keyboard_layout_engine_ = std::make_unique<ui::StubKeyboardLayoutEngine>(); |
| // Inject custom table to make this closer to en-US behavior. |
| keyboard_layout_engine_->SetCustomLookupTableForTesting({ |
| // Keep MetaRight as MetaRight. |
| {ui::DomCode::META_RIGHT, ui::DomKey::META, ui::DomKey::META, |
| ui::VKEY_RWIN}, |
| |
| // Inject select_task key. |
| {ui::DomCode::SELECT_TASK, ui::DomKey::LAUNCH_MY_COMPUTER, |
| ui::DomKey::LAUNCH_MY_COMPUTER, ui::VKEY_MEDIA_LAUNCH_APP1}, |
| |
| // Map numpad keys. |
| {ui::DomCode::NUMPAD0, ui::DomKey::FromCharacter('0'), |
| ui::DomKey::INSERT, ui::VKEY_NUMPAD0}, |
| {ui::DomCode::NUMPAD1, ui::DomKey::FromCharacter('1'), ui::DomKey::END, |
| ui::VKEY_NUMPAD1}, |
| {ui::DomCode::NUMPAD2, ui::DomKey::FromCharacter('2'), |
| ui::DomKey::ARROW_DOWN, ui::VKEY_NUMPAD2}, |
| {ui::DomCode::NUMPAD3, ui::DomKey::FromCharacter('3'), |
| ui::DomKey::PAGE_DOWN, ui::VKEY_NUMPAD3}, |
| {ui::DomCode::NUMPAD4, ui::DomKey::FromCharacter('4'), |
| ui::DomKey::ARROW_RIGHT, ui::VKEY_NUMPAD4}, |
| {ui::DomCode::NUMPAD5, ui::DomKey::FromCharacter('5'), |
| ui::DomKey::CLEAR, ui::VKEY_NUMPAD5}, |
| {ui::DomCode::NUMPAD6, ui::DomKey::FromCharacter('6'), |
| ui::DomKey::ARROW_LEFT, ui::VKEY_NUMPAD6}, |
| {ui::DomCode::NUMPAD7, ui::DomKey::FromCharacter('7'), ui::DomKey::HOME, |
| ui::VKEY_NUMPAD7}, |
| {ui::DomCode::NUMPAD8, ui::DomKey::FromCharacter('8'), |
| ui::DomKey::ARROW_UP, ui::VKEY_NUMPAD8}, |
| {ui::DomCode::NUMPAD9, ui::DomKey::FromCharacter('9'), |
| ui::DomKey::PAGE_UP, ui::VKEY_NUMPAD9}, |
| }); |
| keyboard_capability_ = |
| ui::KeyboardCapability::CreateStubKeyboardCapability(); |
| input_method_manager_mock_ = new input_method::MockInputMethodManagerImpl; |
| input_method::InitializeForTesting( |
| input_method_manager_mock_); // pass ownership |
| auto deprecation_controller = |
| std::make_unique<DeprecationNotificationController>(&message_center_); |
| deprecation_controller_ = deprecation_controller.get(); |
| auto input_device_settings_notification_controller = |
| std::make_unique<InputDeviceSettingsNotificationController>( |
| &message_center_); |
| input_device_settings_notification_controller_ = |
| input_device_settings_notification_controller.get(); |
| ChromeAshTestBase::SetUp(); |
| |
| input_device_settings_controller_resetter_ = std::make_unique< |
| InputDeviceSettingsController::ScopedResetterForTest>(); |
| input_device_settings_controller_mock_ = |
| std::make_unique<MockInputDeviceSettingsController>(); |
| keyboard_settings = mojom::KeyboardSettings::New(); |
| // Disable F11/F12 settings by default. |
| keyboard_settings->f11 = ui::mojom::ExtendedFkeysModifier::kDisabled; |
| keyboard_settings->f12 = ui::mojom::ExtendedFkeysModifier::kDisabled; |
| EXPECT_CALL(*input_device_settings_controller_mock_, |
| GetKeyboardSettings(testing::_)) |
| .WillRepeatedly(testing::Return(keyboard_settings.get())); |
| |
| delegate_ = std::make_unique<EventRewriterDelegateImpl>( |
| nullptr, std::move(deprecation_controller), |
| std::move(input_device_settings_notification_controller), |
| input_device_settings_controller_mock_.get()); |
| delegate_->set_pref_service_for_testing(prefs()); |
| device_data_manager_test_api_.SetKeyboardDevices({}); |
| keyboard_device_id_event_rewriter_ = |
| std::make_unique<ui::KeyboardDeviceIdEventRewriter>( |
| keyboard_capability_.get()); |
| keyboard_modifier_event_rewriter_ = |
| std::make_unique<ui::KeyboardModifierEventRewriter>( |
| std::make_unique<TestKeyboardModifierEventRewriterDelegate>( |
| delegate_.get()), |
| keyboard_layout_engine_.get(), keyboard_capability_.get(), |
| &fake_ime_keyboard_); |
| caps_lock_event_rewriter_ = std::make_unique<ui::CapsLockEventRewriter>( |
| keyboard_layout_engine_.get(), keyboard_capability_.get(), |
| &fake_ime_keyboard_); |
| event_rewriter_ash_ = std::make_unique<ui::EventRewriterAsh>( |
| delegate_.get(), keyboard_capability_.get(), |
| Shell::Get()->sticky_keys_controller(), false, &fake_ime_keyboard_); |
| discard_key_event_rewriter_ = |
| std::make_unique<ui::DiscardKeyEventRewriter>(); |
| |
| source_.AddEventRewriter(keyboard_device_id_event_rewriter_.get()); |
| if (ash::features::IsKeyboardRewriterFixEnabled()) { |
| source_.AddEventRewriter(keyboard_modifier_event_rewriter_.get()); |
| } |
| if (ash::features::IsKeyboardRewriterFixEnabled() && |
| features::IsModifierSplitEnabled()) { |
| source_.AddEventRewriter(caps_lock_event_rewriter_.get()); |
| } |
| source_.AddEventRewriter(event_rewriter_ash_.get()); |
| if (!ash::features::IsKeyboardRewriterFixEnabled() && |
| features::IsModifierSplitEnabled()) { |
| source_.AddEventRewriter(caps_lock_event_rewriter_.get()); |
| } |
| if (features::IsModifierSplitEnabled()) { |
| source_.AddEventRewriter(discard_key_event_rewriter_.get()); |
| } |
| } |
| |
| void TearDown() override { |
| if (features::IsModifierSplitEnabled()) { |
| source_.RemoveEventRewriter(discard_key_event_rewriter_.get()); |
| } |
| if (!ash::features::IsKeyboardRewriterFixEnabled() && |
| features::IsModifierSplitEnabled()) { |
| source_.RemoveEventRewriter(caps_lock_event_rewriter_.get()); |
| } |
| source_.RemoveEventRewriter(event_rewriter_ash_.get()); |
| if (ash::features::IsKeyboardRewriterFixEnabled() && |
| features::IsModifierSplitEnabled()) { |
| source_.RemoveEventRewriter(caps_lock_event_rewriter_.get()); |
| } |
| if (ash::features::IsKeyboardRewriterFixEnabled()) { |
| source_.RemoveEventRewriter(keyboard_modifier_event_rewriter_.get()); |
| } |
| source_.RemoveEventRewriter(keyboard_device_id_event_rewriter_.get()); |
| |
| event_rewriter_ash_.reset(); |
| caps_lock_event_rewriter_.reset(); |
| keyboard_modifier_event_rewriter_.reset(); |
| keyboard_device_id_event_rewriter_.reset(); |
| |
| input_device_settings_controller_mock_.reset(); |
| input_device_settings_controller_resetter_.reset(); |
| ChromeAshTestBase::TearDown(); |
| // Shutdown() deletes the IME mock object. |
| input_method::Shutdown(); |
| keyboard_capability_.reset(); |
| keyboard_layout_engine_.reset(); |
| } |
| |
| ui::test::TestEventSource& source() { return source_; } |
| |
| protected: |
| std::vector<TestKeyEvent> RunRewriter( |
| const std::vector<TestKeyEvent>& events, |
| ui::EventFlags extra_flags = ui::EF_NONE, |
| int device_id = kKeyboardDeviceId) { |
| struct ModifierInfo { |
| ui::EventFlags flag; |
| ui::DomCode code; |
| ui::DomKey key; |
| ui::KeyboardCode keycode; |
| }; |
| // We'll use modifier keys at left side heuristically. |
| static constexpr ModifierInfo kModifierList[] = { |
| {ui::EF_SHIFT_DOWN, ui::DomCode::SHIFT_LEFT, ui::DomKey::SHIFT, |
| ui::VKEY_SHIFT}, |
| {ui::EF_CONTROL_DOWN, ui::DomCode::CONTROL_LEFT, ui::DomKey::CONTROL, |
| ui::VKEY_CONTROL}, |
| {ui::EF_ALT_DOWN, ui::DomCode::ALT_LEFT, ui::DomKey::ALT, |
| ui::VKEY_MENU}, |
| {ui::EF_COMMAND_DOWN, ui::DomCode::META_LEFT, ui::DomKey::META, |
| ui::VKEY_LWIN}, |
| {ui::EF_MOD3_DOWN, ui::DomCode::CAPS_LOCK, ui::DomKey::CAPS_LOCK, |
| ui::VKEY_CAPITAL}, |
| {ui::EF_FUNCTION_DOWN, ui::DomCode::FN, ui::DomKey::FN, |
| ui::VKEY_FUNCTION}, |
| }; |
| |
| // Send modifier key press events to update rewriter's modifier flag state. |
| ui::EventFlags current_flags = 0; |
| for (const auto& modifier : kModifierList) { |
| if (!(extra_flags & modifier.flag)) { |
| continue; |
| } |
| current_flags |= modifier.flag; |
| SendKeyEvent(TestKeyEvent{ui::EventType::kKeyPressed, modifier.code, |
| modifier.key, modifier.keycode, current_flags}, |
| device_id); |
| } |
| CHECK_EQ(current_flags, extra_flags); |
| |
| // Add extra_flags to each TestkeyEvent. |
| std::vector<TestKeyEvent> key_events; |
| for (const auto& event : events) { |
| key_events.push_back(TestKeyEvent{ |
| event.type, event.code, event.key, event.keycode, |
| event.flags | current_flags, event.scan_code, event.properties}); |
| } |
| auto result = SendKeyEvents(key_events, device_id); |
| |
| // Send modifier key release events to unset rewriter'.s modifier flag |
| // state. |
| for (const auto& modifier : base::Reversed(kModifierList)) { |
| if (!(extra_flags & modifier.flag)) { |
| continue; |
| } |
| current_flags &= ~modifier.flag; |
| SendKeyEvent(TestKeyEvent{ui::EventType::kKeyReleased, modifier.code, |
| modifier.key, modifier.keycode, current_flags}, |
| device_id); |
| } |
| CHECK_EQ(current_flags, 0); |
| |
| return result; |
| } |
| |
| // Sends a KeyEvent to the rewriter, returns the rewritten events. |
| // Note: one event may be rewritten into multiple events. |
| std::vector<TestKeyEvent> SendKeyEvent(const TestKeyEvent& event, |
| int device_id = kKeyboardDeviceId) { |
| return SendKeyEvents({event}, device_id); |
| } |
| |
| std::vector<TestKeyEvent> SendKeyEvents( |
| const std::vector<TestKeyEvent>& events, |
| int device_id = kKeyboardDeviceId) { |
| // Just in case some events may be there. |
| if (!TakeEvents().empty()) { |
| ADD_FAILURE() << "Rewritten events were left"; |
| } |
| |
| // Convert TestKeyEvent into ui::KeyEvent, then dispatch it to the |
| // rewriter. |
| for (const TestKeyEvent& event : events) { |
| ui::KeyEvent key_event(event.type, event.keycode, event.code, event.flags, |
| event.key, ui::EventTimeForNow()); |
| key_event.set_scan_code(event.scan_code); |
| key_event.set_source_device_id(device_id); |
| ui::EventDispatchDetails details = source_.Send(&key_event); |
| CHECK(!details.dispatcher_destroyed); |
| } |
| |
| // Convert the rewritten ui::Events back to TestKeyEvent. |
| auto rewritten_events = TakeEvents(); |
| std::vector<TestKeyEvent> result; |
| for (const auto& rewritten_event : rewritten_events) { |
| auto* rewritten_key_event = rewritten_event->AsKeyEvent(); |
| if (!rewritten_key_event) { |
| ADD_FAILURE() << "Unexpected rewritten key event: " |
| << rewritten_event->ToString(); |
| continue; |
| } |
| std::vector<std::pair<std::string, std::vector<uint8_t>>> properties; |
| if (rewritten_key_event->properties()) { |
| for (const auto& property : *rewritten_key_event->properties()) { |
| properties.push_back(property); |
| } |
| } |
| result.push_back( |
| {rewritten_key_event->type(), rewritten_key_event->code(), |
| rewritten_key_event->GetDomKey(), rewritten_key_event->key_code(), |
| rewritten_key_event->flags(), rewritten_key_event->scan_code(), |
| std::move(properties)}); |
| } |
| return result; |
| } |
| |
| // Parameterized version of test depending on feature flag values. The feature |
| // kUseSearchClickForRightClick determines if this should test for alt-click |
| // or search-click. |
| void DontRewriteIfNotRewritten(int right_click_flags); |
| |
| ui::MouseEvent RewriteMouseButtonEvent(const ui::MouseEvent& event) { |
| TestEventRewriterContinuation continuation; |
| event_rewriter_ash_->RewriteMouseButtonEventForTesting( |
| event, continuation.weak_ptr_factory_.GetWeakPtr()); |
| if (!continuation.rewritten_events.empty()) { |
| return ui::MouseEvent(*continuation.rewritten_events[0]->AsMouseEvent()); |
| } |
| return ui::MouseEvent(event); |
| } |
| |
| sync_preferences::TestingPrefServiceSyncable* prefs() { return &prefs_; } |
| |
| void InitModifierKeyPref(IntegerPrefMember* int_pref, |
| const std::string& pref_name, |
| ui::mojom::ModifierKey remap_from, |
| ui::mojom::ModifierKey remap_to) { |
| if (!features::IsInputDeviceSettingsSplitEnabled()) { |
| if (int_pref->GetPrefName() != |
| pref_name) { // skip if already initialized. |
| int_pref->Init(pref_name, prefs()); |
| } |
| int_pref->SetValue(static_cast<int>(remap_to)); |
| return; |
| } |
| if (remap_from == remap_to) { |
| keyboard_settings->modifier_remappings.erase(remap_from); |
| return; |
| } |
| |
| keyboard_settings->modifier_remappings[remap_from] = remap_to; |
| } |
| |
| void SetUpKeyboard(const TestKeyboard& test_keyboard) { |
| // Add a fake device to udev. |
| const ui::KeyboardDevice keyboard( |
| kKeyboardDeviceId, test_keyboard.type, test_keyboard.name, |
| /*phys=*/"", base::FilePath(kKbdSysPath), |
| /*vendor=*/-1, |
| /*product=*/-1, /*version=*/-1, |
| /*has_assistant_key=*/test_keyboard.has_assistant_key, |
| /*has_function_key=*/test_keyboard.has_function_key); |
| |
| // Old CrOS keyboards supply an integer/enum as a sysfs property to identify |
| // their layout type. New keyboards provide the mapping of scan codes to |
| // F-Key position via an attribute. |
| std::map<std::string, std::string> sysfs_properties; |
| std::map<std::string, std::string> sysfs_attributes; |
| if (!std::string_view(test_keyboard.layout).empty()) { |
| (test_keyboard.has_custom_top_row |
| ? sysfs_attributes[kKbdTopRowLayoutAttributeName] |
| : sysfs_properties[kKbdTopRowPropertyName]) = test_keyboard.layout; |
| } |
| |
| fake_udev_.Reset(); |
| fake_udev_.AddFakeDevice(keyboard.name, keyboard.sys_path.value(), |
| /*subsystem=*/"input", /*devnode=*/std::nullopt, |
| /*devtype=*/std::nullopt, |
| std::move(sysfs_attributes), |
| std::move(sysfs_properties)); |
| |
| // Reset the state of the device manager. |
| device_data_manager_test_api_.SetKeyboardDevices({}); |
| device_data_manager_test_api_.SetKeyboardDevices({keyboard}); |
| |
| // Reset the state of the EventRewriter. |
| event_rewriter_ash_->ResetStateForTesting(); |
| event_rewriter_ash_->set_last_keyboard_device_id_for_testing( |
| kKeyboardDeviceId); |
| } |
| |
| void SetExtensionCommands( |
| std::optional<base::flat_set<std::pair<ui::KeyboardCode, int>>> |
| commands) { |
| delegate_->SetExtensionCommandsOverrideForTesting(std::move(commands)); |
| } |
| |
| std::vector<std::unique_ptr<ui::Event>> TakeEvents() { |
| return sink_.TakeEvents(); |
| } |
| |
| void ClearNotifications() { |
| message_center_.RemoveAllNotifications( |
| false, message_center::FakeMessageCenter::RemoveType::ALL); |
| deprecation_controller_->ResetStateForTesting(); |
| } |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| raw_ptr<FakeChromeUserManager, DanglingUntriaged> |
| fake_user_manager_; // Not owned. |
| user_manager::ScopedUserManager user_manager_enabler_; |
| raw_ptr<input_method::MockInputMethodManagerImpl, DanglingUntriaged> |
| input_method_manager_mock_; |
| testing::FakeUdevLoader fake_udev_; |
| ui::DeviceDataManagerTestApi device_data_manager_test_api_; |
| std::unique_ptr<InputDeviceSettingsController::ScopedResetterForTest> |
| input_device_settings_controller_resetter_; |
| std::unique_ptr<MockInputDeviceSettingsController> |
| input_device_settings_controller_mock_; |
| mojom::KeyboardSettingsPtr keyboard_settings; |
| |
| sync_preferences::TestingPrefServiceSyncable prefs_; |
| std::unique_ptr<EventRewriterDelegateImpl> delegate_; |
| std::unique_ptr<ui::StubKeyboardLayoutEngine> keyboard_layout_engine_; |
| std::unique_ptr<ui::KeyboardCapability> keyboard_capability_; |
| input_method::FakeImeKeyboard fake_ime_keyboard_; |
| std::unique_ptr<ui::KeyboardDeviceIdEventRewriter> |
| keyboard_device_id_event_rewriter_; |
| std::unique_ptr<ui::KeyboardModifierEventRewriter> |
| keyboard_modifier_event_rewriter_; |
| std::unique_ptr<ui::CapsLockEventRewriter> caps_lock_event_rewriter_; |
| std::unique_ptr<ui::EventRewriterAsh> event_rewriter_ash_; |
| std::unique_ptr<ui::DiscardKeyEventRewriter> discard_key_event_rewriter_; |
| TestEventSink sink_; |
| ui::test::TestEventSource source_{&sink_}; |
| message_center::FakeMessageCenter message_center_; |
| raw_ptr<DeprecationNotificationController> |
| deprecation_controller_; // Not owned. |
| raw_ptr<InputDeviceSettingsNotificationController> |
| input_device_settings_notification_controller_; // Not owned. |
| }; |
| |
| class EventRewriterTest |
| : public EventRewriterTestBase, |
| public testing::WithParamInterface<std::tuple<bool, bool>> { |
| public: |
| void SetUp() override { |
| auto [enable_keyboard_rewriter_fix, enable_modifier_split] = GetParam(); |
| if (enable_keyboard_rewriter_fix) { |
| fix_feature_list_.InitAndEnableFeature( |
| ash::features::kEnableKeyboardRewriterFix); |
| } else { |
| fix_feature_list_.InitAndDisableFeature( |
| ash::features::kEnableKeyboardRewriterFix); |
| } |
| |
| if (enable_modifier_split) { |
| modifier_split_feature_list_.InitAndEnableFeature( |
| ash::features::kModifierSplit); |
| } else { |
| modifier_split_feature_list_.InitAndDisableFeature( |
| ash::features::kModifierSplit); |
| } |
| |
| EventRewriterTestBase::SetUp(); |
| } |
| |
| void TearDown() override { |
| EventRewriterTestBase::TearDown(); |
| modifier_split_feature_list_.Reset(); |
| fix_feature_list_.Reset(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList fix_feature_list_; |
| base::test::ScopedFeatureList modifier_split_feature_list_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| EventRewriterTest, |
| testing::Combine(testing::Bool(), testing::Bool())); |
| |
| // TestKeyRewriteLatency checks that the event rewriter |
| // publishes a latency metric every time a key is pressed. |
| TEST_P(EventRewriterTest, TestKeyRewriteLatency) { |
| SendKeyEvent(KeyLControl::Pressed()); |
| |
| base::HistogramTester histogram_tester; |
| EXPECT_EQ(std::vector({KeyB::Pressed(ui::EF_CONTROL_DOWN), |
| KeyB::Pressed(ui::EF_CONTROL_DOWN)}), |
| SendKeyEvents({KeyB::Pressed(ui::EF_CONTROL_DOWN), |
| KeyB::Pressed(ui::EF_CONTROL_DOWN)})); |
| histogram_tester.ExpectTotalCount( |
| "ChromeOS.Inputs.EventRewriter.KeyRewriteLatency", 2); |
| } |
| |
| TEST_P(EventRewriterTest, ModifiersNotRemappedWhenSuppressed) { |
| // Remap Control -> Alt. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember control; |
| InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo, |
| ui::mojom::ModifierKey::kControl, |
| ui::mojom::ModifierKey::kAlt); |
| |
| // Pressing Control + B should now be remapped to Alt + B. |
| delegate_->SuppressModifierKeyRewrites(false); |
| EXPECT_EQ(KeyB::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyB::Typed(), ui::EF_CONTROL_DOWN)); |
| |
| // Pressing Control + B should no longer be remapped. |
| delegate_->SuppressModifierKeyRewrites(true); |
| EXPECT_EQ(KeyB::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyB::Typed(), ui::EF_CONTROL_DOWN)); |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteNumPadKeys) { |
| // Even if most Chrome OS keyboards do not have numpad, they should still |
| // handle it the same way as generic PC keyboards. |
| for (const auto& keyboard : kNonAppleKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // XK_KP_Insert (= NumPad 0 without Num Lock), no modifier. |
| EXPECT_EQ(KeyNumpad0::Typed(), RunRewriter(KeyNumpadInsert::Typed())); |
| |
| // XK_KP_Insert (= NumPad 0 without Num Lock), Alt modifier. |
| EXPECT_EQ(KeyNumpad0::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyNumpadInsert::Typed(), ui::EF_ALT_DOWN)); |
| |
| // XK_KP_Delete (= NumPad . without Num Lock), Alt modifier. |
| EXPECT_EQ(KeyNumpadDecimal::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyNumpadDelete::Typed(), ui::EF_ALT_DOWN)); |
| |
| // XK_KP_End (= NumPad 1 without Num Lock), Alt modifier. |
| EXPECT_EQ(KeyNumpad1::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyNumpadEnd::Typed(), ui::EF_ALT_DOWN)); |
| |
| // XK_KP_Down (= NumPad 2 without Num Lock), Alt modifier. |
| EXPECT_EQ(KeyNumpad2::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyNumpadArrowDown::Typed(), ui::EF_ALT_DOWN)); |
| |
| // XK_KP_Next (= NumPad 3 without Num Lock), Alt modifier. |
| EXPECT_EQ(KeyNumpad3::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyNumpadPageDown::Typed(), ui::EF_ALT_DOWN)); |
| |
| // XK_KP_Left (= NumPad 4 without Num Lock), Alt modifier. |
| EXPECT_EQ(KeyNumpad4::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyNumpadArrowLeft::Typed(), ui::EF_ALT_DOWN)); |
| |
| // XK_KP_Begin (= NumPad 5 without Num Lock), Alt modifier. |
| EXPECT_EQ(KeyNumpad5::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyNumpadClear::Typed(), ui::EF_ALT_DOWN)); |
| |
| // XK_KP_Right (= NumPad 6 without Num Lock), Alt modifier. |
| EXPECT_EQ(KeyNumpad6::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyNumpadArrowRight::Typed(), ui::EF_ALT_DOWN)); |
| |
| // XK_KP_Home (= NumPad 7 without Num Lock), Alt modifier. |
| EXPECT_EQ(KeyNumpad7::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyNumpadHome::Typed(), ui::EF_ALT_DOWN)); |
| |
| // XK_KP_Up (= NumPad 8 without Num Lock), Alt modifier. |
| EXPECT_EQ(KeyNumpad8::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyNumpadArrowUp::Typed(), ui::EF_ALT_DOWN)); |
| |
| // XK_KP_Prior (= NumPad 9 without Num Lock), Alt modifier. |
| EXPECT_EQ(KeyNumpad9::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyNumpadPageUp::Typed(), ui::EF_ALT_DOWN)); |
| |
| // XK_KP_{N} (= NumPad {N} with Num Lock), Num Lock modifier. |
| EXPECT_EQ(KeyNumpad0::Typed(), RunRewriter(KeyNumpad0::Typed())); |
| EXPECT_EQ(KeyNumpad1::Typed(), RunRewriter(KeyNumpad1::Typed())); |
| EXPECT_EQ(KeyNumpad2::Typed(), RunRewriter(KeyNumpad2::Typed())); |
| EXPECT_EQ(KeyNumpad3::Typed(), RunRewriter(KeyNumpad3::Typed())); |
| EXPECT_EQ(KeyNumpad4::Typed(), RunRewriter(KeyNumpad4::Typed())); |
| EXPECT_EQ(KeyNumpad5::Typed(), RunRewriter(KeyNumpad5::Typed())); |
| EXPECT_EQ(KeyNumpad6::Typed(), RunRewriter(KeyNumpad6::Typed())); |
| EXPECT_EQ(KeyNumpad7::Typed(), RunRewriter(KeyNumpad7::Typed())); |
| EXPECT_EQ(KeyNumpad8::Typed(), RunRewriter(KeyNumpad8::Typed())); |
| EXPECT_EQ(KeyNumpad9::Typed(), RunRewriter(KeyNumpad9::Typed())); |
| |
| // XK_KP_DECIMAL (= NumPad . with Num Lock), Num Lock modifier. |
| EXPECT_EQ(KeyNumpadDecimal::Typed(), |
| RunRewriter(KeyNumpadDecimal::Typed())); |
| } |
| } |
| |
| // Tests if the rewriter can handle a Command + Num Pad event. |
| TEST_P(EventRewriterTest, TestRewriteNumPadKeysOnAppleKeyboard) { |
| // Simulate the default initialization of the Apple Command key remap pref to |
| // Ctrl. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| if (features::IsInputDeviceSettingsSplitEnabled()) { |
| keyboard_settings->modifier_remappings[ui::mojom::ModifierKey::kMeta] = |
| ui::mojom::ModifierKey::kControl; |
| } |
| |
| SetUpKeyboard(kExternalAppleKeyboard); |
| |
| // XK_KP_End (= NumPad 1 without Num Lock), Win modifier. |
| // The result should be "Num Pad 1 with Control + Num Lock modifiers". |
| EXPECT_EQ(KeyNumpad1::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyNumpadEnd::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // XK_KP_1 (= NumPad 1 with Num Lock), Win modifier. |
| // The result should also be "Num Pad 1 with Control + Num Lock |
| // modifiers". |
| EXPECT_EQ(KeyNumpad1::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyNumpad1::Typed(), ui::EF_COMMAND_DOWN)); |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteModifiersNoRemap) { |
| for (const auto& keyboard : kAllKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Press Search. Confirm the event is not rewritten. |
| EXPECT_EQ(KeyLMeta::Typed(), RunRewriter(KeyLMeta::Typed())); |
| |
| // Press left Control. Confirm the event is not rewritten. |
| EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyLControl::Typed())); |
| |
| // Press right Control. Confirm the event is not rewritten. |
| EXPECT_EQ(KeyRControl::Typed(), RunRewriter(KeyRControl::Typed())); |
| |
| // Press left Alt. Confirm the event is not rewritten. |
| EXPECT_EQ(KeyLAlt::Typed(), RunRewriter(KeyLAlt::Typed())); |
| |
| // Press quick insert. Confirm the event is not rewritten. |
| EXPECT_EQ(KeyRAlt::Typed(), RunRewriter(KeyRAlt::Typed())); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteModifiersNoRemapMultipleKeys) { |
| for (const auto& keyboard : kAllKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Press Alt with Shift. Confirm the event is not rewritten. |
| EXPECT_EQ(KeyLAlt::Typed(ui::EF_SHIFT_DOWN), |
| RunRewriter(KeyLAlt::Typed(), ui::EF_SHIFT_DOWN)); |
| |
| // Press Escape with Alt and Shift. Confirm the event is not rewritten. |
| EXPECT_EQ( |
| KeyEscape::Typed(ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN), |
| RunRewriter(KeyEscape::Typed(), ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN)); |
| |
| // Toggling on CapsLock. |
| EXPECT_EQ(KeyCapsLock::Typed(ui::EF_CAPS_LOCK_ON), |
| RunRewriter(KeyCapsLock::Typed(ui::EF_CAPS_LOCK_ON))); |
| |
| // Press Search with Caps Lock mask. Confirm the event is not rewritten. |
| EXPECT_EQ(KeyLMeta::Typed(ui::EF_CAPS_LOCK_ON), |
| RunRewriter(KeyLMeta::Typed(ui::EF_CAPS_LOCK_ON))); |
| |
| // Toggling off CapsLock. |
| EXPECT_EQ(KeyCapsLock::Typed(), RunRewriter(KeyCapsLock::Typed())); |
| |
| // Press Shift+Ctrl+Alt+Search+Escape. Confirm the event is not rewritten. |
| EXPECT_EQ(KeyEscape::Typed(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | |
| ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyEscape::Typed(), |
| ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | |
| ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN)); |
| |
| // Press Shift+Ctrl+Alt+Search+B. Confirm the event is not rewritten. |
| EXPECT_EQ(KeyB::Typed(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | |
| ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN), |
| RunRewriter( |
| // In this case, SHIFT modifier will be set on pressing B, |
| // thus we should use capital 'B' as DomKey, which the current |
| // factory does not support. |
| // Modifier flags will be annotated to TestKeyEvents inside |
| // RunRewriter. |
| {TestKeyEvent{ui::EventType::kKeyPressed, ui::DomCode::US_B, |
| ui::DomKey::FromCharacter('B'), ui::VKEY_B}, |
| TestKeyEvent{ui::EventType::kKeyReleased, ui::DomCode::US_B, |
| ui::DomKey::FromCharacter('B'), ui::VKEY_B}}, |
| ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | |
| ui::EF_COMMAND_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteModifiersDisableSome) { |
| // Disable Search, Control and Escape keys. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember search; |
| InitModifierKeyPref(&search, ::prefs::kLanguageRemapSearchKeyTo, |
| ui::mojom::ModifierKey::kMeta, |
| ui::mojom::ModifierKey::kVoid); |
| IntegerPrefMember control; |
| InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo, |
| ui::mojom::ModifierKey::kControl, |
| ui::mojom::ModifierKey::kVoid); |
| IntegerPrefMember escape; |
| InitModifierKeyPref(&escape, ::prefs::kLanguageRemapEscapeKeyTo, |
| ui::mojom::ModifierKey::kEscape, |
| ui::mojom::ModifierKey::kVoid); |
| |
| for (const auto& keyboard : kChromeKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Press Alt with Shift. This key press shouldn't be affected by the |
| // pref. Confirm the event is not rewritten. |
| EXPECT_EQ(KeyLAlt::Typed(ui::EF_SHIFT_DOWN), |
| RunRewriter(KeyLAlt::Typed(ui::EF_SHIFT_DOWN))); |
| |
| // Press Search. Confirm the event is now VKEY_UNKNOWN. |
| EXPECT_EQ(KeyUnknown::Typed(), RunRewriter(KeyLMeta::Typed())); |
| |
| // Press Control. Confirm the event is now VKEY_UNKNOWN. |
| EXPECT_EQ(KeyUnknown::Typed(), RunRewriter(KeyLControl::Typed())); |
| |
| // Press Escape. Confirm the event is now VKEY_UNKNOWN. |
| EXPECT_EQ(KeyUnknown::Typed(), RunRewriter(KeyEscape::Typed())); |
| |
| // Press Control+Search. Confirm the event is now VKEY_UNKNOWN |
| // without any modifiers. |
| if (ash::features::IsKeyboardRewriterFixEnabled()) { |
| EXPECT_EQ(KeyUnknown::Typed(), |
| RunRewriter(KeyLMeta::Typed(), ui::EF_CONTROL_DOWN)); |
| } else { |
| // TODO(crbug.com/40265877): Release key event is not dispatched in old |
| // rewriter. Remove this once the old rewriter is no longer used. |
| EXPECT_EQ(std::vector({KeyUnknown::Pressed()}), |
| RunRewriter(KeyLMeta::Typed(), ui::EF_CONTROL_DOWN)); |
| } |
| |
| // Press Control+Search+a. Confirm the event is now VKEY_A without any |
| // modifiers. |
| EXPECT_EQ(KeyA::Typed(), RunRewriter(KeyA::Typed(), ui::EF_CONTROL_DOWN)); |
| |
| // Press Control+Search+Alt+a. Confirm the event is now VKEY_A only with |
| // the Alt modifier. |
| EXPECT_EQ( |
| KeyA::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)); |
| } |
| |
| // Remap Alt to Control. |
| IntegerPrefMember alt; |
| InitModifierKeyPref(&alt, ::prefs::kLanguageRemapAltKeyTo, |
| ui::mojom::ModifierKey::kAlt, |
| ui::mojom::ModifierKey::kControl); |
| |
| for (const auto& keyboard : kChromeKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Press left Alt. Confirm the event is now VKEY_CONTROL |
| // even though the Control key itself is disabled. |
| EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyLAlt::Typed())); |
| |
| // Press Alt+a. Confirm the event is now Control+a even though the Control |
| // key itself is disabled. |
| EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteModifiersRemapToControl) { |
| // Remap Search to Control. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember search; |
| InitModifierKeyPref(&search, ::prefs::kLanguageRemapSearchKeyTo, |
| ui::mojom::ModifierKey::kMeta, |
| ui::mojom::ModifierKey::kControl); |
| |
| for (const auto& keyboard : kChromeKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Press Search. Confirm the event is now VKEY_CONTROL. |
| EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyLMeta::Typed())); |
| } |
| |
| // Remap Alt to Control too. |
| IntegerPrefMember alt; |
| InitModifierKeyPref(&alt, ::prefs::kLanguageRemapAltKeyTo, |
| ui::mojom::ModifierKey::kAlt, |
| ui::mojom::ModifierKey::kControl); |
| |
| for (const auto& keyboard : kChromeKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Press Alt. Confirm the event is now VKEY_CONTROL. |
| EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyLAlt::Typed())); |
| |
| // Press Alt+Search. Confirm the event is now VKEY_CONTROL. |
| if (ash::features::IsKeyboardRewriterFixEnabled()) { |
| // In this case, both pressed/released events have EF_CONTROL_DOWN, |
| // because ALT key mapped to CONTROL is held. |
| EXPECT_EQ(KeyLControl::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN)); |
| } else { |
| // TODO(crbug.com/40265877): Release key event is not dispatched in old |
| // rewriter. Remove this once the old rewriter is no longer used. |
| EXPECT_EQ(std::vector({KeyLControl::Pressed()}), |
| RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN)); |
| } |
| |
| // Press Control+Alt+Search. Confirm the event is now VKEY_CONTROL. |
| if (ash::features::IsKeyboardRewriterFixEnabled()) { |
| // In this case, both pressed/released events have EF_CONTROL_DOWN, |
| // because ALT key mapped to CONTROL is held. |
| EXPECT_EQ(KeyLControl::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyLMeta::Typed(), |
| ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN)); |
| } else { |
| // TODO(crbug.com/40265877): Release key event is not dispatched in old |
| // rewriter. Remove this once the old rewriter is no longer used. |
| EXPECT_EQ(std::vector({KeyLControl::Pressed()}), |
| RunRewriter(KeyLMeta::Typed(), |
| ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN)); |
| } |
| |
| // Press Shift+Control+Alt+Search. Confirm the event is now Control with |
| // Shift and Control modifiers. |
| if (ash::features::IsKeyboardRewriterFixEnabled()) { |
| // In this case, both pressed/released events have EF_CONTROL_DOWN, |
| // because ALT key mapped to CONTROL is held. |
| EXPECT_EQ(KeyLControl::Typed(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyLMeta::Typed(), ui::EF_SHIFT_DOWN | |
| ui::EF_CONTROL_DOWN | |
| ui::EF_ALT_DOWN)); |
| } else { |
| // TODO(crbug.com/40265877): Release key event is not dispatched in old |
| // rewriter. Remove this once the old rewriter is no longer used. |
| EXPECT_EQ(std::vector({KeyLControl::Pressed(ui::EF_SHIFT_DOWN)}), |
| RunRewriter(KeyLMeta::Typed(), ui::EF_SHIFT_DOWN | |
| ui::EF_CONTROL_DOWN | |
| ui::EF_ALT_DOWN)); |
| } |
| |
| // Press Shift+Control+Alt+Search+B. Confirm the event is now B with Shift |
| // and Control modifiers. |
| EXPECT_EQ( |
| KeyB::Typed(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyB::Typed(), ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | |
| ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteModifiersRemapToEscape) { |
| // Remap Search to Escape. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember search; |
| InitModifierKeyPref(&search, ::prefs::kLanguageRemapSearchKeyTo, |
| ui::mojom::ModifierKey::kMeta, |
| ui::mojom::ModifierKey::kEscape); |
| |
| for (const auto& keyboard : kChromeKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Press Search. Confirm the event is now VKEY_ESCAPE. |
| EXPECT_EQ(KeyEscape::Typed(), RunRewriter(KeyLMeta::Typed())); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteModifiersRemapEscapeToAlt) { |
| // Remap Escape to Alt. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember escape; |
| InitModifierKeyPref(&escape, ::prefs::kLanguageRemapEscapeKeyTo, |
| ui::mojom::ModifierKey::kEscape, |
| ui::mojom::ModifierKey::kAlt); |
| |
| for (const auto& keyboard : kAllKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Press Escape. Confirm the event is now VKEY_MENU. |
| EXPECT_EQ(KeyLAlt::Typed(), RunRewriter(KeyEscape::Typed())); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteModifiersRemapAltToControl) { |
| // Remap Alt to Control. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember alt; |
| InitModifierKeyPref(&alt, ::prefs::kLanguageRemapAltKeyTo, |
| ui::mojom::ModifierKey::kAlt, |
| ui::mojom::ModifierKey::kControl); |
| |
| for (const auto& keyboard : kAllKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Press left Alt. Confirm the event is now VKEY_CONTROL. |
| EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyLAlt::Typed())); |
| |
| // Press Shift+comma. Verify that only the flags are changed. |
| EXPECT_EQ( |
| KeyComma::Typed(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyComma::Typed(), ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN)); |
| |
| // Press Shift+9. Verify that only the flags are changed. |
| EXPECT_EQ( |
| KeyDigit9::Typed(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyDigit9::Typed(), ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteModifiersRemapUnderEscapeControlAlt) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| // Remap Escape to Alt. |
| IntegerPrefMember escape; |
| InitModifierKeyPref(&escape, ::prefs::kLanguageRemapEscapeKeyTo, |
| ui::mojom::ModifierKey::kEscape, |
| ui::mojom::ModifierKey::kAlt); |
| |
| // Remap Alt to Control. |
| IntegerPrefMember alt; |
| InitModifierKeyPref(&alt, ::prefs::kLanguageRemapAltKeyTo, |
| ui::mojom::ModifierKey::kAlt, |
| ui::mojom::ModifierKey::kControl); |
| |
| // Remap Control to Search. |
| IntegerPrefMember control; |
| InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo, |
| ui::mojom::ModifierKey::kControl, |
| ui::mojom::ModifierKey::kMeta); |
| |
| for (const auto& keyboard : kAllKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Press left Control. Confirm the event is now VKEY_LWIN. |
| EXPECT_EQ(KeyLMeta::Typed(), RunRewriter(KeyLControl::Typed())); |
| |
| // Then, press all of the three, Control+Alt+Escape. |
| EXPECT_EQ( |
| KeyLAlt::Typed(ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyEscape::Typed(), ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)); |
| |
| // Press Shift+Control+Alt+Escape. |
| EXPECT_EQ( |
| KeyLAlt::Typed(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | |
| ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyEscape::Typed(), |
| ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)); |
| |
| // Press Shift+Control+Alt+B |
| EXPECT_EQ( |
| KeyB::Typed(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | |
| ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyB::Typed(), |
| ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, |
| TestRewriteModifiersRemapUnderEscapeControlAltSearch) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| // Remap Escape to Alt. |
| IntegerPrefMember escape; |
| InitModifierKeyPref(&escape, ::prefs::kLanguageRemapEscapeKeyTo, |
| ui::mojom::ModifierKey::kEscape, |
| ui::mojom::ModifierKey::kAlt); |
| |
| // Remap Alt to Control. |
| IntegerPrefMember alt; |
| InitModifierKeyPref(&alt, ::prefs::kLanguageRemapAltKeyTo, |
| ui::mojom::ModifierKey::kAlt, |
| ui::mojom::ModifierKey::kControl); |
| |
| // Remap Control to Search. |
| IntegerPrefMember control; |
| InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo, |
| ui::mojom::ModifierKey::kControl, |
| ui::mojom::ModifierKey::kMeta); |
| |
| // Remap Search to Backspace. |
| IntegerPrefMember search; |
| InitModifierKeyPref(&search, ::prefs::kLanguageRemapSearchKeyTo, |
| ui::mojom::ModifierKey::kMeta, |
| ui::mojom::ModifierKey::kBackspace); |
| |
| for (const auto& keyboard : kChromeKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Release Control and Escape, as Search and Alt would transform Backspace |
| // to Delete. |
| EXPECT_EQ(std::vector({KeyLMeta::Pressed()}), |
| SendKeyEvent(KeyLControl::Pressed())); |
| EXPECT_EQ(std::vector({KeyLAlt::Pressed(ui::EF_COMMAND_DOWN)}), |
| SendKeyEvent(KeyEscape::Pressed(ui::EF_CONTROL_DOWN))); |
| |
| if (ash::features::IsKeyboardRewriterFixEnabled()) { |
| EXPECT_EQ(std::vector({KeyLMeta::Released(ui::EF_ALT_DOWN)}), |
| SendKeyEvent(KeyLControl::Released())); |
| EXPECT_EQ(std::vector({KeyLAlt::Released()}), |
| SendKeyEvent(KeyEscape::Released())); |
| } else { |
| // TODO(crbug.com/40265877): Due to old rewriter implementation, |
| // unexpected key release events are dispatched, followed by wrongly |
| // un-rewritten event is dispatched. Fix them. |
| EXPECT_EQ(std::vector({KeyLMeta::Released(ui::EF_ALT_DOWN), |
| KeyLAlt::Released(ui::EF_ALT_DOWN)}), |
| SendKeyEvent(KeyLControl::Released())); |
| EXPECT_EQ(std::vector({KeyEscape::Released()}), |
| SendKeyEvent(KeyEscape::Released())); |
| } |
| |
| // Press Search. Confirm the event is now VKEY_BACK. |
| EXPECT_EQ(KeyBackspace::Typed(), RunRewriter(KeyLMeta::Typed())); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteModifiersRemapBackspaceToEscape) { |
| // Remap Backspace to Escape. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember backspace; |
| InitModifierKeyPref(&backspace, ::prefs::kLanguageRemapBackspaceKeyTo, |
| ui::mojom::ModifierKey::kBackspace, |
| ui::mojom::ModifierKey::kEscape); |
| |
| for (const auto& keyboard : kAllKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Press Backspace. Confirm the event is now VKEY_ESCAPE. |
| EXPECT_EQ(KeyEscape::Typed(), RunRewriter(KeyBackspace::Typed())); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, |
| TestRewriteNonModifierToModifierWithRemapBetweenKeyEvents) { |
| // Remap Escape to Alt. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember escape; |
| InitModifierKeyPref(&escape, ::prefs::kLanguageRemapEscapeKeyTo, |
| ui::mojom::ModifierKey::kEscape, |
| ui::mojom::ModifierKey::kAlt); |
| |
| SetUpKeyboard(kInternalChromeKeyboard); |
| |
| // Press Escape. |
| EXPECT_EQ(std::vector({KeyLAlt::Pressed()}), |
| SendKeyEvent(KeyEscape::Pressed())); |
| |
| // Remap Escape to Control before releasing Escape. |
| InitModifierKeyPref(&escape, ::prefs::kLanguageRemapEscapeKeyTo, |
| ui::mojom::ModifierKey::kEscape, |
| ui::mojom::ModifierKey::kControl); |
| |
| // Release Escape. |
| if (ash::features::IsKeyboardRewriterFixEnabled()) { |
| EXPECT_EQ(std::vector({KeyLAlt::Released()}), |
| SendKeyEvent(KeyEscape::Released())); |
| } else { |
| EXPECT_EQ(std::vector({KeyEscape::Released()}), |
| SendKeyEvent(KeyEscape::Released())); |
| } |
| |
| // Type A, expect that Alt is not stickied. |
| EXPECT_EQ(KeyA::Typed(), RunRewriter(KeyA::Typed())); |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteModifiersRemapToCapsLock) { |
| // Remap Search to Caps Lock. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember search; |
| InitModifierKeyPref(&search, ::prefs::kLanguageRemapSearchKeyTo, |
| ui::mojom::ModifierKey::kMeta, |
| ui::mojom::ModifierKey::kCapsLock); |
| |
| SetUpKeyboard(kInternalChromeKeyboard); |
| EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // Press Search. |
| EXPECT_EQ(std::vector({KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)}), |
| SendKeyEvent(KeyLMeta::Pressed())); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // Release Search. |
| EXPECT_EQ(std::vector({KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)}), |
| SendKeyEvent(KeyLMeta::Released())); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // Press Search. |
| EXPECT_EQ(std::vector({KeyCapsLock::Pressed()}), |
| SendKeyEvent(KeyLMeta::Pressed())); |
| EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // Release Search. |
| EXPECT_EQ(std::vector({KeyCapsLock::Released()}), |
| SendKeyEvent(KeyLMeta::Released())); |
| EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // Do the same on external Chrome OS keyboard. |
| SetUpKeyboard(kExternalChromeKeyboard); |
| |
| // Press Search. |
| EXPECT_EQ(std::vector({KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)}), |
| SendKeyEvent(KeyLMeta::Pressed())); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // Release Search. |
| EXPECT_EQ(std::vector({KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)}), |
| SendKeyEvent(KeyLMeta::Released())); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // Press Search. |
| EXPECT_EQ(std::vector({KeyCapsLock::Pressed()}), |
| SendKeyEvent(KeyLMeta::Pressed())); |
| EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // Release Search. |
| EXPECT_EQ(std::vector({KeyCapsLock::Released()}), |
| SendKeyEvent(KeyLMeta::Released())); |
| EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // Try external keyboard with Caps Lock. |
| SetUpKeyboard(kExternalGenericKeyboard); |
| |
| // Press Caps Lock. |
| EXPECT_EQ(std::vector({KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)}), |
| SendKeyEvent(KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON))); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // Release Caps Lock. |
| EXPECT_EQ(std::vector({KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)}), |
| SendKeyEvent(KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON))); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteCapsLock) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| SetUpKeyboard(kExternalGenericKeyboard); |
| EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // On Chrome OS, CapsLock is mapped to CapsLock with Mod3Mask. |
| EXPECT_EQ(std::vector({KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)}), |
| SendKeyEvent(KeyCapsLock::Pressed())); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // Key repeating should not toggle CapsLock state. |
| EXPECT_EQ(std::vector({KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)}), |
| SendKeyEvent(KeyCapsLock::Pressed())); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| EXPECT_EQ(std::vector({KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)}), |
| SendKeyEvent(KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON))); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // Remap Caps Lock to Control. |
| IntegerPrefMember caps_lock; |
| InitModifierKeyPref(&caps_lock, ::prefs::kLanguageRemapCapsLockKeyTo, |
| ui::mojom::ModifierKey::kCapsLock, |
| ui::mojom::ModifierKey::kControl); |
| |
| // Press Caps Lock. CapsLock is enabled but we have remapped the key to |
| // now be Control. We want to ensure that the CapsLock modifier is still |
| // active even after pressing the remapped Capslock key. |
| EXPECT_EQ(std::vector({KeyLControl::Pressed(ui::EF_CAPS_LOCK_ON)}), |
| SendKeyEvent(KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON))); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // Release Caps Lock. |
| EXPECT_EQ(std::vector({KeyLControl::Released(ui::EF_CAPS_LOCK_ON)}), |
| SendKeyEvent(KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON))); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteExternalCapsLockWithDifferentScenarios) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| SetUpKeyboard(kExternalGenericKeyboard); |
| EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // Turn on CapsLock. |
| EXPECT_EQ(std::vector({KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)}), |
| SendKeyEvent(KeyCapsLock::Pressed())); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| EXPECT_EQ(std::vector({KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)}), |
| SendKeyEvent(KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON))); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // Remap CapsLock to Search. |
| IntegerPrefMember search; |
| InitModifierKeyPref(&search, ::prefs::kLanguageRemapCapsLockKeyTo, |
| ui::mojom::ModifierKey::kCapsLock, |
| ui::mojom::ModifierKey::kMeta); |
| |
| // Now that CapsLock is enabled, press the remapped CapsLock button again |
| // and expect to not disable CapsLock. |
| EXPECT_EQ(std::vector({KeyLMeta::Pressed(ui::EF_CAPS_LOCK_ON)}), |
| SendKeyEvent(KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON))); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| EXPECT_EQ(std::vector({KeyLMeta::Released(ui::EF_CAPS_LOCK_ON)}), |
| SendKeyEvent(KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON))); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| // Remap CapsLock key back to CapsLock. |
| IntegerPrefMember capslock; |
| InitModifierKeyPref(&capslock, ::prefs::kLanguageRemapCapsLockKeyTo, |
| ui::mojom::ModifierKey::kCapsLock, |
| ui::mojom::ModifierKey::kCapsLock); |
| |
| // Now press CapsLock again and now expect that the CapsLock modifier is |
| // removed and the key is disabled. |
| EXPECT_EQ(std::vector({KeyCapsLock::Pressed()}), |
| SendKeyEvent(KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON))); |
| EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteCapsLockToControl) { |
| // Remap CapsLock to Control. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember control; |
| InitModifierKeyPref(&control, ::prefs::kLanguageRemapCapsLockKeyTo, |
| ui::mojom::ModifierKey::kCapsLock, |
| ui::mojom::ModifierKey::kControl); |
| |
| SetUpKeyboard(kExternalGenericKeyboard); |
| |
| // Press CapsLock+a. Confirm that Mod3Mask is rewritten to ControlMask. |
| // On Chrome OS, CapsLock works as a Mod3 modifier. |
| EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_MOD3_DOWN)); |
| |
| // Press Control+CapsLock+a. Confirm that Mod3Mask is rewritten to |
| // ControlMask |
| EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_CONTROL_DOWN | ui::EF_MOD3_DOWN)); |
| |
| // Press Alt+CapsLock+a. Confirm that Mod3Mask is rewritten to |
| // ControlMask. |
| EXPECT_EQ(KeyA::Typed(ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN | ui::EF_MOD3_DOWN)); |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteCapsLockMod3InUse) { |
| // Remap CapsLock to Control. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember control; |
| InitModifierKeyPref(&control, ::prefs::kLanguageRemapCapsLockKeyTo, |
| ui::mojom::ModifierKey::kCapsLock, |
| ui::mojom::ModifierKey::kControl); |
| |
| SetUpKeyboard(kExternalGenericKeyboard); |
| input_method_manager_mock_->set_mod3_used(true); |
| |
| // Press CapsLock+a. Confirm that Mod3Mask is NOT rewritten to ControlMask |
| // when Mod3Mask is already in use by the current XKB layout. |
| EXPECT_EQ(KeyA::Typed(), RunRewriter(KeyA::Typed())); |
| |
| input_method_manager_mock_->set_mod3_used(false); |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteToQuickInsert) { |
| // Remap QuickInsert to Control |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember control; |
| InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo, |
| ui::mojom::ModifierKey::kControl, |
| ui::mojom::ModifierKey::kQuickInsert); |
| |
| IntegerPrefMember search; |
| InitModifierKeyPref(&search, ::prefs::kLanguageRemapSearchKeyTo, |
| ui::mojom::ModifierKey::kMeta, |
| ui::mojom::ModifierKey::kQuickInsert); |
| |
| for (const auto& keyboard : kChromeKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| EXPECT_EQ(KeyQuickInsert::Typed(ui::EF_NONE, {kPropertyQuickInsert}), |
| RunRewriter(KeyLControl::Typed())); |
| EXPECT_EQ(KeyQuickInsert::Typed(ui::EF_NONE, {kPropertyQuickInsert}), |
| RunRewriter(KeyLMeta::Typed())); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, FnAndQuickInsertKeyPressedMetrics) { |
| if (!features::IsModifierSplitEnabled()) { |
| GTEST_SKIP() << "Test is only valid with the modifier split flag enabled"; |
| } |
| base::HistogramTester histogram_tester; |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kInputDeviceSettingsSplit); |
| SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard); |
| SendKeyEvent(KeyFunction::Pressed()); |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.Internal", |
| ui::ModifierKeyUsageMetric::kFunction, 1); |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal", |
| ui::ModifierKeyUsageMetric::kFunction, 1); |
| |
| SendKeyEvent(KeyQuickInsert::Pressed()); |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.Internal", |
| ui::ModifierKeyUsageMetric::kQuickInsert, 1); |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal", |
| ui::ModifierKeyUsageMetric::kQuickInsert, 1); |
| |
| // Remap QuickInsert to Assistant |
| InitModifierKeyPref(nullptr, "", ui::mojom::ModifierKey::kQuickInsert, |
| ui::mojom::ModifierKey::kAssistant); |
| |
| RunRewriter(KeyQuickInsert::Typed()); |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.Internal", |
| ui::ModifierKeyUsageMetric::kQuickInsert, 2); |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal", |
| ui::ModifierKeyUsageMetric::kQuickInsert, 1); |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal", |
| ui::ModifierKeyUsageMetric::kAssistant, 1); |
| scoped_feature_list_.Reset(); |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteToFunction) { |
| if (!features::IsModifierSplitEnabled()) { |
| GTEST_SKIP() << "Test is only valid with the modifier split flag enabled"; |
| } |
| |
| SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard); |
| |
| // Remap QuickInsert to Control |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember control; |
| InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo, |
| ui::mojom::ModifierKey::kControl, |
| ui::mojom::ModifierKey::kFunction); |
| |
| IntegerPrefMember search; |
| InitModifierKeyPref(&search, ::prefs::kLanguageRemapSearchKeyTo, |
| ui::mojom::ModifierKey::kMeta, |
| ui::mojom::ModifierKey::kFunction); |
| |
| // Keys + rewritten modifiers produce rewritten six-pack keys. |
| EXPECT_EQ(KeyPageUp::Typed(), |
| RunRewriter(KeyArrowUp::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyPageDown::Typed(), |
| RunRewriter(KeyArrowDown::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // After command + control are released, events are not affected. |
| EXPECT_EQ(KeyA::Typed(), RunRewriter(KeyA::Typed())); |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteFromFunction) { |
| // Function is only available when InputDeviceSettingsSplit is enabled. |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kInputDeviceSettingsSplit); |
| |
| // Remap Function to Control |
| InitModifierKeyPref(nullptr, "", ui::mojom::ModifierKey::kFunction, |
| ui::mojom::ModifierKey::kControl); |
| |
| for (const auto& keyboard : kChromeKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyFunction::Typed())); |
| |
| // A + rewritten modifiers produce events with function flag down. |
| EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_FUNCTION_DOWN)); |
| // After command + control are released, events are not affected. |
| EXPECT_EQ(KeyA::Typed(), RunRewriter(KeyA::Typed())); |
| } |
| |
| // Remap Function to CapsLock |
| InitModifierKeyPref(nullptr, "", ui::mojom::ModifierKey::kFunction, |
| ui::mojom::ModifierKey::kCapsLock); |
| |
| for (const auto& keyboard : kChromeKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Toggle CapsLock on |
| EXPECT_EQ(KeyCapsLock::Typed(ui::EF_CAPS_LOCK_ON), |
| RunRewriter(KeyFunction::Typed(ui::EF_CAPS_LOCK_ON))); |
| |
| // Toggle CapsLock off |
| EXPECT_EQ(KeyCapsLock::Typed(), RunRewriter(KeyFunction::Typed())); |
| } |
| |
| // Remap Function to Void |
| InitModifierKeyPref(nullptr, "", ui::mojom::ModifierKey::kFunction, |
| ui::mojom::ModifierKey::kVoid); |
| |
| for (const auto& keyboard : kChromeKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| EXPECT_EQ(KeyUnknown::Typed(), RunRewriter(KeyFunction::Typed())); |
| } |
| |
| scoped_feature_list_.Reset(); |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteFromQuickInsert) { |
| if (!features::IsModifierSplitEnabled()) { |
| GTEST_SKIP() << "Test is only valid with the modifier split flag enabled"; |
| } |
| |
| // QuickInsert is only available when InputDeviceSettingsSplit is enabled. |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kInputDeviceSettingsSplit); |
| SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard); |
| |
| // Test that identity is working as expected. |
| EXPECT_EQ(KeyQuickInsert::Typed(ui::EF_NONE, {kPropertyQuickInsert}), |
| RunRewriter(KeyLaunchAssistant::Typed())); |
| |
| // Remap QuickInsert to Control |
| InitModifierKeyPref(nullptr, "", ui::mojom::ModifierKey::kQuickInsert, |
| ui::mojom::ModifierKey::kControl); |
| |
| EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyQuickInsert::Typed())); |
| |
| // Test QuickInsert remapped to Control properly applies the flag to other |
| // events. |
| EXPECT_EQ( |
| (std::vector<TestKeyEvent>{KeyLControl::Pressed()}), |
| (RunRewriter(std::vector<TestKeyEvent>{KeyQuickInsert::Pressed()}))); |
| EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN), RunRewriter(KeyA::Typed())); |
| EXPECT_EQ( |
| (std::vector<TestKeyEvent>{KeyLControl::Released()}), |
| (RunRewriter(std::vector<TestKeyEvent>{KeyQuickInsert::Released()}))); |
| |
| // Remap QuickInsert to CapsLock |
| InitModifierKeyPref(nullptr, "", ui::mojom::ModifierKey::kQuickInsert, |
| ui::mojom::ModifierKey::kCapsLock); |
| // Toggle CapsLock on/off |
| EXPECT_EQ(KeyCapsLock::Typed(ui::EF_CAPS_LOCK_ON), |
| RunRewriter(KeyQuickInsert::Typed(ui::EF_CAPS_LOCK_ON))); |
| EXPECT_EQ(KeyCapsLock::Typed(), RunRewriter(KeyQuickInsert::Typed())); |
| |
| // Remap QuickInsert to Void |
| InitModifierKeyPref(nullptr, "", ui::mojom::ModifierKey::kQuickInsert, |
| ui::mojom::ModifierKey::kVoid); |
| EXPECT_EQ(KeyUnknown::Typed(), RunRewriter(KeyQuickInsert::Typed())); |
| |
| scoped_feature_list_.Reset(); |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteExtendedKeysAltVariants) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndDisableFeature( |
| features::kAltClickAndSixPackCustomization); |
| // All the previously supported Alt based rewrites no longer have any |
| // effect. The Search workarounds no longer take effect and the Search+Key |
| // portion is rewritten as expected. |
| for (const auto keyboard : kNonAppleKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Alt+Backspace -> No Rewrite |
| EXPECT_EQ(KeyBackspace::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyBackspace::Typed(), ui::EF_ALT_DOWN)); |
| EXPECT_EQ(1u, message_center_.NotificationCount()); |
| ClearNotifications(); |
| |
| // Control+Alt+Backspace -> No Rewrite |
| EXPECT_EQ(KeyBackspace::Typed(ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyBackspace::Typed(), |
| ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(1u, message_center_.NotificationCount()); |
| ClearNotifications(); |
| |
| // Search+Alt+Backspace -> Alt+Delete |
| EXPECT_EQ(KeyDelete::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyBackspace::Typed(), |
| ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN)); |
| |
| // Search+Control+Alt+Backspace -> Control+Alt+Delete |
| EXPECT_EQ(KeyDelete::Typed(ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyBackspace::Typed(), ui::EF_COMMAND_DOWN | |
| ui::EF_CONTROL_DOWN | |
| ui::EF_ALT_DOWN)); |
| |
| // Alt+Up -> No Rewrite |
| EXPECT_EQ(KeyArrowUp::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyArrowUp::Typed(), ui::EF_ALT_DOWN)); |
| EXPECT_EQ(1u, message_center_.NotificationCount()); |
| ClearNotifications(); |
| |
| // Alt+Down -> No Rewrite |
| EXPECT_EQ(KeyArrowDown::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyArrowDown::Typed(), ui::EF_ALT_DOWN)); |
| EXPECT_EQ(1u, message_center_.NotificationCount()); |
| ClearNotifications(); |
| |
| // Ctrl+Alt+Up -> No Rewrite |
| EXPECT_EQ(KeyArrowUp::Typed(ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyArrowUp::Typed(), |
| ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(1u, message_center_.NotificationCount()); |
| ClearNotifications(); |
| |
| // Ctrl+Alt+Down -> No Rewrite |
| EXPECT_EQ(KeyArrowDown::Typed(ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyArrowDown::Typed(), |
| ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(1u, message_center_.NotificationCount()); |
| ClearNotifications(); |
| |
| // NOTE: The following were workarounds to avoid rewriting the |
| // Alt variants by additionally pressing Search. |
| |
| // Search+Ctrl+Alt+Up -> Ctrl+Alt+PageUp(aka Prior) |
| EXPECT_EQ(KeyPageUp::Typed(ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN), |
| RunRewriter(KeyArrowUp::Typed(), ui::EF_COMMAND_DOWN | |
| ui::EF_CONTROL_DOWN | |
| ui::EF_ALT_DOWN)); |
| // Search+Ctrl+Alt+Down -> Ctrl+Alt+PageDown(aka Next) |
| EXPECT_EQ(KeyPageDown::Typed(ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN), |
| RunRewriter(KeyArrowDown::Typed(), ui::EF_COMMAND_DOWN | |
| ui::EF_CONTROL_DOWN | |
| ui::EF_ALT_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteExtendedKeyInsertDeprecatedNotification) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitWithFeatures( |
| {}, {features::kAltClickAndSixPackCustomization}); |
| |
| for (const auto keyboard : kNonAppleKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Period -> Period |
| EXPECT_EQ(KeyPeriod::Typed(), RunRewriter(KeyPeriod::Typed())); |
| |
| // Search+Period -> No rewrite (and shows notification) |
| EXPECT_EQ(KeyPeriod::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyPeriod::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(1u, message_center_.NotificationCount()); |
| ClearNotifications(); |
| |
| // Control+Search+Period -> No rewrite (and shows notification) |
| EXPECT_EQ(KeyPeriod::Typed(ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyPeriod::Typed(), |
| ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(1u, message_center_.NotificationCount()); |
| ClearNotifications(); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteExtendedKeyInsert) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitWithFeatures( |
| {}, {features::kAltClickAndSixPackCustomization}); |
| |
| for (const auto keyboard : kNonAppleKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Search+Shift+Backspace -> Insert |
| EXPECT_EQ(KeyInsert::Typed(), |
| RunRewriter(KeyBackspace::Typed(), |
| ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN)); |
| |
| // Control+Search+Shift+Backspace -> Control+Insert |
| EXPECT_EQ(KeyInsert::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyBackspace::Typed(), ui::EF_COMMAND_DOWN | |
| ui::EF_CONTROL_DOWN | |
| ui::EF_SHIFT_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteExtendedKeysSearchVariants) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndDisableFeature( |
| features::kAltClickAndSixPackCustomization); |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| for (const auto keyboard : kNonAppleKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Search+Backspace -> Delete |
| EXPECT_EQ(KeyDelete::Typed(), |
| RunRewriter(KeyBackspace::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Search+Up -> Prior |
| EXPECT_EQ(KeyPageUp::Typed(), |
| RunRewriter(KeyArrowUp::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Search+Down -> Next |
| EXPECT_EQ(KeyPageDown::Typed(), |
| RunRewriter(KeyArrowDown::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Search+Left -> Home |
| EXPECT_EQ(KeyHome::Typed(), |
| RunRewriter(KeyArrowLeft::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Control+Search+Left -> Control+Home |
| EXPECT_EQ(KeyHome::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyArrowLeft::Typed(), |
| ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN)); |
| |
| // Search+Right -> End |
| EXPECT_EQ(KeyEnd::Typed(), |
| RunRewriter(KeyArrowRight::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Control+Search+Right -> Control+End |
| EXPECT_EQ(KeyEnd::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyArrowRight::Typed(), |
| ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestNumberRowIsNotRewritten) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndDisableFeature( |
| features::kAltClickAndSixPackCustomization); |
| for (const auto& keyboard : kNonAppleNonCustomLayoutKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // The number row should not be rewritten without Search key. |
| EXPECT_EQ(KeyDigit1::Typed(), RunRewriter(KeyDigit1::Typed())); |
| EXPECT_EQ(KeyDigit2::Typed(), RunRewriter(KeyDigit2::Typed())); |
| EXPECT_EQ(KeyDigit3::Typed(), RunRewriter(KeyDigit3::Typed())); |
| EXPECT_EQ(KeyDigit4::Typed(), RunRewriter(KeyDigit4::Typed())); |
| EXPECT_EQ(KeyDigit5::Typed(), RunRewriter(KeyDigit5::Typed())); |
| EXPECT_EQ(KeyDigit6::Typed(), RunRewriter(KeyDigit6::Typed())); |
| EXPECT_EQ(KeyDigit7::Typed(), RunRewriter(KeyDigit7::Typed())); |
| EXPECT_EQ(KeyDigit8::Typed(), RunRewriter(KeyDigit8::Typed())); |
| EXPECT_EQ(KeyDigit9::Typed(), RunRewriter(KeyDigit9::Typed())); |
| EXPECT_EQ(KeyDigit0::Typed(), RunRewriter(KeyDigit0::Typed())); |
| EXPECT_EQ(KeyMinus::Typed(), RunRewriter(KeyMinus::Typed())); |
| EXPECT_EQ(KeyEqual::Typed(), RunRewriter(KeyEqual::Typed())); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteSearchNumberToFunctionKey) { |
| TestShortcutMappingPrefDelegate delegate; |
| CHECK(!::features::IsImprovedKeyboardShortcutsEnabled()); |
| ASSERT_FALSE(::features::IsImprovedKeyboardShortcutsEnabled()); |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| for (const auto& keyboard : kNonAppleNonCustomLayoutKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // The number row should be rewritten as the F<number> row with Search |
| // key. |
| EXPECT_EQ(KeyF1::Typed(), |
| RunRewriter(KeyDigit1::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF2::Typed(), |
| RunRewriter(KeyDigit2::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF3::Typed(), |
| RunRewriter(KeyDigit3::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF4::Typed(), |
| RunRewriter(KeyDigit4::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF5::Typed(), |
| RunRewriter(KeyDigit5::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF6::Typed(), |
| RunRewriter(KeyDigit6::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF7::Typed(), |
| RunRewriter(KeyDigit7::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF8::Typed(), |
| RunRewriter(KeyDigit8::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF9::Typed(), |
| RunRewriter(KeyDigit9::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF10::Typed(), |
| RunRewriter(KeyDigit0::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF11::Typed(), |
| RunRewriter(KeyMinus::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF12::Typed(), |
| RunRewriter(KeyEqual::Typed(), ui::EF_COMMAND_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteSearchNumberToFunctionKeyNoAction) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| for (const auto& keyboard : kNonAppleNonCustomLayoutKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Search+Number should now have no effect but a notification will |
| // be shown the first time F1 to F10 is pressed. |
| EXPECT_EQ(KeyDigit1::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyDigit1::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyDigit2::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyDigit2::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyDigit3::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyDigit3::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyDigit4::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyDigit4::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyDigit5::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyDigit5::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyDigit6::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyDigit6::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyDigit7::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyDigit7::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyDigit8::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyDigit8::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyDigit9::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyDigit9::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyDigit0::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyDigit0::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyMinus::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyMinus::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyEqual::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyEqual::Typed(), ui::EF_COMMAND_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestFunctionKeysNotRewrittenBySearch) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| for (const auto& keyboard : kNonAppleNonCustomLayoutKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // The function keys should not be rewritten with Search key pressed. |
| EXPECT_EQ(KeyF1::Typed(), RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF2::Typed(), RunRewriter(KeyF2::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF3::Typed(), RunRewriter(KeyF3::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF4::Typed(), RunRewriter(KeyF4::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF5::Typed(), RunRewriter(KeyF5::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF6::Typed(), RunRewriter(KeyF6::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF7::Typed(), RunRewriter(KeyF7::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF8::Typed(), RunRewriter(KeyF8::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF9::Typed(), RunRewriter(KeyF9::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF10::Typed(), |
| RunRewriter(KeyF10::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF11::Typed(), |
| RunRewriter(KeyF11::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF12::Typed(), |
| RunRewriter(KeyF12::Typed(), ui::EF_COMMAND_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteFunctionKeysNonCustomLayouts) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| // Old CrOS keyboards that do not have custom layouts send F-Keys by default |
| // and are translated by default to Actions based on hardcoded mappings. |
| // New CrOS keyboards are not tested here because they do not remap F-Keys. |
| for (const auto& keyboard : kNonAppleNonCustomLayoutKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // F1 -> Back |
| EXPECT_EQ(KeyBrowserBack::Typed(), RunRewriter(KeyF1::Typed())); |
| EXPECT_EQ(KeyBrowserBack::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF1::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyBrowserBack::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF1::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F2 -> Forward |
| EXPECT_EQ(KeyBrowserForward::Typed(), RunRewriter(KeyF2::Typed())); |
| EXPECT_EQ(KeyBrowserForward::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF2::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyBrowserForward::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF2::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F3 -> Refresh |
| EXPECT_EQ(KeyBrowserRefresh::Typed(), RunRewriter(KeyF3::Typed())); |
| EXPECT_EQ(KeyBrowserRefresh::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF3::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyBrowserRefresh::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF3::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F4 -> Zoom (aka Fullscreen) |
| EXPECT_EQ(KeyZoomToggle::Typed(), RunRewriter(KeyF4::Typed())); |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF4::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF4::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F5 -> Launch App 1 |
| EXPECT_EQ(KeySelectTask::Typed(), RunRewriter(KeyF5::Typed())); |
| EXPECT_EQ(KeySelectTask::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF5::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeySelectTask::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF5::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F6 -> Brightness down |
| EXPECT_EQ(KeyBrightnessDown::Typed(), RunRewriter(KeyF6::Typed())); |
| EXPECT_EQ(KeyBrightnessDown::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF6::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyBrightnessDown::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF6::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F7 -> Brightness up |
| EXPECT_EQ(KeyBrightnessUp::Typed(), RunRewriter(KeyF7::Typed())); |
| EXPECT_EQ(KeyBrightnessUp::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF7::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyBrightnessUp::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF7::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F8 -> Volume Mute |
| EXPECT_EQ(KeyVolumeMute::Typed(), RunRewriter(KeyF8::Typed())); |
| EXPECT_EQ(KeyVolumeMute::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF8::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyVolumeMute::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF8::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F9 -> Volume Down |
| EXPECT_EQ(KeyVolumeDown::Typed(), RunRewriter(KeyF9::Typed())); |
| EXPECT_EQ(KeyVolumeDown::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF9::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyVolumeDown::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF9::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F10 -> Volume Up |
| EXPECT_EQ(KeyVolumeUp::Typed(), RunRewriter(KeyF10::Typed())); |
| EXPECT_EQ(KeyVolumeUp::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF10::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyVolumeUp::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF10::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F11 -> F11 |
| EXPECT_EQ(KeyF11::Typed(), RunRewriter(KeyF11::Typed())); |
| EXPECT_EQ(KeyF11::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF11::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF11::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF11::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F12 -> F12 |
| EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyF12::Typed())); |
| EXPECT_EQ(KeyF12::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF12::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF12::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF12::Typed(), ui::EF_ALT_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteFunctionKeysCustomLayoutsFKeyUnchanged) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| // On devices with custom layouts, the F-Keys are never remapped. |
| for (const auto& keyboard : kChromeCustomKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| for (const auto typed : |
| {KeyF1::Typed, KeyF2::Typed, KeyF3::Typed, KeyF4::Typed, KeyF5::Typed, |
| KeyF6::Typed, KeyF7::Typed, KeyF8::Typed, KeyF9::Typed, KeyF10::Typed, |
| KeyF11::Typed, KeyF12::Typed, KeyF13::Typed, KeyF14::Typed, |
| KeyF15::Typed}) { |
| EXPECT_EQ(typed(ui::EF_NONE, {}), RunRewriter(typed(ui::EF_NONE, {}))); |
| EXPECT_EQ(typed(ui::EF_CONTROL_DOWN, {}), |
| RunRewriter(typed(ui::EF_NONE, {}), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(typed(ui::EF_ALT_DOWN, {}), |
| RunRewriter(typed(ui::EF_NONE, {}), ui::EF_ALT_DOWN)); |
| EXPECT_EQ(typed(ui::EF_COMMAND_DOWN, {}), |
| RunRewriter(typed(ui::EF_NONE, {}), ui::EF_COMMAND_DOWN)); |
| } |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteFunctionKeysCustomLayoutsActionUnchanged) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| // An action key on these devices is one where the scan code matches an entry |
| // in the layout map. It doesn't matter what the action is, as long the |
| // search key isn't pressed it will pass through unchanged. |
| SetUpKeyboard({.name = "Internal Custom LayoutKeyboard", |
| .layout = "a1 a2 a3", |
| .type = ui::INPUT_DEVICE_INTERNAL, |
| .has_custom_top_row = true}); |
| auto browser_refresh = KeyBrowserRefresh::Pressed(); |
| browser_refresh.scan_code = 0xa1; |
| EXPECT_EQ(std::vector({browser_refresh}), SendKeyEvent(browser_refresh)); |
| |
| auto volume_up = KeyVolumeUp::Pressed(); |
| volume_up.scan_code = 0xa2; |
| EXPECT_EQ(std::vector({volume_up}), SendKeyEvent(volume_up)); |
| |
| auto volume_down = KeyVolumeDown::Pressed(); |
| volume_down.scan_code = 0xa3; |
| EXPECT_EQ(std::vector({volume_down}), SendKeyEvent(volume_down)); |
| } |
| |
| TEST_P(EventRewriterTest, |
| TestRewriteFunctionKeysCustomLayoutsActionSuppressedUnchanged) { |
| // For EF_COMMAND_DOWN modifier. |
| SendKeyEvent(KeyLMeta::Pressed()); |
| |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| delegate_->SuppressMetaTopRowKeyComboRewrites(true); |
| keyboard_settings->suppress_meta_fkey_rewrites = true; |
| |
| // An action key on these devices is one where the scan code matches an entry |
| // in the layout map. With Meta + Top Row Key rewrites being suppressed, the |
| // input should be equivalent to the output for all tested keys. |
| SetUpKeyboard({.name = "Internal Custom Layout Keyboard", |
| .layout = "a1 a2 a3", |
| .type = ui::INPUT_DEVICE_INTERNAL, |
| .has_custom_top_row = true}); |
| |
| auto browser_refresh = KeyBrowserRefresh::Pressed(ui::EF_COMMAND_DOWN); |
| browser_refresh.scan_code = 0xa1; |
| EXPECT_EQ(std::vector({browser_refresh}), SendKeyEvent(browser_refresh)); |
| |
| auto volume_up = KeyVolumeUp::Pressed(ui::EF_COMMAND_DOWN); |
| volume_up.scan_code = 0xa2; |
| EXPECT_EQ(std::vector({volume_up}), SendKeyEvent(volume_up)); |
| |
| auto volume_down = KeyVolumeDown::Pressed(ui::EF_COMMAND_DOWN); |
| volume_down.scan_code = 0xa3; |
| EXPECT_EQ(std::vector({volume_down}), SendKeyEvent(volume_down)); |
| } |
| |
| TEST_P(EventRewriterTest, |
| TestRewriteFunctionKeysCustomLayoutsActionSuppressedWithTopRowAreFKeys) { |
| // For EF_COMMAND_DOWN. |
| SendKeyEvent(KeyLMeta::Pressed()); |
| |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| delegate_->SuppressMetaTopRowKeyComboRewrites(true); |
| keyboard_settings->suppress_meta_fkey_rewrites = true; |
| |
| BooleanPrefMember send_function_keys_pref; |
| send_function_keys_pref.Init(prefs::kSendFunctionKeys, prefs()); |
| send_function_keys_pref.SetValue(true); |
| keyboard_settings->top_row_are_fkeys = true; |
| |
| // An action key on these devices is one where the scan code matches an entry |
| // in the layout map. With Meta + Top Row Key rewrites being suppressed, the |
| // input should be remapped to F-Keys and the Search modifier should not be |
| // removed. |
| SetUpKeyboard({.name = "Internal Custom Layout Keyboard", |
| .layout = "a1 a2 a3", |
| .type = ui::INPUT_DEVICE_INTERNAL, |
| .has_custom_top_row = true}); |
| |
| auto browser_refresh = KeyBrowserRefresh::Pressed(ui::EF_COMMAND_DOWN); |
| browser_refresh.scan_code = 0xa1; |
| auto f1 = KeyF1::Pressed(ui::EF_COMMAND_DOWN); |
| f1.scan_code = 0xa1; |
| EXPECT_EQ(std::vector({f1}), SendKeyEvent(browser_refresh)); |
| |
| auto volume_up = KeyVolumeUp::Pressed(ui::EF_COMMAND_DOWN); |
| volume_up.scan_code = 0xa2; |
| auto f2 = KeyF2::Pressed(ui::EF_COMMAND_DOWN); |
| f2.scan_code = 0xa2; |
| EXPECT_EQ(std::vector({f2}), SendKeyEvent(volume_up)); |
| |
| auto volume_down = KeyVolumeDown::Pressed(ui::EF_COMMAND_DOWN); |
| volume_down.scan_code = 0xa3; |
| auto f3 = KeyF3::Pressed(ui::EF_COMMAND_DOWN); |
| f3.scan_code = 0xa3; |
| EXPECT_EQ(std::vector({f3}), SendKeyEvent(volume_down)); |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteFunctionKeysCustomLayouts) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| // On devices with custom layouts, scan codes that match the layout |
| // map get mapped to F-Keys based only on the scan code. The search |
| // key also gets treated as unpressed in the remapped event. |
| SetUpKeyboard({.name = "Internal Custom Layout Keyboard", |
| .layout = "a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af", |
| .type = ui::INPUT_DEVICE_INTERNAL, |
| .has_custom_top_row = true}); |
| |
| struct TestCase { |
| std::vector<TestKeyEvent> (*pressed)( |
| ui::EventFlags, |
| std::vector<std::pair<std::string, std::vector<uint8_t>>>); |
| uint32_t scan_code; |
| }; |
| // Action -> F1..F15 |
| for (const auto& [typed, scan_code] : |
| std::initializer_list<TestCase>{{KeyF1::Typed, 0xa1}, |
| {KeyF2::Typed, 0xa2}, |
| {KeyF3::Typed, 0xa3}, |
| {KeyF4::Typed, 0xa4}, |
| {KeyF5::Typed, 0xa5}, |
| {KeyF6::Typed, 0xa6}, |
| {KeyF7::Typed, 0xa7}, |
| {KeyF8::Typed, 0xa8}, |
| {KeyF9::Typed, 0xa9}, |
| {KeyF10::Typed, 0xaa}, |
| {KeyF11::Typed, 0xab}, |
| {KeyF12::Typed, 0xac}, |
| {KeyF13::Typed, 0xad}, |
| {KeyF14::Typed, 0xae}, |
| {KeyF15::Typed, 0xaf}}) { |
| auto unknowns = KeyUnknown::Typed(); |
| for (auto& unknown : unknowns) { |
| unknown.scan_code = scan_code; |
| } |
| auto expected_events = typed(ui::EF_NONE, {}); |
| for (auto& event : expected_events) { |
| event.scan_code = scan_code; |
| } |
| EXPECT_EQ(expected_events, RunRewriter(unknowns, ui::EF_COMMAND_DOWN)); |
| |
| if (features::IsModifierSplitEnabled()) { |
| // With fn down, nothing should change since this keyboard uses Search |
| // based rewriting. |
| EXPECT_EQ(unknowns, RunRewriter(unknowns, ui::EF_FUNCTION_DOWN)); |
| } |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteFunctionKeysCustomLayoutsWithFunction) { |
| if (!features::IsModifierSplitEnabled()) { |
| GTEST_SKIP() << "Test is only valid with the modifier split flag enabled"; |
| } |
| |
| // On devices with custom layouts, scan codes that match the layout |
| // map get mapped to F-Keys based only on the scan code. The search |
| // key also gets treated as unpressed in the remapped event. |
| SetUpKeyboard({.name = "Internal Custom Layout Keyboard", |
| .layout = "a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af", |
| .type = ui::INPUT_DEVICE_INTERNAL, |
| .has_custom_top_row = true, |
| .has_assistant_key = true, |
| .has_function_key = true}); |
| |
| struct TestCase { |
| std::vector<TestKeyEvent> (*pressed)( |
| ui::EventFlags, |
| std::vector<std::pair<std::string, std::vector<uint8_t>>>); |
| uint32_t scan_code; |
| }; |
| // Action -> F1..F15 |
| for (const auto& [typed, scan_code] : |
| std::initializer_list<TestCase>{{KeyF1::Typed, 0xa1}, |
| {KeyF2::Typed, 0xa2}, |
| {KeyF3::Typed, 0xa3}, |
| {KeyF4::Typed, 0xa4}, |
| {KeyF5::Typed, 0xa5}, |
| {KeyF6::Typed, 0xa6}, |
| {KeyF7::Typed, 0xa7}, |
| {KeyF8::Typed, 0xa8}, |
| {KeyF9::Typed, 0xa9}, |
| {KeyF10::Typed, 0xaa}, |
| {KeyF11::Typed, 0xab}, |
| {KeyF12::Typed, 0xac}, |
| {KeyF13::Typed, 0xad}, |
| {KeyF14::Typed, 0xae}, |
| {KeyF15::Typed, 0xaf}}) { |
| auto unknowns = KeyUnknown::Typed(); |
| for (auto& unknown : unknowns) { |
| unknown.scan_code = scan_code; |
| } |
| auto unknowns_with_search = KeyUnknown::Typed(ui::EF_COMMAND_DOWN); |
| for (auto& unknown : unknowns_with_search) { |
| unknown.scan_code = scan_code; |
| } |
| auto expected_events = typed(ui::EF_NONE, {}); |
| for (auto& event : expected_events) { |
| event.scan_code = scan_code; |
| } |
| // Do not rewrite when search key is down since the keyboard should use the |
| // fn key. |
| EXPECT_EQ(unknowns_with_search, RunRewriter(unknowns, ui::EF_COMMAND_DOWN)); |
| |
| // Rewrite correctly with the fn key down. |
| EXPECT_EQ(expected_events, RunRewriter(unknowns, ui::EF_FUNCTION_DOWN)); |
| } |
| scoped_feature_list_.Reset(); |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteFunctionKeysLayout2) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| SetUpKeyboard({.name = "Internal Keyboard", |
| .layout = kKbdTopRowLayout2Tag, |
| .type = ui::INPUT_DEVICE_INTERNAL, |
| .has_custom_top_row = false}); |
| |
| // F1 -> Back |
| EXPECT_EQ(KeyBrowserBack::Typed(), RunRewriter(KeyF1::Typed())); |
| EXPECT_EQ(KeyBrowserBack::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF1::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyBrowserBack::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF1::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F2 -> Refresh |
| EXPECT_EQ(KeyBrowserRefresh::Typed(), RunRewriter(KeyF2::Typed())); |
| EXPECT_EQ(KeyBrowserRefresh::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF2::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyBrowserRefresh::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF2::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F3 -> Zoom (aka Fullscreen) |
| EXPECT_EQ(KeyZoomToggle::Typed(), RunRewriter(KeyF3::Typed())); |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF3::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF3::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F4 -> Launch App 1 |
| EXPECT_EQ(KeySelectTask::Typed(), RunRewriter(KeyF4::Typed())); |
| EXPECT_EQ(KeySelectTask::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF4::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeySelectTask::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF4::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F5 -> Brightness down |
| EXPECT_EQ(KeyBrightnessDown::Typed(), RunRewriter(KeyF5::Typed())); |
| EXPECT_EQ(KeyBrightnessDown::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF5::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyBrightnessDown::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF5::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F6 -> Brightness up |
| EXPECT_EQ(KeyBrightnessUp::Typed(), RunRewriter(KeyF6::Typed())); |
| EXPECT_EQ(KeyBrightnessUp::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF6::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyBrightnessUp::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF6::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F7 -> Media Play/Pause |
| EXPECT_EQ(KeyMediaPlayPause::Typed(), RunRewriter(KeyF7::Typed())); |
| EXPECT_EQ(KeyMediaPlayPause::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF7::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyMediaPlayPause::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF7::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F8 -> Volume Mute |
| EXPECT_EQ(KeyVolumeMute::Typed(), RunRewriter(KeyF8::Typed())); |
| EXPECT_EQ(KeyVolumeMute::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF8::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyVolumeMute::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF8::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F9 -> Volume Down |
| EXPECT_EQ(KeyVolumeDown::Typed(), RunRewriter(KeyF9::Typed())); |
| EXPECT_EQ(KeyVolumeDown::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF9::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyVolumeDown::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF9::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F10 -> Volume Up |
| EXPECT_EQ(KeyVolumeUp::Typed(), RunRewriter(KeyF10::Typed())); |
| EXPECT_EQ(KeyVolumeUp::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF10::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyVolumeUp::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF10::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F11 -> F11 |
| EXPECT_EQ(KeyF11::Typed(), RunRewriter(KeyF11::Typed())); |
| EXPECT_EQ(KeyF11::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF11::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF11::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF11::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F12 -> F12 |
| EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyF12::Typed())); |
| EXPECT_EQ(KeyF12::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF12::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF12::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF12::Typed(), ui::EF_ALT_DOWN)); |
| } |
| |
| TEST_P(EventRewriterTest, |
| TestFunctionKeysLayout2SuppressMetaTopRowKeyRewrites) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| delegate_->SuppressMetaTopRowKeyComboRewrites(true); |
| keyboard_settings->suppress_meta_fkey_rewrites = true; |
| |
| // With Meta + Top Row Key rewrites suppressed, F-Keys should be translated to |
| // the equivalent action key and not lose the Search modifier. |
| SetUpKeyboard({.name = "Internal Keyboard", |
| .layout = kKbdTopRowLayout2Tag, |
| .type = ui::INPUT_DEVICE_INTERNAL, |
| .has_custom_top_row = false}); |
| |
| // F1 -> Back |
| EXPECT_EQ(KeyBrowserBack::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // F2 -> Refresh |
| EXPECT_EQ(KeyBrowserRefresh::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF2::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // F3 -> Zoom (aka Fullscreen) |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF3::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // F4 -> Launch App 1 |
| EXPECT_EQ(KeySelectTask::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF4::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // F5 -> Brightness down |
| EXPECT_EQ(KeyBrightnessDown::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF5::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // F6 -> Brightness up |
| EXPECT_EQ(KeyBrightnessUp::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF6::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // F7 -> Media Play/Pause |
| EXPECT_EQ(KeyMediaPlayPause::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF7::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // F8 -> Volume Mute |
| EXPECT_EQ(KeyVolumeMute::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF8::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // F9 -> Volume Down |
| EXPECT_EQ(KeyVolumeDown::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF9::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // F10 -> Volume Up |
| EXPECT_EQ(KeyVolumeUp::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF10::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // F11 -> F11 |
| EXPECT_EQ(KeyF11::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF11::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // F12 -> F12 |
| EXPECT_EQ(KeyF12::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF12::Typed(), ui::EF_COMMAND_DOWN)); |
| } |
| |
| TEST_P(EventRewriterTest, RecordEventRemappedToRightClick) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember alt_remap_to_right_click; |
| IntegerPrefMember search_remap_to_right_click; |
| alt_remap_to_right_click.Init(prefs::kAltEventRemappedToRightClick, prefs()); |
| alt_remap_to_right_click.SetValue(0); |
| search_remap_to_right_click.Init(prefs::kSearchEventRemappedToRightClick, |
| prefs()); |
| search_remap_to_right_click.SetValue(0); |
| delegate_->RecordEventRemappedToRightClick(/*alt_based_right_click=*/false); |
| EXPECT_EQ(1, prefs()->GetInteger(prefs::kSearchEventRemappedToRightClick)); |
| delegate_->RecordEventRemappedToRightClick(/*alt_based_right_click=*/true); |
| EXPECT_EQ(1, prefs()->GetInteger(prefs::kAltEventRemappedToRightClick)); |
| } |
| |
| TEST_P( |
| EventRewriterTest, |
| TestFunctionKeysLayout2SuppressMetaTopRowKeyRewritesWithTreatTopRowAsFKeys) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| delegate_->SuppressMetaTopRowKeyComboRewrites(true); |
| keyboard_settings->suppress_meta_fkey_rewrites = true; |
| |
| // Enable preference treat-top-row-as-function-keys. |
| // That causes action keys to be mapped back to Fn keys. |
| BooleanPrefMember top_row_as_fn_key; |
| top_row_as_fn_key.Init(prefs::kSendFunctionKeys, prefs()); |
| top_row_as_fn_key.SetValue(true); |
| keyboard_settings->top_row_are_fkeys = true; |
| |
| // With Meta + Top Row Key rewrites suppressed and TopRowAsFKeys enabled, |
| // F-Keys should not be translated and search modifier should be kept. |
| SetUpKeyboard({.name = "Internal Keyboard", |
| .layout = kKbdTopRowLayout2Tag, |
| .type = ui::INPUT_DEVICE_INTERNAL, |
| .has_custom_top_row = false}); |
| |
| EXPECT_EQ(KeyF1::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF2::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF2::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF3::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF3::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF4::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF4::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF5::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF5::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF6::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF6::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF7::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF7::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF8::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF8::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF9::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF9::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF10::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF10::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF11::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF11::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF12::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF12::Typed(), ui::EF_COMMAND_DOWN)); |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteFunctionKeysWilcoLayouts) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| for (const auto& keyboard : kWilcoKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // F1 -> F1, Search + F1 -> Back |
| EXPECT_EQ(KeyF1::Typed(), RunRewriter(KeyF1::Typed())); |
| EXPECT_EQ(KeyBrowserBack::Typed(), |
| RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF1::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF1::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF1::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF1::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F2 -> F2, Search + F2 -> Refresh |
| EXPECT_EQ(KeyF2::Typed(), RunRewriter(KeyF2::Typed())); |
| EXPECT_EQ(KeyBrowserRefresh::Typed(), |
| RunRewriter(KeyF2::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF2::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF2::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF2::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF2::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F3 -> F3, Search + F3 -> Zoom (aka Fullscreen) |
| EXPECT_EQ(KeyF3::Typed(), RunRewriter(KeyF3::Typed())); |
| EXPECT_EQ(KeyZoomToggle::Typed(), |
| RunRewriter(KeyF3::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF3::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF3::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF3::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF3::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F4 -> F4, Search + F4 -> Launch App 1 |
| EXPECT_EQ(KeyF4::Typed(), RunRewriter(KeyF4::Typed())); |
| EXPECT_EQ( |
| std::vector({TestKeyEvent{ui::EventType::kKeyPressed, ui::DomCode::F4, |
| ui::DomKey::F4, ui::VKEY_MEDIA_LAUNCH_APP1, |
| ui::EF_NONE}, |
| TestKeyEvent{ui::EventType::kKeyReleased, ui::DomCode::F4, |
| ui::DomKey::F4, ui::VKEY_MEDIA_LAUNCH_APP1, |
| ui::EF_NONE}}), |
| RunRewriter(KeyF4::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF4::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF4::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF4::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF4::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F5 -> F5, Search + F5 -> Brightness down |
| EXPECT_EQ(KeyF5::Typed(), RunRewriter(KeyF5::Typed())); |
| EXPECT_EQ(KeyBrightnessDown::Typed(), |
| RunRewriter(KeyF5::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF5::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF5::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF5::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF5::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F6 -> F6, Search + F6 -> Brightness up |
| EXPECT_EQ(KeyF6::Typed(), RunRewriter(KeyF6::Typed())); |
| EXPECT_EQ(KeyBrightnessUp::Typed(), |
| RunRewriter(KeyF6::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF6::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF6::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF6::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF6::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F7 -> F7, Search + F7 -> Volume mute |
| EXPECT_EQ(KeyF7::Typed(), RunRewriter(KeyF7::Typed())); |
| EXPECT_EQ(KeyVolumeMute::Typed(), |
| RunRewriter(KeyF7::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF7::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF7::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF7::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF7::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F8 -> F8, Search + F8 -> Volume Down |
| EXPECT_EQ(KeyF8::Typed(), RunRewriter(KeyF8::Typed())); |
| EXPECT_EQ(KeyVolumeDown::Typed(), |
| RunRewriter(KeyF8::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF8::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF8::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF8::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF8::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F9 -> F9, Search + F9 -> Volume Up |
| EXPECT_EQ(KeyF9::Typed(), RunRewriter(KeyF9::Typed())); |
| EXPECT_EQ(KeyVolumeUp::Typed(), |
| RunRewriter(KeyF9::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF9::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF9::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF9::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF9::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F10 -> F10, Search + F10 -> F10 |
| EXPECT_EQ(KeyF10::Typed(), RunRewriter(KeyF10::Typed())); |
| EXPECT_EQ(KeyF10::Typed(), |
| RunRewriter(KeyF10::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF10::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF10::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF10::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF10::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F11 -> F11, Search + F11 -> F11 |
| EXPECT_EQ(KeyF11::Typed(), RunRewriter(KeyF11::Typed())); |
| EXPECT_EQ(KeyF11::Typed(), |
| RunRewriter(KeyF11::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF11::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF11::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF11::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF11::Typed(), ui::EF_ALT_DOWN)); |
| |
| // F12 -> F12 |
| // Search + F12 differs between Wilco devices so it is tested separately. |
| EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyF12::Typed())); |
| EXPECT_EQ(KeyF12::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyF12::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF12::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF12::Typed(), ui::EF_ALT_DOWN)); |
| |
| // The number row should not be rewritten without Search key. |
| EXPECT_EQ(KeyDigit1::Typed(), RunRewriter(KeyDigit1::Typed())); |
| EXPECT_EQ(KeyDigit2::Typed(), RunRewriter(KeyDigit2::Typed())); |
| EXPECT_EQ(KeyDigit3::Typed(), RunRewriter(KeyDigit3::Typed())); |
| EXPECT_EQ(KeyDigit4::Typed(), RunRewriter(KeyDigit4::Typed())); |
| EXPECT_EQ(KeyDigit5::Typed(), RunRewriter(KeyDigit5::Typed())); |
| EXPECT_EQ(KeyDigit6::Typed(), RunRewriter(KeyDigit6::Typed())); |
| EXPECT_EQ(KeyDigit7::Typed(), RunRewriter(KeyDigit7::Typed())); |
| EXPECT_EQ(KeyDigit8::Typed(), RunRewriter(KeyDigit8::Typed())); |
| EXPECT_EQ(KeyDigit9::Typed(), RunRewriter(KeyDigit9::Typed())); |
| EXPECT_EQ(KeyDigit0::Typed(), RunRewriter(KeyDigit0::Typed())); |
| EXPECT_EQ(KeyMinus::Typed(), RunRewriter(KeyMinus::Typed())); |
| EXPECT_EQ(KeyEqual::Typed(), RunRewriter(KeyEqual::Typed())); |
| } |
| |
| SetUpKeyboard(kWilco1_0Keyboard); |
| // Search + F12 -> Ctrl + Zoom (aka Fullscreen) (Display toggle) |
| EXPECT_EQ( |
| std::vector( |
| {TestKeyEvent{ui::EventType::kKeyPressed, ui::DomCode::F12, |
| ui::DomKey::F12, ui::VKEY_ZOOM, ui::EF_CONTROL_DOWN}, |
| TestKeyEvent{ui::EventType::kKeyReleased, ui::DomCode::F12, |
| ui::DomKey::F12, ui::VKEY_ZOOM, ui::EF_CONTROL_DOWN}}), |
| RunRewriter(KeyF12::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| SetUpKeyboard(kWilco1_5Keyboard); |
| // Search + F12 -> F12 (Privacy screen not supported) |
| event_rewriter_ash_->set_privacy_screen_for_testing(false); |
| EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyF12::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // F12 -> F12, Search + F12 -> Privacy Screen Toggle |
| event_rewriter_ash_->set_privacy_screen_for_testing(true); |
| EXPECT_EQ(KeyPrivacyScreenToggle::Typed(), |
| RunRewriter(KeyF12::Typed(), ui::EF_COMMAND_DOWN)); |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteActionKeysWilcoLayouts) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| for (const auto& keyboard : kWilcoKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Back -> Back, Search + Back -> F1 |
| EXPECT_EQ(KeyBrowserBack::Typed(), RunRewriter(KeyBrowserBack::Typed())); |
| EXPECT_EQ(KeyF1::Typed(), |
| RunRewriter(KeyBrowserBack::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Refresh -> Refresh, Search + Refresh -> F2 |
| EXPECT_EQ(KeyBrowserRefresh::Typed(), |
| RunRewriter(KeyBrowserRefresh::Typed())); |
| EXPECT_EQ(KeyF2::Typed(), |
| RunRewriter(KeyBrowserRefresh::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Full Screen -> Full Screen, Search + Full Screen -> F3 |
| EXPECT_EQ(KeyZoomToggle::Typed(), RunRewriter(KeyZoomToggle::Typed())); |
| EXPECT_EQ(KeyF3::Typed(), |
| RunRewriter(KeyZoomToggle::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Launch App 1 -> Launch App 1, Search + Launch App 1 -> F4 |
| EXPECT_EQ(KeySelectTask::Typed(), RunRewriter(KeySelectTask::Typed())); |
| EXPECT_EQ(KeyF4::Typed(), |
| RunRewriter(KeySelectTask::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Brightness down -> Brightness Down, Search Brightness Down -> F5 |
| EXPECT_EQ(KeyBrightnessDown::Typed(), |
| RunRewriter(KeyBrightnessDown::Typed())); |
| EXPECT_EQ(KeyF5::Typed(), |
| RunRewriter(KeyBrightnessDown::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Brightness up -> Brightness Up, Search + Brightness Up -> F6 |
| EXPECT_EQ(KeyBrightnessUp::Typed(), RunRewriter(KeyBrightnessUp::Typed())); |
| EXPECT_EQ(KeyF6::Typed(), |
| RunRewriter(KeyBrightnessUp::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Volume mute -> Volume Mute, Search + Volume Mute -> F7 |
| EXPECT_EQ(KeyVolumeMute::Typed(), RunRewriter(KeyVolumeMute::Typed())); |
| EXPECT_EQ(KeyF7::Typed(), |
| RunRewriter(KeyVolumeMute::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Volume Down -> Volume Down, Search + Volume Down -> F8 |
| EXPECT_EQ(KeyVolumeDown::Typed(), RunRewriter(KeyVolumeDown::Typed())); |
| EXPECT_EQ(KeyF8::Typed(), |
| RunRewriter(KeyVolumeDown::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Volume Up -> Volume Up, Search + Volume Up -> F9 |
| EXPECT_EQ(KeyVolumeUp::Typed(), RunRewriter(KeyVolumeUp::Typed())); |
| EXPECT_EQ(KeyF9::Typed(), |
| RunRewriter(KeyVolumeUp::Typed(), ui::EF_COMMAND_DOWN)); |
| } |
| |
| SetUpKeyboard(kWilco1_0Keyboard); |
| // Ctrl + Zoom (Display toggle) -> Unchanged |
| // Search + Ctrl + Zoom (Display toggle) -> F12 |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyZoomToggle::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyF12::Typed(), |
| RunRewriter(KeyZoomToggle::Typed(), |
| ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN)); |
| |
| SetUpKeyboard(kWilco1_5Keyboard); |
| { |
| // Drallion specific key tests (no privacy screen) |
| event_rewriter_ash_->set_privacy_screen_for_testing(false); |
| |
| // Privacy Screen Toggle -> F12 (Privacy Screen not supported), |
| // Search + Privacy Screen Toggle -> F12 |
| EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyPrivacyScreenToggle::Typed())); |
| EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyPrivacyScreenToggle::Typed(), |
| ui::EF_COMMAND_DOWN)); |
| |
| // Ctrl + Zoom (Display toggle) -> Unchanged |
| // Search + Ctrl + Zoom (Display toggle) -> Unchanged |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyZoomToggle::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyZoomToggle::Typed(), |
| ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN)); |
| } |
| |
| { |
| // Drallion specific key tests (privacy screen supported) |
| event_rewriter_ash_->set_privacy_screen_for_testing(true); |
| |
| // Privacy Screen Toggle -> Privacy Screen Toggle, |
| // Search + Privacy Screen Toggle -> F12 |
| EXPECT_EQ(KeyPrivacyScreenToggle::Typed(), |
| RunRewriter(KeyPrivacyScreenToggle::Typed())); |
| EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyPrivacyScreenToggle::Typed(), |
| ui::EF_COMMAND_DOWN)); |
| |
| // Ctrl + Zoom (Display toggle) -> Unchanged |
| // Search + Ctrl + Zoom (Display toggle) -> Unchanged |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyZoomToggle::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyZoomToggle::Typed(), |
| ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, |
| TestRewriteActionKeysWilcoLayoutsSuppressMetaTopRowKeyRewrites) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| delegate_->SuppressMetaTopRowKeyComboRewrites(true); |
| keyboard_settings->suppress_meta_fkey_rewrites = true; |
| |
| // With |SuppressMetaTopRowKeyComboRewrites|, all action keys should be |
| // unchanged and keep the search modifier. |
| |
| for (const auto& keyboard : kWilcoKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| EXPECT_EQ(KeyBrowserBack::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyBrowserBack::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyBrowserRefresh::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyBrowserRefresh::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyZoomToggle::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeySelectTask::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeySelectTask::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyBrightnessDown::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyBrightnessDown::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyBrightnessUp::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyBrightnessUp::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyVolumeMute::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyVolumeMute::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyVolumeDown::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyVolumeDown::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyVolumeUp::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyVolumeUp::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // F-Keys do not remove Search when pressed. |
| EXPECT_EQ(KeyF10::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF10::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF11::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF11::Typed(), ui::EF_COMMAND_DOWN)); |
| } |
| |
| SetUpKeyboard(kWilco1_0Keyboard); |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyZoomToggle::Typed(), |
| ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN)); |
| |
| SetUpKeyboard(kWilco1_5Keyboard); |
| { |
| // Drallion specific key tests (no privacy screen) |
| event_rewriter_ash_->set_privacy_screen_for_testing(false); |
| |
| // Search + Privacy Screen Toggle -> Search + F12 |
| EXPECT_EQ( |
| KeyF12::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyPrivacyScreenToggle::Typed(), ui::EF_COMMAND_DOWN)); |
| // Search + Ctrl + Zoom (Display toggle) -> Unchanged |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyZoomToggle::Typed(), |
| ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN)); |
| } |
| |
| { |
| // Drallion specific key tests (privacy screen supported) |
| event_rewriter_ash_->set_privacy_screen_for_testing(true); |
| |
| // Search + Privacy Screen Toggle -> F12 TODO |
| EXPECT_EQ( |
| KeyPrivacyScreenToggle::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyPrivacyScreenToggle::Typed(), ui::EF_COMMAND_DOWN)); |
| // Ctrl + Zoom (Display toggle) -> Unchanged TODO |
| // Search + Ctrl + Zoom (Display toggle) -> Unchanged |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyZoomToggle::Typed(), |
| ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN)); |
| } |
| } |
| |
| TEST_P( |
| EventRewriterTest, |
| TestRewriteActionKeysWilcoLayoutsSuppressMetaTopRowKeyRewritesWithTopRowAreFkeys) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| delegate_->SuppressMetaTopRowKeyComboRewrites(true); |
| keyboard_settings->suppress_meta_fkey_rewrites = true; |
| |
| // Enable preference treat-top-row-as-function-keys. |
| // That causes action keys to be mapped back to Fn keys. |
| BooleanPrefMember top_row_as_fn_key; |
| top_row_as_fn_key.Init(prefs::kSendFunctionKeys, prefs()); |
| top_row_as_fn_key.SetValue(true); |
| keyboard_settings->top_row_are_fkeys = true; |
| |
| // With |SuppressMetaTopRowKeyComboRewrites| and TopRowAreFKeys, all action |
| // keys should be remapped to F-Keys and keep the Search modifier. |
| for (const auto& keyboard : kWilcoKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| EXPECT_EQ(KeyF1::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyBrowserBack::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF2::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyBrowserRefresh::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF3::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyZoomToggle::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF4::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeySelectTask::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF5::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyBrightnessDown::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF6::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyBrightnessUp::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF7::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyVolumeMute::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF8::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyVolumeDown::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF9::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyVolumeUp::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| EXPECT_EQ(KeyF10::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF10::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF11::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF11::Typed(), ui::EF_COMMAND_DOWN)); |
| } |
| |
| SetUpKeyboard(kWilco1_0Keyboard); |
| EXPECT_EQ(KeyF12::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyZoomToggle::Typed(), |
| ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN)); |
| |
| SetUpKeyboard(kWilco1_5Keyboard); |
| { |
| // Drallion specific key tests (no privacy screen) |
| event_rewriter_ash_->set_privacy_screen_for_testing(false); |
| |
| // Search + Privacy Screen Toggle -> Search + F12 |
| EXPECT_EQ( |
| KeyF12::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyPrivacyScreenToggle::Typed(), ui::EF_COMMAND_DOWN)); |
| // Search + Ctrl + Zoom (Display toggle) -> Unchanged |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyZoomToggle::Typed(), |
| ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN)); |
| } |
| |
| { |
| // Drallion specific key tests (privacy screen supported) |
| event_rewriter_ash_->set_privacy_screen_for_testing(true); |
| |
| // Search + Privacy Screen Toggle -> F12 |
| EXPECT_EQ( |
| KeyF12::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyPrivacyScreenToggle::Typed(), ui::EF_COMMAND_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestTopRowAsFnKeysForKeyboardWilcoLayouts) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| // Enable preference treat-top-row-as-function-keys. |
| // That causes action keys to be mapped back to Fn keys, unless the search |
| // key is pressed. |
| BooleanPrefMember top_row_as_fn_key; |
| top_row_as_fn_key.Init(prefs::kSendFunctionKeys, prefs()); |
| top_row_as_fn_key.SetValue(true); |
| keyboard_settings->top_row_are_fkeys = true; |
| |
| for (const auto& keyboard : kWilcoKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Back -> F1, Search + Back -> Back |
| EXPECT_EQ(KeyF1::Typed(), RunRewriter(KeyBrowserBack::Typed())); |
| EXPECT_EQ(KeyBrowserBack::Typed(), |
| RunRewriter(KeyBrowserBack::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Refresh -> F2, Search + Refresh -> Refresh |
| EXPECT_EQ(KeyF2::Typed(), RunRewriter(KeyBrowserRefresh::Typed())); |
| EXPECT_EQ(KeyBrowserRefresh::Typed(), |
| RunRewriter(KeyBrowserRefresh::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Full Screen -> F3, Search + Full Screen -> Full Screen |
| EXPECT_EQ(KeyF3::Typed(), RunRewriter(KeyZoomToggle::Typed())); |
| EXPECT_EQ(KeyZoomToggle::Typed(), |
| RunRewriter(KeyZoomToggle::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Launch App 1 -> F4, Search + Launch App 1 -> Launch App 1 |
| EXPECT_EQ(KeyF4::Typed(), RunRewriter(KeySelectTask::Typed())); |
| EXPECT_EQ(KeySelectTask::Typed(), |
| RunRewriter(KeySelectTask::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Brightness down -> F5, Search Brightness Down -> Brightness Down |
| EXPECT_EQ(KeyF5::Typed(), RunRewriter(KeyBrightnessDown::Typed())); |
| EXPECT_EQ(KeyBrightnessDown::Typed(), |
| RunRewriter(KeyBrightnessDown::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Brightness up -> F6, Search + Brightness Up -> Brightness Up |
| EXPECT_EQ(KeyF6::Typed(), RunRewriter(KeyBrightnessUp::Typed())); |
| EXPECT_EQ(KeyBrightnessUp::Typed(), |
| RunRewriter(KeyBrightnessUp::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Volume mute -> F7, Search + Volume Mute -> Volume Mute |
| EXPECT_EQ(KeyF7::Typed(), RunRewriter(KeyVolumeMute::Typed())); |
| EXPECT_EQ(KeyVolumeMute::Typed(), |
| RunRewriter(KeyVolumeMute::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Volume Down -> F8, Search + Volume Down -> Volume Down |
| EXPECT_EQ(KeyF8::Typed(), RunRewriter(KeyVolumeDown::Typed())); |
| EXPECT_EQ(KeyVolumeDown::Typed(), |
| RunRewriter(KeyVolumeDown::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // Volume Up -> F9, Search + Volume Up -> Volume Up |
| EXPECT_EQ(KeyF9::Typed(), RunRewriter(KeyVolumeUp::Typed())); |
| EXPECT_EQ(KeyVolumeUp::Typed(), |
| RunRewriter(KeyVolumeUp::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // F10 -> F10 |
| EXPECT_EQ(KeyF10::Typed(), RunRewriter(KeyF10::Typed())); |
| EXPECT_EQ(KeyF10::Typed(), |
| RunRewriter(KeyF10::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // F11 -> F11 |
| EXPECT_EQ(KeyF11::Typed(), RunRewriter(KeyF11::Typed())); |
| EXPECT_EQ(KeyF11::Typed(), |
| RunRewriter(KeyF11::Typed(), ui::EF_COMMAND_DOWN)); |
| } |
| |
| SetUpKeyboard(kWilco1_0Keyboard); |
| // Ctrl + Zoom (Display toggle) -> F12 |
| // Search + Ctrl + Zoom (Display toggle) -> Search modifier should be removed |
| EXPECT_EQ(KeyF12::Typed(), |
| RunRewriter(KeyZoomToggle::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyZoomToggle::Typed(), |
| ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN)); |
| |
| SetUpKeyboard(kWilco1_5Keyboard); |
| { |
| // Drallion specific key tests (no privacy screen) |
| event_rewriter_ash_->set_privacy_screen_for_testing(false); |
| |
| // Privacy Screen Toggle -> F12, |
| // Search + Privacy Screen Toggle -> F12 (Privacy screen not supported) |
| EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyPrivacyScreenToggle::Typed())); |
| EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyPrivacyScreenToggle::Typed(), |
| ui::EF_COMMAND_DOWN)); |
| |
| // Ctrl + Zoom (Display toggle) -> Unchanged |
| // Search + Ctrl + Zoom (Display toggle) -> Unchanged |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyZoomToggle::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyZoomToggle::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyZoomToggle::Typed(), |
| ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN)); |
| } |
| |
| { |
| // Drallion specific key tests (privacy screen supported) |
| event_rewriter_ash_->set_privacy_screen_for_testing(true); |
| |
| // Privacy Screen Toggle -> F12, |
| // Search + Privacy Screen Toggle -> Unchanged |
| EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyPrivacyScreenToggle::Typed())); |
| EXPECT_EQ( |
| KeyPrivacyScreenToggle::Typed(), |
| RunRewriter(KeyPrivacyScreenToggle::Typed(), ui::EF_COMMAND_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteFunctionKeysInvalidLayout) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| // Not adding a keyboard simulates a failure in getting top row layout, which |
| // will fallback to Layout1 in which case keys are rewritten to their default |
| // values. |
| EXPECT_EQ(KeyBrowserForward::Typed(), RunRewriter(KeyF2::Typed())); |
| EXPECT_EQ(KeyBrowserRefresh::Typed(), RunRewriter(KeyF3::Typed())); |
| EXPECT_EQ(KeyZoomToggle::Typed(), RunRewriter(KeyF4::Typed())); |
| EXPECT_EQ(KeyBrightnessUp::Typed(), RunRewriter(KeyF7::Typed())); |
| |
| // Adding a keyboard with a valid layout will take effect. |
| SetUpKeyboard({.name = "Internal Keyboard", |
| .layout = kKbdTopRowLayout2Tag, |
| .type = ui::INPUT_DEVICE_INTERNAL, |
| .has_custom_top_row = false}); |
| EXPECT_EQ(KeyBrowserRefresh::Typed(), RunRewriter(KeyF2::Typed())); |
| EXPECT_EQ(KeyZoomToggle::Typed(), RunRewriter(KeyF3::Typed())); |
| EXPECT_EQ(KeySelectTask::Typed(), RunRewriter(KeyF4::Typed())); |
| EXPECT_EQ(KeyMediaPlayPause::Typed(), RunRewriter(KeyF7::Typed())); |
| } |
| |
| // Tests that event rewrites still work even if modifiers are remapped. |
| TEST_P(EventRewriterTest, TestRewriteExtendedKeysWithControlRemapped) { |
| // Remap Control to Search. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndDisableFeature( |
| features::kAltClickAndSixPackCustomization); |
| IntegerPrefMember search; |
| InitModifierKeyPref(&search, ::prefs::kLanguageRemapControlKeyTo, |
| ui::mojom::ModifierKey::kControl, |
| ui::mojom::ModifierKey::kMeta); |
| |
| for (const auto& keyboard : kChromeKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| EXPECT_EQ(KeyEnd::Typed(), |
| RunRewriter(KeyArrowRight::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyEnd::Typed(ui::EF_SHIFT_DOWN), |
| RunRewriter(KeyArrowRight::Typed(), |
| ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteKeyEventSentByXSendEvent) { |
| // Remap Control to Alt. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember control; |
| InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo, |
| ui::mojom::ModifierKey::kControl, |
| ui::mojom::ModifierKey::kAlt); |
| |
| SetUpKeyboard(kInternalChromeKeyboard); |
| |
| // Send left control press. |
| { |
| // Control should NOT be remapped to Alt if EF_FINAL is set. |
| EXPECT_EQ(KeyLControl::Typed(ui::EF_FINAL), |
| SendKeyEvents(KeyLControl::Typed(ui::EF_FINAL))); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, TestRewriteNonNativeEvent) { |
| // Remap Control to Alt. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember control; |
| InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo, |
| ui::mojom::ModifierKey::kControl, |
| ui::mojom::ModifierKey::kAlt); |
| |
| SetUpKeyboard(kInternalChromeKeyboard); |
| |
| // For EF_CONTROL_DOWN modifier. |
| SendKeyEvent(KeyLControl::Pressed()); |
| |
| const int kTouchId = 2; |
| gfx::Point location(0, 0); |
| ui::TouchEvent press( |
| ui::EventType::kTouchPressed, location, base::TimeTicks(), |
| ui::PointerDetails(ui::EventPointerType::kTouch, kTouchId)); |
| press.SetFlags(ui::EF_CONTROL_DOWN); |
| |
| source().Send(&press); |
| auto events = TakeEvents(); |
| ASSERT_EQ(1u, events.size()); |
| // Control should be remapped to Alt. |
| EXPECT_EQ(ui::EF_ALT_DOWN, |
| events[0]->flags() & (ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)); |
| } |
| |
| TEST_P(EventRewriterTest, TopRowKeysAreFunctionKeys) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(1)); |
| wm::ActivateWindow(window.get()); |
| |
| // Create a simulated keypress of F1 targetted at the window. |
| ui::KeyEvent press_f1(ui::EventType::kKeyPressed, ui::VKEY_F1, |
| ui::DomCode::F1, ui::EF_NONE, ui::DomKey::F1, |
| ui::EventTimeForNow()); |
| |
| // The event should also not be rewritten if the send-function-keys pref is |
| // additionally set, for both apps v2 and regular windows. |
| BooleanPrefMember send_function_keys_pref; |
| send_function_keys_pref.Init(prefs::kSendFunctionKeys, prefs()); |
| send_function_keys_pref.SetValue(true); |
| keyboard_settings->top_row_are_fkeys = true; |
| EXPECT_EQ(KeyF1::Typed(), RunRewriter(KeyF1::Typed())); |
| |
| // If the pref isn't set when an event is sent to a regular window, F1 is |
| // rewritten to the back key. |
| send_function_keys_pref.SetValue(false); |
| keyboard_settings->top_row_are_fkeys = false; |
| EXPECT_EQ(KeyBrowserBack::Typed(), RunRewriter(KeyF1::Typed())); |
| } |
| |
| // Parameterized version of test with the same name that accepts the |
| // event flags that correspond to a right-click. This will be either |
| // Alt+Click or Search+Click. After a transition period this will |
| // default to Search+Click and the Alt+Click logic will be removed. |
| void EventRewriterTestBase::DontRewriteIfNotRewritten(int right_click_flags) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| ui::DeviceDataManager* device_data_manager = |
| ui::DeviceDataManager::GetInstance(); |
| std::vector<ui::TouchpadDevice> touchpad_devices(2); |
| touchpad_devices[0].id = kTouchpadId1; |
| touchpad_devices[1].id = kTouchpadId2; |
| static_cast<ui::DeviceHotplugEventObserver*>(device_data_manager) |
| ->OnTouchpadDevicesUpdated(touchpad_devices); |
| std::vector<ui::InputDevice> mouse_devices(1); |
| constexpr int kMouseId = 12; |
| touchpad_devices[0].id = kMouseId; |
| static_cast<ui::DeviceHotplugEventObserver*>(device_data_manager) |
| ->OnMouseDevicesUpdated(mouse_devices); |
| |
| // Test (Alt|Search) + Left click. |
| { |
| ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), right_click_flags, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_press(&press); |
| test_press.set_source_device_id(kTouchpadId1); |
| // Sanity check. |
| EXPECT_EQ(ui::EventType::kMousePressed, press.type()); |
| EXPECT_EQ(right_click_flags, press.flags()); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(press); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & result.flags()); |
| EXPECT_NE(right_click_flags, right_click_flags & result.flags()); |
| EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, result.changed_button_flags()); |
| } |
| { |
| ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), |
| right_click_flags, ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_release(&release); |
| test_release.set_source_device_id(kTouchpadId1); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(release); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & result.flags()); |
| EXPECT_NE(right_click_flags, right_click_flags & result.flags()); |
| EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, result.changed_button_flags()); |
| } |
| |
| // No (ALT|SEARCH) in first click. |
| { |
| ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), |
| ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_press(&press); |
| test_press.set_source_device_id(kTouchpadId1); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(press); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & result.flags()); |
| EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags()); |
| } |
| { |
| ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), |
| right_click_flags, ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_release(&release); |
| test_release.set_source_device_id(kTouchpadId1); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(release); |
| EXPECT_EQ(right_click_flags, right_click_flags & result.flags()); |
| EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags()); |
| } |
| |
| // ALT on different device. |
| { |
| ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), right_click_flags, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_press(&press); |
| test_press.set_source_device_id(kTouchpadId2); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(press); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & result.flags()); |
| EXPECT_NE(right_click_flags, right_click_flags & result.flags()); |
| EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, result.changed_button_flags()); |
| } |
| { |
| ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), |
| right_click_flags, ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_release(&release); |
| test_release.set_source_device_id(kTouchpadId1); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(release); |
| EXPECT_EQ(right_click_flags, right_click_flags & result.flags()); |
| EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags()); |
| } |
| { |
| ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), |
| right_click_flags, ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_release(&release); |
| test_release.set_source_device_id(kTouchpadId2); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(release); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & result.flags()); |
| EXPECT_NE(right_click_flags, right_click_flags & result.flags()); |
| EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, result.changed_button_flags()); |
| } |
| |
| // No rewrite for non-touchpad devices. |
| { |
| ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), right_click_flags, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_press(&press); |
| test_press.set_source_device_id(kMouseId); |
| EXPECT_EQ(ui::EventType::kMousePressed, press.type()); |
| EXPECT_EQ(right_click_flags, press.flags()); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(press); |
| EXPECT_EQ(right_click_flags, right_click_flags & result.flags()); |
| EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags()); |
| } |
| { |
| ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), |
| right_click_flags, ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_release(&release); |
| test_release.set_source_device_id(kMouseId); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(release); |
| EXPECT_EQ(right_click_flags, right_click_flags & result.flags()); |
| EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags()); |
| } |
| |
| // Still rewrite to right button, even if the modifier key is already |
| // released when the mouse release event happens |
| // This is for regressions such as: |
| // https://crbug.com/1399284 |
| // https://crbug.com/1417079 |
| { |
| ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), right_click_flags, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_press(&press); |
| test_press.set_source_device_id(kTouchpadId1); |
| // Sanity check. |
| EXPECT_EQ(ui::EventType::kMousePressed, press.type()); |
| EXPECT_EQ(right_click_flags, press.flags()); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(press); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & result.flags()); |
| EXPECT_NE(right_click_flags, right_click_flags & result.flags()); |
| EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, result.changed_button_flags()); |
| } |
| { |
| ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), |
| ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_release(&release); |
| test_release.set_source_device_id(kTouchpadId1); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(release); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & result.flags()); |
| EXPECT_NE(right_click_flags, right_click_flags & result.flags()); |
| EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, result.changed_button_flags()); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, DontRewriteIfNotRewritten_AltClickIsRightClick) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndDisableFeature( |
| features::kAltClickAndSixPackCustomization); |
| DontRewriteIfNotRewritten(ui::EF_LEFT_MOUSE_BUTTON | ui::EF_ALT_DOWN); |
| EXPECT_EQ(message_center_.NotificationCount(), 0u); |
| } |
| |
| TEST_P(EventRewriterTest, DontRewriteIfNotRewritten_AltClickIsRightClick_New) { |
| // Enabling the kImprovedKeyboardShortcuts feature does not change alt+click |
| // behavior or create a notification. |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitWithFeatures( |
| {}, {features::kAltClickAndSixPackCustomization}); |
| DontRewriteIfNotRewritten(ui::EF_LEFT_MOUSE_BUTTON | ui::EF_ALT_DOWN); |
| EXPECT_EQ(message_center_.NotificationCount(), 0u); |
| } |
| |
| TEST_P(EventRewriterTest, DontRewriteIfNotRewritten_SearchClickIsRightClick) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitWithFeatures( |
| {features::kUseSearchClickForRightClick}, |
| {features::kAltClickAndSixPackCustomization}); |
| DontRewriteIfNotRewritten(ui::EF_LEFT_MOUSE_BUTTON | ui::EF_COMMAND_DOWN); |
| EXPECT_EQ(message_center_.NotificationCount(), 0u); |
| } |
| |
| TEST_P(EventRewriterTest, DontRewriteIfNotRewritten_AltClickDeprecated) { |
| // Pressing search+click with alt+click deprecated works, but does not |
| // generate a notification. |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitWithFeatures( |
| {::features::kDeprecateAltClick}, |
| {features::kAltClickAndSixPackCustomization}); |
| DontRewriteIfNotRewritten(ui::EF_LEFT_MOUSE_BUTTON | ui::EF_COMMAND_DOWN); |
| EXPECT_EQ(message_center_.NotificationCount(), 0u); |
| } |
| |
| TEST_P(EventRewriterTest, DeprecatedAltClickGeneratesNotification) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitWithFeatures( |
| {::features::kDeprecateAltClick}, |
| {features::kAltClickAndSixPackCustomization}); |
| ui::DeviceDataManager* device_data_manager = |
| ui::DeviceDataManager::GetInstance(); |
| std::vector<ui::TouchpadDevice> touchpad_devices(1); |
| constexpr int kTouchpadId1 = 10; |
| touchpad_devices[0].id = kTouchpadId1; |
| static_cast<ui::DeviceHotplugEventObserver*>(device_data_manager) |
| ->OnTouchpadDevicesUpdated(touchpad_devices); |
| std::vector<ui::InputDevice> mouse_devices(1); |
| constexpr int kMouseId = 12; |
| touchpad_devices[0].id = kMouseId; |
| static_cast<ui::DeviceHotplugEventObserver*>(device_data_manager) |
| ->OnMouseDevicesUpdated(mouse_devices); |
| |
| const int deprecated_flags = ui::EF_LEFT_MOUSE_BUTTON | ui::EF_ALT_DOWN; |
| |
| // Alt + Left click => No rewrite. |
| { |
| ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), deprecated_flags, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_press(&press); |
| test_press.set_source_device_id(kTouchpadId1); |
| // Sanity check. |
| EXPECT_EQ(ui::EventType::kMousePressed, press.type()); |
| EXPECT_EQ(deprecated_flags, press.flags()); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(press); |
| |
| // No rewrite occurred. |
| EXPECT_EQ(deprecated_flags, deprecated_flags & result.flags()); |
| EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags()); |
| |
| // Expect a deprecation notification. |
| EXPECT_EQ(message_center_.NotificationCount(), 1u); |
| ClearNotifications(); |
| } |
| { |
| ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), |
| deprecated_flags, ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_release(&release); |
| test_release.set_source_device_id(kTouchpadId1); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(release); |
| |
| // No rewrite occurred. |
| EXPECT_EQ(deprecated_flags, deprecated_flags & result.flags()); |
| EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags()); |
| |
| // Don't expect a new notification on release. |
| EXPECT_EQ(message_center_.NotificationCount(), 0u); |
| } |
| |
| // No rewrite or notification for non-touchpad devices. |
| { |
| ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), deprecated_flags, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_press(&press); |
| test_press.set_source_device_id(kMouseId); |
| EXPECT_EQ(ui::EventType::kMousePressed, press.type()); |
| EXPECT_EQ(deprecated_flags, press.flags()); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(press); |
| EXPECT_EQ(deprecated_flags, deprecated_flags & result.flags()); |
| EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags()); |
| |
| // No notification expected for this case. |
| EXPECT_EQ(message_center_.NotificationCount(), 0u); |
| } |
| { |
| ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), |
| deprecated_flags, ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_release(&release); |
| test_release.set_source_device_id(kMouseId); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(release); |
| EXPECT_EQ(deprecated_flags, deprecated_flags & result.flags()); |
| EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags()); |
| |
| // No notification expected for this case. |
| EXPECT_EQ(message_center_.NotificationCount(), 0u); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, StickyKeyEventDispatchImpl) { |
| Shell::Get()->sticky_keys_controller()->Enable(true); |
| // Test the actual key event dispatch implementation. |
| { |
| auto events = SendKeyEvents(KeyLControl::Typed()); |
| ASSERT_EQ(1u, events.size()); |
| EXPECT_EQ(KeyLControl::Pressed(), events[0]); |
| } |
| |
| // Test key press event is correctly modified and modifier release |
| // event is sent. |
| { |
| auto events = SendKeyEvent(KeyC::Pressed()); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_EQ(KeyC::Pressed(ui::EF_CONTROL_DOWN), events[0]); |
| EXPECT_EQ(KeyLControl::Released(), events[1]); |
| } |
| |
| // Test key release event is not modified. |
| { |
| auto events = SendKeyEvent(KeyC::Released()); |
| ASSERT_EQ(1u, events.size()); |
| EXPECT_EQ(KeyC::Released(), events[0]); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, MouseEventDispatchImpl) { |
| Shell::Get()->sticky_keys_controller()->Enable(true); |
| SendKeyEvents(KeyLControl::Typed()); |
| |
| // Test mouse press event is correctly modified. |
| gfx::Point location(0, 0); |
| ui::MouseEvent press(ui::EventType::kMousePressed, location, location, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventDispatchDetails details = source().Send(&press); |
| ASSERT_FALSE(details.dispatcher_destroyed); |
| { |
| auto events = TakeEvents(); |
| ASSERT_EQ(1u, events.size()); |
| EXPECT_EQ(ui::EventType::kMousePressed, events[0]->type()); |
| EXPECT_TRUE(events[0]->flags() & ui::EF_CONTROL_DOWN); |
| } |
| |
| // Test mouse release event is correctly modified and modifier release |
| // event is sent. The mouse event should have the correct DIP location. |
| ui::MouseEvent release(ui::EventType::kMouseReleased, location, location, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| details = source().Send(&release); |
| ASSERT_FALSE(details.dispatcher_destroyed); |
| { |
| auto events = TakeEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_EQ(ui::EventType::kMouseReleased, events[0]->type()); |
| EXPECT_TRUE(events[0]->flags() & ui::EF_CONTROL_DOWN); |
| |
| ASSERT_EQ(ui::EventType::kKeyReleased, events[1]->type()); |
| EXPECT_EQ(ui::VKEY_CONTROL, events[1]->AsKeyEvent()->key_code()); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, MouseWheelEventDispatchImpl) { |
| Shell::Get()->sticky_keys_controller()->Enable(true); |
| // Test positive mouse wheel event is correctly modified and modifier release |
| // event is sent. |
| SendKeyEvents(KeyLControl::Typed()); |
| |
| gfx::Point location(0, 0); |
| ui::MouseWheelEvent positive( |
| gfx::Vector2d(0, ui::MouseWheelEvent::kWheelDelta), location, location, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventDispatchDetails details = source().Send(&positive); |
| ASSERT_FALSE(details.dispatcher_destroyed); |
| { |
| auto events = TakeEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_TRUE(events[0]->IsMouseWheelEvent()); |
| EXPECT_TRUE(events[0]->flags() & ui::EF_CONTROL_DOWN); |
| |
| ASSERT_EQ(ui::EventType::kKeyReleased, events[1]->type()); |
| EXPECT_EQ(ui::VKEY_CONTROL, events[1]->AsKeyEvent()->key_code()); |
| } |
| |
| // Test negative mouse wheel event is correctly modified and modifier release |
| // event is sent. |
| SendKeyEvents({KeyLControl::Pressed(), KeyLControl::Released()}); |
| |
| ui::MouseWheelEvent negative( |
| gfx::Vector2d(0, -ui::MouseWheelEvent::kWheelDelta), location, location, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| details = source().Send(&negative); |
| ASSERT_FALSE(details.dispatcher_destroyed); |
| { |
| auto events = TakeEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_TRUE(events[0]->IsMouseWheelEvent()); |
| EXPECT_TRUE(events[0]->flags() & ui::EF_CONTROL_DOWN); |
| |
| ASSERT_EQ(ui::EventType::kKeyReleased, events[1]->type()); |
| EXPECT_EQ(ui::VKEY_CONTROL, events[1]->AsKeyEvent()->key_code()); |
| } |
| } |
| |
| // Tests that if modifier keys are remapped, the flags of a mouse wheel event |
| // will be rewritten properly. |
| TEST_P(EventRewriterTest, MouseWheelEventModifiersRewritten) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| // For EF_CONTROL_DOWN modifier. |
| SendKeyEvent(KeyLControl::Pressed()); |
| |
| // Generate a mouse wheel event that has a CONTROL_DOWN modifier flag and |
| // expect that no rewriting happens as no modifier remapping is active. |
| gfx::Point location(0, 0); |
| ui::MouseWheelEvent positive( |
| gfx::Vector2d(0, ui::MouseWheelEvent::kWheelDelta), location, location, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON | ui::EF_CONTROL_DOWN, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventDispatchDetails details = source().Send(&positive); |
| ASSERT_FALSE(details.dispatcher_destroyed); |
| { |
| auto events = TakeEvents(); |
| ASSERT_EQ(1u, events.size()); |
| EXPECT_TRUE(events[0]->IsMouseWheelEvent()); |
| EXPECT_TRUE(events[0]->flags() & ui::EF_CONTROL_DOWN); |
| } |
| |
| // Remap Control to Alt. |
| IntegerPrefMember control; |
| InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo, |
| ui::mojom::ModifierKey::kControl, |
| ui::mojom::ModifierKey::kAlt); |
| |
| // Sends the same events once again and expect that it will be rewritten to |
| // ALT_DOWN in older implementation, or not rewritten (as Control is held) |
| // in the new implementation. |
| details = source().Send(&positive); |
| ASSERT_FALSE(details.dispatcher_destroyed); |
| { |
| auto events = TakeEvents(); |
| ASSERT_EQ(1u, events.size()); |
| EXPECT_TRUE(events[0]->IsMouseWheelEvent()); |
| if (ash::features::IsKeyboardRewriterFixEnabled()) { |
| EXPECT_TRUE(events[0]->flags() & ui::EF_CONTROL_DOWN); |
| EXPECT_FALSE(events[0]->flags() & ui::EF_ALT_DOWN); |
| } else { |
| EXPECT_FALSE(events[0]->flags() & ui::EF_CONTROL_DOWN); |
| EXPECT_TRUE(events[0]->flags() & ui::EF_ALT_DOWN); |
| } |
| } |
| } |
| |
| TEST_P(EventRewriterTest, MouseEventMaintainNativeEvent) { |
| if (!features::IsKeyboardRewriterFixEnabled()) { |
| GTEST_SKIP() << "Test is only valid with keyboard rewriter fix enabled"; |
| } |
| |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| gfx::Point location(0, 0); |
| ui::MouseEvent native_event(ui::EventType::kMouseMoved, location, location, |
| /*time_stamp=*/{}, ui::EF_CONTROL_DOWN, |
| ui::EF_NONE); |
| ui::MouseEvent mouse_event(&native_event); |
| // Remap Control to Alt. |
| IntegerPrefMember control; |
| InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo, |
| ui::mojom::ModifierKey::kControl, |
| ui::mojom::ModifierKey::kAlt); |
| |
| SendKeyEvent(KeyLControl::Pressed()); |
| |
| // Sends the same events once again and expect that it will be rewritten to |
| // ALT_DOWN in older implementation, or not rewritten (as Control is held) |
| // in the new implementation. |
| ui::EventDispatchDetails details = source().Send(&mouse_event); |
| ASSERT_FALSE(details.dispatcher_destroyed); |
| { |
| auto events = TakeEvents(); |
| ASSERT_EQ(1u, events.size()); |
| EXPECT_TRUE(events[0]->IsMouseEvent()); |
| EXPECT_FALSE(events[0]->flags() & ui::EF_CONTROL_DOWN); |
| EXPECT_TRUE(events[0]->flags() & ui::EF_ALT_DOWN); |
| EXPECT_TRUE(events[0]->HasNativeEvent()); |
| } |
| } |
| |
| // Tests edge cases of key event rewriting (see https://crbug.com/913209). |
| TEST_P(EventRewriterTest, KeyEventRewritingEdgeCases) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndDisableFeature( |
| features::kAltClickAndSixPackCustomization); |
| |
| // Edge case 1: Press the Launcher button first. Then press the Up Arrow |
| // button. |
| { |
| auto events = SendKeyEvents( |
| {KeyLMeta::Pressed(), KeyArrowUp::Pressed(ui::EF_COMMAND_DOWN)}); |
| EXPECT_EQ(2u, events.size()); |
| } |
| |
| // When releasing the Launcher button, the rewritten event should be released |
| // as well. |
| if (features::IsKeyboardRewriterFixEnabled()) { |
| EXPECT_EQ(std::vector({KeyLMeta::Released()}), |
| SendKeyEvent(KeyLMeta::Released())); |
| EXPECT_EQ(std::vector({KeyPageUp::Released(), KeyArrowUp::Pressed()}), |
| SendKeyEvent(KeyArrowUp::Pressed())); |
| } else { |
| auto events = SendKeyEvent(KeyLMeta::Released()); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_EQ(KeyLMeta::Released(), events[0]); |
| EXPECT_EQ(KeyPageUp::Released(), events[1]); |
| } |
| |
| // Edge case 2: Press the Up Arrow button first. Then press the Launch button. |
| { |
| auto events = SendKeyEvents({KeyArrowUp::Pressed(), KeyLMeta::Pressed()}); |
| EXPECT_EQ(2u, events.size()); |
| } |
| |
| // When releasing the Up Arrow button, the rewritten event should be blocked. |
| { |
| auto events = SendKeyEvent(KeyArrowUp::Released(ui::EF_COMMAND_DOWN)); |
| ASSERT_EQ(1u, events.size()); |
| EXPECT_EQ(KeyArrowUp::Released(ui::EF_COMMAND_DOWN), events[0]); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, ScrollEventDispatchImpl) { |
| Shell::Get()->sticky_keys_controller()->Enable(true); |
| // Test scroll event is correctly modified. |
| SendKeyEvents(KeyLControl::Typed()); |
| |
| gfx::PointF location(0, 0); |
| ui::ScrollEvent scroll(ui::EventType::kScroll, location, location, |
| ui::EventTimeForNow(), 0 /* flag */, 0 /* x_offset */, |
| 1 /* y_offset */, 0 /* x_offset_ordinal */, |
| 1 /* y_offset_ordinal */, 2 /* finger */); |
| ui::EventDispatchDetails details = source().Send(&scroll); |
| ASSERT_FALSE(details.dispatcher_destroyed); |
| { |
| auto events = TakeEvents(); |
| ASSERT_EQ(1u, events.size()); |
| EXPECT_TRUE(events[0]->IsScrollEvent()); |
| EXPECT_TRUE(events[0]->flags() & ui::EF_CONTROL_DOWN); |
| } |
| |
| // Test FLING_START event deactivates the sticky key, but is modified. |
| ui::ScrollEvent fling_start( |
| ui::EventType::kScrollFlingStart, location, location, |
| ui::EventTimeForNow(), 0 /* flag */, 0 /* x_offset */, 0 /* y_offset */, |
| 0 /* x_offset_ordinal */, 0 /* y_offset_ordinal */, 2 /* finger */); |
| details = source().Send(&fling_start); |
| { |
| auto events = TakeEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_TRUE(events[0]->IsScrollEvent()); |
| EXPECT_TRUE(events[0]->flags() & ui::EF_CONTROL_DOWN); |
| |
| ASSERT_EQ(ui::EventType::kKeyReleased, events[1]->type()); |
| EXPECT_EQ(ui::VKEY_CONTROL, events[1]->AsKeyEvent()->key_code()); |
| } |
| |
| // Test scroll direction change causes that modifier release event is sent. |
| SendKeyEvents(KeyLControl::Typed()); |
| |
| details = source().Send(&scroll); |
| ASSERT_FALSE(details.dispatcher_destroyed); |
| std::ignore = TakeEvents(); |
| |
| ui::ScrollEvent scroll2(ui::EventType::kScroll, location, location, |
| ui::EventTimeForNow(), 0 /* flag */, 0 /* x_offset */, |
| -1 /* y_offset */, 0 /* x_offset_ordinal */, |
| -1 /* y_offset_ordinal */, 2 /* finger */); |
| details = source().Send(&scroll2); |
| ASSERT_FALSE(details.dispatcher_destroyed); |
| { |
| auto events = TakeEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_TRUE(events[0]->IsScrollEvent()); |
| EXPECT_FALSE(events[0]->flags() & ui::EF_CONTROL_DOWN); |
| |
| ASSERT_EQ(ui::EventType::kKeyReleased, events[1]->type()); |
| EXPECT_EQ(ui::VKEY_CONTROL, events[1]->AsKeyEvent()->key_code()); |
| } |
| } |
| |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| TEST_P(EventRewriterTest, RemapHangulOnCros1p) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember alt; |
| InitModifierKeyPref(&alt, ::prefs::kLanguageRemapAltKeyTo, |
| ui::mojom::ModifierKey::kAlt, |
| ui::mojom::ModifierKey::kAlt); |
| |
| scoped_refptr<input_method::MockInputMethodManagerImpl::State> state = |
| base::MakeRefCounted<input_method::MockInputMethodManagerImpl::State>( |
| input_method_manager_mock_); |
| input_method_manager_mock_->SetState(state); |
| |
| for (const auto& keyboard : kAllKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| state->current_input_method_id = |
| base::StrCat({kCros1pInputMethodIdPrefix, "ko-t-i0-und"}); |
| EXPECT_EQ(KeyHangulMode::Typed(), RunRewriter(KeyHangulMode::Typed())); |
| EXPECT_EQ(KeyLAlt::Typed(), RunRewriter(KeyLAlt::Typed())); |
| EXPECT_EQ(KeyRAlt::Typed(), RunRewriter(KeyRAlt::Typed())); |
| |
| state->current_input_method_id = |
| base::StrCat({kCros1pInputMethodIdPrefix, "xkb:us::eng"}); |
| EXPECT_EQ(KeyRAlt::Typed(), RunRewriter(KeyHangulMode::Typed())); |
| EXPECT_EQ(KeyLAlt::Typed(), RunRewriter(KeyLAlt::Typed())); |
| EXPECT_EQ(KeyRAlt::Typed(), RunRewriter(KeyRAlt::Typed())); |
| } |
| } |
| #endif |
| |
| class EventRewriterInputSettingsSplitDisabledTest : public EventRewriterTest { |
| public: |
| void SetUp() override { |
| settings_split_disable_feature_list_.InitAndDisableFeature( |
| features::kInputDeviceSettingsSplit); |
| EventRewriterTest::SetUp(); |
| } |
| |
| void TearDown() override { |
| EventRewriterTest::TearDown(); |
| settings_split_disable_feature_list_.Reset(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList settings_split_disable_feature_list_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| EventRewriterInputSettingsSplitDisabledTest, |
| testing::Combine(testing::Bool(), testing::Bool())); |
| |
| TEST_P(EventRewriterInputSettingsSplitDisabledTest, |
| TestRewriteCommandToControl) { |
| // First, test non Apple keyboards, they should all behave the same. |
| for (const auto& keyboard : kNonAppleKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // VKEY_A, Alt modifier. |
| EXPECT_EQ(KeyA::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN)); |
| |
| // VKEY_A, Win modifier. |
| EXPECT_EQ(KeyA::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // VKEY_A, Alt+Win modifier. |
| EXPECT_EQ( |
| KeyA::Typed(ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN)); |
| |
| // VKEY_LWIN (left Windows key), Alt modifier. |
| EXPECT_EQ(KeyLMeta::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN)); |
| |
| // VKEY_RWIN (right Windows key), Alt modifier. |
| EXPECT_EQ(KeyRMeta::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyRMeta::Typed(), ui::EF_ALT_DOWN)); |
| } |
| |
| // Simulate the default initialization of the Apple Command key remap pref to |
| // Ctrl. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| { |
| SCOPED_TRACE(kExternalAppleKeyboard.name); |
| SetUpKeyboard(kExternalAppleKeyboard); |
| |
| // VKEY_A, Alt modifier. |
| EXPECT_EQ(KeyA::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN)); |
| |
| // VKEY_A, Win modifier. |
| EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // VKEY_A, Alt+Win modifier. |
| EXPECT_EQ( |
| KeyA::Typed(ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN)); |
| |
| // VKEY_LWIN (left Windows key), Alt modifier. |
| EXPECT_EQ(KeyLControl::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN)); |
| |
| // VKEY_RWIN (right Windows key), Alt modifier. |
| EXPECT_EQ(KeyRControl::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyRMeta::Typed(), ui::EF_ALT_DOWN)); |
| } |
| |
| // Now simulate the user remapped the Command key back to Search. |
| IntegerPrefMember command; |
| InitModifierKeyPref(&command, ::prefs::kLanguageRemapExternalCommandKeyTo, |
| ui::mojom::ModifierKey::kMeta, |
| ui::mojom::ModifierKey::kMeta); |
| { |
| SCOPED_TRACE(kExternalAppleKeyboard.name); |
| SetUpKeyboard(kExternalAppleKeyboard); |
| |
| // VKEY_A, Alt modifier. |
| EXPECT_EQ(KeyA::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN)); |
| |
| // VKEY_A, Win modifier. |
| EXPECT_EQ(KeyA::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // VKEY_A, Alt+Win modifier. |
| EXPECT_EQ( |
| KeyA::Typed(ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN)); |
| |
| // VKEY_LWIN (left Windows key), Alt modifier. |
| EXPECT_EQ(KeyLMeta::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN)); |
| |
| // VKEY_RWIN (right Windows key), Alt modifier. |
| EXPECT_EQ(KeyRMeta::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyRMeta::Typed(), ui::EF_ALT_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterInputSettingsSplitDisabledTest, |
| TestRewriteExternalMetaKey) { |
| // Simulate the default initialization of the Meta key on external keyboards |
| // remap pref to Search. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| // By default, the Meta key on all keyboards, internal, external Chrome OS |
| // branded keyboards, and Generic keyboards should produce Search. |
| for (const auto& keyboard : kNonAppleKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // VKEY_A, Win modifier. |
| EXPECT_EQ(KeyA::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // VKEY_A, Alt+Win modifier. |
| EXPECT_EQ( |
| KeyA::Typed(ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN)); |
| |
| // VKEY_LWIN (left Windows key), Alt modifier. |
| EXPECT_EQ(KeyLMeta::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN)); |
| |
| // VKEY_RWIN (right Windows key), Alt modifier. |
| EXPECT_EQ(KeyRMeta::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyRMeta::Typed(), ui::EF_ALT_DOWN)); |
| } |
| |
| // Both preferences for Search on Chrome keyboards, and external Meta on |
| // generic external keyboards are independent, even if one or both are |
| // modified. |
| |
| // Remap Chrome OS Search to Ctrl. |
| IntegerPrefMember internal_search; |
| InitModifierKeyPref(&internal_search, ::prefs::kLanguageRemapSearchKeyTo, |
| ui::mojom::ModifierKey::kMeta, |
| ui::mojom::ModifierKey::kControl); |
| |
| // Remap external Meta to Alt. |
| IntegerPrefMember meta; |
| InitModifierKeyPref(&meta, ::prefs::kLanguageRemapExternalMetaKeyTo, |
| ui::mojom::ModifierKey::kMeta, |
| ui::mojom::ModifierKey::kAlt); |
| for (const auto& keyboard : kChromeKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // VKEY_A, Win modifier. |
| EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // VKEY_A, Alt+Win modifier. |
| EXPECT_EQ( |
| KeyA::Typed(ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN)); |
| |
| // VKEY_LWIN (left Windows key), Alt modifier. |
| EXPECT_EQ(KeyLControl::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN)); |
| |
| // VKEY_RWIN (right Windows key), Alt modifier. |
| EXPECT_EQ(KeyRControl::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyRMeta::Typed(), ui::EF_ALT_DOWN)); |
| } |
| |
| SetUpKeyboard(kExternalGenericKeyboard); |
| |
| // VKEY_A, Win modifier. |
| EXPECT_EQ(KeyA::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| // VKEY_A, Alt+Win modifier. |
| EXPECT_EQ(KeyA::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN)); |
| |
| if (ash::features::IsKeyboardRewriterFixEnabled()) { |
| // VKEY_LWIN (left Windows key), Alt modifier. |
| EXPECT_EQ(KeyLAlt::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN)); |
| // VKEY_RWIN (right Windows key), Alt modifier. |
| EXPECT_EQ(KeyRAlt::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyRMeta::Typed(), ui::EF_ALT_DOWN)); |
| } else { |
| // VKEY_LWIN (left Windows key), Alt modifier. |
| // Older implementation has an issue that release event is not dispatched. |
| EXPECT_EQ(std::vector({KeyLAlt::Pressed()}), |
| RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN)); |
| // VKEY_RWIN (right Windows key), Alt modifier. |
| EXPECT_EQ(KeyRAlt::Typed(), |
| RunRewriter(KeyRMeta::Typed(), ui::EF_ALT_DOWN)); |
| } |
| } |
| |
| // For crbug.com/133896. |
| TEST_P(EventRewriterInputSettingsSplitDisabledTest, |
| TestRewriteCommandToControlWithControlRemapped) { |
| // Remap Control to Alt. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember control; |
| InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo, |
| ui::mojom::ModifierKey::kControl, |
| ui::mojom::ModifierKey::kAlt); |
| |
| for (const auto& keyboard : kNonAppleKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| EXPECT_EQ(KeyLAlt::Typed(), RunRewriter(KeyLControl::Typed())); |
| } |
| |
| // Now verify that remapping does not affect Apple keyboard. |
| SetUpKeyboard(kExternalAppleKeyboard); |
| |
| // VKEY_LWIN (left Command key) with Alt modifier. The remapped Command |
| // key should never be re-remapped to Alt. |
| EXPECT_EQ(KeyLControl::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyLMeta::Typed(), ui::EF_ALT_DOWN)); |
| |
| // VKEY_RWIN (right Command key) with Alt modifier. The remapped Command |
| // key should never be re-remapped to Alt. |
| EXPECT_EQ(KeyRControl::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyRMeta::Typed(), ui::EF_ALT_DOWN)); |
| } |
| |
| class StickyKeysOverlayTest |
| : public EventRewriterTestBase, |
| public testing::WithParamInterface<std::tuple<bool, bool>> { |
| public: |
| StickyKeysOverlayTest() : overlay_(nullptr) {} |
| |
| ~StickyKeysOverlayTest() override = default; |
| |
| void SetUp() override { |
| auto [enable_keyboard_rewriter_fix, enable_modifier_split] = GetParam(); |
| if (enable_keyboard_rewriter_fix) { |
| fix_feature_list_.InitAndEnableFeature( |
| ash::features::kEnableKeyboardRewriterFix); |
| } else { |
| fix_feature_list_.InitAndDisableFeature( |
| ash::features::kEnableKeyboardRewriterFix); |
| } |
| |
| if (enable_modifier_split) { |
| modifier_split_feature_list_.InitAndEnableFeature( |
| ash::features::kModifierSplit); |
| } else { |
| modifier_split_feature_list_.InitAndDisableFeature( |
| ash::features::kModifierSplit); |
| } |
| |
| EventRewriterTestBase::SetUp(); |
| auto* sticky_keys_controller = Shell::Get()->sticky_keys_controller(); |
| sticky_keys_controller->Enable(true); |
| overlay_ = sticky_keys_controller->GetOverlayForTest(); |
| ASSERT_TRUE(overlay_); |
| } |
| |
| raw_ptr<StickyKeysOverlay, DanglingUntriaged> overlay_; |
| |
| private: |
| base::test::ScopedFeatureList fix_feature_list_; |
| base::test::ScopedFeatureList modifier_split_feature_list_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| StickyKeysOverlayTest, |
| testing::Combine(testing::Bool(), testing::Bool())); |
| |
| TEST_P(StickyKeysOverlayTest, OneModifierEnabled) { |
| EXPECT_FALSE(overlay_->is_visible()); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN)); |
| |
| // Pressing modifier key should show overlay. |
| SendKeyEvents(KeyLControl::Typed()); |
| EXPECT_TRUE(overlay_->is_visible()); |
| EXPECT_EQ(STICKY_KEY_STATE_ENABLED, |
| overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN)); |
| |
| // Pressing a normal key should hide overlay. |
| SendKeyEvents(KeyT::Typed()); |
| EXPECT_FALSE(overlay_->is_visible()); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN)); |
| } |
| |
| TEST_P(StickyKeysOverlayTest, TwoModifiersEnabled) { |
| EXPECT_FALSE(overlay_->is_visible()); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN)); |
| |
| // Pressing two modifiers should show overlay. |
| SendKeyEvents(KeyLShift::Typed()); |
| SendKeyEvents(KeyLControl::Typed()); |
| EXPECT_TRUE(overlay_->is_visible()); |
| EXPECT_EQ(STICKY_KEY_STATE_ENABLED, |
| overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN)); |
| EXPECT_EQ(STICKY_KEY_STATE_ENABLED, |
| overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN)); |
| |
| // Pressing a normal key should hide overlay. |
| SendKeyEvents(KeyN::Typed()); |
| EXPECT_FALSE(overlay_->is_visible()); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN)); |
| } |
| |
| TEST_P(StickyKeysOverlayTest, LockedModifier) { |
| EXPECT_FALSE(overlay_->is_visible()); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_ALT_DOWN)); |
| |
| // Pressing a modifier key twice should lock modifier and show overlay. |
| SendKeyEvents(KeyLAlt::Typed()); |
| SendKeyEvents(KeyLAlt::Typed()); |
| EXPECT_TRUE(overlay_->is_visible()); |
| EXPECT_EQ(STICKY_KEY_STATE_LOCKED, |
| overlay_->GetModifierKeyState(ui::EF_ALT_DOWN)); |
| |
| // Pressing a normal key should not hide overlay. |
| SendKeyEvents(KeyD::Typed()); |
| EXPECT_TRUE(overlay_->is_visible()); |
| EXPECT_EQ(STICKY_KEY_STATE_LOCKED, |
| overlay_->GetModifierKeyState(ui::EF_ALT_DOWN)); |
| } |
| |
| TEST_P(StickyKeysOverlayTest, LockedAndNormalModifier) { |
| EXPECT_FALSE(overlay_->is_visible()); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN)); |
| |
| // Pressing a modifier key twice should lock modifier and show overlay. |
| SendKeyEvents(KeyLControl::Typed()); |
| SendKeyEvents(KeyLControl::Typed()); |
| EXPECT_TRUE(overlay_->is_visible()); |
| EXPECT_EQ(STICKY_KEY_STATE_LOCKED, |
| overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN)); |
| |
| // Pressing another modifier key should still show overlay. |
| SendKeyEvents(KeyLShift::Typed()); |
| EXPECT_TRUE(overlay_->is_visible()); |
| EXPECT_EQ(STICKY_KEY_STATE_LOCKED, |
| overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(STICKY_KEY_STATE_ENABLED, |
| overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN)); |
| |
| // Pressing a normal key should not hide overlay but disable normal modifier. |
| SendKeyEvents(KeyD::Typed()); |
| EXPECT_TRUE(overlay_->is_visible()); |
| EXPECT_EQ(STICKY_KEY_STATE_LOCKED, |
| overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN)); |
| } |
| |
| TEST_P(StickyKeysOverlayTest, ModifiersDisabled) { |
| EXPECT_FALSE(overlay_->is_visible()); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN)); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_ALT_DOWN)); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_COMMAND_DOWN)); |
| |
| // Enable modifiers. |
| SendKeyEvents(KeyLControl::Typed()); |
| SendKeyEvents(KeyLShift::Typed()); |
| SendKeyEvents(KeyLShift::Typed()); |
| SendKeyEvents(KeyLAlt::Typed()); |
| SendKeyEvents(KeyLMeta::Typed()); |
| SendKeyEvents(KeyLMeta::Typed()); |
| |
| EXPECT_TRUE(overlay_->is_visible()); |
| EXPECT_EQ(STICKY_KEY_STATE_ENABLED, |
| overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(STICKY_KEY_STATE_LOCKED, |
| overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN)); |
| EXPECT_EQ(STICKY_KEY_STATE_ENABLED, |
| overlay_->GetModifierKeyState(ui::EF_ALT_DOWN)); |
| EXPECT_EQ(STICKY_KEY_STATE_LOCKED, |
| overlay_->GetModifierKeyState(ui::EF_COMMAND_DOWN)); |
| |
| // Disable modifiers and overlay should be hidden. |
| SendKeyEvents(KeyLControl::Typed()); |
| SendKeyEvents(KeyLControl::Typed()); |
| SendKeyEvents(KeyLShift::Typed()); |
| SendKeyEvents(KeyLAlt::Typed()); |
| SendKeyEvents(KeyLAlt::Typed()); |
| SendKeyEvents(KeyLMeta::Typed()); |
| |
| EXPECT_FALSE(overlay_->is_visible()); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_SHIFT_DOWN)); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_ALT_DOWN)); |
| EXPECT_EQ(STICKY_KEY_STATE_DISABLED, |
| overlay_->GetModifierKeyState(ui::EF_COMMAND_DOWN)); |
| } |
| |
| TEST_P(StickyKeysOverlayTest, ModifierVisibility) { |
| // All but AltGr and Mod3 should initially be visible. |
| EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_CONTROL_DOWN)); |
| EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_SHIFT_DOWN)); |
| EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_ALT_DOWN)); |
| EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_COMMAND_DOWN)); |
| EXPECT_FALSE(overlay_->GetModifierVisible(ui::EF_ALTGR_DOWN)); |
| EXPECT_FALSE(overlay_->GetModifierVisible(ui::EF_MOD3_DOWN)); |
| EXPECT_FALSE(overlay_->GetModifierVisible(ui::EF_FUNCTION_DOWN)); |
| |
| // Turn all modifiers on. |
| auto* sticky_keys_controller = Shell::Get()->sticky_keys_controller(); |
| sticky_keys_controller->SetMod3AndAltGrModifiersEnabled(true, true); |
| sticky_keys_controller->SetFnModifierEnabled(true); |
| EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_CONTROL_DOWN)); |
| EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_SHIFT_DOWN)); |
| EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_ALT_DOWN)); |
| EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_COMMAND_DOWN)); |
| EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_ALTGR_DOWN)); |
| EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_MOD3_DOWN)); |
| EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_FUNCTION_DOWN)); |
| |
| // Turn off Fn. |
| sticky_keys_controller->SetFnModifierEnabled(false); |
| EXPECT_FALSE(overlay_->GetModifierVisible(ui::EF_FUNCTION_DOWN)); |
| |
| // Turn off Mod3. |
| sticky_keys_controller->SetMod3AndAltGrModifiersEnabled(false, true); |
| EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_ALTGR_DOWN)); |
| EXPECT_FALSE(overlay_->GetModifierVisible(ui::EF_MOD3_DOWN)); |
| |
| // Turn off AltGr. |
| sticky_keys_controller->SetMod3AndAltGrModifiersEnabled(true, false); |
| EXPECT_FALSE(overlay_->GetModifierVisible(ui::EF_ALTGR_DOWN)); |
| EXPECT_TRUE(overlay_->GetModifierVisible(ui::EF_MOD3_DOWN)); |
| |
| // Turn off AltGr and Mod3. |
| sticky_keys_controller->SetMod3AndAltGrModifiersEnabled(false, false); |
| EXPECT_FALSE(overlay_->GetModifierVisible(ui::EF_ALTGR_DOWN)); |
| EXPECT_FALSE(overlay_->GetModifierVisible(ui::EF_MOD3_DOWN)); |
| } |
| |
| TEST_P(EventRewriterTest, RewrittenModifier) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| |
| // Register Control + B as an extension shortcut. |
| SetExtensionCommands({{{ui::VKEY_B, ui::EF_CONTROL_DOWN}}}); |
| |
| // Check that standard extension input has no rewritten modifiers. |
| EXPECT_EQ(KeyB::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyB::Typed(), ui::EF_CONTROL_DOWN)); |
| |
| // Remap Control -> Alt. |
| IntegerPrefMember control; |
| InitModifierKeyPref(&control, ::prefs::kLanguageRemapControlKeyTo, |
| ui::mojom::ModifierKey::kControl, |
| ui::mojom::ModifierKey::kAlt); |
| // Pressing Control + B should now be remapped to Alt + B. |
| EXPECT_EQ(KeyB::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyB::Typed(), ui::EF_CONTROL_DOWN)); |
| |
| // Remap Alt -> Control. |
| IntegerPrefMember alt; |
| InitModifierKeyPref(&alt, ::prefs::kLanguageRemapAltKeyTo, |
| ui::mojom::ModifierKey::kAlt, |
| ui::mojom::ModifierKey::kControl); |
| // Pressing Alt + B should now be remapped to Control + B. |
| EXPECT_EQ(KeyB::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyB::Typed(), ui::EF_ALT_DOWN)); |
| |
| // Remove all extension shortcuts and still expect the remapping to work. |
| SetExtensionCommands(std::nullopt); |
| |
| EXPECT_EQ(KeyB::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyB::Typed(), ui::EF_CONTROL_DOWN)); |
| EXPECT_EQ(KeyB::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyB::Typed(), ui::EF_ALT_DOWN)); |
| } |
| |
| TEST_P(EventRewriterTest, RewriteNumpadExtensionCommand) { |
| // Register Control + NUMPAD1 as an extension shortcut. |
| SetExtensionCommands({{{ui::VKEY_NUMPAD1, ui::EF_CONTROL_DOWN}}}); |
| // Check that extension shortcuts that involve numpads keys are properly |
| // rewritten. Note that VKEY_END is associated with NUMPAD1 if Num Lock is |
| // disabled. The result should be "NumPad 1 with Control". |
| EXPECT_EQ(KeyNumpad1::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyNumpadEnd::Typed(), ui::EF_CONTROL_DOWN)); |
| |
| // Remove the extension shortcut and expect the numpad event to still be |
| // rewritten. |
| SetExtensionCommands(std::nullopt); |
| EXPECT_EQ(KeyNumpad1::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyNumpadEnd::Typed(), ui::EF_CONTROL_DOWN)); |
| } |
| |
| TEST_P(EventRewriterTest, RecordRewritingToFunctionKeys) { |
| TestShortcutMappingPrefDelegate delegate; |
| ASSERT_FALSE(::features::IsImprovedKeyboardShortcutsEnabled()); |
| |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitWithFeatures({features::kInputDeviceSettingsSplit}, |
| {}); |
| |
| base::HistogramTester histogram_tester; |
| histogram_tester.ExpectTotalCount("ChromeOS.Inputs.Keyboard.F1Pressed", 0); |
| |
| // Search + back -> F1. |
| SetUpKeyboard(kWilco1_0Keyboard); |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.F1Pressed", |
| /*SearchTopRowTranslated*/ |
| static_cast<int>(ui::InputKeyEventToFunctionKey::kSearchTopRowTranslated), |
| 0); |
| EXPECT_EQ(KeyF1::Typed(), |
| RunRewriter(KeyBrowserBack::Typed(), ui::EF_COMMAND_DOWN)); |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.F1Pressed", |
| /*SearchTopRowTranslated*/ |
| static_cast<int>(ui::InputKeyEventToFunctionKey::kSearchTopRowTranslated), |
| 1u); |
| histogram_tester.ExpectTotalCount("ChromeOS.Inputs.Keyboard.F1Pressed", 1u); |
| |
| mojom::KeyboardSettings settings; |
| EXPECT_CALL(*input_device_settings_controller_mock_, |
| GetKeyboardSettings(kKeyboardDeviceId)) |
| .WillRepeatedly(testing::Return(&settings)); |
| |
| // Back + Search -> F1 + Search. Back was automatically translated to F1, |
| // with search key unaffected so it should be mapped to |
| // kTopRowAutoTranslated. |
| settings.top_row_are_fkeys = true; |
| settings.suppress_meta_fkey_rewrites = true; |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.F1Pressed", |
| /*TopRowAutoTranslated*/ |
| static_cast<int>(ui::InputKeyEventToFunctionKey::kTopRowAutoTranslated), |
| 0); |
| EXPECT_EQ(KeyF1::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyBrowserBack::Typed(), ui::EF_COMMAND_DOWN)); |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.F1Pressed", |
| /*TopRowAutoTranslated*/ |
| static_cast<int>(ui::InputKeyEventToFunctionKey::kTopRowAutoTranslated), |
| 1u); |
| histogram_tester.ExpectTotalCount("ChromeOS.Inputs.Keyboard.F1Pressed", 2u); |
| |
| SetUpKeyboard(kExternalGenericKeyboard); |
| |
| // F1 + search -> F1. |
| settings.top_row_are_fkeys = false; |
| settings.suppress_meta_fkey_rewrites = false; |
| |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.F1Pressed", |
| /*DirectlyWithSearch*/ |
| static_cast<int>(ui::InputKeyEventToFunctionKey::kDirectlyWithSearch), 0); |
| // The keyboard sends F1 + search and the result is F1 only without search so |
| // it should be mapped to kDirectlyWithSearch. |
| EXPECT_EQ(KeyF1::Typed(), RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN)); |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.F1Pressed", |
| /*DirectlyWithSearch*/ |
| static_cast<int>(ui::InputKeyEventToFunctionKey::kDirectlyWithSearch), |
| 1u); |
| histogram_tester.ExpectTotalCount("ChromeOS.Inputs.Keyboard.F1Pressed", 3u); |
| |
| // No change. |
| settings.top_row_are_fkeys = true; |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.F1Pressed", |
| /*DirectlyFromKeyboard*/ |
| static_cast<int>(ui::InputKeyEventToFunctionKey::kDirectlyFromKeyboard), |
| 0); |
| EXPECT_EQ(KeyF1::Typed(), RunRewriter(KeyF1::Typed())); |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.F1Pressed", |
| /*DirectlyFromKeyboard*/ |
| static_cast<int>(ui::InputKeyEventToFunctionKey::kDirectlyFromKeyboard), |
| 1u); |
| histogram_tester.ExpectTotalCount("ChromeOS.Inputs.Keyboard.F1Pressed", 4u); |
| |
| // Search + number. |
| SetUpKeyboard(kInternalChromeKeyboard); |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.F1Pressed", |
| /*SearchDigitTranslated*/ |
| static_cast<int>(ui::InputKeyEventToFunctionKey::kSearchDigitTranslated), |
| 0); |
| EXPECT_EQ(KeyF1::Typed(), |
| RunRewriter(KeyDigit1::Typed(), ui::EF_COMMAND_DOWN)); |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.F1Pressed", |
| /*SearchDigitTranslated*/ |
| static_cast<int>(ui::InputKeyEventToFunctionKey::kSearchDigitTranslated), |
| 1u); |
| histogram_tester.ExpectTotalCount("ChromeOS.Inputs.Keyboard.F1Pressed", 5u); |
| |
| // F1 + search to F1 + search |
| settings.suppress_meta_fkey_rewrites = true; |
| EXPECT_EQ(KeyF1::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN)); |
| histogram_tester.ExpectBucketCount( |
| "ChromeOS.Inputs.Keyboard.F1Pressed", |
| /*DirectlyFromKeyboard*/ |
| static_cast<int>(ui::InputKeyEventToFunctionKey::kDirectlyFromKeyboard), |
| 2u); |
| histogram_tester.ExpectTotalCount("ChromeOS.Inputs.Keyboard.F1Pressed", 6u); |
| } |
| |
| TEST_P(EventRewriterTest, AltgrLatch) { |
| // TODO(b/331906341): Consider to use real latvian layout. |
| keyboard_layout_engine_->SetCustomLookupTableForTesting({ |
| {ui::DomCode::QUOTE, ui::DomKey::ALT_GRAPH_LATCH, |
| ui::DomKey::FromCharacter(u'"'), ui::VKEY_ALTGR}, |
| }); |
| |
| // Use fake latvian quote key. |
| using KeyLatvianQuote = |
| TestKey<ui::DomCode::QUOTE, ui::DomKey::ALT_GRAPH_LATCH, ui::VKEY_ALTGR, |
| ui::EF_ALTGR_DOWN>; |
| EXPECT_EQ(std::vector<TestKeyEvent>( |
| {{ui::EventType::kKeyPressed, ui::DomCode::QUOTE, |
| ui::DomKey::ALT_GRAPH, ui::VKEY_ALTGR, ui::EF_ALTGR_DOWN}, |
| // EF_ALTGR_DOWN is still here, because it's latched. |
| {ui::EventType::kKeyReleased, ui::DomCode::QUOTE, |
| ui::DomKey::ALT_GRAPH, ui::VKEY_ALTGR, ui::EF_ALTGR_DOWN}}), |
| SendKeyEvents(KeyLatvianQuote::Typed())); |
| EXPECT_EQ(std::vector({KeyA::Pressed(ui::EF_ALTGR_DOWN), KeyA::Released()}), |
| SendKeyEvents(KeyA::Typed())); |
| |
| // Hold the quote. |
| EXPECT_EQ(std::vector<TestKeyEvent>( |
| {{ui::EventType::kKeyPressed, ui::DomCode::QUOTE, |
| ui::DomKey::ALT_GRAPH, ui::VKEY_ALTGR, ui::EF_ALTGR_DOWN}}), |
| SendKeyEvent(KeyLatvianQuote::Pressed())); |
| |
| // Type A followed by C. |
| EXPECT_EQ(KeyA::Typed(ui::EF_ALTGR_DOWN), |
| SendKeyEvents(KeyA::Typed(ui::EF_ALTGR_DOWN))); |
| EXPECT_EQ(KeyC::Typed(ui::EF_ALTGR_DOWN), |
| SendKeyEvents(KeyC::Typed(ui::EF_ALTGR_DOWN))); |
| |
| // Release the quote, where EF_ALTGR_DOWN should not be set. |
| EXPECT_EQ(std::vector<TestKeyEvent>( |
| {{ui::EventType::kKeyReleased, ui::DomCode::QUOTE, |
| ui::DomKey::ALT_GRAPH, ui::VKEY_ALTGR}}), |
| SendKeyEvent(KeyLatvianQuote::Released())); |
| } |
| |
| TEST_P(EventRewriterTest, SixPackRemappingsFnBased) { |
| if (!features::IsModifierSplitEnabled()) { |
| GTEST_SKIP() << "Test is only valid with the modifier split flag enabled"; |
| } |
| |
| SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard); |
| |
| // Test each case while applying additional flags to confirm flags get |
| // properly applied to rewritten events. |
| for (const auto flag : {ui::EF_NONE, ui::EF_COMMAND_DOWN, ui::EF_CONTROL_DOWN, |
| ui::EF_ALT_DOWN, ui::EF_SHIFT_DOWN}) { |
| EXPECT_EQ(KeyDelete::Typed(flag), |
| RunRewriter(KeyBackspace::Typed(), ui::EF_FUNCTION_DOWN | flag)); |
| EXPECT_EQ(KeyHome::Typed(flag), |
| RunRewriter(KeyArrowLeft::Typed(), ui::EF_FUNCTION_DOWN | flag)); |
| EXPECT_EQ(KeyEnd::Typed(flag), |
| RunRewriter(KeyArrowRight::Typed(), ui::EF_FUNCTION_DOWN | flag)); |
| EXPECT_EQ(KeyPageUp::Typed(flag), |
| RunRewriter(KeyArrowUp::Typed(), ui::EF_FUNCTION_DOWN | flag)); |
| EXPECT_EQ(KeyPageDown::Typed(flag), |
| RunRewriter(KeyArrowDown::Typed(), ui::EF_FUNCTION_DOWN | flag)); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, NotifyShortcutEventRewriteBlockedByFnKey) { |
| if (!features::IsModifierSplitEnabled()) { |
| GTEST_SKIP() << "Test is only valid with the modifier split flag enabled"; |
| } |
| |
| AnchoredNudgeManagerImpl* nudge_manager = |
| Shell::Get()->anchored_nudge_manager(); |
| ASSERT_TRUE(nudge_manager); |
| SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard); |
| EXPECT_EQ(KeyArrowLeft::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyArrowLeft::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| EXPECT_TRUE(nudge_manager->GetNudgeIfShown(kSixPackKeyNoMatchNudgeId)); |
| nudge_manager->Cancel(kSixPackKeyNoMatchNudgeId); |
| |
| // Set the scan code so the key event is recognized as top row key. |
| std::vector<TestKeyEvent> key_events; |
| for (auto event : KeyF1::Typed()) { |
| event.scan_code = 1; |
| key_events.push_back(std::move(event)); |
| } |
| |
| std::vector<TestKeyEvent> expected_events; |
| for (auto event : KeyF1::Typed(ui::EF_COMMAND_DOWN)) { |
| event.scan_code = 1; |
| expected_events.push_back(std::move(event)); |
| } |
| |
| EXPECT_EQ(expected_events, RunRewriter(key_events, ui::EF_COMMAND_DOWN)); |
| EXPECT_TRUE(nudge_manager->GetNudgeIfShown(kTopRowKeyNoMatchNudgeId)); |
| nudge_manager->Cancel(kTopRowKeyNoMatchNudgeId); |
| } |
| |
| TEST_P(EventRewriterTest, CapsLockRemappingFnBased) { |
| if (!features::IsModifierSplitEnabled()) { |
| GTEST_SKIP() << "Test is only valid with the modifier split flag enabled"; |
| } |
| |
| SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard); |
| |
| for (const auto flag : |
| {ui::EF_NONE, ui::EF_CONTROL_DOWN, ui::EF_SHIFT_DOWN, ui::EF_ALT_DOWN}) { |
| EXPECT_EQ( |
| KeyCapsLock::Typed(flag | ui::EF_CAPS_LOCK_ON), |
| RunRewriter(KeyQuickInsert::Typed(ui::EF_NONE, {kPropertyQuickInsert}), |
| ui::EF_FUNCTION_DOWN | flag)); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| EXPECT_EQ(KeyCapsLock::Typed(flag), |
| RunRewriter(KeyQuickInsert::Typed(ui::EF_CAPS_LOCK_ON, |
| {kPropertyQuickInsert}), |
| ui::EF_FUNCTION_DOWN | flag)); |
| EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| } |
| } |
| |
| TEST_P(EventRewriterTest, CapsLockRemappingFnBasedJpnLayout) { |
| if (!features::IsModifierSplitEnabled()) { |
| GTEST_SKIP() << "Test is only valid with the modifier split flag enabled"; |
| } |
| |
| SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard); |
| |
| EXPECT_EQ(KeyCapsLock::Typed(ui::EF_CAPS_LOCK_ON), |
| RunRewriter( |
| KeyJpnAlphanumeric::Typed(ui::EF_NONE, {kPropertyQuickInsert}), |
| ui::EF_FUNCTION_DOWN)); |
| EXPECT_TRUE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| |
| EXPECT_EQ(KeyCapsLock::Typed(), |
| RunRewriter(KeyJpnAlphanumeric::Typed(ui::EF_CAPS_LOCK_ON, |
| {kPropertyQuickInsert}), |
| ui::EF_FUNCTION_DOWN)); |
| EXPECT_FALSE(fake_ime_keyboard_.IsCapsLockEnabled()); |
| } |
| |
| TEST_P(EventRewriterTest, FnDiscarded) { |
| if (!features::IsModifierSplitEnabled()) { |
| GTEST_SKIP() << "Test is only valid with the modifier split flag enabled"; |
| } |
| |
| SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard); |
| |
| EXPECT_EQ(KeyA::Typed(), RunRewriter(KeyA::Typed(), ui::EF_FUNCTION_DOWN)); |
| EXPECT_EQ( |
| KeyA::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_FUNCTION_DOWN | ui::EF_CONTROL_DOWN)); |
| |
| EXPECT_EQ(std::vector<TestKeyEvent>(), RunRewriter(KeyFunction::Typed())); |
| } |
| |
| // Tests that when you press Fn -> Quick Insert -> Release Fn -> Release Quick |
| // Insert that the release of Quick Insert is remapped to CapsLock to match the |
| // remapped press. |
| TEST_P(EventRewriterTest, CapsLockRemappingFnBasedReleaseOrdering) { |
| if (!features::IsModifierSplitEnabled()) { |
| GTEST_SKIP() << "Test is only valid with the modifier split flag enabled"; |
| } |
| |
| SetUpKeyboard(kInternalChromeSplitModifierLayoutKeyboard); |
| |
| EXPECT_EQ(std::vector<TestKeyEvent>(), |
| RunRewriter(std::vector<TestKeyEvent>{KeyFunction::Pressed()})); |
| EXPECT_EQ( |
| std::vector<TestKeyEvent>({KeyCapsLock::Pressed(ui::EF_CAPS_LOCK_ON)}), |
| RunRewriter(std::vector<TestKeyEvent>{KeyQuickInsert::Pressed( |
| ui::EF_FUNCTION_DOWN, {kPropertyQuickInsert})})); |
| EXPECT_EQ(std::vector<TestKeyEvent>(), |
| RunRewriter(std::vector<TestKeyEvent>{ |
| KeyFunction::Released(ui::EF_CAPS_LOCK_ON)})); |
| EXPECT_EQ( |
| std::vector<TestKeyEvent>({KeyCapsLock::Released(ui::EF_CAPS_LOCK_ON)}), |
| RunRewriter(std::vector<TestKeyEvent>{KeyQuickInsert::Released( |
| ui::EF_CAPS_LOCK_ON, {kPropertyQuickInsert})})); |
| } |
| |
| class ModifierPressedMetricsTest |
| : public EventRewriterTestBase, |
| public testing::WithParamInterface< |
| std::tuple<bool, |
| std::tuple<TestKeyEvent, |
| ui::ModifierKeyUsageMetric, |
| std::vector<std::string>>>> { |
| public: |
| void SetUp() override { |
| bool fix_enabled; |
| auto tuple = std::tie(event_, modifier_key_usage_mapping_, key_pref_names_); |
| std::tie(fix_enabled, tuple) = GetParam(); |
| std::vector<base::test::FeatureRef> enabled_features, disabled_features; |
| (fix_enabled ? enabled_features : disabled_features) |
| .push_back(ash::features::kEnableKeyboardRewriterFix); |
| disabled_features.push_back(features::kInputDeviceSettingsSplit); |
| scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features); |
| EventRewriterTestBase::SetUp(); |
| } |
| |
| protected: |
| TestKeyEvent event_; |
| ui::ModifierKeyUsageMetric modifier_key_usage_mapping_; |
| std::vector<std::string> key_pref_names_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| ModifierPressedMetricsTest, |
| testing::Combine( |
| testing::Bool(), |
| testing::ValuesIn(std::vector<std::tuple<TestKeyEvent, |
| ui::ModifierKeyUsageMetric, |
| std::vector<std::string>>>{ |
| {KeyLMeta::Pressed(), |
| ui::ModifierKeyUsageMetric::kMetaLeft, |
| {::prefs::kLanguageRemapSearchKeyTo, |
| ::prefs::kLanguageRemapExternalCommandKeyTo, |
| ::prefs::kLanguageRemapExternalMetaKeyTo}}, |
| {KeyRMeta::Pressed(), |
| ui::ModifierKeyUsageMetric::kMetaRight, |
| {::prefs::kLanguageRemapSearchKeyTo, |
| ::prefs::kLanguageRemapExternalCommandKeyTo, |
| ::prefs::kLanguageRemapExternalMetaKeyTo}}, |
| {KeyLControl::Pressed(), |
| ui::ModifierKeyUsageMetric::kControlLeft, |
| {::prefs::kLanguageRemapControlKeyTo}}, |
| {KeyRControl::Pressed(), |
| ui::ModifierKeyUsageMetric::kControlRight, |
| {::prefs::kLanguageRemapControlKeyTo}}, |
| {KeyLAlt::Pressed(), |
| ui::ModifierKeyUsageMetric::kAltLeft, |
| {::prefs::kLanguageRemapAltKeyTo}}, |
| {KeyRAlt::Pressed(), |
| ui::ModifierKeyUsageMetric::kAltRight, |
| {::prefs::kLanguageRemapAltKeyTo}}, |
| {KeyLShift::Pressed(), |
| ui::ModifierKeyUsageMetric::kShiftLeft, |
| // Shift keys cannot be remapped and therefore do not have a real |
| // "pref" path. |
| {"fakePrefPath"}}, |
| {KeyRShift::Pressed(), |
| ui::ModifierKeyUsageMetric::kShiftRight, |
| // Shift keys cannot be remapped and therefore do not have a real |
| // "pref" path. |
| {"fakePrefPath"}}, |
| {KeyCapsLock::Pressed(), |
| ui::ModifierKeyUsageMetric::kCapsLock, |
| {::prefs::kLanguageRemapCapsLockKeyTo}}, |
| {KeyBackspace::Pressed(), |
| ui::ModifierKeyUsageMetric::kBackspace, |
| {::prefs::kLanguageRemapBackspaceKeyTo}}, |
| {KeyEscape::Pressed(), |
| ui::ModifierKeyUsageMetric::kEscape, |
| {::prefs::kLanguageRemapEscapeKeyTo}}, |
| {KeyLaunchAssistant::Pressed(), |
| ui::ModifierKeyUsageMetric::kAssistant, |
| {::prefs::kLanguageRemapAssistantKeyTo}}}))); |
| |
| TEST_P(ModifierPressedMetricsTest, KeyPressedTest) { |
| auto expected = event_; |
| if (expected.code == ui::DomCode::CAPS_LOCK) { |
| expected.flags |= ui::EF_CAPS_LOCK_ON; |
| } |
| |
| base::HistogramTester histogram_tester; |
| SetUpKeyboard(kInternalChromeKeyboard); |
| EXPECT_EQ(std::vector({expected}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.Internal", |
| modifier_key_usage_mapping_, 1); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal", |
| modifier_key_usage_mapping_, 1); |
| // Unset CapsLock for each press. |
| SendKeyEvent(TestKeyEvent{ui::EventType::kKeyReleased, event_.code, |
| event_.key, event_.keycode, event_.flags, |
| event_.scan_code}); |
| fake_ime_keyboard_.SetCapsLockEnabled(false); |
| |
| SetUpKeyboard(kExternalChromeKeyboard); |
| EXPECT_EQ(std::vector({expected}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.CrOSExternal", |
| modifier_key_usage_mapping_, 1); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.CrOSExternal", |
| modifier_key_usage_mapping_, 1); |
| SendKeyEvent(TestKeyEvent{ui::EventType::kKeyReleased, event_.code, |
| event_.key, event_.keycode, event_.flags, |
| event_.scan_code}); |
| fake_ime_keyboard_.SetCapsLockEnabled(false); |
| |
| SetUpKeyboard(kExternalAppleKeyboard); |
| EXPECT_EQ(std::vector({expected}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.AppleExternal", |
| modifier_key_usage_mapping_, 1); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.AppleExternal", |
| modifier_key_usage_mapping_, 1); |
| SendKeyEvent(TestKeyEvent{ui::EventType::kKeyReleased, event_.code, |
| event_.key, event_.keycode, event_.flags, |
| event_.scan_code}); |
| fake_ime_keyboard_.SetCapsLockEnabled(false); |
| |
| SetUpKeyboard(kExternalGenericKeyboard); |
| EXPECT_EQ(std::vector({expected}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.External", |
| modifier_key_usage_mapping_, 1); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.External", |
| modifier_key_usage_mapping_, 1); |
| SendKeyEvent(TestKeyEvent{ui::EventType::kKeyReleased, event_.code, |
| event_.key, event_.keycode, event_.flags, |
| event_.scan_code}); |
| fake_ime_keyboard_.SetCapsLockEnabled(false); |
| } |
| |
| TEST_P(ModifierPressedMetricsTest, KeyPressedWithRemappingToBackspaceTest) { |
| if (event_.keycode == ui::VKEY_SHIFT) { |
| GTEST_SKIP() << "Shift cannot be remapped"; |
| } |
| |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| base::HistogramTester histogram_tester; |
| for (const auto& pref_name : key_pref_names_) { |
| IntegerPrefMember pref_member; |
| InitModifierKeyPref(&pref_member, pref_name, |
| ui::mojom::ModifierKey::kControl, |
| ui::mojom::ModifierKey::kBackspace); |
| } |
| |
| SetUpKeyboard(kInternalChromeKeyboard); |
| EXPECT_EQ(std::vector({KeyBackspace::Pressed()}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.Internal", |
| modifier_key_usage_mapping_, 1); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal", |
| ui::ModifierKeyUsageMetric::kBackspace, 1); |
| |
| SetUpKeyboard(kExternalChromeKeyboard); |
| EXPECT_EQ(std::vector({KeyBackspace::Pressed()}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.CrOSExternal", |
| modifier_key_usage_mapping_, 1); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.CrOSExternal", |
| ui::ModifierKeyUsageMetric::kBackspace, 1); |
| |
| SetUpKeyboard(kExternalAppleKeyboard); |
| EXPECT_EQ(std::vector({KeyBackspace::Pressed()}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.AppleExternal", |
| modifier_key_usage_mapping_, 1); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.AppleExternal", |
| ui::ModifierKeyUsageMetric::kBackspace, 1); |
| |
| SetUpKeyboard(kExternalGenericKeyboard); |
| EXPECT_EQ(std::vector({KeyBackspace::Pressed()}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.External", |
| modifier_key_usage_mapping_, 1); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.External", |
| ui::ModifierKeyUsageMetric::kBackspace, 1); |
| } |
| |
| TEST_P(ModifierPressedMetricsTest, KeyPressedWithRemappingToControlTest) { |
| if (event_.keycode == ui::VKEY_SHIFT) { |
| GTEST_SKIP() << "Shift cannot be remapped"; |
| } |
| |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| base::HistogramTester histogram_tester; |
| |
| const bool right = ui::KeycodeConverter::DomCodeToLocation(event_.code) == |
| ui::DomKeyLocation::RIGHT; |
| const ui::ModifierKeyUsageMetric remapped_modifier_key_usage_mapping = |
| right ? ui::ModifierKeyUsageMetric::kControlRight |
| : ui::ModifierKeyUsageMetric::kControlLeft; |
| const auto control_event = |
| right ? KeyRControl::Pressed() : KeyLControl::Pressed(); |
| |
| for (const auto& pref_name : key_pref_names_) { |
| IntegerPrefMember pref_member; |
| InitModifierKeyPref(&pref_member, pref_name, |
| ui::mojom::ModifierKey::kControl, |
| ui::mojom::ModifierKey::kControl); |
| } |
| |
| SetUpKeyboard(kInternalChromeKeyboard); |
| EXPECT_EQ(std::vector({control_event}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.Internal", |
| modifier_key_usage_mapping_, 1); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal", |
| remapped_modifier_key_usage_mapping, 1); |
| |
| SetUpKeyboard(kExternalChromeKeyboard); |
| EXPECT_EQ(std::vector({control_event}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.CrOSExternal", |
| modifier_key_usage_mapping_, 1); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.CrOSExternal", |
| remapped_modifier_key_usage_mapping, 1); |
| |
| SetUpKeyboard(kExternalAppleKeyboard); |
| EXPECT_EQ(std::vector({control_event}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.AppleExternal", |
| modifier_key_usage_mapping_, 1); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.AppleExternal", |
| remapped_modifier_key_usage_mapping, 1); |
| |
| SetUpKeyboard(kExternalGenericKeyboard); |
| EXPECT_EQ(std::vector({control_event}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.External", |
| modifier_key_usage_mapping_, 1); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.External", |
| remapped_modifier_key_usage_mapping, 1); |
| } |
| |
| TEST_P(ModifierPressedMetricsTest, KeyRepeatTest) { |
| if (event_.code == ui::DomCode::CAPS_LOCK) { |
| GTEST_SKIP() << "CapsLock Key will not be marked as EF_IS_REPEAT"; |
| } |
| |
| base::HistogramTester histogram_tester; |
| // No metrics should be published if it is a repeated key. |
| event_.flags |= ui::EF_IS_REPEAT; |
| |
| SetUpKeyboard(kInternalChromeKeyboard); |
| EXPECT_EQ(std::vector({event_}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.Internal", |
| modifier_key_usage_mapping_, 0); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal", |
| modifier_key_usage_mapping_, 0); |
| |
| SetUpKeyboard(kExternalChromeKeyboard); |
| EXPECT_EQ(std::vector({event_}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.CrOSExternal", |
| modifier_key_usage_mapping_, 0); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.CrOSExternal", |
| modifier_key_usage_mapping_, 0); |
| |
| SetUpKeyboard(kExternalAppleKeyboard); |
| EXPECT_EQ(std::vector({event_}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.AppleExternal", |
| modifier_key_usage_mapping_, 0); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.AppleExternal", |
| modifier_key_usage_mapping_, 0); |
| |
| SetUpKeyboard(kExternalGenericKeyboard); |
| EXPECT_EQ(std::vector({event_}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.External", |
| modifier_key_usage_mapping_, 0); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.External", |
| modifier_key_usage_mapping_, 0); |
| } |
| |
| TEST_P(ModifierPressedMetricsTest, KeyReleasedTest) { |
| if (event_.code == ui::DomCode::CAPS_LOCK) { |
| GTEST_SKIP() << "CapsLock Key will not be marked as EF_IS_REPEAT"; |
| } |
| |
| base::HistogramTester histogram_tester; |
| // No metrics should be published if it is a repeated key. |
| event_.flags |= ui::EF_IS_REPEAT; |
| |
| auto expected = event_; |
| if (expected.code == ui::DomCode::CAPS_LOCK) { |
| expected.flags |= ui::EF_CAPS_LOCK_ON; |
| } |
| |
| SetUpKeyboard(kInternalChromeKeyboard); |
| EXPECT_EQ(std::vector({expected}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.Internal", |
| modifier_key_usage_mapping_, 0); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.Internal", |
| modifier_key_usage_mapping_, 0); |
| |
| SetUpKeyboard(kExternalChromeKeyboard); |
| EXPECT_EQ(std::vector({expected}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.CrOSExternal", |
| modifier_key_usage_mapping_, 0); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.CrOSExternal", |
| modifier_key_usage_mapping_, 0); |
| |
| SetUpKeyboard(kExternalAppleKeyboard); |
| EXPECT_EQ(std::vector({expected}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.AppleExternal", |
| modifier_key_usage_mapping_, 0); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.AppleExternal", |
| modifier_key_usage_mapping_, 0); |
| |
| SetUpKeyboard(kExternalGenericKeyboard); |
| EXPECT_EQ(std::vector({expected}), SendKeyEvent(event_)); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.ModifierPressed.External", |
| modifier_key_usage_mapping_, 0); |
| histogram_tester.ExpectUniqueSample( |
| "ChromeOS.Inputs.Keyboard.RemappedModifierPressed.External", |
| modifier_key_usage_mapping_, 0); |
| } |
| |
| class EventRewriterSixPackKeysTest |
| : public EventRewriterTestBase, |
| public testing::WithParamInterface<std::tuple<bool, bool>> { |
| public: |
| void SetUp() override { |
| auto [enable_keyboard_rewriter_fix, enable_modifier_split] = GetParam(); |
| |
| std::vector<base::test::FeatureRef> enabled_features, disabled_features; |
| enabled_features.push_back(features::kInputDeviceSettingsSplit); |
| enabled_features.push_back(features::kAltClickAndSixPackCustomization); |
| (enable_keyboard_rewriter_fix ? enabled_features : disabled_features) |
| .push_back(ash::features::kEnableKeyboardRewriterFix); |
| (enable_modifier_split ? enabled_features : disabled_features) |
| .push_back(ash::features::kModifierSplit); |
| scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features); |
| |
| EventRewriterTestBase::SetUp(); |
| } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| EventRewriterSixPackKeysTest, |
| testing::Combine(testing::Bool(), testing::Bool())); |
| |
| TEST_P(EventRewriterSixPackKeysTest, TestRewriteSixPackKeysSearchVariants) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| mojom::KeyboardSettings settings; |
| settings.six_pack_key_remappings = ash::mojom::SixPackKeyInfo::New(); |
| EXPECT_CALL(*input_device_settings_controller_mock_, |
| GetKeyboardSettings(kKeyboardDeviceId)) |
| .WillRepeatedly(testing::Return(&settings)); |
| for (const auto& keyboard : kNonAppleKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Search+Shift+Backspace -> Insert |
| EXPECT_EQ(KeyInsert::Typed(), |
| RunRewriter(KeyBackspace::Typed(), |
| ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN)); |
| // Search+Backspace -> Delete |
| EXPECT_EQ(KeyDelete::Typed(), |
| RunRewriter(KeyBackspace::Typed(), ui::EF_COMMAND_DOWN)); |
| // Search+Up -> Prior (aka PageUp) |
| EXPECT_EQ(KeyPageUp::Typed(), |
| RunRewriter(KeyArrowUp::Typed(), ui::EF_COMMAND_DOWN)); |
| // Search+Down -> Next (aka PageDown) |
| EXPECT_EQ(KeyPageDown::Typed(), |
| RunRewriter(KeyArrowDown::Typed(), ui::EF_COMMAND_DOWN)); |
| // Search+Left -> Home |
| EXPECT_EQ(KeyHome::Typed(), |
| RunRewriter(KeyArrowLeft::Typed(), ui::EF_COMMAND_DOWN)); |
| // Search+Right -> End |
| EXPECT_EQ(KeyEnd::Typed(), |
| RunRewriter(KeyArrowRight::Typed(), ui::EF_COMMAND_DOWN)); |
| // Search+Shift+Down -> Shift+Next (aka PageDown) |
| EXPECT_EQ(KeyPageDown::Typed(ui::EF_SHIFT_DOWN), |
| RunRewriter(KeyArrowDown::Typed(), |
| ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN)); |
| // Search+Ctrl+Up -> Ctrl+Prior (aka PageUp) |
| EXPECT_EQ(KeyPageUp::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyArrowUp::Typed(), |
| ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN)); |
| // Search+Alt+Left -> Alt+Home |
| EXPECT_EQ(KeyHome::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyArrowLeft::Typed(), |
| ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterSixPackKeysTest, TestRewriteSixPackKeysAltVariants) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| mojom::KeyboardSettings settings; |
| settings.six_pack_key_remappings = ash::mojom::SixPackKeyInfo::New(); |
| settings.six_pack_key_remappings->del = |
| ui::mojom::SixPackShortcutModifier::kAlt; |
| settings.six_pack_key_remappings->end = |
| ui::mojom::SixPackShortcutModifier::kAlt; |
| settings.six_pack_key_remappings->home = |
| ui::mojom::SixPackShortcutModifier::kAlt; |
| settings.six_pack_key_remappings->page_down = |
| ui::mojom::SixPackShortcutModifier::kAlt; |
| settings.six_pack_key_remappings->page_up = |
| ui::mojom::SixPackShortcutModifier::kAlt; |
| |
| EXPECT_CALL(*input_device_settings_controller_mock_, |
| GetKeyboardSettings(kKeyboardDeviceId)) |
| .WillRepeatedly(testing::Return(&settings)); |
| for (const auto& keyboard : kNonAppleKeyboardVariants) { |
| SCOPED_TRACE(keyboard.name); |
| SetUpKeyboard(keyboard); |
| |
| // Alt+Backspace -> Delete |
| EXPECT_EQ(KeyDelete::Typed(), |
| RunRewriter(KeyBackspace::Typed(), ui::EF_ALT_DOWN)); |
| // Alt+Up -> Prior |
| EXPECT_EQ(KeyPageUp::Typed(), |
| RunRewriter(KeyArrowUp::Typed(), ui::EF_ALT_DOWN)); |
| // Alt+Down -> Next |
| EXPECT_EQ(KeyPageDown::Typed(), |
| RunRewriter(KeyArrowDown::Typed(), ui::EF_ALT_DOWN)); |
| // Ctrl+Alt+Up -> Home |
| EXPECT_EQ(KeyHome::Typed(), |
| RunRewriter(KeyArrowUp::Typed(), |
| ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)); |
| // Ctrl+Alt+Down -> End |
| EXPECT_EQ(KeyEnd::Typed(), |
| RunRewriter(KeyArrowDown::Typed(), |
| ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)); |
| // Ctrl+Alt+Shift+Up -> Shift+Home |
| EXPECT_EQ( |
| KeyHome::Typed(ui::EF_SHIFT_DOWN), |
| RunRewriter(KeyArrowUp::Typed(), |
| ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN)); |
| // Ctrl+Alt+Search+Down -> Search+End |
| EXPECT_EQ(KeyEnd::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyArrowDown::Typed(), ui::EF_CONTROL_DOWN | |
| ui::EF_ALT_DOWN | |
| ui::EF_COMMAND_DOWN)); |
| } |
| } |
| |
| TEST_P(EventRewriterSixPackKeysTest, TestRewriteSixPackKeysBlockedBySetting) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| mojom::KeyboardSettings settings; |
| // "six pack" key settings use the search modifier by default. |
| settings.six_pack_key_remappings = ash::mojom::SixPackKeyInfo::New(); |
| EXPECT_CALL(*input_device_settings_controller_mock_, |
| GetKeyboardSettings(kKeyboardDeviceId)) |
| .WillRepeatedly(testing::Return(&settings)); |
| // No rewrite should occur since the search-based rewrite is the setting for |
| // the "Delete" 6-pack key. |
| SetUpKeyboard(kInternalChromeKeyboard); |
| EXPECT_EQ(KeyBackspace::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyBackspace::Typed(), ui::EF_ALT_DOWN)); |
| EXPECT_EQ(1u, message_center_.NotificationCount()); |
| ClearNotifications(); |
| |
| settings.six_pack_key_remappings->del = |
| ui::mojom::SixPackShortcutModifier::kAlt; |
| // Rewrite should occur now that the alt rewrite is the current setting. |
| // Alt+Backspace -> Delete |
| EXPECT_EQ(KeyDelete::Typed(), |
| RunRewriter(KeyBackspace::Typed(), ui::EF_ALT_DOWN)); |
| |
| settings.six_pack_key_remappings->del = |
| ui::mojom::SixPackShortcutModifier::kNone; |
| // No rewrite should occur since remapping a key event to the "Delete" |
| // 6-pack key is disabled. |
| EXPECT_EQ(KeyBackspace::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyBackspace::Typed(), ui::EF_ALT_DOWN)); |
| EXPECT_EQ(1u, message_center_.NotificationCount()); |
| ClearNotifications(); |
| } |
| |
| class EventRewriterExtendedFkeysTest |
| : public EventRewriterTestBase, |
| public testing::WithParamInterface<std::tuple<bool, bool>> { |
| public: |
| void SetUp() override { |
| auto [enable_keyboard_rewriter_fix, enable_modifier_split] = GetParam(); |
| std::vector<base::test::FeatureRef> enabled_features, disabled_features; |
| enabled_features.push_back(ash::features::kInputDeviceSettingsSplit); |
| enabled_features.push_back(::features::kSupportF11AndF12KeyShortcuts); |
| (enable_keyboard_rewriter_fix ? enabled_features : disabled_features) |
| .push_back(ash::features::kEnableKeyboardRewriterFix); |
| (enable_modifier_split ? enabled_features : disabled_features) |
| .push_back(ash::features::kModifierSplit); |
| scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features); |
| |
| EventRewriterTestBase::SetUp(); |
| } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| EventRewriterExtendedFkeysTest, |
| testing::Combine(testing::Bool(), testing::Bool())); |
| |
| TEST_P(EventRewriterExtendedFkeysTest, TestRewriteExtendedFkeys) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| mojom::KeyboardSettings settings; |
| settings.f11 = ui::mojom::ExtendedFkeysModifier::kAlt; |
| settings.f12 = ui::mojom::ExtendedFkeysModifier::kShift; |
| settings.top_row_are_fkeys = true; |
| |
| EXPECT_CALL(*input_device_settings_controller_mock_, |
| GetKeyboardSettings(kKeyboardDeviceId)) |
| .WillRepeatedly(testing::Return(&settings)); |
| |
| SetUpKeyboard(kInternalChromeKeyboard); |
| EXPECT_EQ(KeyF11::Typed(), RunRewriter(KeyF1::Typed(), ui::EF_ALT_DOWN)); |
| EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyF2::Typed(), ui::EF_SHIFT_DOWN)); |
| |
| settings.f11 = ui::mojom::ExtendedFkeysModifier::kCtrlShift; |
| settings.f12 = ui::mojom::ExtendedFkeysModifier::kAlt; |
| |
| EXPECT_EQ( |
| KeyF11::Typed(), |
| RunRewriter(KeyF1::Typed(), ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN)); |
| EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyF2::Typed(), ui::EF_ALT_DOWN)); |
| } |
| |
| TEST_P(EventRewriterExtendedFkeysTest, |
| TestRewriteExtendedFkeysBlockedBySetting) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| mojom::KeyboardSettings settings; |
| settings.f11 = ui::mojom::ExtendedFkeysModifier::kDisabled; |
| settings.f12 = ui::mojom::ExtendedFkeysModifier::kDisabled; |
| settings.top_row_are_fkeys = true; |
| |
| EXPECT_CALL(*input_device_settings_controller_mock_, |
| GetKeyboardSettings(kKeyboardDeviceId)) |
| .WillRepeatedly(testing::Return(&settings)); |
| SetUpKeyboard(kInternalChromeKeyboard); |
| |
| EXPECT_EQ(KeyF1::Typed(ui::EF_ALT_DOWN), |
| RunRewriter(KeyF1::Typed(), ui::EF_ALT_DOWN)); |
| } |
| |
| TEST_P(EventRewriterExtendedFkeysTest, TestRewriteExtendedFkeysTopRowAreFkeys) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| mojom::KeyboardSettings settings; |
| settings.f11 = ui::mojom::ExtendedFkeysModifier::kAlt; |
| settings.f12 = ui::mojom::ExtendedFkeysModifier::kShift; |
| settings.top_row_are_fkeys = true; |
| |
| EXPECT_CALL(*input_device_settings_controller_mock_, |
| GetKeyboardSettings(kKeyboardDeviceId)) |
| .WillRepeatedly(testing::Return(&settings)); |
| SetUpKeyboard(kInternalChromeKeyboard); |
| EXPECT_EQ(KeyF11::Typed(), RunRewriter(KeyF1::Typed(), ui::EF_ALT_DOWN)); |
| EXPECT_EQ( |
| KeyF11::Typed(ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN), |
| RunRewriter(KeyF1::Typed(), |
| ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN)); |
| EXPECT_EQ(KeyF12::Typed(), RunRewriter(KeyF2::Typed(), ui::EF_SHIFT_DOWN)); |
| |
| settings.top_row_are_fkeys = false; |
| EXPECT_EQ(KeyF11::Typed(), |
| RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN)); |
| EXPECT_EQ( |
| KeyF12::Typed(), |
| RunRewriter(KeyF2::Typed(), ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN)); |
| } |
| |
| class EventRewriterSettingsSplitTest |
| : public EventRewriterTestBase, |
| public testing::WithParamInterface<std::tuple<bool, bool>> { |
| public: |
| void SetUp() override { |
| auto [enable_keyboard_rewriter_fix, enable_modifier_split] = GetParam(); |
| |
| std::vector<base::test::FeatureRef> enabled_features, disabled_features; |
| enabled_features.push_back(ash::features::kInputDeviceSettingsSplit); |
| (enable_keyboard_rewriter_fix ? enabled_features : disabled_features) |
| .push_back(ash::features::kEnableKeyboardRewriterFix); |
| (enable_modifier_split ? enabled_features : disabled_features) |
| .push_back(ash::features::kModifierSplit); |
| scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features); |
| |
| EventRewriterTestBase::SetUp(); |
| } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| EventRewriterSettingsSplitTest, |
| testing::Combine(testing::Bool(), testing::Bool())); |
| |
| TEST_P(EventRewriterSettingsSplitTest, TopRowAreFKeys) { |
| mojom::KeyboardSettings settings; |
| EXPECT_CALL(*input_device_settings_controller_mock_, |
| GetKeyboardSettings(kKeyboardDeviceId)) |
| .WillRepeatedly(testing::Return(&settings)); |
| SetUpKeyboard(kExternalGenericKeyboard); |
| |
| settings.top_row_are_fkeys = false; |
| settings.suppress_meta_fkey_rewrites = false; |
| |
| EXPECT_EQ(KeyBrowserBack::Typed(), RunRewriter(KeyF1::Typed())); |
| |
| settings.top_row_are_fkeys = true; |
| EXPECT_EQ(KeyF1::Typed(), RunRewriter(KeyF1::Typed())); |
| } |
| |
| TEST_P(EventRewriterSettingsSplitTest, |
| TopRowAreFKeys_unknownDeviceRespectsPreference) { |
| // Create the preference. |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| BooleanPrefMember top_row_as_fn_key_pref; |
| top_row_as_fn_key_pref.Init(prefs::kSendFunctionKeys, prefs()); |
| |
| // Pretend the settings controller doesn't know the keyboard. |
| EXPECT_CALL(*input_device_settings_controller_mock_, GetKeyboardSettings) |
| .WillRepeatedly(testing::Return(nullptr)); |
| |
| top_row_as_fn_key_pref.SetValue(true); |
| EXPECT_EQ(RunRewriter(KeyF1::Typed()), KeyF1::Typed()); |
| |
| top_row_as_fn_key_pref.SetValue(false); |
| EXPECT_EQ(RunRewriter(KeyF1::Typed()), KeyBrowserBack::Typed()); |
| } |
| |
| TEST_P(EventRewriterSettingsSplitTest, RewriteMetaTopRowKeyComboEvents) { |
| mojom::KeyboardSettings settings; |
| settings.top_row_are_fkeys = true; |
| EXPECT_CALL(*input_device_settings_controller_mock_, |
| GetKeyboardSettings(kKeyboardDeviceId)) |
| .WillRepeatedly(testing::Return(&settings)); |
| SetUpKeyboard(kExternalGenericKeyboard); |
| |
| settings.suppress_meta_fkey_rewrites = false; |
| EXPECT_EQ(KeyBrowserBack::Typed(), |
| RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN)); |
| |
| settings.suppress_meta_fkey_rewrites = true; |
| EXPECT_EQ(KeyF1::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN)); |
| } |
| |
| TEST_P(EventRewriterSettingsSplitTest, ModifierRemapping) { |
| mojom::KeyboardSettings settings; |
| EXPECT_CALL(*input_device_settings_controller_mock_, |
| GetKeyboardSettings(kKeyboardDeviceId)) |
| .WillRepeatedly(testing::Return(&settings)); |
| SetUpKeyboard(kExternalGenericKeyboard); |
| |
| settings.modifier_remappings = { |
| {ui::mojom::ModifierKey::kAlt, ui::mojom::ModifierKey::kControl}, |
| {ui::mojom::ModifierKey::kMeta, ui::mojom::ModifierKey::kBackspace}}; |
| |
| // Test remapping modifier keys. |
| EXPECT_EQ(KeyRControl::Typed(), RunRewriter(KeyRAlt::Typed())); |
| EXPECT_EQ(KeyBackspace::Typed(), RunRewriter(KeyLMeta::Typed())); |
| EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyLControl::Typed())); |
| |
| // Test remapping modifier flags. |
| EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_ALT_DOWN)); |
| EXPECT_EQ(KeyA::Typed(), RunRewriter(KeyA::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyA::Typed(ui::EF_CONTROL_DOWN), |
| RunRewriter(KeyA::Typed(), ui::EF_CONTROL_DOWN)); |
| } |
| |
| class KeyEventRemappedToSixPackKeyTest |
| : public EventRewriterTestBase, |
| public testing::WithParamInterface< |
| std::tuple<bool, |
| std::tuple<ui::KeyboardCode, bool, int, const char*>>> { |
| public: |
| void SetUp() override { |
| bool fix_enabled; |
| auto tuple = |
| std::tie(key_code_, alt_based_, expected_pref_value_, pref_name_); |
| std::tie(fix_enabled, tuple) = GetParam(); |
| if (fix_enabled) { |
| scoped_feature_list_.InitAndEnableFeature( |
| ash::features::kEnableKeyboardRewriterFix); |
| } else { |
| scoped_feature_list_.InitAndDisableFeature( |
| ash::features::kEnableKeyboardRewriterFix); |
| } |
| EventRewriterTestBase::SetUp(); |
| } |
| |
| protected: |
| ui::KeyboardCode key_code_; |
| bool alt_based_; |
| int expected_pref_value_; |
| const char* pref_name_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| KeyEventRemappedToSixPackKeyTest, |
| testing::Combine( |
| testing::Bool(), |
| testing::ValuesIn( |
| std::vector<std::tuple<ui::KeyboardCode, bool, int, const char*>>{ |
| {ui::VKEY_DELETE, false, -1, |
| prefs::kKeyEventRemappedToSixPackDelete}, |
| {ui::VKEY_HOME, true, 1, prefs::kKeyEventRemappedToSixPackHome}, |
| {ui::VKEY_PRIOR, false, -1, |
| prefs::kKeyEventRemappedToSixPackPageDown}, |
| {ui::VKEY_END, true, 1, prefs::kKeyEventRemappedToSixPackEnd}, |
| {ui::VKEY_NEXT, false, -1, |
| prefs::kKeyEventRemappedToSixPackPageUp}}))); |
| |
| TEST_P(KeyEventRemappedToSixPackKeyTest, KeyEventRemappedTest) { |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| IntegerPrefMember int_pref; |
| int_pref.Init(pref_name_, prefs()); |
| int_pref.SetValue(0); |
| delegate_->RecordSixPackEventRewrite(key_code_, alt_based_); |
| EXPECT_EQ(expected_pref_value_, prefs()->GetInteger(pref_name_)); |
| } |
| |
| class EventRewriterRemapToRightClickTest |
| : public EventRewriterTestBase, |
| public message_center::MessageCenterObserver, |
| public testing::WithParamInterface<std::tuple<bool, bool>> { |
| public: |
| void SetUp() override { |
| auto [enable_keyboard_rewriter_fix, enable_modifier_split] = GetParam(); |
| std::vector<base::test::FeatureRef> enabled_features, disabled_features; |
| enabled_features.push_back(features::kInputDeviceSettingsSplit); |
| enabled_features.push_back(features::kAltClickAndSixPackCustomization); |
| (enable_keyboard_rewriter_fix ? enabled_features : disabled_features) |
| .push_back(ash::features::kEnableKeyboardRewriterFix); |
| (enable_modifier_split ? enabled_features : disabled_features) |
| .push_back(ash::features::kModifierSplit); |
| scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features); |
| |
| EventRewriterTestBase::SetUp(); |
| |
| Preferences::RegisterProfilePrefs(prefs()->registry()); |
| ui::DeviceDataManager* device_data_manager = |
| ui::DeviceDataManager::GetInstance(); |
| std::vector<ui::TouchpadDevice> touchpad_devices(1); |
| touchpad_devices[0].id = kTouchpadId1; |
| static_cast<ui::DeviceHotplugEventObserver*>(device_data_manager) |
| ->OnTouchpadDevicesUpdated(touchpad_devices); |
| |
| EXPECT_CALL(*input_device_settings_controller_mock_, |
| GetTouchpadSettings(kTouchpadId1)) |
| .WillRepeatedly(testing::Return(&settings_)); |
| |
| observation_.Observe(&message_center_); |
| } |
| |
| void TearDown() override { |
| observation_.Reset(); |
| EventRewriterTestBase::TearDown(); |
| } |
| |
| // message_center::MessageCenterObserver: |
| void OnNotificationAdded(const std::string& notification_id) override { |
| ++notification_count_; |
| } |
| |
| void SetSimulateRightClickSetting( |
| ui::mojom::SimulateRightClickModifier modifier) { |
| settings_.simulate_right_click = modifier; |
| } |
| |
| int notification_count() { return notification_count_; } |
| |
| private: |
| mojom::TouchpadSettings settings_; |
| int notification_count_ = 0; |
| base::ScopedObservation<message_center::MessageCenter, |
| message_center::MessageCenterObserver> |
| observation_{this}; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| EventRewriterRemapToRightClickTest, |
| testing::Combine(testing::Bool(), testing::Bool())); |
| |
| TEST_P(EventRewriterRemapToRightClickTest, AltClickRemappedToRightClick) { |
| SetSimulateRightClickSetting(ui::mojom::SimulateRightClickModifier::kAlt); |
| int flag_masks = ui::EF_ALT_DOWN | ui::EF_LEFT_MOUSE_BUTTON; |
| |
| ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(), gfx::Point(), |
| ui::EventTimeForNow(), flag_masks, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_press(&press); |
| test_press.set_source_device_id(kTouchpadId1); |
| EXPECT_EQ(ui::EventType::kMousePressed, press.type()); |
| EXPECT_EQ(flag_masks, press.flags()); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(press); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & result.flags()); |
| EXPECT_NE(flag_masks, flag_masks & result.flags()); |
| EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, result.changed_button_flags()); |
| } |
| |
| TEST_P(EventRewriterRemapToRightClickTest, SearchClickRemappedToRightClick) { |
| SetSimulateRightClickSetting(ui::mojom::SimulateRightClickModifier::kSearch); |
| int flag_masks = ui::EF_COMMAND_DOWN | ui::EF_LEFT_MOUSE_BUTTON; |
| |
| ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(), gfx::Point(), |
| ui::EventTimeForNow(), flag_masks, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_press(&press); |
| test_press.set_source_device_id(kTouchpadId1); |
| EXPECT_EQ(ui::EventType::kMousePressed, press.type()); |
| EXPECT_EQ(flag_masks, press.flags()); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(press); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & result.flags()); |
| EXPECT_NE(flag_masks, flag_masks & result.flags()); |
| EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, result.changed_button_flags()); |
| } |
| |
| TEST_P(EventRewriterRemapToRightClickTest, RemapToRightClickBlockedBySetting) { |
| ui::DeviceDataManager* device_data_manager = |
| ui::DeviceDataManager::GetInstance(); |
| std::vector<ui::TouchpadDevice> touchpad_devices(1); |
| touchpad_devices[0].id = kTouchpadId1; |
| static_cast<ui::DeviceHotplugEventObserver*>(device_data_manager) |
| ->OnTouchpadDevicesUpdated(touchpad_devices); |
| SetSimulateRightClickSetting(ui::mojom::SimulateRightClickModifier::kAlt); |
| |
| { |
| ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), |
| ui::EF_COMMAND_DOWN | ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_press(&press); |
| test_press.set_source_device_id(kTouchpadId1); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(press); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & result.flags()); |
| EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags()); |
| EXPECT_EQ(notification_count(), 1); |
| } |
| { |
| SetSimulateRightClickSetting( |
| ui::mojom::SimulateRightClickModifier::kSearch); |
| ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(), |
| gfx::Point(), ui::EventTimeForNow(), |
| ui::EF_ALT_DOWN | ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_press(&press); |
| test_press.set_source_device_id(kTouchpadId1); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(press); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & result.flags()); |
| EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags()); |
| EXPECT_EQ(notification_count(), 2); |
| } |
| } |
| |
| TEST_P(EventRewriterRemapToRightClickTest, RemapToRightClickIsDisabled) { |
| ui::DeviceDataManager* device_data_manager = |
| ui::DeviceDataManager::GetInstance(); |
| std::vector<ui::TouchpadDevice> touchpad_devices(1); |
| touchpad_devices[0].id = kTouchpadId1; |
| static_cast<ui::DeviceHotplugEventObserver*>(device_data_manager) |
| ->OnTouchpadDevicesUpdated(touchpad_devices); |
| SetSimulateRightClickSetting(ui::mojom::SimulateRightClickModifier::kNone); |
| |
| ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(), gfx::Point(), |
| ui::EventTimeForNow(), |
| ui::EF_COMMAND_DOWN | ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| ui::EventTestApi test_press(&press); |
| test_press.set_source_device_id(kTouchpadId1); |
| const ui::MouseEvent result = RewriteMouseButtonEvent(press); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & result.flags()); |
| EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, result.changed_button_flags()); |
| EXPECT_EQ(notification_count(), 1); |
| } |
| |
| class FKeysRewritingPeripheralCustomizationTest |
| : public EventRewriterTestBase, |
| public testing::WithParamInterface<std::tuple<bool, bool>> { |
| public: |
| void SetUp() override { |
| auto [enable_keyboard_rewriter_fix, enable_modifier_split] = GetParam(); |
| |
| std::vector<base::test::FeatureRef> enabled_features, disabled_features; |
| enabled_features.push_back(features::kInputDeviceSettingsSplit); |
| enabled_features.push_back(features::kPeripheralCustomization); |
| (enable_keyboard_rewriter_fix ? enabled_features : disabled_features) |
| .push_back(ash::features::kEnableKeyboardRewriterFix); |
| (enable_modifier_split ? enabled_features : disabled_features) |
| .push_back(ash::features::kModifierSplit); |
| scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features); |
| |
| EventRewriterTestBase::SetUp(); |
| } |
| |
| protected: |
| mojom::MouseSettings mouse_settings_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| FKeysRewritingPeripheralCustomizationTest, |
| testing::Combine(testing::Bool(), testing::Bool())); |
| |
| TEST_P(FKeysRewritingPeripheralCustomizationTest, FKeysNotRewritten) { |
| EXPECT_CALL(*input_device_settings_controller_mock_, |
| GetKeyboardSettings(kMouseDeviceId)) |
| .WillRepeatedly(testing::Return(nullptr)); |
| EXPECT_CALL(*input_device_settings_controller_mock_, |
| GetMouseSettings(kMouseDeviceId)) |
| .WillRepeatedly(testing::Return(&mouse_settings_)); |
| |
| SetUpKeyboard(kExternalGenericKeyboard); |
| |
| // Mice that press F-Keys do not get rewritten to actions. |
| EXPECT_EQ(KeyF1::Typed(), |
| RunRewriter(KeyF1::Typed(), ui::EF_NONE, kMouseDeviceId)); |
| EXPECT_EQ(KeyF2::Typed(), |
| RunRewriter(KeyF2::Typed(), ui::EF_NONE, kMouseDeviceId)); |
| EXPECT_EQ(KeyF1::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN, kMouseDeviceId)); |
| EXPECT_EQ(KeyF2::Typed(ui::EF_COMMAND_DOWN), |
| RunRewriter(KeyF2::Typed(), ui::EF_COMMAND_DOWN, kMouseDeviceId)); |
| |
| // Keyboards that press F-Keys do get rewritten to actions. |
| EXPECT_EQ(KeyBrowserBack::Typed(), RunRewriter(KeyF1::Typed())); |
| EXPECT_EQ(KeyBrowserForward::Typed(), RunRewriter(KeyF2::Typed())); |
| EXPECT_EQ(KeyF1::Typed(), RunRewriter(KeyF1::Typed(), ui::EF_COMMAND_DOWN)); |
| EXPECT_EQ(KeyF2::Typed(), RunRewriter(KeyF2::Typed(), ui::EF_COMMAND_DOWN)); |
| } |
| |
| } // namespace ash |