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

Commit e203623

Browse files
authored
[Web, keyboard] Locale layout mapping (#34625)
* WIP * WIP: Better debug log * WIP * Finish logic (untested) * Use third_party for layouts * Update gen * Fix filtering bugs * Format * Fix build * Add scripts * Try license * Copyright * Rename license * Use case insensitive * more lower case * Sort. Migrate to better types. * Fix template * Marshall and unmarshall * Comments * Gen types file * gen json * engine compilable * benchmark_detector * Move github to separate file * Generate full mapping * test cases * Unified dead key * Correct key * remove duplicate file * letter in test cases * Compile * int keycode, and fix compile * Correct gen directory * Heuristic * Heuristic benchmark * Reorganize logical key * Move to common * Docs * Combine into bin * Remove layout_types * Rename to locale_keymap * Rename class * Rename to heuristicMapper * Format * Fix license * Fix test * Test license * Fix tests * Fix import as * Fix analyze problem * Fix license * Update license pattern * The MIT license * License diff * Fix license * Update signature * readme * fix signature? * fix signature? * Fix license count * Fix build * Compression * Fix _eventKeyIsKeyname * remove gitignore * Fix nullable * Fix doc * Add underscore lead. Make everything lower case. Print by line. * Better event code encoding * Signature * Comment. Change dead key mapping to the last. * Better digit heuristic. Better _eventKeyIsKeyName * en-in test * Remove unnecessary build change * Better order
1 parent 782d3a6 commit e203623

35 files changed

+3321
-127
lines changed

ci/analyze.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ echo ""
5252

5353
"$DART" analyze --fatal-infos --fatal-warnings "$FLUTTER_DIR/flutter_frontend_server"
5454

55+
(cd "$FLUTTER_DIR/tools/gen_web_locale_keymap"; "$DART" pub get)
5556
"$DART" analyze --fatal-infos --fatal-warnings "$FLUTTER_DIR/tools"
5657

5758
(cd "$FLUTTER_DIR/testing/skia_gold_client"; "$DART" pub get)

ci/licenses.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ function verify_licenses() (
121121

122122
local actualLicenseCount
123123
actualLicenseCount="$(tail -n 1 flutter/ci/licenses_golden/licenses_flutter | tr -dc '0-9')"
124-
local expectedLicenseCount=17 # When changing this number: Update the error message below as well describing all expected license types.
124+
local expectedLicenseCount=19 # When changing this number: Update the error message below as well describing all expected license types.
125125

126126
if [[ $actualLicenseCount -ne $expectedLicenseCount ]]; then
127127
echo "=============================== ERROR ==============================="

ci/licenses_golden/licenses_flutter

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,31 @@
11
UNUSED LICENSES:
22

3-
3+
====================================================================================================
4+
ORIGIN: ../../../flutter/third_party/web_locale_keymap/License.txt
5+
TYPE: LicenseType.mit
6+
----------------------------------------------------------------------------------------------------
7+
MIT License
8+
9+
Copyright (c) 2015 - present Microsoft Corporation
10+
11+
Permission is hereby granted, free of charge, to any person obtaining a copy
12+
of this software and associated documentation files (the "Software"), to deal
13+
in the Software without restriction, including without limitation the rights
14+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15+
copies of the Software, and to permit persons to whom the Software is
16+
furnished to do so, subject to the following conditions:
17+
18+
The above copyright notice and this permission notice shall be included in all
19+
copies or substantial portions of the Software.
20+
21+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27+
SOFTWARE.
28+
====================================================================================================
429
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
530
USED LICENSES:
631

@@ -565,6 +590,35 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
565590
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
566591
====================================================================================================
567592

593+
====================================================================================================
594+
LIBRARY: web_locale_keymap
595+
ORIGIN: ../../../flutter/third_party/web_locale_keymap/lib/web_locale_keymap/key_mappings.g.dart + ../../../flutter/third_party/web_locale_keymap/License.txt
596+
TYPE: LicenseType.mit
597+
FILE: ../../../flutter/third_party/web_locale_keymap/lib/web_locale_keymap.dart
598+
FILE: ../../../flutter/third_party/web_locale_keymap/lib/web_locale_keymap/key_mappings.g.dart
599+
FILE: ../../../flutter/third_party/web_locale_keymap/lib/web_locale_keymap/locale_keymap.dart
600+
----------------------------------------------------------------------------------------------------
601+
Copyright (c) 2022 Google LLC
602+
603+
Permission is hereby granted, free of charge, to any person obtaining a copy
604+
of this software and associated documentation files (the "Software"), to deal
605+
in the Software without restriction, including without limitation the rights
606+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
607+
copies of the Software, and to permit persons to whom the Software is
608+
furnished to do so, subject to the following conditions:
609+
610+
The above copyright notice and this permission notice shall be included in all
611+
copies or substantial portions of the Software.
612+
613+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
614+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
615+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
616+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
617+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
618+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
619+
SOFTWARE.
620+
====================================================================================================
621+
568622
====================================================================================================
569623
LIBRARY: accessibility
570624
ORIGIN: ../../../flutter/third_party/accessibility/ax/ax_export.h + ../../../LICENSE
@@ -3767,4 +3821,4 @@ shall not be used in advertising or otherwise to promote the sale,
37673821
use or other dealings in these Data Files or Software without prior
37683822
written authorization of the copyright holder.
37693823
====================================================================================================
3770-
Total license count: 17
3824+
Total license count: 19

ci/licenses_golden/tool_signature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
Signature: 027af91b165acaa447651bfca8c7c704
1+
Signature: f6d8146c82d268e2e2549bf5019ebf07
22

lib/web_ui/lib/src/engine/keyboard_binding.dart

Lines changed: 85 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'package:meta/meta.dart';
66
import 'package:ui/ui.dart' as ui;
7+
import 'package:web_locale_keymap/web_locale_keymap.dart' as locale_keymap;
78

89
import '../engine.dart' show registerHotRestartListener;
910
import '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-
6758
const String _kPhysicalCapsLock = 'CapsLock';
6859

6960
const 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+
101101
class 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.
213220
class 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
);

lib/web_ui/pubspec.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ environment:
88
dependencies:
99
js: 0.6.4
1010
meta: ^1.7.0
11+
web_locale_keymap:
12+
path: ../../third_party/web_locale_keymap
1113

1214
web_unicode:
1315
path: ../../third_party/web_unicode

lib/web_ui/test/engine/pointer_binding_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ void testMain() {
5252
return KeyboardConverter((ui.KeyData key) {
5353
keyDataList.add(key);
5454
return true;
55-
});
55+
}, OperatingSystem.linux);
5656
}
5757

5858
test('ios workaround', () {

0 commit comments

Comments
 (0)