diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index 68cccd27ab19f..81240286e84d5 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -631,7 +631,7 @@ FlutterWindowsEngine::CreateKeyboardKeyHandler( BinaryMessenger* messenger, KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state, KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan) { - auto keyboard_key_handler = std::make_unique(); + auto keyboard_key_handler = std::make_unique(messenger); keyboard_key_handler->AddDelegate( std::make_unique( [this](const FlutterKeyEvent& event, FlutterKeyEventCallback callback, @@ -641,6 +641,7 @@ FlutterWindowsEngine::CreateKeyboardKeyHandler( get_key_state, map_vk_to_scan)); keyboard_key_handler->AddDelegate( std::make_unique(messenger)); + keyboard_key_handler->InitKeyboardChannel(); return keyboard_key_handler; } diff --git a/shell/platform/windows/keyboard_handler_base.h b/shell/platform/windows/keyboard_handler_base.h index a6a151d421292..e2fd211403a5a 100644 --- a/shell/platform/windows/keyboard_handler_base.h +++ b/shell/platform/windows/keyboard_handler_base.h @@ -33,6 +33,12 @@ class KeyboardHandlerBase { // If needed, synthesize modifier keys events by comparing the // given modifiers state to the known pressing state.. virtual void SyncModifiersIfNeeded(int modifiers_state) = 0; + + // Returns the keyboard pressed state. + // + // Returns the keyboard pressed state. The map contains one entry per + // pressed keys, mapping from the logical key to the physical key. + virtual std::map GetPressedState() = 0; }; } // namespace flutter diff --git a/shell/platform/windows/keyboard_key_channel_handler.cc b/shell/platform/windows/keyboard_key_channel_handler.cc index 164684650fb83..eaa1a7696b6f3 100644 --- a/shell/platform/windows/keyboard_key_channel_handler.cc +++ b/shell/platform/windows/keyboard_key_channel_handler.cc @@ -110,7 +110,15 @@ KeyboardKeyChannelHandler::KeyboardKeyChannelHandler( KeyboardKeyChannelHandler::~KeyboardKeyChannelHandler() = default; void KeyboardKeyChannelHandler::SyncModifiersIfNeeded(int modifiers_state) { - // Do nothing + // Do nothing. +} + +std::map KeyboardKeyChannelHandler::GetPressedState() { + // Returns an empty state because it will never be called. + // KeyboardKeyEmbedderHandler is the only KeyboardKeyHandlerDelegate to handle + // GetPressedState() calls. + std::map empty_state; + return empty_state; } void KeyboardKeyChannelHandler::KeyboardHook( diff --git a/shell/platform/windows/keyboard_key_channel_handler.h b/shell/platform/windows/keyboard_key_channel_handler.h index 3b2e899fe2b1a..7a5d96f73ac17 100644 --- a/shell/platform/windows/keyboard_key_channel_handler.h +++ b/shell/platform/windows/keyboard_key_channel_handler.h @@ -41,6 +41,8 @@ class KeyboardKeyChannelHandler void SyncModifiersIfNeeded(int modifiers_state); + std::map GetPressedState(); + 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 227c8cc1dd241..efe7677940827 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler.cc +++ b/shell/platform/windows/keyboard_key_embedder_handler.cc @@ -350,6 +350,10 @@ void KeyboardKeyEmbedderHandler::KeyboardHook( } } +std::map KeyboardKeyEmbedderHandler::GetPressedState() { + return pressingRecords_; +} + void KeyboardKeyEmbedderHandler::UpdateLastSeenCriticalKey( int virtual_key, uint64_t physical_key, diff --git a/shell/platform/windows/keyboard_key_embedder_handler.h b/shell/platform/windows/keyboard_key_embedder_handler.h index a3537c3b23a3e..16dd9c0893db9 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler.h +++ b/shell/platform/windows/keyboard_key_embedder_handler.h @@ -73,6 +73,8 @@ class KeyboardKeyEmbedderHandler void SyncModifiersIfNeeded(int modifiers_state) override; + std::map GetPressedState() override; + private: struct PendingResponse { std::function callback; diff --git a/shell/platform/windows/keyboard_key_handler.cc b/shell/platform/windows/keyboard_key_handler.cc index 6866b955ddd49..f4feebbb3a132 100644 --- a/shell/platform/windows/keyboard_key_handler.cc +++ b/shell/platform/windows/keyboard_key_handler.cc @@ -7,6 +7,7 @@ #include #include "flutter/fml/logging.h" +#include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_method_codec.h" #include "flutter/shell/platform/windows/keyboard_utils.h" namespace flutter { @@ -17,15 +18,51 @@ namespace { // emitting a warning on the console about unhandled events. static constexpr int kMaxPendingEvents = 1000; +// The name of the channel for keyboard state queries. +static constexpr char kChannelName[] = "flutter/keyboard"; + +static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState"; + } // namespace KeyboardKeyHandler::KeyboardKeyHandlerDelegate::~KeyboardKeyHandlerDelegate() = default; -KeyboardKeyHandler::KeyboardKeyHandler() : last_sequence_id_(1) {} +KeyboardKeyHandler::KeyboardKeyHandler(flutter::BinaryMessenger* messenger) + : last_sequence_id_(1), + channel_(std::make_unique>( + messenger, + kChannelName, + &StandardMethodCodec::GetInstance())) {} KeyboardKeyHandler::~KeyboardKeyHandler() = default; +void KeyboardKeyHandler::InitKeyboardChannel() { + channel_->SetMethodCallHandler( + [this](const MethodCall& call, + std::unique_ptr> result) { + HandleMethodCall(call, std::move(result)); + }); +} + +void KeyboardKeyHandler::HandleMethodCall( + const MethodCall& method_call, + std::unique_ptr> result) { + const std::string& method = method_call.method_name(); + if (method.compare(kGetKeyboardStateMethod) == 0) { + EncodableMap value; + const auto& pressed_state = GetPressedState(); + for (const auto& pressed_key : pressed_state) { + EncodableValue physical_value(static_cast(pressed_key.first)); + EncodableValue logical_value(static_cast(pressed_key.second)); + value[physical_value] = logical_value; + } + result->Success(EncodableValue(value)); + } else { + result->NotImplemented(); + } +} + void KeyboardKeyHandler::AddDelegate( std::unique_ptr delegate) { delegates_.push_back(std::move(delegate)); @@ -37,6 +74,12 @@ void KeyboardKeyHandler::SyncModifiersIfNeeded(int modifiers_state) { key_embedder_handler->SyncModifiersIfNeeded(modifiers_state); } +std::map KeyboardKeyHandler::GetPressedState() { + // The embedder responder is the first element in delegates_. + auto& key_embedder_handler = delegates_.front(); + return key_embedder_handler->GetPressedState(); +} + 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 874ed0e15b40c..2752c1045c189 100644 --- a/shell/platform/windows/keyboard_key_handler.h +++ b/shell/platform/windows/keyboard_key_handler.h @@ -7,13 +7,15 @@ #include #include +#include #include #include #include "flutter/fml/macros.h" -#include "flutter/shell/platform/common/client_wrapper/include/flutter/basic_message_channel.h" +#include "flutter/shell/platform/common/client_wrapper/include/flutter/binary_messenger.h" +#include "flutter/shell/platform/common/client_wrapper/include/flutter/encodable_value.h" +#include "flutter/shell/platform/common/client_wrapper/include/flutter/method_channel.h" #include "flutter/shell/platform/windows/keyboard_handler_base.h" -#include "rapidjson/document.h" namespace flutter { @@ -50,14 +52,20 @@ class KeyboardKeyHandler : public KeyboardHandlerBase { virtual void SyncModifiersIfNeeded(int modifiers_state) = 0; + virtual std::map GetPressedState() = 0; + virtual ~KeyboardKeyHandlerDelegate(); }; - // Create a KeyboardKeyHandler. - explicit KeyboardKeyHandler(); + // Create a |KeyboardKeyHandler| by specifying the messenger + // through which the messages are sent. + explicit KeyboardKeyHandler(flutter::BinaryMessenger* messenger); ~KeyboardKeyHandler(); + // Init the keyboard channel used to answer to pressed state queries. + void InitKeyboardChannel(); + // Add a delegate that handles events received by |KeyboardHook|. void AddDelegate(std::unique_ptr delegate); @@ -97,6 +105,12 @@ class KeyboardKeyHandler : public KeyboardHandlerBase { bool was_down, KeyEventCallback callback) override; + // Returns the keyboard pressed state. + // + // Returns the keyboard pressed state. The dictionary contains one entry per + // pressed keys, mapping from the logical key to the physical key. + std::map GetPressedState() override; + private: struct PendingEvent { // Self-incrementing ID attached to an event sent to the framework. @@ -114,6 +128,11 @@ class KeyboardKeyHandler : public KeyboardHandlerBase { void ResolvePendingEvent(uint64_t sequence_id, bool handled); + // Called when a method is called on |channel_|; + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + std::vector> delegates_; // The queue of key events that have been sent to the framework but have not @@ -123,6 +142,9 @@ class KeyboardKeyHandler : public KeyboardHandlerBase { // The sequence_id attached to the last event sent to the framework. uint64_t last_sequence_id_; + // The Flutter system channel for keyboard state messages. + std::unique_ptr> channel_; + FML_DISALLOW_COPY_AND_ASSIGN(KeyboardKeyHandler); }; diff --git a/shell/platform/windows/keyboard_key_handler_unittests.cc b/shell/platform/windows/keyboard_key_handler_unittests.cc index 72b5719642cdc..3b42c9507c859 100644 --- a/shell/platform/windows/keyboard_key_handler_unittests.cc +++ b/shell/platform/windows/keyboard_key_handler_unittests.cc @@ -4,7 +4,16 @@ #include "flutter/shell/platform/windows/keyboard_key_handler.h" #include +#include #include +#include "flutter/shell/platform/common/client_wrapper/include/flutter/method_result_functions.h" +#include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_message_codec.h" +#include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_method_codec.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 "flutter/shell/platform/windows/testing/test_binary_messenger.h" #include "flutter/fml/macros.h" #include "gmock/gmock.h" @@ -15,6 +24,63 @@ namespace testing { namespace { +static constexpr char kChannelName[] = "flutter/keyboard"; +static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState"; + +constexpr SHORT kStateMaskToggled = 0x01; +constexpr SHORT kStateMaskPressed = 0x80; + +class TestFlutterKeyEvent : public FlutterKeyEvent { + public: + TestFlutterKeyEvent(const FlutterKeyEvent& src, + FlutterKeyEventCallback callback, + void* user_data) + : character_str(src.character), callback(callback), user_data(user_data) { + struct_size = src.struct_size; + timestamp = src.timestamp; + type = src.type; + physical = src.physical; + logical = src.logical; + character = character_str.c_str(); + synthesized = src.synthesized; + } + + TestFlutterKeyEvent(TestFlutterKeyEvent&& source) + : FlutterKeyEvent(source), + callback(std::move(source.callback)), + user_data(source.user_data) { + character = character_str.c_str(); + } + + FlutterKeyEventCallback callback; + void* user_data; + + private: + const std::string character_str; +}; + +class TestKeystate { + public: + void Set(int virtual_key, bool pressed, bool toggled_on = false) { + state_[virtual_key] = (pressed ? kStateMaskPressed : 0) | + (toggled_on ? kStateMaskToggled : 0); + } + + SHORT Get(int virtual_key) { return state_[virtual_key]; } + + KeyboardKeyEmbedderHandler::GetKeyStateHandler Getter() { + return [this](int virtual_key) { return Get(virtual_key); }; + } + + private: + std::map state_; +}; + +UINT DefaultMapVkToScan(UINT virtual_key, bool extended) { + return MapVirtualKey(virtual_key, + extended ? MAPVK_VK_TO_VSC_EX : MAPVK_VK_TO_VSC); +} + static constexpr int kHandledScanCode = 20; static constexpr int kHandledScanCode2 = 22; static constexpr int kUnhandledScanCode = 21; @@ -23,6 +89,9 @@ constexpr uint64_t kScanCodeShiftRight = 0x36; constexpr uint64_t kScanCodeControl = 0x1D; constexpr uint64_t kScanCodeAltLeft = 0x38; +constexpr uint64_t kScanCodeKeyA = 0x1e; +constexpr uint64_t kVirtualKeyA = 0x41; + typedef std::function Callback; typedef std::function CallbackHandler; void dont_respond(Callback& callback) {} @@ -92,6 +161,11 @@ class MockKeyHandlerDelegate // Do Nothing } + virtual std::map GetPressedState() { + std::map Empty_State; + return Empty_State; + } + CallbackHandler callback_handler; int delegate_id; std::list* hook_history; @@ -112,12 +186,33 @@ void OnKeyEventResult(bool handled) { key_event_response = handled ? kHandled : kUnhandled; } +void SimulateKeyboardMessage(TestBinaryMessenger* messenger, + const std::string& method_name, + std::unique_ptr arguments, + MethodResult* result_handler) { + MethodCall<> call(method_name, std::move(arguments)); + auto message = StandardMethodCodec::GetInstance().EncodeMethodCall(call); + + EXPECT_TRUE(messenger->SimulateEngineMessage( + kChannelName, message->data(), message->size(), + [&result_handler](const uint8_t* reply, size_t reply_size) { + StandardMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( + reply, reply_size, result_handler); + })); +} + } // namespace +using namespace ::flutter::testing::keycodes; + TEST(KeyboardKeyHandlerTest, SingleDelegateWithAsyncResponds) { std::list hook_history; - KeyboardKeyHandler handler; + TestBinaryMessenger messenger([](const std::string& channel, + const uint8_t* message, size_t message_size, + BinaryReply reply) {}); + KeyboardKeyHandler handler(&messenger); + // Add one delegate auto delegate = std::make_unique(1, &hook_history); handler.AddDelegate(std::move(delegate)); @@ -175,7 +270,10 @@ TEST(KeyboardKeyHandlerTest, SingleDelegateWithAsyncResponds) { TEST(KeyboardKeyHandlerTest, SingleDelegateWithSyncResponds) { std::list hook_history; - KeyboardKeyHandler handler; + TestBinaryMessenger messenger([](const std::string& channel, + const uint8_t* message, size_t message_size, + BinaryReply reply) {}); + KeyboardKeyHandler handler(&messenger); // Add one delegate auto delegate = std::make_unique(1, &hook_history); CallbackHandler& delegate_handler = delegate->callback_handler; @@ -210,5 +308,64 @@ TEST(KeyboardKeyHandlerTest, SingleDelegateWithSyncResponds) { key_event_response = kNoResponse; } +TEST(KeyboardKeyHandlerTest, HandlerGetPressedState) { + TestKeystate key_state; + + TestBinaryMessenger messenger([](const std::string& channel, + const uint8_t* message, size_t message_size, + BinaryReply reply) {}); + KeyboardKeyHandler handler(&messenger); + + std::unique_ptr embedder_handler = + std::make_unique( + [](const FlutterKeyEvent& event, FlutterKeyEventCallback callback, + void* user_data) {}, + key_state.Getter(), DefaultMapVkToScan); + handler.AddDelegate(std::move(embedder_handler)); + + // Dispatch a key event. + handler.KeyboardHook(kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false, + false, OnKeyEventResult); + + std::map pressed_state = handler.GetPressedState(); + EXPECT_EQ(pressed_state.size(), 1); + EXPECT_EQ(pressed_state.at(kPhysicalKeyA), kLogicalKeyA); +} + +TEST(KeyboardKeyHandlerTest, KeyboardChannelGetPressedState) { + TestKeystate key_state; + TestBinaryMessenger messenger; + KeyboardKeyHandler handler(&messenger); + + std::unique_ptr embedder_handler = + std::make_unique( + [](const FlutterKeyEvent& event, FlutterKeyEventCallback callback, + void* user_data) {}, + key_state.Getter(), DefaultMapVkToScan); + handler.AddDelegate(std::move(embedder_handler)); + handler.InitKeyboardChannel(); + + // Dispatch a key event. + handler.KeyboardHook(kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false, + false, OnKeyEventResult); + + bool success = false; + + MethodResultFunctions<> result_handler( + [&success](const EncodableValue* result) { + success = true; + auto& map = std::get(*result); + EXPECT_EQ(map.size(), 1); + EncodableValue physical_value(static_cast(kPhysicalKeyA)); + EncodableValue logical_value(static_cast(kLogicalKeyA)); + EXPECT_EQ(map.at(physical_value), logical_value); + }, + nullptr, nullptr); + + SimulateKeyboardMessage(&messenger, kGetKeyboardStateMethod, nullptr, + &result_handler); + EXPECT_TRUE(success); +} + } // namespace testing } // namespace flutter