mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-26 23:35:46 +00:00
perf(keyboard): shortcuts, parse the config once per stored value
The Flutter matcher read the local config three times and parsed its JSON three times on every key press in legacy mode, on Web and in view-only. Keep one parsed snapshot keyed by the raw stored string, so a key press costs one config read and no parsing while the config is unchanged. The existing readers are thin views of the same snapshot. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ra4my61t8q1FN5n59wB16D
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
63654263ed
commit
8102f728f1
@@ -1070,11 +1070,11 @@ class InputModel {
|
||||
required bool altPressed,
|
||||
required bool shiftPressed,
|
||||
required bool commandPressed}) {
|
||||
if (isViewCamera ||
|
||||
!ShortcutModel.isEnabled() ||
|
||||
ShortcutModel.isPassThrough()) return null;
|
||||
if (isViewCamera) return null;
|
||||
final keyName = physicalKeyName(key);
|
||||
if (keyName == null) return null;
|
||||
final config = ShortcutModel.config();
|
||||
if (!config.enabled || config.passThrough) return null;
|
||||
final mods = <String>[];
|
||||
if (isMacOS || isIOS || isWebOnMacOs) {
|
||||
if (commandPressed) mods.add('primary');
|
||||
@@ -1084,7 +1084,7 @@ class InputModel {
|
||||
}
|
||||
if (altPressed) mods.add('alt');
|
||||
if (shiftPressed) mods.add('shift');
|
||||
for (final binding in ShortcutModel.readBindings()) {
|
||||
for (final binding in config.bindings) {
|
||||
final action = binding['action'];
|
||||
final key = binding['key'];
|
||||
final bindingMods =
|
||||
|
||||
@@ -99,39 +99,31 @@ class ShortcutModel {
|
||||
}));
|
||||
}
|
||||
|
||||
/// Read the bindings JSON from LocalConfig.
|
||||
static List<Map<String, dynamic>> readBindings() {
|
||||
static String _configRaw = '';
|
||||
static ShortcutConfig _config = ShortcutConfig.parse('');
|
||||
|
||||
/// The stored config, parsed once per distinct stored value. The Flutter
|
||||
/// matcher reads it on every key press, so a key press costs one config
|
||||
/// read and no JSON parsing while the config is unchanged.
|
||||
static ShortcutConfig config() {
|
||||
final raw = bind.mainGetLocalOption(key: kShortcutLocalConfigKey);
|
||||
if (raw.isEmpty) return [];
|
||||
try {
|
||||
final parsed = jsonDecode(raw) as Map<String, dynamic>;
|
||||
return shortcutBindingMapsFrom(parsed['bindings']);
|
||||
} catch (_) {
|
||||
return [];
|
||||
if (raw != _configRaw) {
|
||||
_config = ShortcutConfig.parse(raw);
|
||||
_configRaw = raw;
|
||||
}
|
||||
return _config;
|
||||
}
|
||||
|
||||
static bool isEnabled() {
|
||||
final raw = bind.mainGetLocalOption(key: kShortcutLocalConfigKey);
|
||||
if (raw.isEmpty) return false;
|
||||
try {
|
||||
final parsed = jsonDecode(raw) as Map<String, dynamic>;
|
||||
return parsed['enabled'] == true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/// Read the bindings JSON from LocalConfig. Returns a copy the caller may
|
||||
/// edit.
|
||||
static List<Map<String, dynamic>> readBindings() => [
|
||||
for (final binding in config().bindings)
|
||||
Map<String, dynamic>.of(binding)
|
||||
];
|
||||
|
||||
static bool isPassThrough() {
|
||||
final raw = bind.mainGetLocalOption(key: kShortcutLocalConfigKey);
|
||||
if (raw.isEmpty) return false;
|
||||
try {
|
||||
final parsed = jsonDecode(raw) as Map<String, dynamic>;
|
||||
return parsed['pass_through'] == true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
static bool isEnabled() => config().enabled;
|
||||
|
||||
static bool isPassThrough() => config().passThrough;
|
||||
|
||||
/// Persistent companion to [isEnabled]: when on, the matchers return early
|
||||
/// and every keystroke flows through to the remote (i.e. all bindings are
|
||||
@@ -223,6 +215,30 @@ class ShortcutModel {
|
||||
}
|
||||
}
|
||||
|
||||
/// One parsed view of the `keyboard-shortcuts` LocalConfig value. Malformed
|
||||
/// or missing input parses as disabled with no bindings.
|
||||
class ShortcutConfig {
|
||||
final bool enabled;
|
||||
final bool passThrough;
|
||||
final List<Map<String, dynamic>> bindings;
|
||||
|
||||
const ShortcutConfig._(this.enabled, this.passThrough, this.bindings);
|
||||
|
||||
static ShortcutConfig parse(String raw) {
|
||||
if (raw.isEmpty) return const ShortcutConfig._(false, false, []);
|
||||
try {
|
||||
final parsed = jsonDecode(raw) as Map<String, dynamic>;
|
||||
return ShortcutConfig._(
|
||||
parsed['enabled'] == true,
|
||||
parsed['pass_through'] == true,
|
||||
List.unmodifiable(shortcutBindingMapsFrom(parsed['bindings'])),
|
||||
);
|
||||
} catch (_) {
|
||||
return const ShortcutConfig._(false, false, []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Register the default-bound shortcut actions that aren't already wired by
|
||||
/// `toolbarControls(...)` (which handles things like Ctrl+Alt+Shift+Del and the
|
||||
/// screenshot action). Called once per session from the desktop / mobile
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_hbb/common/widgets/keyboard_shortcuts/shortcut_actions.dart';
|
||||
import 'package:flutter_hbb/common/widgets/keyboard_shortcuts/shortcut_constants.dart';
|
||||
import 'package:flutter_hbb/common/widgets/keyboard_shortcuts/shortcut_utils.dart';
|
||||
import 'package:flutter_hbb/models/shortcut_model.dart';
|
||||
|
||||
ShortcutPlatformCapabilities capabilities({
|
||||
bool includeFullscreenShortcut = true,
|
||||
@@ -463,6 +464,29 @@ void main() {
|
||||
isFalse);
|
||||
});
|
||||
|
||||
test('ShortcutConfig.parse reads the flags and bindings once', () {
|
||||
final config = ShortcutConfig.parse(jsonEncode({
|
||||
'enabled': true,
|
||||
'pass_through': false,
|
||||
'bindings': [
|
||||
{'action': 'screenshot', 'mods': ['primary'], 'key': 'p'},
|
||||
'not a binding',
|
||||
],
|
||||
}));
|
||||
expect(config.enabled, isTrue);
|
||||
expect(config.passThrough, isFalse);
|
||||
expect(config.bindings, [
|
||||
{'action': 'screenshot', 'mods': ['primary'], 'key': 'p'},
|
||||
]);
|
||||
|
||||
for (final raw in ['', 'not json', '[]', '{"bindings": "x"}']) {
|
||||
final broken = ShortcutConfig.parse(raw);
|
||||
expect(broken.enabled, isFalse, reason: raw);
|
||||
expect(broken.passThrough, isFalse, reason: raw);
|
||||
expect(broken.bindings, isEmpty, reason: raw);
|
||||
}
|
||||
});
|
||||
|
||||
test('non-US layouts record and match the physical key', () {
|
||||
// AZERTY: the key labelled "A" sits where US QWERTY has Q. The native
|
||||
// matcher only sees the physical position (USB HID usage), so the
|
||||
|
||||
Reference in New Issue
Block a user