Skip to content
This repository has been archived by the owner on Feb 25, 2025. It is now read-only.

Commit

Permalink
Rewrite algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
dkwingsmt committed Jun 4, 2022
1 parent a9b2825 commit c1fd021
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 78 deletions.
180 changes: 106 additions & 74 deletions shell/platform/windows/keyboard_key_embedder_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -162,23 +162,57 @@ void KeyboardKeyEmbedderHandler::KeyboardHookImpl(
const uint64_t logical_key = GetLogicalKey(key, extended, scancode);
assert(action == WM_KEYDOWN || action == WM_KEYUP ||
action == WM_SYSKEYDOWN || action == WM_SYSKEYUP);
const bool is_physical_down = action == WM_KEYDOWN || action == WM_SYSKEYDOWN;

auto last_logical_record_iter = pressingRecords_.find(physical_key);
const bool had_record = last_logical_record_iter != pressingRecords_.end();
const 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_physical_down = action == WM_KEYDOWN || action == WM_SYSKEYDOWN;

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_physical_down);
// 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_physical_down);

// The resulting event's `type`.
FlutterKeyEventType type;
// The resulting event's `logical_key`.
uint64_t result_logical_key;
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;
char character_bytes[kCharacterCacheSize];

character = UndeadChar(character);
uint64_t result_logical_key;

if (is_physical_down) {
if (had_record) {
Expand Down Expand Up @@ -223,35 +257,6 @@ void KeyboardKeyEmbedderHandler::KeyboardHookImpl(
}
}

if (result_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;
}

UpdateLastSeenCritialKey(key, physical_key, result_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, type == kFlutterKeyEventTypeDown);
// 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, type);

if (eventual_logical_record != 0) {
pressingRecords_[physical_key] = eventual_logical_record;
} else {
Expand Down Expand Up @@ -333,57 +338,73 @@ void KeyboardKeyEmbedderHandler::UpdateLastSeenCritialKey(
}

void KeyboardKeyEmbedderHandler::SynchronizeCritialToggledStates(
int this_virtual_key,
bool is_down_event) {
for (auto& kv : critical_keys_) {
UINT virtual_key = kv.first;
CriticalKey& key_info = kv.second;
if (key_info.physical_key == 0) {
int event_virtual_key,
bool event_is_down) {
// NowState ----------------> PreEventState --------------> TrueState
// Synchronization Event
for (auto& target : critical_keys_) {
UINT target_virtual_key = target.first;
CriticalKey& target_info = target.second;
if (target_info.physical_key == 0) {
// Never seen this key.
continue;
}
assert(key_info.logical_key != 0);
assert(target_info.logical_key != 0);

// Check toggling state first, because it might alter pressing state.
if (key_info.check_toggled) {
if (target_info.check_toggled) {
const bool target_is_pressed =
pressingRecords_.find(target_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
SHORT state = get_key_state_(virtual_key);
bool should_toggled = state & kStateMaskToggled;
if (virtual_key == this_virtual_key && is_down_event) {
key_info.toggled_on = !key_info.toggled_on;
const bool true_toggled = get_key_state_(target_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 (target_virtual_key == event_virtual_key && !target_is_pressed &&
event_is_down) {
pre_event_toggled = !pre_event_toggled;
}
if (key_info.toggled_on != should_toggled) {
if (target_info.toggled_on != pre_event_toggled) {
// If the key is pressed, release it first.
if (pressingRecords_.find(key_info.physical_key) !=
pressingRecords_.end()) {
if (target_is_pressed) {
SendEvent(SynthesizeSimpleEvent(
kFlutterKeyEventTypeUp, key_info.physical_key,
key_info.logical_key, empty_character),
kFlutterKeyEventTypeUp, target_info.physical_key,
target_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;
pressingRecords_[target_info.physical_key] = target_info.logical_key;
SendEvent(SynthesizeSimpleEvent(kFlutterKeyEventTypeDown,
key_info.physical_key,
key_info.logical_key, empty_character),
target_info.physical_key,
target_info.logical_key, empty_character),
nullptr, nullptr);
}
key_info.toggled_on = should_toggled;
target_info.toggled_on = true_toggled;
}
}
}

void KeyboardKeyEmbedderHandler::SynchronizeCritialPressedStates(
int this_virtual_key,
int event_virtual_key,
int this_physical_key,
FlutterKeyEventType this_event_type) {
auto this_key_pressed_iter = pressingRecords_.find(this_physical_key);
bool this_key_pressed = this_key_pressed_iter != pressingRecords_.end();
bool is_physical_down) {
// 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;
Expand All @@ -393,29 +414,40 @@ void KeyboardKeyEmbedderHandler::SynchronizeCritialPressedStates(
}
assert(key_info.logical_key != 0);
if (key_info.check_pressed) {
SHORT state = get_key_state_(virtual_key);
auto recorded_pressed_iter = pressingRecords_.find(key_info.physical_key);
bool recorded_pressed = recorded_pressed_iter != pressingRecords_.end();
bool should_pressed = state & kStateMaskPressed;
bool pressed_state_will_change = false;
if (this_event_type == kFlutterKeyEventTypeDown) {
if (virtual_key == this_virtual_key) {
should_pressed = !should_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_physical_down) {
// For down events, this key is the event key if they have the same
// virtual key, because virtual key represents "functionality."
if (virtual_key == event_virtual_key) {
pre_event_pressed = false;
}
} else if (this_event_type == kFlutterKeyEventTypeUp) {
} 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 (this_physical_key == key_info.physical_key) {
should_pressed = !should_pressed;
continue;
}
}
if (recorded_pressed != should_pressed) {
if (recorded_pressed) {
pressingRecords_.erase(recorded_pressed_iter);
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(recorded_pressed ? kFlutterKeyEventTypeUp
SynthesizeSimpleEvent(now_pressed ? kFlutterKeyEventTypeUp
: kFlutterKeyEventTypeDown,
key_info.physical_key, key_info.logical_key,
empty_character),
Expand Down
10 changes: 6 additions & 4 deletions shell/platform/windows/keyboard_key_embedder_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,14 @@ class KeyboardKeyEmbedderHandler
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 virtual_key, bool is_down);
void SynchronizeCritialToggledStates(int this_virtual_key,
bool is_physical_down);
// Check each key's state from |get_key_state_| and synthesize events
// if their pressing states have been desynchronized.
void SynchronizeCritialPressedStates(int this_virtual_key,
int this_physical_key,
FlutterKeyEventType this_event_type);
void SynchronizeCritialPressedStates(
int this_virtual_key,
int this_physical_key,
bool is_physical_down);

// Wraps perform_send_event_ with state tracking. Use this instead of
// |perform_send_event_| to send events to the framework.
Expand Down

0 comments on commit c1fd021

Please sign in to comment.