44
55import 'package:meta/meta.dart' ;
66import 'package:ui/ui.dart' as ui;
7+ import 'package:web_locale_keymap/web_locale_keymap.dart' as locale_keymap;
78
89import '../engine.dart' show registerHotRestartListener;
910import 'browser_detection.dart' ;
@@ -54,16 +55,6 @@ final Map<int, _ModifierGetter> _kLogicalKeyToModifierGetter = <int, _ModifierGe
5455 _kLogicalMetaRight: (FlutterHtmlKeyboardEvent event) => event.metaKey,
5556};
5657
57- // ASCII for a, z, A, and Z
58- const int _kCharLowerA = 0x61 ;
59- const int _kCharLowerZ = 0x7a ;
60- const int _kCharUpperA = 0x41 ;
61- const int _kCharUpperZ = 0x5a ;
62- bool isAlphabet (int charCode) {
63- return (charCode >= _kCharLowerA && charCode <= _kCharLowerZ)
64- || (charCode >= _kCharUpperA && charCode <= _kCharUpperZ);
65- }
66-
6758const String _kPhysicalCapsLock = 'CapsLock' ;
6859
6960const String _kLogicalDead = 'Dead' ;
@@ -98,9 +89,24 @@ Duration _eventTimeStampToDuration(num milliseconds) {
9889 return Duration (milliseconds: ms, microseconds: micro);
9990}
10091
92+ // Returns a function that caches the result of `body`, ensuring that `body` is
93+ // only run once.
94+ ValueGetter <T > _cached <T >(ValueGetter <T > body) {
95+ T ? cache;
96+ return () {
97+ return cache ?? = body ();
98+ };
99+ }
100+
101101class KeyboardBinding {
102102 KeyboardBinding ._() {
103- _setup ();
103+ _addEventListener ('keydown' , allowInterop ((DomEvent domEvent) {
104+ final FlutterHtmlKeyboardEvent event = FlutterHtmlKeyboardEvent (domEvent as DomKeyboardEvent );
105+ return _converter.handleEvent (event);
106+ }));
107+ _addEventListener ('keyup' , allowInterop ((DomEvent event) {
108+ return _converter.handleEvent (FlutterHtmlKeyboardEvent (event as DomKeyboardEvent ));
109+ }));
104110 }
105111
106112 /// The singleton instance of this object.
@@ -117,8 +123,19 @@ class KeyboardBinding {
117123 }
118124 }
119125
126+ /// The platform as used in the initialization.
127+ ///
128+ /// By default it is derived from [operatingSystem] .
129+ @protected
130+ OperatingSystem get localPlatform {
131+ return operatingSystem;
132+ }
133+
120134 KeyboardConverter get converter => _converter;
121- late final KeyboardConverter _converter;
135+ late final KeyboardConverter _converter = KeyboardConverter (
136+ _onKeyData,
137+ localPlatform,
138+ );
122139 final Map <String , DomEventListener > _listeners = < String , DomEventListener > {};
123140
124141 void _addEventListener (String eventName, DomEventListener handler) {
@@ -154,16 +171,6 @@ class KeyboardBinding {
154171 return result! ;
155172 }
156173
157- void _setup () {
158- _addEventListener ('keydown' , allowInterop ((DomEvent event) {
159- return _converter.handleEvent (FlutterHtmlKeyboardEvent (event as DomKeyboardEvent ));
160- }));
161- _addEventListener ('keyup' , allowInterop ((DomEvent event) {
162- return _converter.handleEvent (FlutterHtmlKeyboardEvent (event as DomKeyboardEvent ));
163- }));
164- _converter = KeyboardConverter (_onKeyData, onMacOs: operatingSystem == OperatingSystem .macOs);
165- }
166-
167174 void _reset () {
168175 _clearListeners ();
169176 _converter.dispose ();
@@ -211,10 +218,30 @@ class FlutterHtmlKeyboardEvent {
211218// [dispatchKeyData] as given in the constructor. Some key data might be
212219// dispatched asynchronously.
213220class KeyboardConverter {
214- KeyboardConverter (this .performDispatchKeyData, {this .onMacOs = false });
221+ KeyboardConverter (this .performDispatchKeyData, OperatingSystem platform)
222+ : onMacOs = platform == OperatingSystem .macOs,
223+ _mapping = _mappingFromPlatform (platform);
215224
216225 final DispatchKeyData performDispatchKeyData;
226+ /// Whether the current platform is macOS, which affects how certain key events
227+ /// are comprehended.
217228 final bool onMacOs;
229+ /// Maps logical keys from key event properties.
230+ final locale_keymap.LocaleKeymap _mapping;
231+
232+ static locale_keymap.LocaleKeymap _mappingFromPlatform (OperatingSystem platform) {
233+ switch (platform) {
234+ case OperatingSystem .iOs:
235+ case OperatingSystem .macOs:
236+ return locale_keymap.LocaleKeymap .darwin ();
237+ case OperatingSystem .windows:
238+ return locale_keymap.LocaleKeymap .win ();
239+ case OperatingSystem .android:
240+ case OperatingSystem .linux:
241+ case OperatingSystem .unknown:
242+ return locale_keymap.LocaleKeymap .linux ();
243+ }
244+ }
218245
219246 // The `performDispatchKeyData` wrapped with tracking logic.
220247 //
@@ -273,29 +300,14 @@ class KeyboardConverter {
273300 (metaDown ? _kDeadKeyMeta : 0 );
274301 }
275302
276- // Whether `event.key` should be considered a key name.
303+ // Whether `event.key` is a key name, such as "Shift", or otherwise a
304+ // character, such as "S" or "ж".
277305 //
278- // The `event.key` can either be a key name or the printable character. If the
279- // first character is an alphabet, it must be either 'A' to 'Z' ( and return
280- // true), or be a key name (and return false). Otherwise, return true.
281- static bool _eventKeyIsKeyname (String key) {
282- assert (key.isNotEmpty);
283- return isAlphabet (key.codeUnitAt (0 )) && key.length > 1 ;
284- }
285-
286- static int _characterToLogicalKey (String key) {
287- // Assume the length being <= 2 to be sufficient in all cases. If not,
288- // extend the algorithm.
289- assert (key.length <= 2 );
290- int result = key.codeUnitAt (0 ) & 0xffff ;
291- if (key.length == 2 ) {
292- result += key.codeUnitAt (1 ) << 16 ;
293- }
294- // Convert upper letters to lower letters
295- if (result >= _kCharUpperA && result <= _kCharUpperZ) {
296- result = result + _kCharLowerA - _kCharUpperA;
297- }
298- return result;
306+ // A key name always has more than 1 code unit, and they are all alnums.
307+ // Character keys, however, can also have more than 1 code unit: en-in
308+ // maps KeyL to L̥/l̥. To resolve this, we check the second code unit.
309+ static bool _eventKeyIsKeyName (String key) {
310+ return key.length > 1 && key.codeUnitAt (0 ) < 0x7F && key.codeUnitAt (1 ) < 0x7F ;
299311 }
300312
301313 static int _deadKeyToLogicalKey (int physicalKey, FlutterHtmlKeyboardEvent event) {
@@ -307,10 +319,6 @@ class KeyboardConverter {
307319 return physicalKey + _getModifierMask (event) + _kWebKeyIdPlane;
308320 }
309321
310- static int _otherLogicalKey (String key) {
311- return kWebToLogicalKey[key] ?? (key.hashCode + _kWebKeyIdPlane);
312- }
313-
314322 // Map from pressed physical key to corresponding pressed logical key.
315323 //
316324 // Multiple physical keys can be mapped to the same logical key, usually due
@@ -369,22 +377,36 @@ class KeyboardConverter {
369377 final String eventKey = event.key! ;
370378
371379 final int physicalKey = _getPhysicalCode (event.code! );
372- final bool logicalKeyIsCharacter = ! _eventKeyIsKeyname (eventKey);
373- final String ? character = logicalKeyIsCharacter ? eventKey : null ;
374- final int logicalKey = () {
380+ final bool logicalKeyIsCharacter = ! _eventKeyIsKeyName (eventKey);
381+ // The function body might or might not be evaluated. If the event is a key
382+ // up event, the resulting event will simply use the currently pressed
383+ // logical key.
384+ final ValueGetter <int > logicalKey = _cached <int >(() {
385+ // Mapped logical keys, such as ArrowLeft, Escape, AudioVolumeDown.
386+ final int ? mappedLogicalKey = kWebToLogicalKey[eventKey];
387+ if (mappedLogicalKey != null ) {
388+ return mappedLogicalKey;
389+ }
390+ // Keys with locations, such as modifier keys (Shift) or numpad keys.
375391 if (kWebLogicalLocationMap.containsKey (event.key)) {
376392 final int ? result = kWebLogicalLocationMap[event.key! ]? [event.location! ];
377393 assert (result != null , 'Invalid modifier location: ${event .key }, ${event .location }' );
378394 return result! ;
379395 }
380- if (character != null ) {
381- return _characterToLogicalKey (character);
396+ // Locale-sensitive keys: letters, digits, and certain symbols.
397+ if (logicalKeyIsCharacter) {
398+ final int ? localeLogicalKeys = _mapping.getLogicalKey (event.code, event.key, event.keyCode);
399+ if (localeLogicalKeys != null ) {
400+ return localeLogicalKeys;
401+ }
382402 }
403+ // Dead keys that are not handled by the locale mapping.
383404 if (eventKey == _kLogicalDead) {
384405 return _deadKeyToLogicalKey (physicalKey, event);
385406 }
386- return _otherLogicalKey (eventKey);
387- }();
407+ // Minted logical keys.
408+ return eventKey.hashCode + _kWebKeyIdPlane;
409+ });
388410
389411 assert (event.type == 'keydown' || event.type == 'keyup' );
390412 final bool isPhysicalDown = event.type == 'keydown' ||
@@ -406,7 +428,7 @@ class KeyboardConverter {
406428 timeStamp: timeStamp,
407429 type: ui.KeyEventType .up,
408430 physical: physicalKey,
409- logical: logicalKey,
431+ logical: logicalKey () ,
410432 character: null ,
411433 synthesized: true ,
412434 ),
@@ -441,7 +463,7 @@ class KeyboardConverter {
441463 timeStamp: timeStamp,
442464 type: ui.KeyEventType .up,
443465 physical: physicalKey,
444- logical: logicalKey,
466+ logical: logicalKey () ,
445467 character: null ,
446468 synthesized: true ,
447469 ));
@@ -474,7 +496,7 @@ class KeyboardConverter {
474496 switch (type) {
475497 case ui.KeyEventType .down:
476498 assert (lastLogicalRecord == null );
477- nextLogicalRecord = logicalKey;
499+ nextLogicalRecord = logicalKey () ;
478500 break ;
479501 case ui.KeyEventType .up:
480502 assert (lastLogicalRecord != null );
@@ -499,7 +521,7 @@ class KeyboardConverter {
499521 _kLogicalKeyToModifierGetter.forEach ((int testeeLogicalKey, _ModifierGetter getModifier) {
500522 // Do not synthesize for the key of the current event. The event is the
501523 // ground truth.
502- if (logicalKey == testeeLogicalKey) {
524+ if (logicalKey () == testeeLogicalKey) {
503525 return ;
504526 }
505527 if (_pressingRecords.containsValue (testeeLogicalKey) && ! getModifier (event)) {
@@ -525,17 +547,18 @@ class KeyboardConverter {
525547 // Update key guards
526548 if (logicalKeyIsCharacter) {
527549 if (nextLogicalRecord != null ) {
528- _startGuardingKey (physicalKey, logicalKey, timeStamp);
550+ _startGuardingKey (physicalKey, logicalKey () , timeStamp);
529551 } else {
530552 _stopGuardingKey (physicalKey);
531553 }
532554 }
533555
556+ final String ? character = logicalKeyIsCharacter ? eventKey : null ;
534557 final ui.KeyData keyData = ui.KeyData (
535558 timeStamp: timeStamp,
536559 type: type,
537560 physical: physicalKey,
538- logical: lastLogicalRecord ?? logicalKey,
561+ logical: lastLogicalRecord ?? logicalKey () ,
539562 character: type == ui.KeyEventType .up ? null : character,
540563 synthesized: false ,
541564 );
0 commit comments