From 6ec1e2c93ddcbd1d455f1fe242c485d7dd8b1cdf Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 27 Aug 2021 03:35:52 -0700 Subject: [PATCH 01/12] Web --- .../lib/src/engine/keyboard_binding.dart | 72 +++++++++++-------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/lib/web_ui/lib/src/engine/keyboard_binding.dart b/lib/web_ui/lib/src/engine/keyboard_binding.dart index 471752dc0c4d7..5909574e636da 100644 --- a/lib/web_ui/lib/src/engine/keyboard_binding.dart +++ b/lib/web_ui/lib/src/engine/keyboard_binding.dart @@ -76,15 +76,6 @@ const int _kDeadKeyShift = 0x20000000; const int _kDeadKeyAlt = 0x40000000; const int _kDeadKeyMeta = 0x80000000; -const ui.KeyData _emptyKeyData = ui.KeyData( - type: ui.KeyEventType.down, - timeStamp: Duration.zero, - logical: 0, - physical: 0, - character: null, - synthesized: false, -); - typedef DispatchKeyData = bool Function(ui.KeyData data); /// Converts a floating number timestamp (in milliseconds) to a [Duration] by @@ -335,17 +326,8 @@ class KeyboardConverter { _keyGuards.remove(physicalKey)?.call(); } - // Parse the HTML event, update states, and dispatch Flutter key data through - // [dispatchKeyData]. - // - // * The method might dispatch some synthesized key data first to update states, - // results discarded. - // * Then it dispatches exactly one non-synthesized key data that corresponds - // to the `event`, i.e. the primary key data. If this dispatching returns - // true, then this event will be invoked `preventDefault`. - // * Some key data might be synthesized to update states after the main key - // data. They are always scheduled asynchronously with results discarded. - void handleEvent(FlutterHtmlKeyboardEvent event) { + List _convertEvent(FlutterHtmlKeyboardEvent event) { + final List result = []; final Duration timeStamp = _eventTimeStampToDuration(event.timeStamp!); final String eventKey = event.key!; @@ -411,9 +393,8 @@ class KeyboardConverter { // 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. - dispatchKeyData(_emptyKeyData); event.preventDefault(); - return; + return result; } } else { // This physical key is not being pressed according to the record. It's a @@ -425,9 +406,8 @@ class KeyboardConverter { if (lastLogicalRecord == null) { // The physical key has been released before. It indicates multiple // keyboards pressed keys with the same physical key. Ignore the up event. - dispatchKeyData(_emptyKeyData); event.preventDefault(); - return; + return result; } type = ui.KeyEventType.up; @@ -456,16 +436,15 @@ class KeyboardConverter { // After updating _pressingRecords, synchronize modifier states. The // `event.***Key` fields can be used to reduce some omitted modifier key - // events. We can deduce key cancel events if they are false. Key sync - // events can not be deduced since we don't know which physical key they - // represent. + // events. We can deduce key up events if they are false. Key down events + // can not be deduced since we don't know which physical key they represent. _kLogicalKeyToModifierGetter.forEach((int logicalKey, _ModifierGetter getModifier) { if (_pressingRecords.containsValue(logicalKey) && !getModifier(event)) { _pressingRecords.removeWhere((int physicalKey, int logicalRecord) { if (logicalRecord != logicalKey) return false; - dispatchKeyData(ui.KeyData( + result.add(ui.KeyData( timeStamp: timeStamp, type: ui.KeyEventType.up, physical: physicalKey, @@ -488,16 +467,47 @@ class KeyboardConverter { } } - final ui.KeyData keyData = ui.KeyData( + result.add(ui.KeyData( timeStamp: timeStamp, type: type, physical: physicalKey, logical: lastLogicalRecord ?? logicalKey, character: type == ui.KeyEventType.up ? null : character, synthesized: false, - ); + )); + return result; + } - final bool primaryHandled = dispatchKeyData(keyData); + // Parse the HTML event, update states, and dispatch Flutter key data through + // [dispatchKeyData]. + // + // * The method might dispatch some synthesized key data first to update states, + // results discarded. + // * Then it dispatches exactly one non-synthesized key data that corresponds + // to the `event`, i.e. the primary key data. If this dispatching returns + // true, then this event will be invoked `preventDefault`. + // * Some key data might be synthesized to update states after the main key + // data. They are always scheduled asynchronously with results discarded. + void handleEvent(FlutterHtmlKeyboardEvent event) { + final List result = _convertEvent(event); + // Add an empty event for the framework to correctly infer KeyDataTransitMode. + if (result.isEmpty) { + result.add(const ui.KeyData( + type: ui.KeyEventType.down, + timeStamp: Duration.zero, + logical: 0, + physical: 0, + character: null, + synthesized: false, + )); + } + bool primaryHandled = false; + for (final ui.KeyData keyData in result) { + // Only count if the primary event is handled. + final bool thisHandled = dispatchKeyData(keyData); + if (!keyData.synthesized) + primaryHandled = thisHandled; + } if (primaryHandled) { event.preventDefault(); } From 6306ccc63e94849e78564b7077900e75b6f47e9f Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 16 Sep 2021 11:01:55 -0700 Subject: [PATCH 02/12] macos --- .../Source/FlutterEmbedderKeyResponder.mm | 33 +++++++++++++------ .../FlutterEmbedderKeyResponderUnittests.mm | 12 +++++-- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm index 4e80d4228fe71..9f22ca0d747e4 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm @@ -274,6 +274,7 @@ - (void)pendTo:(nonnull NSMutableDictionary* - (void)resolveTo:(BOOL)handled; @property(nonatomic) BOOL handled; +@property(nonatomic) BOOL sentAnyEvents; /** * A string indicating how the callback is handled. * @@ -293,6 +294,7 @@ - (nonnull instancetype)initWithCallback:(FlutterAsyncKeyCallback)callback { if (self != nil) { _callback = callback; _handled = FALSE; + _sentAnyEvents = FALSE; } return self; } @@ -305,6 +307,7 @@ - (void)pendTo:(nonnull NSMutableDictionary* } pendingResponses[@(responseId)] = _callback; _handled = TRUE; + _sentAnyEvents = TRUE; NSAssert( ((_debugHandleSource = [NSString stringWithFormat:@"pending event %llu", responseId]), TRUE), @""); @@ -384,10 +387,14 @@ @interface FlutterEmbedderKeyResponder () * * The flags compared are all flags after masking with * |modifierFlagOfInterestMask| and excluding |ignoringFlags|. + * + * The |guard| is basically a regular guarded callback, but instead of being + * called, only used to record whether an event is sent. */ - (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger)ignoringFlags - timestamp:(NSTimeInterval)timestamp; + timestamp:(NSTimeInterval)timestamp + guard:(FlutterKeyCallbackGuard*)guard; /** * Update the pressing state. @@ -495,6 +502,9 @@ - (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { NSAssert(false, @"Unexpected key event type: |%@|.", @(event.type)); } NSAssert(guardedCallback.handled, @"The callback is returned without being handled."); + if (!guardedCallback.sentAnyEvents) { + [self sendEmptyEvent]; + } NSAssert(_lastModifierFlagsOfInterest == (event.modifierFlags & _modifierFlagOfInterestMask), @"The modifier flags are not properly updated: recorded 0x%lx, event with mask 0x%lx", _lastModifierFlagsOfInterest, event.modifierFlags & _modifierFlagOfInterestMask); @@ -504,13 +514,15 @@ - (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { - (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger)ignoringFlags - timestamp:(NSTimeInterval)timestamp { + timestamp:(NSTimeInterval)timestamp + guard:(FlutterKeyCallbackGuard*)guard { const NSUInteger updatingMask = _modifierFlagOfInterestMask & ~ignoringFlags; const NSUInteger currentFlagsOfInterest = currentFlags & updatingMask; const NSUInteger lastFlagsOfInterest = _lastModifierFlagsOfInterest & updatingMask; NSUInteger flagDifference = currentFlagsOfInterest ^ lastFlagsOfInterest; if (flagDifference & NSEventModifierFlagCapsLock) { [self sendCapsLockTapWithTimestamp:timestamp callback:nil]; + guard.sentAnyEvents = TRUE; // At least an up event is sent. flagDifference = flagDifference & ~NSEventModifierFlagCapsLock; } while (true) { @@ -525,6 +537,7 @@ - (void)synchronizeModifiers:(NSUInteger)currentFlags continue; } BOOL isDownEvent = (currentFlagsOfInterest & currentFlag) != 0; + guard.sentAnyEvents = TRUE; [self sendModifierEventOfType:isDownEvent timestamp:timestamp keyCode:[keyCode unsignedShortValue] @@ -551,6 +564,7 @@ - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event [callback pendTo:_pendingResponses withId:responseId]; // The `__bridge_retained` here is matched by `__bridge_transfer` in HandleResponse. _sendEvent(event, HandleResponse, (__bridge_retained void*)pending); + callback.sentAnyEvents = TRUE; } - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp @@ -588,7 +602,6 @@ - (void)sendModifierEventOfType:(BOOL)isDownEvent uint64_t logicalKey = GetLogicalKeyForModifier(keyCode, physicalKey); if (physicalKey == 0 || logicalKey == 0) { NSLog(@"Unrecognized modifier key: keyCode 0x%hx, physical key 0x%llx", keyCode, physicalKey); - [self sendEmptyEvent]; [callback resolveTo:TRUE]; return; } @@ -606,6 +619,7 @@ - (void)sendModifierEventOfType:(BOOL)isDownEvent [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; } else { _sendEvent(flutterEvent, nullptr, nullptr); + callback.sentAnyEvents = TRUE; } } @@ -625,7 +639,7 @@ - (void)sendEmptyEvent { - (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback { uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); uint64_t logicalKey = GetLogicalKeyForEvent(event, physicalKey); - [self synchronizeModifiers:event.modifierFlags ignoringFlags:0 timestamp:event.timestamp]; + [self synchronizeModifiers:event.modifierFlags ignoringFlags:0 timestamp:event.timestamp guard:callback]; bool isARepeat = event.isARepeat; NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; @@ -634,7 +648,6 @@ - (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callb // key up event to the window where the corresponding key down occurred. // However this might happen in add-to-app scenarios if the focus is changed // from the native view to the Flutter view amid the key tap. - [self sendEmptyEvent]; [callback resolveTo:TRUE]; return; } @@ -658,7 +671,7 @@ - (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callb - (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback { NSAssert(!event.isARepeat, @"Unexpected repeated Up event: keyCode %d, char %@, charIM %@", event.keyCode, event.characters, event.charactersIgnoringModifiers); - [self synchronizeModifiers:event.modifierFlags ignoringFlags:0 timestamp:event.timestamp]; + [self synchronizeModifiers:event.modifierFlags ignoringFlags:0 timestamp:event.timestamp guard:callback]; uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; @@ -667,7 +680,6 @@ - (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callbac // key up event to the window where the corresponding key down occurred. // However this might happen in add-to-app scenarios if the focus is changed // from the native view to the Flutter view amid the key tap. - [self sendEmptyEvent]; [callback resolveTo:TRUE]; return; } @@ -688,13 +700,13 @@ - (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callbac - (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback { [self synchronizeModifiers:event.modifierFlags ignoringFlags:NSEventModifierFlagCapsLock - timestamp:event.timestamp]; + timestamp:event.timestamp + guard:callback]; if ((_lastModifierFlagsOfInterest & NSEventModifierFlagCapsLock) != (event.modifierFlags & NSEventModifierFlagCapsLock)) { [self sendCapsLockTapWithTimestamp:event.timestamp callback:callback]; _lastModifierFlagsOfInterest = _lastModifierFlagsOfInterest ^ NSEventModifierFlagCapsLock; } else { - [self sendEmptyEvent]; [callback resolveTo:TRUE]; } } @@ -710,7 +722,8 @@ - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callb [self synchronizeModifiers:event.modifierFlags ignoringFlags:targetModifierFlag - timestamp:event.timestamp]; + timestamp:event.timestamp + guard:callback]; NSNumber* pressedLogicalKey = [_pressingRecords objectForKey:@(targetKey)]; BOOL lastTargetPressed = pressedLogicalKey != nil; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm index 38c7ccdd2a9f2..4a5cde65583b8 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm @@ -750,8 +750,12 @@ - (void)dealloc { handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) callback:keyEventCallback]; - EXPECT_EQ([events count], 0u); + EXPECT_EQ([events count], 1u); + EXPECT_EQ([events lastObject].data->physical, 0u); + EXPECT_EQ([events lastObject].data->logical, 0u); + EXPECT_FALSE([[events lastObject] hasCallback]); EXPECT_EQ(last_handled, TRUE); + [events removeAllObjects]; last_handled = FALSE; [responder @@ -782,8 +786,12 @@ - (void)dealloc { handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) callback:keyEventCallback]; - EXPECT_EQ([events count], 0u); + EXPECT_EQ([events count], 1u); + EXPECT_EQ([events lastObject].data->physical, 0u); + EXPECT_EQ([events lastObject].data->logical, 0u); + EXPECT_FALSE([[events lastObject] hasCallback]); EXPECT_EQ(last_handled, TRUE); + [events removeAllObjects]; // Case 3: // In: L down, (L up), (R down), R up From 47ed53f3f6e81f43f1e5886a72d042fb4b207ef3 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 16 Sep 2021 12:22:50 -0700 Subject: [PATCH 03/12] Linux --- .../linux/fl_key_embedder_responder.cc | 62 ++++++++++++++----- .../linux/fl_key_embedder_responder_test.cc | 18 +----- 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/shell/platform/linux/fl_key_embedder_responder.cc b/shell/platform/linux/fl_key_embedder_responder.cc index 56c27b6c72d7b..ff09ff2fac994 100644 --- a/shell/platform/linux/fl_key_embedder_responder.cc +++ b/shell/platform/linux/fl_key_embedder_responder.cc @@ -358,6 +358,7 @@ typedef struct { uint64_t event_logical_key; bool is_down; double timestamp; + bool sent_any_events; } SyncStateLoopContext; } // namespace @@ -459,6 +460,7 @@ static void synchronize_pressed_states_loop_body(gpointer key, synthesize_simple_event(self, kFlutterKeyEventTypeUp, recorded_physical_key, logical_key, context->timestamp); + context->sent_any_events = true; update_pressing_state(self, recorded_physical_key, 0); } } @@ -480,6 +482,7 @@ static void synchronize_pressed_states_loop_body(gpointer key, } synthesize_simple_event(self, kFlutterKeyEventTypeDown, physical_key, logical_key, context->timestamp); + context->sent_any_events = true; update_pressing_state(self, physical_key, logical_key); } } @@ -667,41 +670,49 @@ static void synchronize_lock_states_loop_body(gpointer key, possibly_update_lock_bit(self, logical_key, is_down_event); synthesize_simple_event(self, type, physical_key, logical_key, context->timestamp); + context->sent_any_events = true; } } -// Sends a key event to the framework. -static void fl_key_embedder_responder_handle_event( +// Implements fl_key_embedder_responder_handle_event and returns whether +// any events has been sent. +static bool fl_key_embedder_responder_handle_event_impl( FlKeyResponder* responder, FlKeyEvent* event, FlKeyResponderAsyncCallback callback, gpointer user_data) { FlKeyEmbedderResponder* self = FL_KEY_EMBEDDER_RESPONDER(responder); + bool sent_any_events = false; - g_return_if_fail(event != nullptr); - g_return_if_fail(callback != nullptr); + g_return_val_if_fail(event != nullptr, sent_any_events); + g_return_val_if_fail(callback != nullptr, sent_any_events); const uint64_t physical_key = event_to_physical_key(event); const uint64_t logical_key = event_to_logical_key(event); const double timestamp = event_to_timestamp(event); const bool is_down_event = event->is_press; - SyncStateLoopContext sync_pressed_state_context; - sync_pressed_state_context.self = self; - sync_pressed_state_context.state = event->state; - sync_pressed_state_context.timestamp = timestamp; - sync_pressed_state_context.is_down = is_down_event; - sync_pressed_state_context.event_logical_key = logical_key; + SyncStateLoopContext sync_state_context; + sync_state_context.self = self; + sync_state_context.state = event->state; + sync_state_context.timestamp = timestamp; + sync_state_context.is_down = is_down_event; + sync_state_context.event_logical_key = logical_key; + sync_state_context.sent_any_events = false; // Update lock mode states g_hash_table_foreach(self->lock_bit_to_checked_keys, synchronize_lock_states_loop_body, - &sync_pressed_state_context); + &sync_state_context); // Update pressing states g_hash_table_foreach(self->modifier_bit_to_checked_keys, synchronize_pressed_states_loop_body, - &sync_pressed_state_context); + &sync_state_context); + + if (sync_state_context.sent_any_events) { + sent_any_events = true; + } // Construct the real event const uint64_t last_logical_record = @@ -722,9 +733,8 @@ static void fl_key_embedder_responder_handle_event( // 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. - fl_engine_send_key_event(self->engine, &empty_event, nullptr, nullptr); callback(true, user_data); - return; + return sent_any_events; } else { out_event.type = kFlutterKeyEventTypeDown; character_to_free = event_to_character(event); // Might be null @@ -735,9 +745,8 @@ static void fl_key_embedder_responder_handle_event( // 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. - fl_engine_send_key_event(self->engine, &empty_event, nullptr, nullptr); callback(true, user_data); - return; + return sent_any_events; } else { out_event.type = kFlutterKeyEventTypeUp; } @@ -753,7 +762,28 @@ static void fl_key_embedder_responder_handle_event( fl_key_embedder_user_data_new(callback, user_data); fl_engine_send_key_event(self->engine, &out_event, handle_response, response_data); + sent_any_events = true; } else { callback(true, user_data); } + return sent_any_events; +} + + +// Sends a key event to the framework. +static void fl_key_embedder_responder_handle_event( + FlKeyResponder* responder, + FlKeyEvent* event, + FlKeyResponderAsyncCallback callback, + gpointer user_data) { + FlKeyEmbedderResponder* self = FL_KEY_EMBEDDER_RESPONDER(responder); + bool sent_any_events = fl_key_embedder_responder_handle_event_impl( + responder, + event, + callback, + user_data + ); + if (!sent_any_events) { + fl_engine_send_key_event(self->engine, &empty_event, nullptr, nullptr); + } } diff --git a/shell/platform/linux/fl_key_embedder_responder_test.cc b/shell/platform/linux/fl_key_embedder_responder_test.cc index 71743d529f55b..48d2681f0dac3 100644 --- a/shell/platform/linux/fl_key_embedder_responder_test.cc +++ b/shell/platform/linux/fl_key_embedder_responder_test.cc @@ -1061,7 +1061,7 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncPressingStateOnSelfEvents) { // Reason: The ControlLeft down is synthesized to synchronize the state // showing Control as pressed. The ControlRight event is ignored because // the event is considered a duplicate up event. - EXPECT_EQ(g_call_records->len, 2u); + EXPECT_EQ(g_call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); EXPECT_EQ(record->event->timestamp, 105000); EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); @@ -1070,13 +1070,6 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncPressingStateOnSelfEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, true); - record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 1)); - EXPECT_EQ(record->event->physical, 0ull); - EXPECT_EQ(record->event->logical, 0ull); - EXPECT_STREQ(record->event->character, nullptr); - EXPECT_EQ(record->event->synthesized, false); - EXPECT_EQ(record->callback, nullptr); - g_ptr_array_clear(g_call_records); clear_g_call_records(); @@ -1522,7 +1515,7 @@ TEST(FlKeyEmbedderResponderTest, SynthesizationOccursOnIgnoredEvents) { kIsNotModifier), verify_response_handled, &user_data); - EXPECT_EQ(g_call_records->len, 3u); + EXPECT_EQ(g_call_records->len, 2u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); EXPECT_EQ(record->event->timestamp, 101000); EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); @@ -1539,13 +1532,6 @@ TEST(FlKeyEmbedderResponderTest, SynthesizationOccursOnIgnoredEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, true); - record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 2)); - EXPECT_EQ(record->event->physical, 0ull); - EXPECT_EQ(record->event->logical, 0ull); - EXPECT_STREQ(record->event->character, nullptr); - EXPECT_EQ(record->event->synthesized, false); - EXPECT_EQ(record->callback, nullptr); - g_ptr_array_clear(g_call_records); clear_g_call_records(); From 48c225bab0da57d64f7c06e29edd60b0dd15652c Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 17 Sep 2021 07:19:39 -0700 Subject: [PATCH 04/12] Easier web impl --- .../lib/src/engine/keyboard_binding.dart | 78 +++++++++++-------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/lib/web_ui/lib/src/engine/keyboard_binding.dart b/lib/web_ui/lib/src/engine/keyboard_binding.dart index 5909574e636da..b1abe2045f304 100644 --- a/lib/web_ui/lib/src/engine/keyboard_binding.dart +++ b/lib/web_ui/lib/src/engine/keyboard_binding.dart @@ -76,6 +76,15 @@ const int _kDeadKeyShift = 0x20000000; const int _kDeadKeyAlt = 0x40000000; const int _kDeadKeyMeta = 0x80000000; +const ui.KeyData _emptyKeyData = ui.KeyData( + type: ui.KeyEventType.down, + timeStamp: Duration.zero, + logical: 0, + physical: 0, + character: null, + synthesized: false, +); + typedef DispatchKeyData = bool Function(ui.KeyData data); /// Converts a floating number timestamp (in milliseconds) to a [Duration] by @@ -195,11 +204,16 @@ class FlutterHtmlKeyboardEvent { // [dispatchKeyData] as given in the constructor. Some key data might be // dispatched asynchronously. class KeyboardConverter { - KeyboardConverter(this.dispatchKeyData, {this.onMacOs = false}); + KeyboardConverter(this.performDispatchKeyData, {this.onMacOs = false}); - final DispatchKeyData dispatchKeyData; + final DispatchKeyData performDispatchKeyData; final bool onMacOs; + // The `performDispatchKeyData` wrapped with tracking logic. + // + // It is non-null only during handleEvent. + DispatchKeyData? _dispatchKeyData; + bool _disposed = false; void dispose() { _disposed = true; @@ -285,7 +299,7 @@ class KeyboardConverter { Future.delayed(duration).then((_) { if (!canceled && !_disposed) { callback(); - dispatchKeyData(getData()); + performDispatchKeyData(getData()); } }); return () { canceled = true; }; @@ -326,8 +340,7 @@ class KeyboardConverter { _keyGuards.remove(physicalKey)?.call(); } - List _convertEvent(FlutterHtmlKeyboardEvent event) { - final List result = []; + void _handleEvent(FlutterHtmlKeyboardEvent event) { final Duration timeStamp = _eventTimeStampToDuration(event.timeStamp!); final String eventKey = event.key!; @@ -394,7 +407,7 @@ class KeyboardConverter { // pressing keys with the same physical key, or the up event was lost // during a loss of focus. The down event is ignored. event.preventDefault(); - return result; + return; } } else { // This physical key is not being pressed according to the record. It's a @@ -407,7 +420,7 @@ class KeyboardConverter { // The physical key has been released before. It indicates multiple // keyboards pressed keys with the same physical key. Ignore the up event. event.preventDefault(); - return result; + return; } type = ui.KeyEventType.up; @@ -436,15 +449,16 @@ class KeyboardConverter { // After updating _pressingRecords, synchronize modifier states. The // `event.***Key` fields can be used to reduce some omitted modifier key - // events. We can deduce key up events if they are false. Key down events - // can not be deduced since we don't know which physical key they represent. + // events. We can deduce key cancel events if they are false. Key sync + // events can not be deduced since we don't know which physical key they + // represent. _kLogicalKeyToModifierGetter.forEach((int logicalKey, _ModifierGetter getModifier) { if (_pressingRecords.containsValue(logicalKey) && !getModifier(event)) { _pressingRecords.removeWhere((int physicalKey, int logicalRecord) { if (logicalRecord != logicalKey) return false; - result.add(ui.KeyData( + _dispatchKeyData!(ui.KeyData( timeStamp: timeStamp, type: ui.KeyEventType.up, physical: physicalKey, @@ -467,15 +481,19 @@ class KeyboardConverter { } } - result.add(ui.KeyData( + final ui.KeyData keyData = ui.KeyData( timeStamp: timeStamp, type: type, physical: physicalKey, logical: lastLogicalRecord ?? logicalKey, character: type == ui.KeyEventType.up ? null : character, synthesized: false, - )); - return result; + ); + + final bool primaryHandled = _dispatchKeyData!(keyData); + if (primaryHandled) { + event.preventDefault(); + } } // Parse the HTML event, update states, and dispatch Flutter key data through @@ -489,27 +507,19 @@ class KeyboardConverter { // * Some key data might be synthesized to update states after the main key // data. They are always scheduled asynchronously with results discarded. void handleEvent(FlutterHtmlKeyboardEvent event) { - final List result = _convertEvent(event); - // Add an empty event for the framework to correctly infer KeyDataTransitMode. - if (result.isEmpty) { - result.add(const ui.KeyData( - type: ui.KeyEventType.down, - timeStamp: Duration.zero, - logical: 0, - physical: 0, - character: null, - synthesized: false, - )); - } - bool primaryHandled = false; - for (final ui.KeyData keyData in result) { - // Only count if the primary event is handled. - final bool thisHandled = dispatchKeyData(keyData); - if (!keyData.synthesized) - primaryHandled = thisHandled; - } - if (primaryHandled) { - event.preventDefault(); + assert(_dispatchKeyData == null); + bool sentAnyEvents = false; + _dispatchKeyData = (ui.KeyData data) { + sentAnyEvents = true; + return performDispatchKeyData(data); + }; + try { + _handleEvent(event); + } finally { + if (!sentAnyEvents) { + _dispatchKeyData!(_emptyKeyData); + } + _dispatchKeyData = null; } } } From 335f598328282c19eb3b3d165dbbc8656a918741 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 17 Sep 2021 07:26:31 -0700 Subject: [PATCH 05/12] doc and format --- lib/web_ui/lib/src/engine/keyboard_binding.dart | 6 +++++- shell/platform/linux/fl_key_embedder_responder.cc | 10 ++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/web_ui/lib/src/engine/keyboard_binding.dart b/lib/web_ui/lib/src/engine/keyboard_binding.dart index b1abe2045f304..bf64fc3ea232d 100644 --- a/lib/web_ui/lib/src/engine/keyboard_binding.dart +++ b/lib/web_ui/lib/src/engine/keyboard_binding.dart @@ -211,7 +211,9 @@ class KeyboardConverter { // The `performDispatchKeyData` wrapped with tracking logic. // - // It is non-null only during handleEvent. + // It is non-null only during `handleEvent`. All events during `handleEvent` + // should be dispatched with `_dispatchKeyData`, others with + // `performDispatchKeyData`. DispatchKeyData? _dispatchKeyData; bool _disposed = false; @@ -299,6 +301,8 @@ class KeyboardConverter { Future.delayed(duration).then((_) { if (!canceled && !_disposed) { callback(); + // This dispatch is performed asynchronously, therefore should not use + // `_dispatchKeyData`. performDispatchKeyData(getData()); } }); diff --git a/shell/platform/linux/fl_key_embedder_responder.cc b/shell/platform/linux/fl_key_embedder_responder.cc index ff09ff2fac994..ff10105bf7a06 100644 --- a/shell/platform/linux/fl_key_embedder_responder.cc +++ b/shell/platform/linux/fl_key_embedder_responder.cc @@ -702,8 +702,7 @@ static bool fl_key_embedder_responder_handle_event_impl( // Update lock mode states g_hash_table_foreach(self->lock_bit_to_checked_keys, - synchronize_lock_states_loop_body, - &sync_state_context); + synchronize_lock_states_loop_body, &sync_state_context); // Update pressing states g_hash_table_foreach(self->modifier_bit_to_checked_keys, @@ -769,7 +768,6 @@ static bool fl_key_embedder_responder_handle_event_impl( return sent_any_events; } - // Sends a key event to the framework. static void fl_key_embedder_responder_handle_event( FlKeyResponder* responder, @@ -778,11 +776,7 @@ static void fl_key_embedder_responder_handle_event( gpointer user_data) { FlKeyEmbedderResponder* self = FL_KEY_EMBEDDER_RESPONDER(responder); bool sent_any_events = fl_key_embedder_responder_handle_event_impl( - responder, - event, - callback, - user_data - ); + responder, event, callback, user_data); if (!sent_any_events) { fl_engine_send_key_event(self->engine, &empty_event, nullptr, nullptr); } From b976a76ca63332b3f90bf8f37f45a45d4f96b92a Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 17 Sep 2021 07:38:03 -0700 Subject: [PATCH 06/12] Better linux impl --- .../linux/fl_key_embedder_responder.cc | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/shell/platform/linux/fl_key_embedder_responder.cc b/shell/platform/linux/fl_key_embedder_responder.cc index ff10105bf7a06..17afeb463fd8b 100644 --- a/shell/platform/linux/fl_key_embedder_responder.cc +++ b/shell/platform/linux/fl_key_embedder_responder.cc @@ -154,6 +154,10 @@ struct _FlKeyEmbedderResponder { // For more information, see #update_caps_lock_state_logic_inferrence. StateLogicInferrence caps_lock_state_logic_inferrence; + // Record if any events has been sent through the engine during a + // |fl_key_embedder_responder_handle_event| call. + bool sent_any_events; + // A static map from GTK modifier bits to #FlKeyEmbedderCheckedKey to // configure the modifier keys that needs to be tracked and kept synchronous // on. @@ -344,6 +348,7 @@ static void synthesize_simple_event(FlKeyEmbedderResponder* self, out_event.character = nullptr; out_event.synthesized = true; if (self->engine != nullptr) { + self->sent_any_events = true; fl_engine_send_key_event(self->engine, &out_event, nullptr, nullptr); } } @@ -358,7 +363,6 @@ typedef struct { uint64_t event_logical_key; bool is_down; double timestamp; - bool sent_any_events; } SyncStateLoopContext; } // namespace @@ -460,7 +464,6 @@ static void synchronize_pressed_states_loop_body(gpointer key, synthesize_simple_event(self, kFlutterKeyEventTypeUp, recorded_physical_key, logical_key, context->timestamp); - context->sent_any_events = true; update_pressing_state(self, recorded_physical_key, 0); } } @@ -482,7 +485,6 @@ static void synchronize_pressed_states_loop_body(gpointer key, } synthesize_simple_event(self, kFlutterKeyEventTypeDown, physical_key, logical_key, context->timestamp); - context->sent_any_events = true; update_pressing_state(self, physical_key, logical_key); } } @@ -670,22 +672,18 @@ static void synchronize_lock_states_loop_body(gpointer key, possibly_update_lock_bit(self, logical_key, is_down_event); synthesize_simple_event(self, type, physical_key, logical_key, context->timestamp); - context->sent_any_events = true; } } -// Implements fl_key_embedder_responder_handle_event and returns whether -// any events has been sent. -static bool fl_key_embedder_responder_handle_event_impl( +static void fl_key_embedder_responder_handle_event_impl( FlKeyResponder* responder, FlKeyEvent* event, FlKeyResponderAsyncCallback callback, gpointer user_data) { FlKeyEmbedderResponder* self = FL_KEY_EMBEDDER_RESPONDER(responder); - bool sent_any_events = false; - g_return_val_if_fail(event != nullptr, sent_any_events); - g_return_val_if_fail(callback != nullptr, sent_any_events); + g_return_if_fail(event != nullptr); + g_return_if_fail(callback != nullptr); const uint64_t physical_key = event_to_physical_key(event); const uint64_t logical_key = event_to_logical_key(event); @@ -698,7 +696,6 @@ static bool fl_key_embedder_responder_handle_event_impl( sync_state_context.timestamp = timestamp; sync_state_context.is_down = is_down_event; sync_state_context.event_logical_key = logical_key; - sync_state_context.sent_any_events = false; // Update lock mode states g_hash_table_foreach(self->lock_bit_to_checked_keys, @@ -709,10 +706,6 @@ static bool fl_key_embedder_responder_handle_event_impl( synchronize_pressed_states_loop_body, &sync_state_context); - if (sync_state_context.sent_any_events) { - sent_any_events = true; - } - // Construct the real event const uint64_t last_logical_record = lookup_hash_table(self->pressing_records, physical_key); @@ -733,7 +726,7 @@ static bool fl_key_embedder_responder_handle_event_impl( // with the same physical key, or the up event was lost during a loss of // focus. The down event is ignored. callback(true, user_data); - return sent_any_events; + return; } else { out_event.type = kFlutterKeyEventTypeDown; character_to_free = event_to_character(event); // Might be null @@ -745,7 +738,7 @@ static bool fl_key_embedder_responder_handle_event_impl( // event due to loss of focus, or multiple keyboards pressed keys with the // same physical key. Ignore the up event. callback(true, user_data); - return sent_any_events; + return; } else { out_event.type = kFlutterKeyEventTypeUp; } @@ -759,13 +752,12 @@ static bool fl_key_embedder_responder_handle_event_impl( if (self->engine != nullptr) { FlKeyEmbedderUserData* response_data = fl_key_embedder_user_data_new(callback, user_data); + self->sent_any_events = true; fl_engine_send_key_event(self->engine, &out_event, handle_response, response_data); - sent_any_events = true; } else { callback(true, user_data); } - return sent_any_events; } // Sends a key event to the framework. @@ -775,9 +767,9 @@ static void fl_key_embedder_responder_handle_event( FlKeyResponderAsyncCallback callback, gpointer user_data) { FlKeyEmbedderResponder* self = FL_KEY_EMBEDDER_RESPONDER(responder); - bool sent_any_events = fl_key_embedder_responder_handle_event_impl( - responder, event, callback, user_data); - if (!sent_any_events) { + self->sent_any_events = false; + fl_key_embedder_responder_handle_event_impl(responder, event, callback, user_data); + if (!self->sent_any_events) { fl_engine_send_key_event(self->engine, &empty_event, nullptr, nullptr); } } From 9a551f3c6721c60d3c06cf2129af80835d3e565c Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 17 Sep 2021 07:38:31 -0700 Subject: [PATCH 07/12] Format --- shell/platform/linux/fl_key_embedder_responder.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shell/platform/linux/fl_key_embedder_responder.cc b/shell/platform/linux/fl_key_embedder_responder.cc index 17afeb463fd8b..982273ffb746e 100644 --- a/shell/platform/linux/fl_key_embedder_responder.cc +++ b/shell/platform/linux/fl_key_embedder_responder.cc @@ -768,7 +768,8 @@ static void fl_key_embedder_responder_handle_event( gpointer user_data) { FlKeyEmbedderResponder* self = FL_KEY_EMBEDDER_RESPONDER(responder); self->sent_any_events = false; - fl_key_embedder_responder_handle_event_impl(responder, event, callback, user_data); + fl_key_embedder_responder_handle_event_impl(responder, event, callback, + user_data); if (!self->sent_any_events) { fl_engine_send_key_event(self->engine, &empty_event, nullptr, nullptr); } From c64337119f4aebae59a43f47a07a98be7b9c4168 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 17 Sep 2021 08:14:38 -0700 Subject: [PATCH 08/12] Better impl mac --- .../Source/FlutterEmbedderKeyResponder.mm | 96 ++++++++++--------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm index 9f22ca0d747e4..b4544a8a455bf 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm @@ -307,7 +307,6 @@ - (void)pendTo:(nonnull NSMutableDictionary* } pendingResponses[@(responseId)] = _callback; _handled = TRUE; - _sentAnyEvents = TRUE; NSAssert( ((_debugHandleSource = [NSString stringWithFormat:@"pending event %llu", responseId]), TRUE), @""); @@ -394,7 +393,7 @@ @interface FlutterEmbedderKeyResponder () - (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger)ignoringFlags timestamp:(NSTimeInterval)timestamp - guard:(FlutterKeyCallbackGuard*)guard; + guard:(nonnull FlutterKeyCallbackGuard*)guard; /** * Update the pressing state. @@ -410,34 +409,34 @@ - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey; - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event callback:(nonnull FlutterKeyCallbackGuard*)callback; +/** + * Send a synthesized key event, never expecting its event result. + * + * The |guard| is basically a regular guarded callback, but instead of being + * called, only used to record whether an event is sent. + */ +- (void)sendSynthesizedFlutterEvent:(const FlutterKeyEvent&)event + guard:(FlutterKeyCallbackGuard*)guard; + /** * Send a CapsLock down event, then a CapsLock up event. * - * If downCallback is nil, then both events will be synthesized. Otherwise, the - * downCallback will be used as the callback for the down event, which is not - * synthesized. + * If synthesizeDown is TRUE, then both events will be synthesized. Otherwise, + * the callback will be used as the callback for the down event, which is not + * synthesized, while the up event will always be synthesized. */ - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp - callback:(nullable FlutterKeyCallbackGuard*)downCallback; + synthesizeDown:(bool)synthesizeDown + callback:(nonnull FlutterKeyCallbackGuard*)callback; /** * Send a key event for a modifier key. - * - * If callback is nil, then the event is synthesized. */ - (void)sendModifierEventOfType:(BOOL)isDownEvent timestamp:(NSTimeInterval)timestamp keyCode:(unsigned short)keyCode - callback:(nullable FlutterKeyCallbackGuard*)callback; - -/** - * Send an empty key event. - * - * The event is never synthesized, and never expects an event result. An empty - * event is sent when no other events should be sent, such as upon back-to-back - * keydown events of the same key. - */ -- (void)sendEmptyEvent; + synthesized:(bool)synthesized + callback:(nonnull FlutterKeyCallbackGuard*)callback; /** * Processes a down event from the system. @@ -503,7 +502,16 @@ - (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { } NSAssert(guardedCallback.handled, @"The callback is returned without being handled."); if (!guardedCallback.sentAnyEvents) { - [self sendEmptyEvent]; + FlutterKeyEvent flutterEvent = { + .struct_size = sizeof(FlutterKeyEvent), + .timestamp = 0, + .type = kFlutterKeyEventTypeDown, + .physical = 0, + .logical = 0, + .character = nil, + .synthesized = false, + }; + _sendEvent(flutterEvent, nullptr, nullptr); } NSAssert(_lastModifierFlagsOfInterest == (event.modifierFlags & _modifierFlagOfInterestMask), @"The modifier flags are not properly updated: recorded 0x%lx, event with mask 0x%lx", @@ -521,8 +529,7 @@ - (void)synchronizeModifiers:(NSUInteger)currentFlags const NSUInteger lastFlagsOfInterest = _lastModifierFlagsOfInterest & updatingMask; NSUInteger flagDifference = currentFlagsOfInterest ^ lastFlagsOfInterest; if (flagDifference & NSEventModifierFlagCapsLock) { - [self sendCapsLockTapWithTimestamp:timestamp callback:nil]; - guard.sentAnyEvents = TRUE; // At least an up event is sent. + [self sendCapsLockTapWithTimestamp:timestamp synthesizeDown:true callback:guard]; flagDifference = flagDifference & ~NSEventModifierFlagCapsLock; } while (true) { @@ -537,11 +544,11 @@ - (void)synchronizeModifiers:(NSUInteger)currentFlags continue; } BOOL isDownEvent = (currentFlagsOfInterest & currentFlag) != 0; - guard.sentAnyEvents = TRUE; [self sendModifierEventOfType:isDownEvent timestamp:timestamp keyCode:[keyCode unsignedShortValue] - callback:nil]; + synthesized:true + callback:guard]; } _lastModifierFlagsOfInterest = (_lastModifierFlagsOfInterest & ~updatingMask) | currentFlagsOfInterest; @@ -567,8 +574,15 @@ - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event callback.sentAnyEvents = TRUE; } +- (void)sendSynthesizedFlutterEvent:(const FlutterKeyEvent&)event + guard:(FlutterKeyCallbackGuard*)guard { + _sendEvent(event, nullptr, nullptr); + guard.sentAnyEvents = TRUE; +} + - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp - callback:(FlutterKeyCallbackGuard*)downCallback { + synthesizeDown:(bool)synthesizeDown + callback:(FlutterKeyCallbackGuard*)callback { // MacOS sends a down *or* an up when CapsLock is tapped, alternatively on // even taps and odd taps. A CapsLock down or CapsLock up should always be // converted to a down *and* an up, and the up should always be a synthesized @@ -581,22 +595,23 @@ - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp .physical = kCapsLockPhysicalKey, .logical = kCapsLockLogicalKey, .character = nil, - .synthesized = downCallback == nil, + .synthesized = synthesizeDown, }; - if (downCallback != nil) { - [self sendPrimaryFlutterEvent:flutterEvent callback:downCallback]; + if (!synthesizeDown) { + [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; } else { - _sendEvent(flutterEvent, nullptr, nullptr); + [self sendSynthesizedFlutterEvent:flutterEvent guard:callback]; } flutterEvent.type = kFlutterKeyEventTypeUp; flutterEvent.synthesized = true; - _sendEvent(flutterEvent, nullptr, nullptr); + [self sendSynthesizedFlutterEvent:flutterEvent guard:callback]; } - (void)sendModifierEventOfType:(BOOL)isDownEvent timestamp:(NSTimeInterval)timestamp keyCode:(unsigned short)keyCode + synthesized:(bool)synthesized callback:(FlutterKeyCallbackGuard*)callback { uint64_t physicalKey = GetPhysicalKeyForKeyCode(keyCode); uint64_t logicalKey = GetLogicalKeyForModifier(keyCode, physicalKey); @@ -612,30 +627,16 @@ - (void)sendModifierEventOfType:(BOOL)isDownEvent .physical = physicalKey, .logical = logicalKey, .character = nil, - .synthesized = callback == nil, + .synthesized = synthesized, }; [self updateKey:physicalKey asPressed:isDownEvent ? logicalKey : 0]; - if (callback != nil) { + if (!synthesized) { [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; } else { - _sendEvent(flutterEvent, nullptr, nullptr); - callback.sentAnyEvents = TRUE; + [self sendSynthesizedFlutterEvent:flutterEvent guard:callback]; } } -- (void)sendEmptyEvent { - FlutterKeyEvent flutterEvent = { - .struct_size = sizeof(FlutterKeyEvent), - .timestamp = 0, - .type = kFlutterKeyEventTypeDown, - .physical = 0, - .logical = 0, - .character = nil, - .synthesized = false, - }; - _sendEvent(flutterEvent, nullptr, nullptr); -} - - (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback { uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); uint64_t logicalKey = GetLogicalKeyForEvent(event, physicalKey); @@ -704,7 +705,7 @@ - (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)c guard:callback]; if ((_lastModifierFlagsOfInterest & NSEventModifierFlagCapsLock) != (event.modifierFlags & NSEventModifierFlagCapsLock)) { - [self sendCapsLockTapWithTimestamp:event.timestamp callback:callback]; + [self sendCapsLockTapWithTimestamp:event.timestamp synthesizeDown:false callback:callback]; _lastModifierFlagsOfInterest = _lastModifierFlagsOfInterest ^ NSEventModifierFlagCapsLock; } else { [callback resolveTo:TRUE]; @@ -745,6 +746,7 @@ - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callb [self sendModifierEventOfType:shouldBePressed timestamp:event.timestamp keyCode:event.keyCode + synthesized:false callback:callback]; } From a81182c864e7584a328e3781b6ebf4d8ddc4044c Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 17 Sep 2021 08:15:02 -0700 Subject: [PATCH 09/12] Format --- .../framework/Source/FlutterEmbedderKeyResponder.mm | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm index b4544a8a455bf..e1409758231b5 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm @@ -640,7 +640,10 @@ - (void)sendModifierEventOfType:(BOOL)isDownEvent - (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback { uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); uint64_t logicalKey = GetLogicalKeyForEvent(event, physicalKey); - [self synchronizeModifiers:event.modifierFlags ignoringFlags:0 timestamp:event.timestamp guard:callback]; + [self synchronizeModifiers:event.modifierFlags + ignoringFlags:0 + timestamp:event.timestamp + guard:callback]; bool isARepeat = event.isARepeat; NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; @@ -672,7 +675,10 @@ - (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callb - (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback { NSAssert(!event.isARepeat, @"Unexpected repeated Up event: keyCode %d, char %@, charIM %@", event.keyCode, event.characters, event.charactersIgnoringModifiers); - [self synchronizeModifiers:event.modifierFlags ignoringFlags:0 timestamp:event.timestamp guard:callback]; + [self synchronizeModifiers:event.modifierFlags + ignoringFlags:0 + timestamp:event.timestamp + guard:callback]; uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; @@ -702,7 +708,7 @@ - (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)c [self synchronizeModifiers:event.modifierFlags ignoringFlags:NSEventModifierFlagCapsLock timestamp:event.timestamp - guard:callback]; + guard:callback]; if ((_lastModifierFlagsOfInterest & NSEventModifierFlagCapsLock) != (event.modifierFlags & NSEventModifierFlagCapsLock)) { [self sendCapsLockTapWithTimestamp:event.timestamp synthesizeDown:false callback:callback]; From 69d55fa0f1d215ceee79557d9ffb0ced423400df Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 20 Sep 2021 00:03:53 -0700 Subject: [PATCH 10/12] Windows --- .../windows/keyboard_key_embedder_handler.cc | 64 +++++++++++-------- .../windows/keyboard_key_embedder_handler.h | 33 +++++++--- 2 files changed, 63 insertions(+), 34 deletions(-) diff --git a/shell/platform/windows/keyboard_key_embedder_handler.cc b/shell/platform/windows/keyboard_key_embedder_handler.cc index 4ac09b0eac5cc..042b9d9132f2b 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler.cc +++ b/shell/platform/windows/keyboard_key_embedder_handler.cc @@ -70,9 +70,9 @@ std::string ConvertChar32ToUtf8(char32_t ch) { } KeyboardKeyEmbedderHandler::KeyboardKeyEmbedderHandler( - SendEvent send_event, + SendEventHandler send_event, GetKeyStateHandler get_key_state) - : sendEvent_(send_event), get_key_state_(get_key_state), response_id_(1) { + : perform_send_event_(send_event), get_key_state_(get_key_state), response_id_(1) { InitCriticalKeys(); } @@ -155,7 +155,7 @@ uint64_t KeyboardKeyEmbedderHandler::GetLogicalKey(int key, return ApplyPlaneToId(toLower(key), windowsPlane); } -void KeyboardKeyEmbedderHandler::KeyboardHook( +void KeyboardKeyEmbedderHandler::KeyboardHookImpl( int key, int scancode, int action, @@ -198,7 +198,6 @@ void KeyboardKeyEmbedderHandler::KeyboardHook( // 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. - sendEvent_(CreateEmptyEvent(), nullptr, nullptr); callback(true); return; } @@ -215,7 +214,6 @@ void KeyboardKeyEmbedderHandler::KeyboardHook( // 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. - sendEvent_(CreateEmptyEvent(), nullptr, nullptr); callback(true); return; } else { @@ -245,7 +243,6 @@ void KeyboardKeyEmbedderHandler::KeyboardHook( // 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. - sendEvent_(CreateEmptyEvent(), nullptr, nullptr); callback(true); return; } @@ -279,10 +276,37 @@ void KeyboardKeyEmbedderHandler::KeyboardHook( }; auto pending_ptr = std::make_unique(std::move(pending)); pending_responses_[response_id] = std::move(pending_ptr); - sendEvent_(key_data, KeyboardKeyEmbedderHandler::HandleResponse, + SendEvent(key_data, KeyboardKeyEmbedderHandler::HandleResponse, reinterpret_cast(pending_responses_[response_id].get())); } +void KeyboardKeyEmbedderHandler::KeyboardHook( + int key, + int scancode, + int action, + char32_t character, + bool extended, + bool was_down, + std::function 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( + std::chrono::duration_cast( + 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, @@ -321,7 +345,7 @@ void KeyboardKeyEmbedderHandler::SynchronizeCritialToggledStates( // If the key is pressed, release it first. if (pressingRecords_.find(key_info.physical_key) != pressingRecords_.end()) { - sendEvent_(SynthesizeSimpleEvent( + SendEvent(SynthesizeSimpleEvent( kFlutterKeyEventTypeUp, key_info.physical_key, key_info.logical_key, empty_character), nullptr, nullptr); @@ -329,7 +353,7 @@ void KeyboardKeyEmbedderHandler::SynchronizeCritialToggledStates( // This key will always be pressed in the following synthesized event. pressingRecords_[key_info.physical_key] = key_info.logical_key; } - sendEvent_(SynthesizeSimpleEvent(kFlutterKeyEventTypeDown, + SendEvent(SynthesizeSimpleEvent(kFlutterKeyEventTypeDown, key_info.physical_key, key_info.logical_key, empty_character), nullptr, nullptr); @@ -366,7 +390,7 @@ void KeyboardKeyEmbedderHandler::SynchronizeCritialPressedStates() { pressingRecords_.erase(recorded_pressed_iter); } const char* empty_character = ""; - sendEvent_( + SendEvent( SynthesizeSimpleEvent(should_pressed ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeUp, key_info.physical_key, key_info.logical_key, @@ -434,21 +458,6 @@ void KeyboardKeyEmbedderHandler::ConvertUtf32ToUtf8_(char* out, char32_t ch) { strcpy_s(out, kCharacterCacheSize, result.c_str()); } -FlutterKeyEvent KeyboardKeyEmbedderHandler::CreateEmptyEvent() { - return FlutterKeyEvent{ - .struct_size = sizeof(FlutterKeyEvent), - .timestamp = static_cast( - std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()) - .count()), - .type = kFlutterKeyEventTypeDown, - .physical = 0, - .logical = 0, - .character = empty_character, - .synthesized = false, - }; -} - FlutterKeyEvent KeyboardKeyEmbedderHandler::SynthesizeSimpleEvent( FlutterKeyEventType type, uint64_t physical, @@ -468,4 +477,9 @@ FlutterKeyEvent KeyboardKeyEmbedderHandler::SynthesizeSimpleEvent( }; } +void KeyboardKeyEmbedderHandler::SendEvent(const FlutterKeyEvent& event, FlutterKeyEventCallback callback, void* user_data) { + sent_any_events = true; + perform_send_event_(event, callback, user_data); +} + } // namespace flutter diff --git a/shell/platform/windows/keyboard_key_embedder_handler.h b/shell/platform/windows/keyboard_key_embedder_handler.h index 7ee88a33e626d..8719621218358 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler.h +++ b/shell/platform/windows/keyboard_key_embedder_handler.h @@ -15,6 +15,9 @@ namespace flutter { +// Encode a 32-bit unicode code point into a UTF-8 byte array. +// +// See https://en.wikipedia.org/wiki/UTF-8#Encoding for the algorithm. std::string ConvertChar32ToUtf8(char32_t ch); // A delegate of |KeyboardKeyHandler| that handles events by sending @@ -30,7 +33,7 @@ std::string ConvertChar32ToUtf8(char32_t ch); class KeyboardKeyEmbedderHandler : public KeyboardKeyHandler::KeyboardKeyHandlerDelegate { public: - using SendEvent = std::function; using GetKeyStateHandler = std::function; @@ -40,12 +43,12 @@ class KeyboardKeyEmbedderHandler // Use `send_event` to define how the class should dispatch converted // flutter events, as well as how to receive the response, to the engine. It's // typically FlutterWindowsEngine::SendKeyEvent. The 2nd and 3rd parameter - // of the SendEvent call might be nullptr. + // of the SendEventHandler call might be nullptr. // // Use `get_key_state` to define how the class should get a reliable result of // the state for a virtual key. It's typically Win32's GetKeyState, but can // also be nullptr (for UWP). - explicit KeyboardKeyEmbedderHandler(SendEvent send_event, + explicit KeyboardKeyEmbedderHandler(SendEventHandler send_event, GetKeyStateHandler get_key_state); virtual ~KeyboardKeyEmbedderHandler(); @@ -85,6 +88,16 @@ class KeyboardKeyEmbedderHandler bool toggled_on; }; + // Implements the core logic of |KeyboardHook|, leaving out some state + // guards. + void KeyboardHookImpl(int key, + int scancode, + int action, + char32_t character, + bool extended, + bool was_down, + std::function callback); + // Assign |critical_keys_| with basic information. void InitCriticalKeys(); // Update |critical_keys_| with last seen logical and physical key. @@ -98,8 +111,12 @@ class KeyboardKeyEmbedderHandler // if their pressing states have been desynchronized. void SynchronizeCritialPressedStates(); + // Wraps perform_send_event_ with state tracking. Use this instead of |perform_send_event_| + // to send events to the framework. + void SendEvent(const FlutterKeyEvent& event, FlutterKeyEventCallback callback, void* user_data); + std::function - sendEvent_; + perform_send_event_; GetKeyStateHandler get_key_state_; // A map from physical keys to logical keys, each entry indicating a pressed @@ -111,6 +128,9 @@ class KeyboardKeyEmbedderHandler // A self-incrementing integer, used as the ID for the next entry for // |pending_responses_|. uint64_t response_id_; + // Whether any events has been sent with |PerformSendEvent| during a + // |KeyboardHook|. + bool sent_any_events; // Important keys whose states are checked and guaranteed synchronized // on every key event. @@ -123,11 +143,6 @@ class KeyboardKeyEmbedderHandler static uint64_t GetLogicalKey(int key, bool extended, int scancode); static void HandleResponse(bool handled, void* user_data); static void ConvertUtf32ToUtf8_(char* out, char32_t ch); - // Create an empty event. - // - // This is used when no key data needs to be sent. For the reason, see the - // |KeyboardKeyEmbedderHandler| class. - static FlutterKeyEvent CreateEmptyEvent(); static FlutterKeyEvent SynthesizeSimpleEvent(FlutterKeyEventType type, uint64_t physical, uint64_t logical, From d5d001e8cef23bb0df3417eb09cc4f9296a994e1 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 20 Sep 2021 00:49:00 -0700 Subject: [PATCH 11/12] Format --- .../windows/keyboard_key_embedder_handler.cc | 25 +++++++++++-------- .../windows/keyboard_key_embedder_handler.h | 15 ++++++----- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/shell/platform/windows/keyboard_key_embedder_handler.cc b/shell/platform/windows/keyboard_key_embedder_handler.cc index 042b9d9132f2b..b30a17c279b19 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler.cc +++ b/shell/platform/windows/keyboard_key_embedder_handler.cc @@ -72,7 +72,9 @@ std::string ConvertChar32ToUtf8(char32_t ch) { KeyboardKeyEmbedderHandler::KeyboardKeyEmbedderHandler( SendEventHandler send_event, GetKeyStateHandler get_key_state) - : perform_send_event_(send_event), get_key_state_(get_key_state), response_id_(1) { + : perform_send_event_(send_event), + get_key_state_(get_key_state), + response_id_(1) { InitCriticalKeys(); } @@ -277,7 +279,7 @@ void KeyboardKeyEmbedderHandler::KeyboardHookImpl( auto pending_ptr = std::make_unique(std::move(pending)); pending_responses_[response_id] = std::move(pending_ptr); SendEvent(key_data, KeyboardKeyEmbedderHandler::HandleResponse, - reinterpret_cast(pending_responses_[response_id].get())); + reinterpret_cast(pending_responses_[response_id].get())); } void KeyboardKeyEmbedderHandler::KeyboardHook( @@ -289,7 +291,8 @@ void KeyboardKeyEmbedderHandler::KeyboardHook( bool was_down, std::function callback) { sent_any_events = false; - KeyboardHookImpl(key, scancode, action, character, extended, was_down, std::move(callback)); + KeyboardHookImpl(key, scancode, action, character, extended, was_down, + std::move(callback)); if (!sent_any_events) { FlutterKeyEvent empty_event{ .struct_size = sizeof(FlutterKeyEvent), @@ -346,17 +349,17 @@ void KeyboardKeyEmbedderHandler::SynchronizeCritialToggledStates( if (pressingRecords_.find(key_info.physical_key) != pressingRecords_.end()) { SendEvent(SynthesizeSimpleEvent( - kFlutterKeyEventTypeUp, key_info.physical_key, - key_info.logical_key, empty_character), - nullptr, nullptr); + kFlutterKeyEventTypeUp, key_info.physical_key, + key_info.logical_key, empty_character), + nullptr, nullptr); } else { // This key will always be pressed in the following synthesized event. pressingRecords_[key_info.physical_key] = key_info.logical_key; } SendEvent(SynthesizeSimpleEvent(kFlutterKeyEventTypeDown, - key_info.physical_key, - key_info.logical_key, empty_character), - nullptr, nullptr); + key_info.physical_key, + key_info.logical_key, empty_character), + nullptr, nullptr); } key_info.toggled_on = should_toggled; } @@ -477,7 +480,9 @@ FlutterKeyEvent KeyboardKeyEmbedderHandler::SynthesizeSimpleEvent( }; } -void KeyboardKeyEmbedderHandler::SendEvent(const FlutterKeyEvent& event, FlutterKeyEventCallback callback, void* user_data) { +void KeyboardKeyEmbedderHandler::SendEvent(const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, + void* user_data) { sent_any_events = true; perform_send_event_(event, callback, user_data); } diff --git a/shell/platform/windows/keyboard_key_embedder_handler.h b/shell/platform/windows/keyboard_key_embedder_handler.h index 8719621218358..d7fc69ede952c 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler.h +++ b/shell/platform/windows/keyboard_key_embedder_handler.h @@ -33,9 +33,10 @@ std::string ConvertChar32ToUtf8(char32_t ch); class KeyboardKeyEmbedderHandler : public KeyboardKeyHandler::KeyboardKeyHandlerDelegate { public: - using SendEventHandler = std::function; + using SendEventHandler = + std::function; using GetKeyStateHandler = std::function; // Build a KeyboardKeyEmbedderHandler. @@ -111,9 +112,11 @@ class KeyboardKeyEmbedderHandler // if their pressing states have been desynchronized. void SynchronizeCritialPressedStates(); - // Wraps perform_send_event_ with state tracking. Use this instead of |perform_send_event_| - // to send events to the framework. - void SendEvent(const FlutterKeyEvent& event, FlutterKeyEventCallback callback, void* user_data); + // Wraps perform_send_event_ with state tracking. Use this instead of + // |perform_send_event_| to send events to the framework. + void SendEvent(const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, + void* user_data); std::function perform_send_event_; From 1846cec416600d5d840cc50f9cc38e3897634743 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 1 Oct 2021 14:01:29 -0700 Subject: [PATCH 12/12] Apply suggestions from code review Co-authored-by: Greg Spencer --- lib/web_ui/lib/src/engine/keyboard_binding.dart | 2 +- .../macos/framework/Source/FlutterEmbedderKeyResponder.mm | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/web_ui/lib/src/engine/keyboard_binding.dart b/lib/web_ui/lib/src/engine/keyboard_binding.dart index bf64fc3ea232d..480f790d53c1c 100644 --- a/lib/web_ui/lib/src/engine/keyboard_binding.dart +++ b/lib/web_ui/lib/src/engine/keyboard_binding.dart @@ -501,7 +501,7 @@ class KeyboardConverter { } // Parse the HTML event, update states, and dispatch Flutter key data through - // [dispatchKeyData]. + // [performDispatchKeyData]. // // * The method might dispatch some synthesized key data first to update states, // results discarded. diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm index e1409758231b5..bcab9b0ecf1c7 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm @@ -388,7 +388,7 @@ @interface FlutterEmbedderKeyResponder () * |modifierFlagOfInterestMask| and excluding |ignoringFlags|. * * The |guard| is basically a regular guarded callback, but instead of being - * called, only used to record whether an event is sent. + * called, it is only used to record whether an event is sent. */ - (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger)ignoringFlags @@ -413,7 +413,7 @@ - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event * Send a synthesized key event, never expecting its event result. * * The |guard| is basically a regular guarded callback, but instead of being - * called, only used to record whether an event is sent. + * called, it is only used to record whether an event is sent. */ - (void)sendSynthesizedFlutterEvent:(const FlutterKeyEvent&)event guard:(FlutterKeyCallbackGuard*)guard;