| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'package:path/path.dart' as path; | |
| import 'base_code_gen.dart'; | |
| import 'constants.dart'; | |
| import 'logical_key_data.dart'; | |
| import 'physical_key_data.dart'; | |
| import 'utils.dart'; | |
| /// Generates the key mapping for GTK, based on the information in the key | |
| /// data structure given to it. | |
| class GtkCodeGenerator extends PlatformCodeGenerator { | |
| GtkCodeGenerator( | |
| super.keyData, | |
| super.logicalData, | |
| String modifierBitMapping, | |
| String lockBitMapping, | |
| this._layoutGoals, | |
| ) : _modifierBitMapping = parseMapOfListOfString(modifierBitMapping), | |
| _lockBitMapping = parseMapOfListOfString(lockBitMapping); | |
| /// This generates the map of XKB scan codes to Flutter physical keys. | |
| String get _xkbScanCodeMap { | |
| final lines = OutputLines<int>('GTK scancode map'); | |
| for (final PhysicalKeyEntry entry in keyData.entries) { | |
| if (entry.xKbScanCode != null) { | |
| lines.add( | |
| entry.xKbScanCode!, | |
| ' {${toHex(entry.xKbScanCode)}, ${toHex(entry.usbHidCode)}}, // ${entry.constantName}', | |
| ); | |
| } | |
| } | |
| return lines.sortedJoin().trimRight(); | |
| } | |
| /// This generates the map of GTK keyval codes to Flutter logical keys. | |
| String get _gtkKeyvalCodeMap { | |
| final lines = OutputLines<int>('GTK keyval map'); | |
| for (final LogicalKeyEntry entry in logicalData.entries) { | |
| zipStrict(entry.gtkValues, entry.gtkNames, (int value, String name) { | |
| lines.add(value, ' {${toHex(value)}, ${toHex(entry.value, digits: 11)}}, // $name'); | |
| }); | |
| } | |
| return lines.sortedJoin().trimRight(); | |
| } | |
| static String constructMapFromModToKeys( | |
| Map<String, List<String>> source, | |
| PhysicalKeyData physicalData, | |
| LogicalKeyData logicalData, | |
| String debugFunctionName, | |
| ) { | |
| final result = StringBuffer(); | |
| source.forEach((String modifierBitName, List<String> keyNames) { | |
| assert(keyNames.length == 2 || keyNames.length == 3); | |
| final String primaryPhysicalName = keyNames[0]; | |
| final String primaryLogicalName = keyNames[1]; | |
| final String? secondaryLogicalName = keyNames.length == 3 ? keyNames[2] : null; | |
| final PhysicalKeyEntry primaryPhysical = physicalData.entryByName(primaryPhysicalName); | |
| final LogicalKeyEntry primaryLogical = logicalData.entryByName(primaryLogicalName); | |
| final LogicalKeyEntry? secondaryLogical = secondaryLogicalName == null | |
| ? null | |
| : logicalData.entryByName(secondaryLogicalName); | |
| if (secondaryLogical == null && secondaryLogicalName != null) { | |
| print( | |
| 'Unrecognized secondary logical key $secondaryLogicalName specified for $debugFunctionName.', | |
| ); | |
| return; | |
| } | |
| final pad = secondaryLogical == null ? '' : ' '; | |
| result.writeln( | |
| ''' | |
| data = g_new(FlKeyEmbedderCheckedKey, 1); | |
| g_hash_table_insert(table, GUINT_TO_POINTER(GDK_${modifierBitName}_MASK), data); | |
| data->is_caps_lock = ${primaryPhysicalName == 'CapsLock' ? 'true' : 'false'}; | |
| data->primary_physical_key = ${toHex(primaryPhysical.usbHidCode, digits: 9)};$pad // ${primaryPhysical.constantName} | |
| data->primary_logical_key = ${toHex(primaryLogical.value, digits: 11)};$pad // ${primaryLogical.constantName}''', | |
| ); | |
| if (secondaryLogical != null) { | |
| result.writeln( | |
| ''' | |
| data->secondary_logical_key = ${toHex(secondaryLogical.value, digits: 11)}; // ${secondaryLogical.constantName}''', | |
| ); | |
| } | |
| }); | |
| return result.toString().trimRight(); | |
| } | |
| String get _gtkModifierBitMap { | |
| return constructMapFromModToKeys( | |
| _modifierBitMapping, | |
| keyData, | |
| logicalData, | |
| 'gtkModifierBitMap', | |
| ); | |
| } | |
| final Map<String, List<String>> _modifierBitMapping; | |
| String get _gtkModeBitMap { | |
| return constructMapFromModToKeys(_lockBitMapping, keyData, logicalData, 'gtkModeBitMap'); | |
| } | |
| final Map<String, List<String>> _lockBitMapping; | |
| final Map<String, bool> _layoutGoals; | |
| String get _layoutGoalsString { | |
| final lines = OutputLines<int>('GTK layout goals'); | |
| _layoutGoals.forEach((String name, bool mandatory) { | |
| final PhysicalKeyEntry physicalEntry = keyData.entryByName(name); | |
| final LogicalKeyEntry logicalEntry = logicalData.entryByName(name); | |
| final line = | |
| 'LayoutGoal{' | |
| '${toHex(physicalEntry.xKbScanCode, digits: 2)}, ' | |
| '${toHex(logicalEntry.value, digits: 2)}, ' | |
| '${mandatory ? 'true' : 'false'}' | |
| '},'; | |
| lines.add( | |
| logicalEntry.value, | |
| ' ${line.padRight(39)}' | |
| '// ${logicalEntry.name}', | |
| ); | |
| }); | |
| return lines.sortedJoin().trimRight(); | |
| } | |
| /// This generates the mask values for the part of a key code that defines its plane. | |
| String get _maskConstants { | |
| final buffer = StringBuffer(); | |
| const maskConstants = <MaskConstant>[kValueMask, kUnicodePlane, kGtkPlane]; | |
| for (final constant in maskConstants) { | |
| buffer.writeln( | |
| 'const uint64_t k${constant.upperCamelName} = ${toHex(constant.value, digits: 11)};', | |
| ); | |
| } | |
| return buffer.toString().trimRight(); | |
| } | |
| @override | |
| String get templatePath => path.join(dataRoot, 'gtk_key_mapping_cc.tmpl'); | |
| @override | |
| String outputPath(String platform) => path.join( | |
| PlatformCodeGenerator.engineRoot, | |
| 'shell', | |
| 'platform', | |
| 'linux', | |
| 'fl_key_mapping.g.cc', | |
| ); | |
| @override | |
| Map<String, String> mappings() { | |
| return <String, String>{ | |
| 'XKB_SCAN_CODE_MAP': _xkbScanCodeMap, | |
| 'GTK_KEYVAL_CODE_MAP': _gtkKeyvalCodeMap, | |
| 'GTK_MODIFIER_BIT_MAP': _gtkModifierBitMap, | |
| 'GTK_MODE_BIT_MAP': _gtkModeBitMap, | |
| 'MASK_CONSTANTS': _maskConstants, | |
| 'LAYOUT_GOALS': _layoutGoalsString, | |
| }; | |
| } | |
| } |