diff --git a/shell/platform/windows/flutter_window.cc b/shell/platform/windows/flutter_window.cc index 18e7210bedda6..2aab541002abd 100644 --- a/shell/platform/windows/flutter_window.cc +++ b/shell/platform/windows/flutter_window.cc @@ -156,8 +156,10 @@ void FlutterWindow::OnPaint() { void FlutterWindow::OnPointerMove(double x, double y, FlutterPointerDeviceKind device_kind, - int32_t device_id) { - binding_handler_delegate_->OnPointerMove(x, y, device_kind, device_id); + int32_t device_id, + int modifiers_state) { + binding_handler_delegate_->OnPointerMove(x, y, device_kind, device_id, + modifiers_state); } void FlutterWindow::OnPointerDown(double x, diff --git a/shell/platform/windows/flutter_window.h b/shell/platform/windows/flutter_window.h index e80d98b801d6d..f9537885ef890 100644 --- a/shell/platform/windows/flutter_window.h +++ b/shell/platform/windows/flutter_window.h @@ -41,7 +41,8 @@ class FlutterWindow : public Window, public WindowBindingHandler { void OnPointerMove(double x, double y, FlutterPointerDeviceKind device_kind, - int32_t device_id) override; + int32_t device_id, + int modifiers_state) override; // |Window| void OnPointerDown(double x, diff --git a/shell/platform/windows/flutter_window_unittests.cc b/shell/platform/windows/flutter_window_unittests.cc index 2d149b6294367..02c57b67629de 100644 --- a/shell/platform/windows/flutter_window_unittests.cc +++ b/shell/platform/windows/flutter_window_unittests.cc @@ -42,6 +42,9 @@ class SpyKeyboardKeyHandler : public KeyboardHandlerBase { ON_CALL(*this, KeyboardHook(_, _, _, _, _, _, _)) .WillByDefault(Invoke(real_implementation_.get(), &KeyboardKeyHandler::KeyboardHook)); + ON_CALL(*this, SyncModifiersIfNeeded(_)) + .WillByDefault(Invoke(real_implementation_.get(), + &KeyboardKeyHandler::SyncModifiersIfNeeded)); } MOCK_METHOD7(KeyboardHook, @@ -53,6 +56,8 @@ class SpyKeyboardKeyHandler : public KeyboardHandlerBase { bool was_down, KeyEventCallback callback)); + MOCK_METHOD1(SyncModifiersIfNeeded, void(int modifiers_state)); + private: std::unique_ptr real_implementation_; }; @@ -266,15 +271,15 @@ TEST(FlutterWindowTest, OnPointerStarSendsDeviceType) { // Move EXPECT_CALL(delegate, OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindMouse, - kDefaultPointerDeviceId)) + kDefaultPointerDeviceId, 0)) .Times(1); EXPECT_CALL(delegate, OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindTouch, - kDefaultPointerDeviceId)) + kDefaultPointerDeviceId, 0)) .Times(1); EXPECT_CALL(delegate, OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindStylus, - kDefaultPointerDeviceId)) + kDefaultPointerDeviceId, 0)) .Times(1); // Down @@ -323,7 +328,7 @@ TEST(FlutterWindowTest, OnPointerStarSendsDeviceType) { .Times(1); win32window.OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindMouse, - kDefaultPointerDeviceId); + kDefaultPointerDeviceId, 0); win32window.OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindMouse, kDefaultPointerDeviceId, WM_LBUTTONDOWN); win32window.OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindMouse, @@ -333,7 +338,7 @@ TEST(FlutterWindowTest, OnPointerStarSendsDeviceType) { // Touch win32window.OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindTouch, - kDefaultPointerDeviceId); + kDefaultPointerDeviceId, 0); win32window.OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindTouch, kDefaultPointerDeviceId, WM_LBUTTONDOWN); win32window.OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindTouch, @@ -343,7 +348,7 @@ TEST(FlutterWindowTest, OnPointerStarSendsDeviceType) { // Pen win32window.OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindStylus, - kDefaultPointerDeviceId); + kDefaultPointerDeviceId, 0); win32window.OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindStylus, kDefaultPointerDeviceId, WM_LBUTTONDOWN); win32window.OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindStylus, diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index 65ae0ef3cef40..a557c2f0ae7e1 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -166,7 +166,9 @@ void FlutterWindowsView::OnWindowRepaint() { void FlutterWindowsView::OnPointerMove(double x, double y, FlutterPointerDeviceKind device_kind, - int32_t device_id) { + int32_t device_id, + int modifiers_state) { + keyboard_key_handler_->SyncModifiersIfNeeded(modifiers_state); SendPointerMove(x, y, GetOrCreatePointerState(device_kind, device_id)); } @@ -285,8 +287,6 @@ void FlutterWindowsView::OnResetImeComposing() { void FlutterWindowsView::InitializeKeyboard() { auto internal_plugin_messenger = internal_plugin_registrar_->messenger(); - // TODO(cbracken): This can be inlined into KeyboardKeyEmedderHandler once - // UWP code is removed. https://github.com/flutter/flutter/issues/102172. KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state = GetKeyState; KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan = [](UINT virtual_key, bool extended) { diff --git a/shell/platform/windows/flutter_windows_view.h b/shell/platform/windows/flutter_windows_view.h index 2c0c719953f7c..c941492c94acd 100644 --- a/shell/platform/windows/flutter_windows_view.h +++ b/shell/platform/windows/flutter_windows_view.h @@ -112,7 +112,8 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, void OnPointerMove(double x, double y, FlutterPointerDeviceKind device_kind, - int32_t device_id) override; + int32_t device_id, + int modifiers_state) override; // |WindowBindingHandlerDelegate| void OnPointerDown(double x, diff --git a/shell/platform/windows/keyboard_handler_base.h b/shell/platform/windows/keyboard_handler_base.h index d27ae1a67f872..a6a151d421292 100644 --- a/shell/platform/windows/keyboard_handler_base.h +++ b/shell/platform/windows/keyboard_handler_base.h @@ -29,6 +29,10 @@ class KeyboardHandlerBase { bool extended, bool was_down, KeyEventCallback callback) = 0; + + // If needed, synthesize modifier keys events by comparing the + // given modifiers state to the known pressing state.. + virtual void SyncModifiersIfNeeded(int modifiers_state) = 0; }; } // namespace flutter diff --git a/shell/platform/windows/keyboard_key_channel_handler.cc b/shell/platform/windows/keyboard_key_channel_handler.cc index 94041cdb15d1f..164684650fb83 100644 --- a/shell/platform/windows/keyboard_key_channel_handler.cc +++ b/shell/platform/windows/keyboard_key_channel_handler.cc @@ -109,6 +109,10 @@ KeyboardKeyChannelHandler::KeyboardKeyChannelHandler( KeyboardKeyChannelHandler::~KeyboardKeyChannelHandler() = default; +void KeyboardKeyChannelHandler::SyncModifiersIfNeeded(int modifiers_state) { + // Do nothing +} + void KeyboardKeyChannelHandler::KeyboardHook( int key, int scancode, diff --git a/shell/platform/windows/keyboard_key_channel_handler.h b/shell/platform/windows/keyboard_key_channel_handler.h index 57ade1e4e8513..89efd5449bc1f 100644 --- a/shell/platform/windows/keyboard_key_channel_handler.h +++ b/shell/platform/windows/keyboard_key_channel_handler.h @@ -38,6 +38,8 @@ class KeyboardKeyChannelHandler bool was_down, std::function callback); + void SyncModifiersIfNeeded(int modifiers_state); + private: // The Flutter system channel for key event messages. std::unique_ptr> channel_; diff --git a/shell/platform/windows/keyboard_key_embedder_handler.cc b/shell/platform/windows/keyboard_key_embedder_handler.cc index 68288b3469b66..f31a5caa7f540 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler.cc +++ b/shell/platform/windows/keyboard_key_embedder_handler.cc @@ -187,7 +187,7 @@ void KeyboardKeyEmbedderHandler::KeyboardHookImpl( const bool is_event_down = action == WM_KEYDOWN || action == WM_SYSKEYDOWN; bool event_key_can_be_repeat = true; - UpdateLastSeenCritialKey(key, physical_key, sequence_logical_key); + UpdateLastSeenCriticalKey(key, physical_key, sequence_logical_key); // Synchronize the toggled states of critical keys (such as whether CapsLocks // is enabled). Toggled states can only be changed upon a down event, so if // the recorded toggled state does not match the true state, this function @@ -197,16 +197,18 @@ void KeyboardKeyEmbedderHandler::KeyboardHookImpl( // After this function, all critical keys will have their toggled state // updated to the true state, while the critical keys whose toggled state have // been changed will be pressed regardless of their true pressed state. - // Updating the pressed state will be done by SynchronizeCritialPressedStates. - SynchronizeCritialToggledStates(key, is_event_down, &event_key_can_be_repeat); + // Updating the pressed state will be done by + // SynchronizeCriticalPressedStates. + SynchronizeCriticalToggledStates(key, is_event_down, + &event_key_can_be_repeat); // Synchronize the pressed states of critical keys (such as whether CapsLocks // is pressed). // // After this function, all critical keys except for the target key will have // their toggled state and pressed state matched with their true states. The // target key's pressed state will be updated immediately after this. - SynchronizeCritialPressedStates(key, physical_key, is_event_down, - event_key_can_be_repeat); + SynchronizeCriticalPressedStates(key, physical_key, is_event_down, + event_key_can_be_repeat); // Reassess the last logical record in case pressingRecords_ was modified // by the above synchronization methods. @@ -317,8 +319,8 @@ void KeyboardKeyEmbedderHandler::KeyboardHookImpl( // received despite that GetKeyState says that CapsLock is not pressed. In // such case, post-event synchronization will synthesize a CapsLock up event // after the main event. - SynchronizeCritialPressedStates(key, physical_key, is_event_down, - event_key_can_be_repeat); + SynchronizeCriticalPressedStates(key, physical_key, is_event_down, + event_key_can_be_repeat); } void KeyboardKeyEmbedderHandler::KeyboardHook( @@ -349,7 +351,7 @@ void KeyboardKeyEmbedderHandler::KeyboardHook( } } -void KeyboardKeyEmbedderHandler::UpdateLastSeenCritialKey( +void KeyboardKeyEmbedderHandler::UpdateLastSeenCriticalKey( int virtual_key, uint64_t physical_key, uint64_t logical_key) { @@ -360,7 +362,7 @@ void KeyboardKeyEmbedderHandler::UpdateLastSeenCritialKey( } } -void KeyboardKeyEmbedderHandler::SynchronizeCritialToggledStates( +void KeyboardKeyEmbedderHandler::SynchronizeCriticalToggledStates( int event_virtual_key, bool is_event_down, bool* event_key_can_be_repeat) { @@ -415,7 +417,7 @@ void KeyboardKeyEmbedderHandler::SynchronizeCritialToggledStates( } } -void KeyboardKeyEmbedderHandler::SynchronizeCritialPressedStates( +void KeyboardKeyEmbedderHandler::SynchronizeCriticalPressedStates( int event_virtual_key, int event_physical_key, bool is_event_down, @@ -492,6 +494,72 @@ void KeyboardKeyEmbedderHandler::SynchronizeCritialPressedStates( } } +void KeyboardKeyEmbedderHandler::SyncModifiersIfNeeded(int modifiers_state) { + // TODO(bleroux): consider exposing these constants in flutter_key_map.g.cc? + const uint64_t physical_shift_left = + windowsToPhysicalMap_.at(kScanCodeShiftLeft); + const uint64_t physical_shift_right = + windowsToPhysicalMap_.at(kScanCodeShiftRight); + const uint64_t logical_shift_left = + windowsToLogicalMap_.at(kKeyCodeShiftLeft); + const uint64_t physical_control_left = + windowsToPhysicalMap_.at(kScanCodeControlLeft); + const uint64_t physical_control_right = + windowsToPhysicalMap_.at(kScanCodeControlRight); + const uint64_t logical_control_left = + windowsToLogicalMap_.at(kKeyCodeControlLeft); + + bool shift_pressed = (modifiers_state & kShift) != 0; + SynthesizeIfNeeded(physical_shift_left, physical_shift_right, + logical_shift_left, shift_pressed); + bool control_pressed = (modifiers_state & kControl) != 0; + SynthesizeIfNeeded(physical_control_left, physical_control_right, + logical_control_left, control_pressed); +} + +void KeyboardKeyEmbedderHandler::SynthesizeIfNeeded(uint64_t physical_left, + uint64_t physical_right, + uint64_t logical_left, + bool is_pressed) { + auto pressing_record_iter_left = pressingRecords_.find(physical_left); + bool left_pressed = pressing_record_iter_left != pressingRecords_.end(); + auto pressing_record_iter_right = pressingRecords_.find(physical_right); + bool right_pressed = pressing_record_iter_right != pressingRecords_.end(); + bool already_pressed = left_pressed || right_pressed; + bool synthesize_down = is_pressed && !already_pressed; + bool synthesize_up = !is_pressed && already_pressed; + + if (synthesize_down) { + SendSynthesizeDownEvent(physical_left, logical_left); + } + + if (synthesize_up && left_pressed) { + uint64_t known_logical = pressing_record_iter_left->second; + SendSynthesizeUpEvent(physical_left, known_logical); + } + + if (synthesize_up && right_pressed) { + uint64_t known_logical = pressing_record_iter_right->second; + SendSynthesizeUpEvent(physical_right, known_logical); + } +} + +void KeyboardKeyEmbedderHandler::SendSynthesizeDownEvent(uint64_t physical, + uint64_t logical) { + SendEvent( + SynthesizeSimpleEvent(kFlutterKeyEventTypeDown, physical, logical, ""), + nullptr, nullptr); + pressingRecords_[physical] = logical; +}; + +void KeyboardKeyEmbedderHandler::SendSynthesizeUpEvent(uint64_t physical, + uint64_t logical) { + SendEvent( + SynthesizeSimpleEvent(kFlutterKeyEventTypeUp, physical, logical, ""), + nullptr, nullptr); + pressingRecords_.erase(physical); +}; + void KeyboardKeyEmbedderHandler::HandleResponse(bool handled, void* user_data) { PendingResponse* pending = reinterpret_cast(user_data); auto callback = std::move(pending->callback); @@ -516,8 +584,6 @@ void KeyboardKeyEmbedderHandler::InitCriticalKeys( }; }; - // TODO(dkwingsmt): Consider adding more critical keys here. - // https://github.com/flutter/flutter/issues/76736 critical_keys_.emplace(VK_LSHIFT, createCheckedKey(VK_LSHIFT, false, true, false)); critical_keys_.emplace(VK_RSHIFT, @@ -532,7 +598,6 @@ void KeyboardKeyEmbedderHandler::InitCriticalKeys( createCheckedKey(VK_RMENU, true, true, false)); critical_keys_.emplace(VK_LWIN, createCheckedKey(VK_LWIN, true, true, false)); critical_keys_.emplace(VK_RWIN, createCheckedKey(VK_RWIN, true, true, false)); - critical_keys_.emplace(VK_CAPITAL, createCheckedKey(VK_CAPITAL, false, true, true)); critical_keys_.emplace(VK_SCROLL, diff --git a/shell/platform/windows/keyboard_key_embedder_handler.h b/shell/platform/windows/keyboard_key_embedder_handler.h index 67ff6490e5fc4..9a027519dee59 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler.h +++ b/shell/platform/windows/keyboard_key_embedder_handler.h @@ -70,6 +70,8 @@ class KeyboardKeyEmbedderHandler bool was_down, std::function callback) override; + void SyncModifiersIfNeeded(int modifiers_state) override; + private: struct PendingResponse { std::function callback; @@ -109,20 +111,20 @@ class KeyboardKeyEmbedderHandler // Assign |critical_keys_| with basic information. void InitCriticalKeys(MapVirtualKeyToScanCode map_virtual_key_to_scan_code); // Update |critical_keys_| with last seen logical and physical key. - void UpdateLastSeenCritialKey(int virtual_key, - uint64_t physical_key, - uint64_t logical_key); + void UpdateLastSeenCriticalKey(int virtual_key, + uint64_t physical_key, + uint64_t logical_key); // Check each key's state from |get_key_state_| and synthesize events // if their toggling states have been desynchronized. - void SynchronizeCritialToggledStates(int event_virtual_key, - bool is_event_down, - bool* event_key_can_be_repeat); + void SynchronizeCriticalToggledStates(int event_virtual_key, + bool is_event_down, + bool* event_key_can_be_repeat); // Check each key's state from |get_key_state_| and synthesize events // if their pressing states have been desynchronized. - void SynchronizeCritialPressedStates(int event_virtual_key, - int event_physical_key, - bool is_event_down, - bool event_key_can_be_repeat); + void SynchronizeCriticalPressedStates(int event_virtual_key, + int event_physical_key, + bool is_event_down, + bool event_key_can_be_repeat); // Wraps perform_send_event_ with state tracking. Use this instead of // |perform_send_event_| to send events to the framework. @@ -130,6 +132,19 @@ class KeyboardKeyEmbedderHandler FlutterKeyEventCallback callback, void* user_data); + // Send a synthesized down event and update pressing records. + void SendSynthesizeDownEvent(uint64_t physical, uint64_t logical); + + // Send a synthesized up event and update pressing records. + void SendSynthesizeUpEvent(uint64_t physical, uint64_t logical); + + // Send a synthesized up or down event depending on the current pressing + // state. + void SynthesizeIfNeeded(uint64_t physical_left, + uint64_t physical_right, + uint64_t logical_left, + bool is_pressed); + std::function perform_send_event_; GetKeyStateHandler get_key_state_; diff --git a/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc b/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc index cf614379c5dca..d8532c2b11730 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc +++ b/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc @@ -10,6 +10,7 @@ #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/embedder/test_utils/key_codes.g.h" #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" +#include "flutter/shell/platform/windows/keyboard_utils.h" #include "flutter/shell/platform/windows/testing/engine_modifier.h" #include "gtest/gtest.h" @@ -513,6 +514,205 @@ TEST(KeyboardKeyEmbedderHandlerTest, ModifierKeysByVirtualKey) { results.clear(); } +// Test if modifiers left key down events are synthesized when left or right +// keys are not pressed. +TEST(KeyboardKeyEmbedderHandlerTest, + SynthesizeModifierLeftKeyDownWhenNotPressed) { + TestKeystate key_state; + std::vector results; + TestFlutterKeyEvent* event; + bool last_handled = false; + + std::unique_ptr handler = + std::make_unique( + [&results](const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, void* user_data) { + results.emplace_back(event, callback, user_data); + }, + key_state.Getter(), DefaultMapVkToScan); + + // Should synthesize shift left key down event. + handler->SyncModifiersIfNeeded(kShift); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalShiftLeft); + EXPECT_EQ(event->logical, kLogicalShiftLeft); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); + results.clear(); + + // Clear the pressing state. + handler->SyncModifiersIfNeeded(0); + results.clear(); + + // Should synthesize control left key down event. + handler->SyncModifiersIfNeeded(kControl); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalControlLeft); + EXPECT_EQ(event->logical, kLogicalControlLeft); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); +} + +// Test if modifiers left key down events are not synthesized when left or right +// keys are pressed. +TEST(KeyboardKeyEmbedderHandlerTest, DoNotSynthesizeModifierDownWhenPressed) { + TestKeystate key_state; + std::vector results; + TestFlutterKeyEvent* event; + bool last_handled = false; + + std::unique_ptr handler = + std::make_unique( + [&results](const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, void* user_data) { + results.emplace_back(event, callback, user_data); + }, + key_state.Getter(), DefaultMapVkToScan); + + // Should not synthesize shift left key down event when shift left key + // is already pressed. + handler->KeyboardHook( + VK_LSHIFT, kScanCodeShiftLeft, WM_KEYDOWN, 0, false, true, + [&last_handled](bool handled) { last_handled = handled; }); + results.clear(); + handler->SyncModifiersIfNeeded(kShift); + EXPECT_EQ(results.size(), 0); + + // Should not synthesize shift left key down event when shift right key + // is already pressed. + handler->KeyboardHook( + VK_RSHIFT, kScanCodeShiftRight, WM_KEYDOWN, 0, false, true, + [&last_handled](bool handled) { last_handled = handled; }); + results.clear(); + handler->SyncModifiersIfNeeded(kShift); + EXPECT_EQ(results.size(), 0); + + // Should not synthesize control left key down event when control left key + // is already pressed. + handler->KeyboardHook( + VK_LCONTROL, kScanCodeControlLeft, WM_KEYDOWN, 0, false, true, + [&last_handled](bool handled) { last_handled = handled; }); + results.clear(); + handler->SyncModifiersIfNeeded(kControl); + EXPECT_EQ(results.size(), 0); + + // Should not synthesize control left key down event when control right key + // is already pressed . + handler->KeyboardHook( + VK_RCONTROL, kScanCodeControlRight, WM_KEYDOWN, 0, true, true, + [&last_handled](bool handled) { last_handled = handled; }); + results.clear(); + handler->SyncModifiersIfNeeded(kControl); + EXPECT_EQ(results.size(), 0); +} + +// Test if modifiers keys up events are synthesized when left or right keys +// are pressed. +TEST(KeyboardKeyEmbedderHandlerTest, SynthesizeModifierUpWhenPressed) { + TestKeystate key_state; + std::vector results; + TestFlutterKeyEvent* event; + bool last_handled = false; + + std::unique_ptr handler = + std::make_unique( + [&results](const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, void* user_data) { + results.emplace_back(event, callback, user_data); + }, + key_state.Getter(), DefaultMapVkToScan); + + // Should synthesize shift left key up event when shift left key is + // already pressed and modifiers state is zero. + handler->KeyboardHook( + VK_LSHIFT, kScanCodeShiftLeft, WM_KEYDOWN, 0, false, true, + [&last_handled](bool handled) { last_handled = handled; }); + results.clear(); + handler->SyncModifiersIfNeeded(0); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalShiftLeft); + EXPECT_EQ(event->logical, kLogicalShiftLeft); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); + results.clear(); + + // Should synthesize shift right key up event when shift right key is + // already pressed and modifiers state is zero. + handler->KeyboardHook( + VK_RSHIFT, kScanCodeShiftRight, WM_KEYDOWN, 0, false, true, + [&last_handled](bool handled) { last_handled = handled; }); + results.clear(); + handler->SyncModifiersIfNeeded(0); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalShiftRight); + EXPECT_EQ(event->logical, kLogicalShiftRight); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); + results.clear(); + + // Should synthesize control left key up event when control left key is + // already pressed and modifiers state is zero. + handler->KeyboardHook( + VK_LCONTROL, kScanCodeControlLeft, WM_KEYDOWN, 0, false, true, + [&last_handled](bool handled) { last_handled = handled; }); + results.clear(); + handler->SyncModifiersIfNeeded(0); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalControlLeft); + EXPECT_EQ(event->logical, kLogicalControlLeft); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); + results.clear(); + + // Should synthesize control right key up event when control right key is + // already pressed and modifiers state is zero. + handler->KeyboardHook( + VK_RCONTROL, kScanCodeControlRight, WM_KEYDOWN, 0, true, true, + [&last_handled](bool handled) { last_handled = handled; }); + results.clear(); + handler->SyncModifiersIfNeeded(0); + EXPECT_EQ(results.size(), 1); + event = &results[0]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalControlRight); + EXPECT_EQ(event->logical, kLogicalControlRight); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); + results.clear(); +} + +// Test if modifiers key up events are not synthesized when left or right +// keys are not pressed. +TEST(KeyboardKeyEmbedderHandlerTest, DoNotSynthesizeModifierUpWhenNotPressed) { + TestKeystate key_state; + std::vector results; + TestFlutterKeyEvent* event; + bool last_handled = false; + + std::unique_ptr handler = + std::make_unique( + [&results](const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, void* user_data) { + results.emplace_back(event, callback, user_data); + }, + key_state.Getter(), DefaultMapVkToScan); + + // Should not synthesize up events when no modifier key is pressed + // in pressing state and modifiers state is zero. + handler->SyncModifiersIfNeeded(0); + EXPECT_EQ(results.size(), 0); +} + TEST(KeyboardKeyEmbedderHandlerTest, RepeatedDownIsIgnored) { TestKeystate key_state; std::vector results; diff --git a/shell/platform/windows/keyboard_key_handler.cc b/shell/platform/windows/keyboard_key_handler.cc index 718c6f2afbcca..0bd71e0e96dac 100644 --- a/shell/platform/windows/keyboard_key_handler.cc +++ b/shell/platform/windows/keyboard_key_handler.cc @@ -31,6 +31,12 @@ void KeyboardKeyHandler::AddDelegate( delegates_.push_back(std::move(delegate)); } +void KeyboardKeyHandler::SyncModifiersIfNeeded(int modifiers_state) { + // Only call SyncModifierIfNeeded on the key embedder handler. + auto& key_embedder_handler = delegates_.front(); + key_embedder_handler->SyncModifiersIfNeeded(modifiers_state); +} + void KeyboardKeyHandler::KeyboardHook(int key, int scancode, int action, diff --git a/shell/platform/windows/keyboard_key_handler.h b/shell/platform/windows/keyboard_key_handler.h index a0a67fad69998..5e152f5ace8db 100644 --- a/shell/platform/windows/keyboard_key_handler.h +++ b/shell/platform/windows/keyboard_key_handler.h @@ -47,6 +47,8 @@ class KeyboardKeyHandler : public KeyboardHandlerBase { bool was_down, KeyEventCallback callback) = 0; + virtual void SyncModifiersIfNeeded(int modifiers_state) = 0; + virtual ~KeyboardKeyHandlerDelegate(); }; @@ -58,6 +60,9 @@ class KeyboardKeyHandler : public KeyboardHandlerBase { // Add a delegate that handles events received by |KeyboardHook|. void AddDelegate(std::unique_ptr delegate); + // Synthesize modifier keys events if needed. + void SyncModifiersIfNeeded(int modifiers_state) override; + // Handles a key event. // // Returns whether this handler claims to handle the event, which is true if diff --git a/shell/platform/windows/keyboard_key_handler_unittests.cc b/shell/platform/windows/keyboard_key_handler_unittests.cc index 5e58739fda5f8..cdb41e6b50f0b 100644 --- a/shell/platform/windows/keyboard_key_handler_unittests.cc +++ b/shell/platform/windows/keyboard_key_handler_unittests.cc @@ -87,6 +87,10 @@ class MockKeyHandlerDelegate callback_handler(hook_history->back().callback); } + virtual void SyncModifiersIfNeeded(int modifiers_state) { + // Do Nothing + } + CallbackHandler callback_handler; int delegate_id; std::list* hook_history; diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index 1d088b9226aac..1a2f4949c83d7 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -1832,7 +1832,7 @@ TEST(KeyboardTest, SynthesizeModifiers) { EXPECT_EQ(tester.RedispatchedMessageCountAndClear(), 1); // CapsLock, phase 0 -> 2 -> 0. - // (For phases, see |SynchronizeCritialToggledStates|.) + // (For phases, see |SynchronizeCriticalToggledStates|.) tester.InjectKeyboardChanges(std::vector{ KeyStateChange{VK_CAPITAL, false, true}, event1}); EXPECT_EQ(key_calls.size(), 3); diff --git a/shell/platform/windows/keyboard_utils.h b/shell/platform/windows/keyboard_utils.h index 9b7ee17896145..f71e12f5ebb5e 100644 --- a/shell/platform/windows/keyboard_utils.h +++ b/shell/platform/windows/keyboard_utils.h @@ -11,6 +11,15 @@ namespace flutter { +constexpr int kShift = 1 << 0; +constexpr int kControl = 1 << 3; +constexpr int kScanCodeShiftLeft = 0x2a; +constexpr int kScanCodeShiftRight = 0x36; +constexpr int kKeyCodeShiftLeft = 0xa0; +constexpr int kScanCodeControlLeft = 0x1d; +constexpr int kScanCodeControlRight = 0xe01d; +constexpr int kKeyCodeControlLeft = 0xa2; + // The bit of a mapped character in a WM_KEYDOWN message that indicates the // character is a dead key. // diff --git a/shell/platform/windows/testing/mock_window.h b/shell/platform/windows/testing/mock_window.h index 78fabc663c1cf..f25732ee63617 100644 --- a/shell/platform/windows/testing/mock_window.h +++ b/shell/platform/windows/testing/mock_window.h @@ -41,8 +41,8 @@ class MockWindow : public Window { MOCK_METHOD1(OnDpiScale, void(unsigned int)); MOCK_METHOD2(OnResize, void(unsigned int, unsigned int)); MOCK_METHOD0(OnPaint, void()); - MOCK_METHOD4(OnPointerMove, - void(double, double, FlutterPointerDeviceKind, int32_t)); + MOCK_METHOD5(OnPointerMove, + void(double, double, FlutterPointerDeviceKind, int32_t, int)); MOCK_METHOD5(OnPointerDown, void(double, double, FlutterPointerDeviceKind, int32_t, UINT)); MOCK_METHOD5(OnPointerUp, diff --git a/shell/platform/windows/testing/mock_window_binding_handler_delegate.h b/shell/platform/windows/testing/mock_window_binding_handler_delegate.h index 2e7df003cc847..89ac5f5333767 100644 --- a/shell/platform/windows/testing/mock_window_binding_handler_delegate.h +++ b/shell/platform/windows/testing/mock_window_binding_handler_delegate.h @@ -23,8 +23,8 @@ class MockWindowBindingHandlerDelegate : public WindowBindingHandlerDelegate { MOCK_METHOD2(OnWindowSizeChanged, void(size_t, size_t)); MOCK_METHOD0(OnWindowRepaint, void()); - MOCK_METHOD4(OnPointerMove, - void(double, double, FlutterPointerDeviceKind, int32_t)); + MOCK_METHOD5(OnPointerMove, + void(double, double, FlutterPointerDeviceKind, int32_t, int)); MOCK_METHOD5(OnPointerDown, void(double, double, diff --git a/shell/platform/windows/window.cc b/shell/platform/windows/window.cc index 8f2a3327a97db..3a5441afad90f 100644 --- a/shell/platform/windows/window.cc +++ b/shell/platform/windows/window.cc @@ -15,6 +15,7 @@ #include #include "flutter/shell/platform/windows/dpi_utils.h" +#include "flutter/shell/platform/windows/keyboard_utils.h" namespace flutter { @@ -379,7 +380,7 @@ Window::HandleMessage(UINT const message, OnPointerDown(x, y, kFlutterPointerDeviceKindTouch, touch_id, WM_LBUTTONDOWN); } else if (touch.dwFlags & TOUCHEVENTF_MOVE) { - OnPointerMove(x, y, kFlutterPointerDeviceKindTouch, touch_id); + OnPointerMove(x, y, kFlutterPointerDeviceKindTouch, touch_id, 0); } else if (touch.dwFlags & TOUCHEVENTF_UP) { OnPointerUp(x, y, kFlutterPointerDeviceKindTouch, touch_id, WM_LBUTTONDOWN); @@ -401,7 +402,15 @@ Window::HandleMessage(UINT const message, mouse_x_ = static_cast(xPos); mouse_y_ = static_cast(yPos); - OnPointerMove(mouse_x_, mouse_y_, device_kind, kDefaultPointerDeviceId); + int mods = 0; + if (wparam & MK_CONTROL) { + mods |= kControl; + } + if (wparam & MK_SHIFT) { + mods |= kShift; + } + OnPointerMove(mouse_x_, mouse_y_, device_kind, kDefaultPointerDeviceId, + mods); } break; case WM_MOUSELEAVE: diff --git a/shell/platform/windows/window.h b/shell/platform/windows/window.h index 6c234ce68fc58..036883ed66a10 100644 --- a/shell/platform/windows/window.h +++ b/shell/platform/windows/window.h @@ -109,7 +109,8 @@ class Window : public KeyboardManager::WindowDelegate { virtual void OnPointerMove(double x, double y, FlutterPointerDeviceKind device_kind, - int32_t device_id) = 0; + int32_t device_id, + int modifiers_state) = 0; // Called when the a mouse button, determined by |button|, goes down. virtual void OnPointerDown(double x, diff --git a/shell/platform/windows/window_binding_handler_delegate.h b/shell/platform/windows/window_binding_handler_delegate.h index 9d0e0253c447a..1812107f2dfa9 100644 --- a/shell/platform/windows/window_binding_handler_delegate.h +++ b/shell/platform/windows/window_binding_handler_delegate.h @@ -32,7 +32,8 @@ class WindowBindingHandlerDelegate { virtual void OnPointerMove(double x, double y, FlutterPointerDeviceKind device_kind, - int32_t device_id) = 0; + int32_t device_id, + int modifiers_state) = 0; // Notifies delegate that backing window mouse pointer button has been // pressed. Typically called by currently configured WindowBindingHandler. diff --git a/shell/platform/windows/window_unittests.cc b/shell/platform/windows/window_unittests.cc index 1557d03b7a6a3..82fbede827fe2 100644 --- a/shell/platform/windows/window_unittests.cc +++ b/shell/platform/windows/window_unittests.cc @@ -166,7 +166,7 @@ TEST(MockWindow, MouseLeave) { const double mouse_y = 20.0; EXPECT_CALL(window, OnPointerMove(mouse_x, mouse_y, - kFlutterPointerDeviceKindMouse, 0)) + kFlutterPointerDeviceKindMouse, 0, 0)) .Times(1); EXPECT_CALL(window, OnPointerLeave(mouse_x, mouse_y, kFlutterPointerDeviceKindMouse, 0))