mirror of
https://github.com/jonasbark/swiftcontrol.git
synced 2026-02-18 00:17:40 +01:00
Merge branch 'main' of github.com:jonasbark/swiftcontrol
This commit is contained in:
1
_codeql_detected_source_root
Symbolic link
1
_codeql_detected_source_root
Symbolic link
@@ -0,0 +1 @@
|
||||
.
|
||||
@@ -88,6 +88,38 @@ void KeypressSimulatorWindowsPlugin::SimulateKeyPress(
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to send modifier key events
|
||||
auto sendModifierKey = [](UINT vkCode, bool down) {
|
||||
WORD sc = (WORD)MapVirtualKey(vkCode, MAPVK_VK_TO_VSC);
|
||||
INPUT in = {0};
|
||||
in.type = INPUT_KEYBOARD;
|
||||
in.ki.wVk = 0;
|
||||
in.ki.wScan = sc;
|
||||
in.ki.dwFlags = KEYEVENTF_SCANCODE | (down ? 0 : KEYEVENTF_KEYUP);
|
||||
SendInput(1, &in, sizeof(INPUT));
|
||||
};
|
||||
|
||||
// Helper function to process modifiers
|
||||
auto processModifiers = [&sendModifierKey](const std::vector<std::string>& mods, bool down) {
|
||||
for (const std::string& modifier : mods) {
|
||||
if (modifier == "shiftModifier") {
|
||||
sendModifierKey(VK_SHIFT, down);
|
||||
} else if (modifier == "controlModifier") {
|
||||
sendModifierKey(VK_CONTROL, down);
|
||||
} else if (modifier == "altModifier") {
|
||||
sendModifierKey(VK_MENU, down);
|
||||
} else if (modifier == "metaModifier") {
|
||||
sendModifierKey(VK_LWIN, down);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Press modifier keys first (if keyDown)
|
||||
if (keyDown) {
|
||||
processModifiers(modifiers, true);
|
||||
}
|
||||
|
||||
// Send the main key
|
||||
WORD sc = (WORD)MapVirtualKey(keyCode, MAPVK_VK_TO_VSC);
|
||||
|
||||
INPUT in = {0};
|
||||
@@ -102,6 +134,11 @@ void KeypressSimulatorWindowsPlugin::SimulateKeyPress(
|
||||
}
|
||||
SendInput(1, &in, sizeof(INPUT));
|
||||
|
||||
// Release modifier keys (if keyUp)
|
||||
if (!keyDown) {
|
||||
processModifiers(modifiers, false);
|
||||
}
|
||||
|
||||
/*BYTE byteValue = static_cast<BYTE>(keyCode);
|
||||
keybd_event(byteValue, 0x45, keyDown ? 0 : KEYEVENTF_KEYUP, 0);*/
|
||||
|
||||
|
||||
@@ -40,11 +40,11 @@ class EliteSquare extends BluetoothDevice {
|
||||
actionStreamInternal.add(LogNotification('Received $fullValue - vs $currentValue (last: $_lastValue)'));
|
||||
|
||||
if (_lastValue != null) {
|
||||
final currentRelevantPart = fullValue.length >= 19
|
||||
? fullValue.substring(6, fullValue.length - 13)
|
||||
final currentRelevantPart = fullValue.length >= 14
|
||||
? fullValue.substring(6, 14)
|
||||
: fullValue.substring(6);
|
||||
final lastRelevantPart = _lastValue!.length >= 19
|
||||
? _lastValue!.substring(6, _lastValue!.length - 13)
|
||||
final lastRelevantPart = _lastValue!.length >= 14
|
||||
? _lastValue!.substring(6, 14)
|
||||
: _lastValue!.substring(6);
|
||||
|
||||
if (currentRelevantPart != lastRelevantPart) {
|
||||
|
||||
@@ -30,14 +30,14 @@ class DesktopActions extends BaseActions {
|
||||
return zwiftEmulator.sendAction(keyPair.inGameAction!, keyPair.inGameActionValue);
|
||||
} else if (keyPair.physicalKey != null) {
|
||||
if (isKeyDown && isKeyUp) {
|
||||
await keyPressSimulator.simulateKeyDown(keyPair.physicalKey);
|
||||
await keyPressSimulator.simulateKeyUp(keyPair.physicalKey);
|
||||
await keyPressSimulator.simulateKeyDown(keyPair.physicalKey, keyPair.modifiers);
|
||||
await keyPressSimulator.simulateKeyUp(keyPair.physicalKey, keyPair.modifiers);
|
||||
return 'Key clicked: $keyPair';
|
||||
} else if (isKeyDown) {
|
||||
await keyPressSimulator.simulateKeyDown(keyPair.physicalKey);
|
||||
await keyPressSimulator.simulateKeyDown(keyPair.physicalKey, keyPair.modifiers);
|
||||
return 'Key pressed: $keyPair';
|
||||
} else {
|
||||
await keyPressSimulator.simulateKeyUp(keyPair.physicalKey);
|
||||
await keyPressSimulator.simulateKeyUp(keyPair.physicalKey, keyPair.modifiers);
|
||||
return 'Key released: $keyPair';
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -52,6 +52,7 @@ class CustomApp extends SupportedApp {
|
||||
ControllerButton zwiftButton, {
|
||||
required PhysicalKeyboardKey? physicalKey,
|
||||
required LogicalKeyboardKey? logicalKey,
|
||||
List<ModifierKey> modifiers = const [],
|
||||
bool isLongPress = false,
|
||||
Offset? touchPosition,
|
||||
InGameAction? inGameAction,
|
||||
@@ -62,6 +63,7 @@ class CustomApp extends SupportedApp {
|
||||
if (keyPair != null) {
|
||||
keyPair.physicalKey = physicalKey;
|
||||
keyPair.logicalKey = logicalKey;
|
||||
keyPair.modifiers = modifiers;
|
||||
keyPair.isLongPress = isLongPress;
|
||||
keyPair.touchPosition = touchPosition ?? Offset.zero;
|
||||
keyPair.inGameAction = inGameAction;
|
||||
@@ -72,6 +74,7 @@ class CustomApp extends SupportedApp {
|
||||
buttons: [zwiftButton],
|
||||
physicalKey: physicalKey,
|
||||
logicalKey: logicalKey,
|
||||
modifiers: modifiers,
|
||||
isLongPress: isLongPress,
|
||||
touchPosition: touchPosition ?? Offset.zero,
|
||||
inGameAction: inGameAction,
|
||||
|
||||
@@ -66,6 +66,7 @@ class KeyPair {
|
||||
final List<ControllerButton> buttons;
|
||||
PhysicalKeyboardKey? physicalKey;
|
||||
LogicalKeyboardKey? logicalKey;
|
||||
List<ModifierKey> modifiers;
|
||||
Offset touchPosition;
|
||||
bool isLongPress;
|
||||
InGameAction? inGameAction;
|
||||
@@ -75,6 +76,7 @@ class KeyPair {
|
||||
required this.buttons,
|
||||
required this.physicalKey,
|
||||
required this.logicalKey,
|
||||
this.modifiers = const [],
|
||||
this.touchPosition = Offset.zero,
|
||||
this.isLongPress = false,
|
||||
this.inGameAction,
|
||||
@@ -109,7 +111,7 @@ class KeyPair {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return logicalKey?.keyLabel ??
|
||||
final baseKey = logicalKey?.keyLabel ??
|
||||
switch (physicalKey) {
|
||||
PhysicalKeyboardKey.mediaPlayPause => 'Play/Pause',
|
||||
PhysicalKeyboardKey.mediaTrackNext => 'Next Track',
|
||||
@@ -119,6 +121,24 @@ class KeyPair {
|
||||
PhysicalKeyboardKey.audioVolumeDown => 'Volume Down',
|
||||
_ => 'Not assigned',
|
||||
};
|
||||
|
||||
if (modifiers.isEmpty || baseKey == 'Not assigned') {
|
||||
return baseKey;
|
||||
}
|
||||
|
||||
// Format modifiers + key (e.g., "Ctrl+Alt+R")
|
||||
final modifierStrings = modifiers.map((m) {
|
||||
return switch (m) {
|
||||
ModifierKey.shiftModifier => 'Shift',
|
||||
ModifierKey.controlModifier => 'Ctrl',
|
||||
ModifierKey.altModifier => 'Alt',
|
||||
ModifierKey.metaModifier => 'Meta',
|
||||
ModifierKey.functionModifier => 'Fn',
|
||||
_ => m.name,
|
||||
};
|
||||
}).toList();
|
||||
|
||||
return '${modifierStrings.join('+')}+$baseKey';
|
||||
}
|
||||
|
||||
String encode() {
|
||||
@@ -128,6 +148,7 @@ class KeyPair {
|
||||
'actions': buttons.map((e) => e.name).toList(),
|
||||
if (logicalKey != null) 'logicalKey': logicalKey?.keyId.toString(),
|
||||
if (physicalKey != null) 'physicalKey': physicalKey?.usbHidUsage.toString() ?? '0',
|
||||
if (modifiers.isNotEmpty) 'modifiers': modifiers.map((e) => e.name).toList(),
|
||||
if (touchPosition != Offset.zero) 'touchPosition': {'x': touchPosition.dx, 'y': touchPosition.dy},
|
||||
'isLongPress': isLongPress,
|
||||
'inGameAction': inGameAction?.name,
|
||||
@@ -156,6 +177,15 @@ class KeyPair {
|
||||
if (buttons.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Decode modifiers if present
|
||||
final List<ModifierKey> modifiers = decoded.containsKey('modifiers')
|
||||
? (decoded['modifiers'] as List)
|
||||
.map<ModifierKey?>((e) => ModifierKey.values.firstOrNullWhere((element) => element.name == e))
|
||||
.whereType<ModifierKey>()
|
||||
.toList()
|
||||
: [];
|
||||
|
||||
return KeyPair(
|
||||
buttons: buttons,
|
||||
logicalKey: decoded.containsKey('logicalKey') && int.parse(decoded['logicalKey']) != 0
|
||||
@@ -164,6 +194,7 @@ class KeyPair {
|
||||
physicalKey: decoded.containsKey('physicalKey') && int.parse(decoded['physicalKey']) != 0
|
||||
? PhysicalKeyboardKey(int.parse(decoded['physicalKey']))
|
||||
: null,
|
||||
modifiers: modifiers,
|
||||
touchPosition: touchPosition,
|
||||
isLongPress: decoded['isLongPress'] ?? false,
|
||||
inGameAction: decoded.containsKey('inGameAction')
|
||||
|
||||
@@ -25,6 +25,7 @@ class _HotKeyListenerState extends State<HotKeyListenerDialog> {
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
KeyDownEvent? _pressedKey;
|
||||
ControllerButton? _pressedButton;
|
||||
final Set<ModifierKey> _activeModifiers = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -52,20 +53,84 @@ class _HotKeyListenerState extends State<HotKeyListenerDialog> {
|
||||
|
||||
void _onKey(KeyEvent event) {
|
||||
setState(() {
|
||||
// Track modifier keys
|
||||
if (event is KeyDownEvent) {
|
||||
_pressedKey = event;
|
||||
widget.customApp.setKey(
|
||||
_pressedButton!,
|
||||
physicalKey: _pressedKey!.physicalKey,
|
||||
logicalKey: _pressedKey!.logicalKey,
|
||||
touchPosition: widget.keyPair?.touchPosition,
|
||||
);
|
||||
final wasModifier = _updateModifierState(event.logicalKey, add: true);
|
||||
// Regular key pressed - record it along with active modifiers
|
||||
if (!wasModifier) {
|
||||
_pressedKey = event;
|
||||
widget.customApp.setKey(
|
||||
_pressedButton!,
|
||||
physicalKey: _pressedKey!.physicalKey,
|
||||
logicalKey: _pressedKey!.logicalKey,
|
||||
modifiers: _activeModifiers.toList(),
|
||||
touchPosition: widget.keyPair?.touchPosition,
|
||||
);
|
||||
}
|
||||
} else if (event is KeyUpEvent) {
|
||||
// Clear modifier when released
|
||||
_updateModifierState(event.logicalKey, add: false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool _updateModifierState(LogicalKeyboardKey key, {required bool add}) {
|
||||
ModifierKey? modifier;
|
||||
|
||||
if (key == LogicalKeyboardKey.shift ||
|
||||
key == LogicalKeyboardKey.shiftLeft ||
|
||||
key == LogicalKeyboardKey.shiftRight) {
|
||||
modifier = ModifierKey.shiftModifier;
|
||||
} else if (key == LogicalKeyboardKey.control ||
|
||||
key == LogicalKeyboardKey.controlLeft ||
|
||||
key == LogicalKeyboardKey.controlRight) {
|
||||
modifier = ModifierKey.controlModifier;
|
||||
} else if (key == LogicalKeyboardKey.alt ||
|
||||
key == LogicalKeyboardKey.altLeft ||
|
||||
key == LogicalKeyboardKey.altRight) {
|
||||
modifier = ModifierKey.altModifier;
|
||||
} else if (key == LogicalKeyboardKey.meta ||
|
||||
key == LogicalKeyboardKey.metaLeft ||
|
||||
key == LogicalKeyboardKey.metaRight) {
|
||||
modifier = ModifierKey.metaModifier;
|
||||
} else if (key == LogicalKeyboardKey.fn) {
|
||||
modifier = ModifierKey.functionModifier;
|
||||
}
|
||||
|
||||
if (modifier != null) {
|
||||
if (add) {
|
||||
_activeModifiers.add(modifier);
|
||||
} else {
|
||||
_activeModifiers.remove(modifier);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String _formatModifierName(ModifierKey m) {
|
||||
return switch (m) {
|
||||
ModifierKey.shiftModifier => 'Shift',
|
||||
ModifierKey.controlModifier => 'Ctrl',
|
||||
ModifierKey.altModifier => 'Alt',
|
||||
ModifierKey.metaModifier => 'Meta',
|
||||
ModifierKey.functionModifier => 'Fn',
|
||||
_ => m.name,
|
||||
};
|
||||
}
|
||||
|
||||
String _formatKey(KeyDownEvent? key) {
|
||||
return key?.logicalKey.keyLabel ?? 'Waiting...';
|
||||
if (key == null) {
|
||||
return _activeModifiers.isEmpty ? 'Waiting...' : '${_activeModifiers.map(_formatModifierName).join('+')}+...';
|
||||
}
|
||||
|
||||
if (_activeModifiers.isEmpty) {
|
||||
return key.logicalKey.keyLabel;
|
||||
}
|
||||
|
||||
final modifierStrings = _activeModifiers.map(_formatModifierName);
|
||||
|
||||
return '${modifierStrings.join('+')}+${key.logicalKey.keyLabel}';
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
132
test/elite_square_test.dart
Normal file
132
test/elite_square_test.dart
Normal file
@@ -0,0 +1,132 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
// Helper function matching the Elite Square implementation
|
||||
// Extracts the 8-character button code from positions 6-14 of the hex string
|
||||
String extractButtonCode(String hexValue) {
|
||||
if (hexValue.length >= 14) {
|
||||
return hexValue.substring(6, 14);
|
||||
}
|
||||
return hexValue.substring(6);
|
||||
}
|
||||
|
||||
String bytesToHex(List<int> bytes) {
|
||||
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Elite Square Button Detection Tests', () {
|
||||
test('Should extract correct button code from hex string', () {
|
||||
// Test with actual dump data
|
||||
expect(extractButtonCode('030153000000020318f40101'), equals('00000002'));
|
||||
expect(extractButtonCode('030153000000010318f40101'), equals('00000001'));
|
||||
expect(extractButtonCode('030153000004000318f40101'), equals('00000400'));
|
||||
expect(extractButtonCode('030153000001000318f40101'), equals('00000100'));
|
||||
expect(extractButtonCode('030153000008000318f40101'), equals('00000800'));
|
||||
expect(extractButtonCode('030153000002000318f40101'), equals('00000200'));
|
||||
expect(extractButtonCode('030153000000000318f40101'), equals('00000000'));
|
||||
});
|
||||
|
||||
test('Should detect button changes correctly', () {
|
||||
// Test that button code extraction is consistent for comparison
|
||||
final idleState = '030153000000000318f40101';
|
||||
final buttonPressed = '030153000000020318f40101';
|
||||
|
||||
final idleCode = extractButtonCode(idleState);
|
||||
final pressedCode = extractButtonCode(buttonPressed);
|
||||
|
||||
expect(idleCode, equals('00000000'));
|
||||
expect(pressedCode, equals('00000002'));
|
||||
expect(idleCode != pressedCode, isTrue);
|
||||
});
|
||||
|
||||
test('Should handle button release correctly', () {
|
||||
// Simulate button press and release
|
||||
final states = [
|
||||
'030153000000000318f40101', // idle
|
||||
'030153000000020318f40101', // button pressed
|
||||
'030153000000000318f40101', // button released (back to idle)
|
||||
];
|
||||
|
||||
final parts = states.map(extractButtonCode).toList();
|
||||
|
||||
expect(parts[0], equals('00000000')); // idle
|
||||
expect(parts[1], equals('00000002')); // pressed
|
||||
expect(parts[2], equals('00000000')); // released
|
||||
|
||||
// Verify state transitions
|
||||
expect(parts[0] != parts[1], isTrue); // idle -> pressed
|
||||
expect(parts[1] != parts[2], isTrue); // pressed -> released
|
||||
expect(parts[0] == parts[2], isTrue); // back to idle
|
||||
});
|
||||
|
||||
test('Should handle all button codes from mapping', () {
|
||||
// Test all button codes from the mapping
|
||||
final buttonCodes = {
|
||||
"00000200": "up",
|
||||
"00000100": "left",
|
||||
"00000800": "down",
|
||||
"00000400": "right",
|
||||
"00002000": "x",
|
||||
"00001000": "square",
|
||||
"00008000": "campagnoloLeft",
|
||||
"00004000": "leftBrake",
|
||||
"00000002": "leftShift1",
|
||||
"00000001": "leftShift2",
|
||||
"02000000": "y",
|
||||
"01000000": "a",
|
||||
"08000000": "b",
|
||||
"04000000": "z",
|
||||
"20000000": "circle",
|
||||
"10000000": "triangle",
|
||||
"80000000": "campagnoloRight",
|
||||
"40000000": "rightBrake",
|
||||
"00020000": "rightShift1",
|
||||
"00010000": "rightShift2",
|
||||
};
|
||||
|
||||
// Verify all button codes are 8 characters
|
||||
for (final code in buttonCodes.keys) {
|
||||
expect(code.length, equals(8), reason: 'Button code $code should be 8 characters');
|
||||
}
|
||||
});
|
||||
|
||||
test('Should convert bytes to hex correctly', () {
|
||||
// Test with sample data from the dump
|
||||
// 030153000000020318f40101 = [0x03, 0x01, 0x53, 0x00, 0x00, 0x00, 0x02, 0x03, 0x18, 0xf4, 0x01, 0x01]
|
||||
final bytes = [0x03, 0x01, 0x53, 0x00, 0x00, 0x00, 0x02, 0x03, 0x18, 0xf4, 0x01, 0x01];
|
||||
final hex = bytesToHex(bytes);
|
||||
|
||||
expect(hex, equals('030153000000020318f40101'));
|
||||
});
|
||||
|
||||
test('Should handle edge cases', () {
|
||||
// Test with short strings
|
||||
expect(extractButtonCode('0123456789'), equals('6789'));
|
||||
expect(extractButtonCode('01234567'), equals('67'));
|
||||
|
||||
// Test with exact length (14 chars extracts positions 6-14)
|
||||
expect(extractButtonCode('01234567890123'), equals('67890123'));
|
||||
|
||||
// Test strings shorter than 14 extract from position 6 to end
|
||||
expect(extractButtonCode('0123456789ABC'), equals('6789ABC'));
|
||||
});
|
||||
});
|
||||
|
||||
group('Elite Square Protocol Tests', () {
|
||||
test('Should recognize button press pattern', () {
|
||||
// According to the dump, the pattern is:
|
||||
// Base: 030153000000000318f40101
|
||||
// Byte positions 6-13 (8 chars) change to indicate button
|
||||
|
||||
final baseHex = '030153000000000318f40101';
|
||||
final buttonHex = '030153000000020318f40101';
|
||||
|
||||
// Extract the button part (positions 6-14)
|
||||
final baseButton = baseHex.substring(6, 14);
|
||||
final pressedButton = buttonHex.substring(6, 14);
|
||||
|
||||
expect(baseButton, equals('00000000'));
|
||||
expect(pressedButton, equals('00000002'));
|
||||
});
|
||||
});
|
||||
}
|
||||
142
test/modifier_keys_test.dart
Normal file
142
test/modifier_keys_test.dart
Normal file
@@ -0,0 +1,142 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:swift_control/bluetooth/devices/zwift/constants.dart';
|
||||
import 'package:swift_control/utils/keymap/keymap.dart';
|
||||
|
||||
void main() {
|
||||
group('Modifier Keys KeyPair Tests', () {
|
||||
test('KeyPair should encode and decode modifiers property', () {
|
||||
// Create a KeyPair with modifiers
|
||||
final keyPair = KeyPair(
|
||||
buttons: [ZwiftButtons.a],
|
||||
physicalKey: PhysicalKeyboardKey.keyR,
|
||||
logicalKey: LogicalKeyboardKey.keyR,
|
||||
modifiers: [ModifierKey.controlModifier, ModifierKey.altModifier],
|
||||
);
|
||||
|
||||
// Encode the KeyPair
|
||||
final encoded = keyPair.encode();
|
||||
|
||||
// Decode the KeyPair
|
||||
final decoded = KeyPair.decode(encoded);
|
||||
|
||||
// Verify the decoded KeyPair has the correct properties
|
||||
expect(decoded, isNotNull);
|
||||
expect(decoded!.modifiers.length, 2);
|
||||
expect(decoded.modifiers, contains(ModifierKey.controlModifier));
|
||||
expect(decoded.modifiers, contains(ModifierKey.altModifier));
|
||||
expect(decoded.buttons, equals([ZwiftButtons.a]));
|
||||
expect(decoded.physicalKey, equals(PhysicalKeyboardKey.keyR));
|
||||
expect(decoded.logicalKey, equals(LogicalKeyboardKey.keyR));
|
||||
});
|
||||
|
||||
test('KeyPair should default modifiers to empty list when not specified in decode', () {
|
||||
// Create a legacy encoded KeyPair without modifiers property
|
||||
const legacyEncoded = '''
|
||||
{
|
||||
"actions": ["a"],
|
||||
"logicalKey": "97",
|
||||
"physicalKey": "458752",
|
||||
"touchPosition": {"x": 0.0, "y": 0.0},
|
||||
"isLongPress": false
|
||||
}
|
||||
''';
|
||||
|
||||
// Decode the legacy KeyPair
|
||||
final decoded = KeyPair.decode(legacyEncoded);
|
||||
|
||||
// Verify the decoded KeyPair defaults modifiers to empty
|
||||
expect(decoded, isNotNull);
|
||||
expect(decoded!.modifiers, isEmpty);
|
||||
});
|
||||
|
||||
test('KeyPair constructor should default modifiers to empty list', () {
|
||||
final keyPair = KeyPair(
|
||||
buttons: [ZwiftButtons.a],
|
||||
physicalKey: PhysicalKeyboardKey.keyA,
|
||||
logicalKey: LogicalKeyboardKey.keyA,
|
||||
);
|
||||
|
||||
expect(keyPair.modifiers, isEmpty);
|
||||
});
|
||||
|
||||
test('KeyPair should correctly encode empty modifiers', () {
|
||||
final keyPair = KeyPair(
|
||||
buttons: [ZwiftButtons.a],
|
||||
physicalKey: PhysicalKeyboardKey.keyA,
|
||||
logicalKey: LogicalKeyboardKey.keyA,
|
||||
modifiers: [],
|
||||
);
|
||||
|
||||
final encoded = keyPair.encode();
|
||||
final decoded = KeyPair.decode(encoded);
|
||||
|
||||
expect(decoded, isNotNull);
|
||||
expect(decoded!.modifiers, isEmpty);
|
||||
});
|
||||
|
||||
test('KeyPair toString should format modifiers correctly', () {
|
||||
final keyPairWithCtrlAlt = KeyPair(
|
||||
buttons: [ZwiftButtons.a],
|
||||
physicalKey: PhysicalKeyboardKey.keyR,
|
||||
logicalKey: LogicalKeyboardKey.keyR,
|
||||
modifiers: [ModifierKey.controlModifier, ModifierKey.altModifier],
|
||||
);
|
||||
|
||||
final result = keyPairWithCtrlAlt.toString();
|
||||
expect(result, contains('Ctrl'));
|
||||
expect(result, contains('Alt'));
|
||||
expect(result, contains('R'));
|
||||
});
|
||||
|
||||
test('KeyPair toString should handle single modifier', () {
|
||||
final keyPairWithShift = KeyPair(
|
||||
buttons: [ZwiftButtons.a],
|
||||
physicalKey: PhysicalKeyboardKey.keyA,
|
||||
logicalKey: LogicalKeyboardKey.keyA,
|
||||
modifiers: [ModifierKey.shiftModifier],
|
||||
);
|
||||
|
||||
final result = keyPairWithShift.toString();
|
||||
expect(result, contains('Shift'));
|
||||
expect(result, contains('A'));
|
||||
});
|
||||
|
||||
test('KeyPair toString should handle no modifiers', () {
|
||||
final keyPairNoModifier = KeyPair(
|
||||
buttons: [ZwiftButtons.a],
|
||||
physicalKey: PhysicalKeyboardKey.keyA,
|
||||
logicalKey: LogicalKeyboardKey.keyA,
|
||||
modifiers: [],
|
||||
);
|
||||
|
||||
final result = keyPairNoModifier.toString();
|
||||
expect(result, equals('A'));
|
||||
expect(result, isNot(contains('+')));
|
||||
});
|
||||
|
||||
test('KeyPair should encode and decode all modifier types', () {
|
||||
final keyPair = KeyPair(
|
||||
buttons: [ZwiftButtons.a],
|
||||
physicalKey: PhysicalKeyboardKey.keyF,
|
||||
logicalKey: LogicalKeyboardKey.keyF,
|
||||
modifiers: [
|
||||
ModifierKey.shiftModifier,
|
||||
ModifierKey.controlModifier,
|
||||
ModifierKey.altModifier,
|
||||
ModifierKey.metaModifier,
|
||||
],
|
||||
);
|
||||
|
||||
final encoded = keyPair.encode();
|
||||
final decoded = KeyPair.decode(encoded);
|
||||
|
||||
expect(decoded, isNotNull);
|
||||
expect(decoded!.modifiers.length, 4);
|
||||
expect(decoded.modifiers, contains(ModifierKey.shiftModifier));
|
||||
expect(decoded.modifiers, contains(ModifierKey.controlModifier));
|
||||
expect(decoded.modifiers, contains(ModifierKey.altModifier));
|
||||
expect(decoded.modifiers, contains(ModifierKey.metaModifier));
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user