blob: 3b42c9507c85979ecc246dabad23ef93a45ceb84 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/windows/keyboard_key_handler.h"
#include <rapidjson/document.h>
#include <map>
#include <memory>
#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"
#include "gtest/gtest.h"
namespace flutter {
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<int, SHORT> 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;
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<void(bool)> Callback;
typedef std::function<void(Callback&)> CallbackHandler;
void dont_respond(Callback& callback) {}
void respond_true(Callback& callback) {
callback(true);
}
void respond_false(Callback& callback) {
callback(false);
}
// A testing |KeyHandlerDelegate| that records all calls
// to |KeyboardHook| and can be customized with whether
// the hook is handled in async.
class MockKeyHandlerDelegate
: public KeyboardKeyHandler::KeyboardKeyHandlerDelegate {
public:
class KeyboardHookCall {
public:
int delegate_id;
int key;
int scancode;
int action;
char32_t character;
bool extended;
bool was_down;
std::function<void(bool)> callback;
};
// Create a |MockKeyHandlerDelegate|.
//
// The |delegate_id| is an arbitrary ID to tell between delegates
// that will be recorded in |KeyboardHookCall|.
//
// The |hook_history| will store every call to |KeyboardHookCall| that are
// handled asynchronously.
//
// The |is_async| is a function that the class calls upon every
// |KeyboardHookCall| to decide whether the call is handled asynchronously.
// Defaults to always returning true (async).
MockKeyHandlerDelegate(int delegate_id,
std::list<KeyboardHookCall>* hook_history)
: delegate_id(delegate_id),
hook_history(hook_history),
callback_handler(dont_respond) {}
virtual ~MockKeyHandlerDelegate() = default;
virtual void KeyboardHook(int key,
int scancode,
int action,
char32_t character,
bool extended,
bool was_down,
std::function<void(bool)> callback) {
hook_history->push_back(KeyboardHookCall{
.delegate_id = delegate_id,
.key = key,
.scancode = scancode,
.character = character,
.extended = extended,
.was_down = was_down,
.callback = std::move(callback),
});
callback_handler(hook_history->back().callback);
}
virtual void SyncModifiersIfNeeded(int modifiers_state) {
// Do Nothing
}
virtual std::map<uint64_t, uint64_t> GetPressedState() {
std::map<uint64_t, uint64_t> Empty_State;
return Empty_State;
}
CallbackHandler callback_handler;
int delegate_id;
std::list<KeyboardHookCall>* hook_history;
private:
FML_DISALLOW_COPY_AND_ASSIGN(MockKeyHandlerDelegate);
};
enum KeyEventResponse {
kNoResponse,
kHandled,
kUnhandled,
};
static KeyEventResponse key_event_response = kNoResponse;
void OnKeyEventResult(bool handled) {
key_event_response = handled ? kHandled : kUnhandled;
}
void SimulateKeyboardMessage(TestBinaryMessenger* messenger,
const std::string& method_name,
std::unique_ptr<EncodableValue> arguments,
MethodResult<EncodableValue>* 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<MockKeyHandlerDelegate::KeyboardHookCall> hook_history;
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<MockKeyHandlerDelegate>(1, &hook_history);
handler.AddDelegate(std::move(delegate));
/// Test 1: One event that is handled by the framework
// Dispatch a key event
handler.KeyboardHook(64, kHandledScanCode, WM_KEYDOWN, L'a', false, true,
OnKeyEventResult);
EXPECT_EQ(key_event_response, kNoResponse);
EXPECT_EQ(hook_history.size(), 1);
EXPECT_EQ(hook_history.back().delegate_id, 1);
EXPECT_EQ(hook_history.back().scancode, kHandledScanCode);
EXPECT_EQ(hook_history.back().was_down, true);
EXPECT_EQ(key_event_response, kNoResponse);
hook_history.back().callback(true);
EXPECT_EQ(key_event_response, kHandled);
key_event_response = kNoResponse;
hook_history.clear();
/// Test 2: Two events that are unhandled by the framework
handler.KeyboardHook(64, kHandledScanCode, WM_KEYDOWN, L'a', false, false,
OnKeyEventResult);
EXPECT_EQ(key_event_response, kNoResponse);
EXPECT_EQ(hook_history.size(), 1);
EXPECT_EQ(hook_history.back().delegate_id, 1);
EXPECT_EQ(hook_history.back().scancode, kHandledScanCode);
EXPECT_EQ(hook_history.back().was_down, false);
// Dispatch another key event
handler.KeyboardHook(65, kHandledScanCode2, WM_KEYUP, L'b', false, true,
OnKeyEventResult);
EXPECT_EQ(key_event_response, kNoResponse);
EXPECT_EQ(hook_history.size(), 2);
EXPECT_EQ(hook_history.back().delegate_id, 1);
EXPECT_EQ(hook_history.back().scancode, kHandledScanCode2);
EXPECT_EQ(hook_history.back().was_down, true);
// Resolve the second event first to test out-of-order response
hook_history.back().callback(false);
EXPECT_EQ(key_event_response, kUnhandled);
key_event_response = kNoResponse;
// Resolve the first event then
hook_history.front().callback(false);
EXPECT_EQ(key_event_response, kUnhandled);
hook_history.clear();
key_event_response = kNoResponse;
}
TEST(KeyboardKeyHandlerTest, SingleDelegateWithSyncResponds) {
std::list<MockKeyHandlerDelegate::KeyboardHookCall> hook_history;
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<MockKeyHandlerDelegate>(1, &hook_history);
CallbackHandler& delegate_handler = delegate->callback_handler;
handler.AddDelegate(std::move(delegate));
/// Test 1: One event that is handled by the framework
// Dispatch a key event
delegate_handler = respond_true;
handler.KeyboardHook(64, kHandledScanCode, WM_KEYDOWN, L'a', false, false,
OnKeyEventResult);
EXPECT_EQ(key_event_response, kHandled);
EXPECT_EQ(hook_history.size(), 1);
EXPECT_EQ(hook_history.back().delegate_id, 1);
EXPECT_EQ(hook_history.back().scancode, kHandledScanCode);
EXPECT_EQ(hook_history.back().was_down, false);
hook_history.clear();
/// Test 2: An event unhandled by the framework
delegate_handler = respond_false;
handler.KeyboardHook(64, kHandledScanCode, WM_KEYDOWN, L'a', false, false,
OnKeyEventResult);
EXPECT_EQ(key_event_response, kUnhandled);
EXPECT_EQ(hook_history.size(), 1);
EXPECT_EQ(hook_history.back().delegate_id, 1);
EXPECT_EQ(hook_history.back().scancode, kHandledScanCode);
EXPECT_EQ(hook_history.back().was_down, false);
hook_history.clear();
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<KeyboardKeyEmbedderHandler> embedder_handler =
std::make_unique<KeyboardKeyEmbedderHandler>(
[](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<uint64_t, uint64_t> 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<KeyboardKeyEmbedderHandler> embedder_handler =
std::make_unique<KeyboardKeyEmbedderHandler>(
[](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<EncodableMap>(*result);
EXPECT_EQ(map.size(), 1);
EncodableValue physical_value(static_cast<long long>(kPhysicalKeyA));
EncodableValue logical_value(static_cast<long long>(kLogicalKeyA));
EXPECT_EQ(map.at(physical_value), logical_value);
},
nullptr, nullptr);
SimulateKeyboardMessage(&messenger, kGetKeyboardStateMethod, nullptr,
&result_handler);
EXPECT_TRUE(success);
}
} // namespace testing
} // namespace flutter