blob: 68288b3469b6635ab88c6b03c3859596d587eb9f [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_embedder_handler.h"
#include <assert.h>
#include <windows.h>
#include <chrono>
#include <iostream>
#include <string>
#include "flutter/shell/platform/windows/keyboard_utils.h"
namespace flutter {
namespace {
// An arbitrary size for the character cache in bytes.
//
// It should hold a UTF-32 character encoded in UTF-8 as well as the trailing
// '\0'.
constexpr size_t kCharacterCacheSize = 8;
constexpr SHORT kStateMaskToggled = 0x01;
constexpr SHORT kStateMaskPressed = 0x80;
const char* empty_character = "";
// Get some bits of the char, from the start'th bit from the right (excluded)
// to the end'th bit from the right (included).
//
// For example, _GetBit(0x1234, 8, 4) => 0x3.
char _GetBit(char32_t ch, size_t start, size_t end) {
return (ch >> end) & ((1 << (start - end)) - 1);
}
} // namespace
std::string ConvertChar32ToUtf8(char32_t ch) {
std::string result;
assert(0 <= ch && ch <= 0x10FFFF);
if (ch <= 0x007F) {
result.push_back(ch);
} else if (ch <= 0x07FF) {
result.push_back(0b11000000 + _GetBit(ch, 11, 6));
result.push_back(0b10000000 + _GetBit(ch, 6, 0));
} else if (ch <= 0xFFFF) {
result.push_back(0b11100000 + _GetBit(ch, 16, 12));
result.push_back(0b10000000 + _GetBit(ch, 12, 6));
result.push_back(0b10000000 + _GetBit(ch, 6, 0));
} else {
result.push_back(0b11110000 + _GetBit(ch, 21, 18));
result.push_back(0b10000000 + _GetBit(ch, 18, 12));
result.push_back(0b10000000 + _GetBit(ch, 12, 6));
result.push_back(0b10000000 + _GetBit(ch, 6, 0));
}
return result;
}
KeyboardKeyEmbedderHandler::KeyboardKeyEmbedderHandler(
SendEventHandler send_event,
GetKeyStateHandler get_key_state,
MapVirtualKeyToScanCode map_virtual_key_to_scan_code)
: perform_send_event_(send_event),
get_key_state_(get_key_state),
response_id_(1) {
InitCriticalKeys(map_virtual_key_to_scan_code);
}
KeyboardKeyEmbedderHandler::~KeyboardKeyEmbedderHandler() = default;
static bool isEasciiPrintable(int codeUnit) {
return (codeUnit <= 0x7f && codeUnit >= 0x20) ||
(codeUnit <= 0xff && codeUnit >= 0x80);
}
// Converts upper letters to lower letters in ASCII and extended ASCII, and
// returns as-is otherwise.
//
// Independent of locale.
static uint64_t toLower(uint64_t n) {
constexpr uint64_t lower_a = 0x61;
constexpr uint64_t upper_a = 0x41;
constexpr uint64_t upper_z = 0x5a;
constexpr uint64_t lower_a_grave = 0xe0;
constexpr uint64_t upper_a_grave = 0xc0;
constexpr uint64_t upper_thorn = 0xde;
constexpr uint64_t division = 0xf7;
// ASCII range.
if (n >= upper_a && n <= upper_z) {
return n - upper_a + lower_a;
}
// EASCII range.
if (n >= upper_a_grave && n <= upper_thorn && n != division) {
return n - upper_a_grave + lower_a_grave;
}
return n;
}
// Transform scancodes sent by windows to scancodes written in Chromium spec.
static uint16_t normalizeScancode(int windowsScanCode, bool extended) {
// In Chromium spec the extended bit is shown as 0xe000 bit,
// e.g. PageUp is represented as 0xe049.
return (windowsScanCode & 0xff) | (extended ? 0xe000 : 0);
}
uint64_t KeyboardKeyEmbedderHandler::ApplyPlaneToId(uint64_t id,
uint64_t plane) {
return (id & valueMask) | plane;
}
uint64_t KeyboardKeyEmbedderHandler::GetPhysicalKey(int scancode,
bool extended) {
int chromiumScancode = normalizeScancode(scancode, extended);
auto resultIt = windowsToPhysicalMap_.find(chromiumScancode);
if (resultIt != windowsToPhysicalMap_.end())
return resultIt->second;
return ApplyPlaneToId(scancode, windowsPlane);
}
uint64_t KeyboardKeyEmbedderHandler::GetLogicalKey(int key,
bool extended,
int scancode) {
if (key == VK_PROCESSKEY) {
return VK_PROCESSKEY;
}
// Normally logical keys should only be derived from key codes, but since some
// key codes are either 0 or ambiguous (multiple keys using the same key
// code), these keys are resolved by scan codes.
auto numpadIter =
scanCodeToLogicalMap_.find(normalizeScancode(scancode, extended));
if (numpadIter != scanCodeToLogicalMap_.cend())
return numpadIter->second;
// Check if the keyCode is one we know about and have a mapping for.
auto logicalIt = windowsToLogicalMap_.find(key);
if (logicalIt != windowsToLogicalMap_.cend())
return logicalIt->second;
// Upper case letters should be normalized into lower case letters.
if (isEasciiPrintable(key)) {
return ApplyPlaneToId(toLower(key), unicodePlane);
}
return ApplyPlaneToId(toLower(key), windowsPlane);
}
void KeyboardKeyEmbedderHandler::KeyboardHookImpl(
int key,
int scancode,
int action,
char32_t character,
bool extended,
bool was_down,
std::function<void(bool)> callback) {
const uint64_t physical_key = GetPhysicalKey(scancode, extended);
const uint64_t logical_key = GetLogicalKey(key, extended, scancode);
assert(action == WM_KEYDOWN || action == WM_KEYUP ||
action == WM_SYSKEYDOWN || action == WM_SYSKEYUP);
auto last_logical_record_iter = pressingRecords_.find(physical_key);
bool had_record = last_logical_record_iter != pressingRecords_.end();
uint64_t last_logical_record =
had_record ? last_logical_record_iter->second : 0;
// The logical key for the current "tap sequence".
//
// Key events are formed in tap sequences: down, repeats, up. The logical key
// stays consistent throughout a tap sequence, which is this value.
uint64_t sequence_logical_key =
had_record ? last_logical_record : logical_key;
if (sequence_logical_key == VK_PROCESSKEY) {
// VK_PROCESSKEY means that the key press is used by an IME. These key
// presses are considered handled and not sent to Flutter. These events must
// be filtered by result_logical_key because the key up event of such
// presses uses the "original" logical key.
callback(true);
return;
}
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);
// 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
// will synthesize (an up event if the key is recorded pressed, then) a down
// event.
//
// 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);
// 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);
// Reassess the last logical record in case pressingRecords_ was modified
// by the above synchronization methods.
last_logical_record_iter = pressingRecords_.find(physical_key);
had_record = last_logical_record_iter != pressingRecords_.end();
last_logical_record = had_record ? last_logical_record_iter->second : 0;
// The resulting event's `type`.
FlutterKeyEventType type;
character = UndeadChar(character);
char character_bytes[kCharacterCacheSize];
// What pressingRecords_[physical_key] should be after the KeyboardHookImpl
// returns (0 if the entry should be removed).
uint64_t eventual_logical_record;
uint64_t result_logical_key;
if (is_event_down) {
if (had_record) {
if (was_down) {
// A normal repeated key.
type = kFlutterKeyEventTypeRepeat;
assert(had_record);
ConvertUtf32ToUtf8_(character_bytes, character);
eventual_logical_record = last_logical_record;
result_logical_key = last_logical_record;
} else {
// A non-repeated key has been pressed that has the exact physical key
// as a currently pressed one, usually indicating multiple keyboards are
// pressing keys with the same physical key, or the up event was lost
// during a loss of focus. The down event is ignored.
callback(true);
return;
}
} else {
// A normal down event (whether the system event is a repeat or not).
type = kFlutterKeyEventTypeDown;
assert(!had_record);
ConvertUtf32ToUtf8_(character_bytes, character);
eventual_logical_record = logical_key;
result_logical_key = logical_key;
}
} else { // isPhysicalDown is false
if (last_logical_record == 0) {
// The physical key has been released before. It might indicate a missed
// event due to loss of focus, or multiple keyboards pressed keys with the
// same physical key. Ignore the up event.
callback(true);
return;
} else {
// A normal up event.
type = kFlutterKeyEventTypeUp;
assert(had_record);
// Up events never have character.
character_bytes[0] = '\0';
eventual_logical_record = 0;
result_logical_key = last_logical_record;
}
}
if (eventual_logical_record != 0) {
pressingRecords_[physical_key] = eventual_logical_record;
} else {
auto record_iter = pressingRecords_.find(physical_key);
// Assert this in debug mode. But in cases that it doesn't satisfy
// (such as due to a bug), make sure the `erase` is only called
// with a valid value to avoid crashing.
if (record_iter != pressingRecords_.end()) {
pressingRecords_.erase(record_iter);
} else {
assert(false);
}
}
FlutterKeyEvent key_data{
.struct_size = sizeof(FlutterKeyEvent),
.timestamp = static_cast<double>(
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch())
.count()),
.type = type,
.physical = physical_key,
.logical = result_logical_key,
.character = character_bytes,
.synthesized = false,
};
response_id_ += 1;
uint64_t response_id = response_id_;
PendingResponse pending{
.callback =
[this, callback = std::move(callback)](bool handled,
uint64_t response_id) {
auto found = pending_responses_.find(response_id);
if (found != pending_responses_.end()) {
pending_responses_.erase(found);
}
callback(handled);
},
.response_id = response_id,
};
auto pending_ptr = std::make_unique<PendingResponse>(std::move(pending));
pending_responses_[response_id] = std::move(pending_ptr);
SendEvent(key_data, KeyboardKeyEmbedderHandler::HandleResponse,
reinterpret_cast<void*>(pending_responses_[response_id].get()));
// Post-event synchronization. It is useful in cases where the true pressing
// state does not match the event type. For example, a CapsLock down event is
// 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);
}
void KeyboardKeyEmbedderHandler::KeyboardHook(
int key,
int scancode,
int action,
char32_t character,
bool extended,
bool was_down,
std::function<void(bool)> callback) {
sent_any_events = false;
KeyboardHookImpl(key, scancode, action, character, extended, was_down,
std::move(callback));
if (!sent_any_events) {
FlutterKeyEvent empty_event{
.struct_size = sizeof(FlutterKeyEvent),
.timestamp = static_cast<double>(
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch())
.count()),
.type = kFlutterKeyEventTypeDown,
.physical = 0,
.logical = 0,
.character = empty_character,
.synthesized = false,
};
SendEvent(empty_event, nullptr, nullptr);
}
}
void KeyboardKeyEmbedderHandler::UpdateLastSeenCritialKey(
int virtual_key,
uint64_t physical_key,
uint64_t logical_key) {
auto found = critical_keys_.find(virtual_key);
if (found != critical_keys_.end()) {
found->second.physical_key = physical_key;
found->second.logical_key = logical_key;
}
}
void KeyboardKeyEmbedderHandler::SynchronizeCritialToggledStates(
int event_virtual_key,
bool is_event_down,
bool* event_key_can_be_repeat) {
// NowState ----------------> PreEventState --------------> TrueState
// Synchronization Event
for (auto& kv : critical_keys_) {
UINT virtual_key = kv.first;
CriticalKey& key_info = kv.second;
if (key_info.physical_key == 0) {
// Never seen this key.
continue;
}
assert(key_info.logical_key != 0);
// Check toggling state first, because it might alter pressing state.
if (key_info.check_toggled) {
const bool target_is_pressed =
pressingRecords_.find(key_info.physical_key) !=
pressingRecords_.end();
// The togglable keys observe a 4-phase cycle:
//
// Phase# 0 1 2 3
// Event Down Up Down Up
// Pressed 0 1 0 1
// Toggled 0 1 1 0
const bool true_toggled = get_key_state_(virtual_key) & kStateMaskToggled;
bool pre_event_toggled = true_toggled;
// Check if the main event's key is the key being checked. If it's the
// non-repeat down event, toggle the state.
if (virtual_key == event_virtual_key && !target_is_pressed &&
is_event_down) {
pre_event_toggled = !pre_event_toggled;
}
if (key_info.toggled_on != pre_event_toggled) {
// If the key is pressed, release it first.
if (target_is_pressed) {
SendEvent(SynthesizeSimpleEvent(
kFlutterKeyEventTypeUp, key_info.physical_key,
key_info.logical_key, empty_character),
nullptr, nullptr);
}
// Synchronizing toggle state always ends with the key being pressed.
pressingRecords_[key_info.physical_key] = key_info.logical_key;
SendEvent(SynthesizeSimpleEvent(kFlutterKeyEventTypeDown,
key_info.physical_key,
key_info.logical_key, empty_character),
nullptr, nullptr);
*event_key_can_be_repeat = false;
}
key_info.toggled_on = true_toggled;
}
}
}
void KeyboardKeyEmbedderHandler::SynchronizeCritialPressedStates(
int event_virtual_key,
int event_physical_key,
bool is_event_down,
bool event_key_can_be_repeat) {
// During an incoming event, there might be a synthesized Flutter event for
// each key of each pressing goal, followed by an eventual main Flutter
// event.
//
// NowState ----------------> PreEventState --------------> TrueState
// Synchronization Event
//
// The goal of the synchronization algorithm is to derive a pre-event state
// that can satisfy the true state (`true_pressed`) after the event, and that
// requires as few synthesized events based on the current state
// (`now_pressed`) as possible.
for (auto& kv : critical_keys_) {
UINT virtual_key = kv.first;
CriticalKey& key_info = kv.second;
if (key_info.physical_key == 0) {
// Never seen this key.
continue;
}
assert(key_info.logical_key != 0);
if (key_info.check_pressed) {
SHORT true_pressed = get_key_state_(virtual_key) & kStateMaskPressed;
auto pressing_record_iter = pressingRecords_.find(key_info.physical_key);
bool now_pressed = pressing_record_iter != pressingRecords_.end();
bool pre_event_pressed = true_pressed;
// Check if the main event is the key being checked to get the correct
// target state.
if (is_event_down) {
// For down events, this key is the event key if they have the same
// virtual key, because virtual key represents "functionality."
//
// In that case, normally Flutter should synthesize nothing since the
// resulting event can adapt to the current state by dispatching either
// a down or a repeat event. However, in certain cases (when Flutter has
// just synchronized the key's toggling state) the event must not be a
// repeat event.
if (virtual_key == event_virtual_key) {
if (event_key_can_be_repeat) {
continue;
} else {
pre_event_pressed = false;
}
}
} else {
// For up events, this key is the event key if they have the same
// physical key, because it is necessary to ensure that the physical
// key is correctly released.
//
// In that case, although the previous state should be pressed, don't
// synthesize a down event even if it's not. The later code will handle
// such cases by skipping abrupt up events. Obviously don't synthesize
// up events either.
if (event_physical_key == key_info.physical_key) {
continue;
}
}
if (now_pressed != pre_event_pressed) {
if (now_pressed) {
pressingRecords_.erase(pressing_record_iter);
} else {
pressingRecords_[key_info.physical_key] = key_info.logical_key;
}
const char* empty_character = "";
SendEvent(
SynthesizeSimpleEvent(
now_pressed ? kFlutterKeyEventTypeUp : kFlutterKeyEventTypeDown,
key_info.physical_key, key_info.logical_key, empty_character),
nullptr, nullptr);
}
}
}
}
void KeyboardKeyEmbedderHandler::HandleResponse(bool handled, void* user_data) {
PendingResponse* pending = reinterpret_cast<PendingResponse*>(user_data);
auto callback = std::move(pending->callback);
callback(handled, pending->response_id);
}
void KeyboardKeyEmbedderHandler::InitCriticalKeys(
MapVirtualKeyToScanCode map_virtual_key_to_scan_code) {
auto createCheckedKey = [this, &map_virtual_key_to_scan_code](
UINT virtual_key, bool extended,
bool check_pressed,
bool check_toggled) -> CriticalKey {
UINT scan_code = map_virtual_key_to_scan_code(virtual_key, extended);
return CriticalKey{
.physical_key = GetPhysicalKey(scan_code, extended),
.logical_key = GetLogicalKey(virtual_key, extended, scan_code),
.check_pressed = check_pressed || check_toggled,
.check_toggled = check_toggled,
.toggled_on = check_toggled
? !!(get_key_state_(virtual_key) & kStateMaskToggled)
: false,
};
};
// 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,
createCheckedKey(VK_RSHIFT, false, true, false));
critical_keys_.emplace(VK_LCONTROL,
createCheckedKey(VK_LCONTROL, false, true, false));
critical_keys_.emplace(VK_RCONTROL,
createCheckedKey(VK_RCONTROL, true, true, false));
critical_keys_.emplace(VK_LMENU,
createCheckedKey(VK_LMENU, false, true, false));
critical_keys_.emplace(VK_RMENU,
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,
createCheckedKey(VK_SCROLL, false, true, true));
critical_keys_.emplace(VK_NUMLOCK,
createCheckedKey(VK_NUMLOCK, true, true, true));
}
void KeyboardKeyEmbedderHandler::ConvertUtf32ToUtf8_(char* out, char32_t ch) {
if (ch == 0) {
out[0] = '\0';
return;
}
std::string result = ConvertChar32ToUtf8(ch);
strcpy_s(out, kCharacterCacheSize, result.c_str());
}
FlutterKeyEvent KeyboardKeyEmbedderHandler::SynthesizeSimpleEvent(
FlutterKeyEventType type,
uint64_t physical,
uint64_t logical,
const char* character) {
return FlutterKeyEvent{
.struct_size = sizeof(FlutterKeyEvent),
.timestamp = static_cast<double>(
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch())
.count()),
.type = type,
.physical = physical,
.logical = logical,
.character = character,
.synthesized = true,
};
}
void KeyboardKeyEmbedderHandler::SendEvent(const FlutterKeyEvent& event,
FlutterKeyEventCallback callback,
void* user_data) {
sent_any_events = true;
perform_send_event_(event, callback, user_data);
}
} // namespace flutter