fix(ui,voice): display 'Space' (not blank) in bind dialog; cut PTT poll latency
Issue 1: tapping Space in the PTT binding capture dialog set _captured to LogicalKeyboardKey.space.keyLabel which is ' ' (a single space character), rendering as a blank string in the 'Captured: ' display. Same problem for Enter, Tab, Backspace, etc. — Flutter's keyLabel returns the printable representation, not a readable name. Added _displayLabelForKey() with a table that maps whitespace and common special keys to canonical English labels matching the entries in crates/chanora_audio/src/ptt_backends/windows_keymap.rs (so the bridge resolves to the right VK_* on Windows). Pure modifier keys (shift / ctrl / alt / meta / caps / num / scroll lock) return null so they don't accidentally bind on their own. Issue 2: physical key press -> 'PTT=on' in the Voice Bar lagged by up to ~500 ms because audioStats was polled at 500 ms intervals. The Rust-side transition is microsecond-fast; the visible delay is purely the Flutter poll interval. Cut to 80 ms (~12 Hz), well below the perceptual lag threshold. Adds ~12 small FFI calls per second, trivially cheap. A push-based BridgeEvent::TransmitActiveChanged would let us drop the poll entirely; noted as a follow-up. flutter analyze: clean (6 pre-existing Radio.groupValue infos).
This commit is contained in:
@@ -246,7 +246,16 @@ class _BetaHomeState extends State<_BetaHome> {
|
||||
|
||||
void _ensureStatsTimer() {
|
||||
if (_statsTimer != null) return;
|
||||
_statsTimer = Timer.periodic(const Duration(milliseconds: 500), (_) async {
|
||||
// Poll at 80 ms (~12 Hz) — this is the loop that drives the
|
||||
// Voice Bar's "PTT=on/off" indicator and the level meter, so
|
||||
// it needs to be fast enough that users don't perceive a lag
|
||||
// between physically pressing the bound key and seeing the
|
||||
// UI change. 80 ms is well below the ~150 ms perceptual
|
||||
// delay threshold and adds only a dozen tiny FFI calls per
|
||||
// second to the load. A future push-based BridgeEvent for
|
||||
// transmit-active transitions would let us drop this poll
|
||||
// entirely.
|
||||
_statsTimer = Timer.periodic(const Duration(milliseconds: 80), (_) async {
|
||||
try {
|
||||
final s = await rust.audioStats();
|
||||
if (!mounted) return;
|
||||
@@ -1273,17 +1282,73 @@ class _PttBindingCaptureDialogState extends State<_PttBindingCaptureDialog> {
|
||||
|
||||
KeyEventResult _onKeyEvent(FocusNode node, KeyEvent event) {
|
||||
if (event is! KeyDownEvent) return KeyEventResult.ignored;
|
||||
// Skip modifier-only presses so the user can chord into the
|
||||
// real binding.
|
||||
final keyLabel = event.logicalKey.keyLabel;
|
||||
if (keyLabel.isEmpty) return KeyEventResult.ignored;
|
||||
final label = _displayLabelForKey(event.logicalKey);
|
||||
if (label == null) return KeyEventResult.ignored;
|
||||
setState(() {
|
||||
_captured = keyLabel;
|
||||
_captured = label;
|
||||
_capturedClass = rust.BridgePttInputClass.keyboard;
|
||||
});
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
/// Translate a [LogicalKeyboardKey] into the platform-neutral
|
||||
/// label string the bridge expects (matching the entries in
|
||||
/// `crates/chanora_audio/src/ptt_backends/windows_keymap.rs`).
|
||||
///
|
||||
/// `LogicalKeyboardKey.keyLabel` returns `" "` for Space, empty
|
||||
/// for pure modifiers (shift / ctrl / alt / meta), and localised
|
||||
/// strings for some special keys; we normalise to the canonical
|
||||
/// English label so the badge displays something readable AND so
|
||||
/// the Windows backend's keymap can resolve to a `VK_*`. Pure
|
||||
/// modifier keys are intentionally rejected — chording into the
|
||||
/// real binding (e.g. Ctrl+Shift+M) is supported by ignoring the
|
||||
/// individual modifier-down events.
|
||||
String? _displayLabelForKey(LogicalKeyboardKey k) {
|
||||
// Whitespace / common control keys whose keyLabel is unhelpful.
|
||||
if (k == LogicalKeyboardKey.space) return 'Space';
|
||||
if (k == LogicalKeyboardKey.enter || k == LogicalKeyboardKey.numpadEnter) {
|
||||
return 'Enter';
|
||||
}
|
||||
if (k == LogicalKeyboardKey.tab) return 'Tab';
|
||||
if (k == LogicalKeyboardKey.escape) return 'Escape';
|
||||
if (k == LogicalKeyboardKey.backspace) return 'Backspace';
|
||||
if (k == LogicalKeyboardKey.delete) return 'Delete';
|
||||
if (k == LogicalKeyboardKey.insert) return 'Insert';
|
||||
if (k == LogicalKeyboardKey.home) return 'Home';
|
||||
if (k == LogicalKeyboardKey.end) return 'End';
|
||||
if (k == LogicalKeyboardKey.pageUp) return 'Page Up';
|
||||
if (k == LogicalKeyboardKey.pageDown) return 'Page Down';
|
||||
if (k == LogicalKeyboardKey.arrowUp) return 'Arrow Up';
|
||||
if (k == LogicalKeyboardKey.arrowDown) return 'Arrow Down';
|
||||
if (k == LogicalKeyboardKey.arrowLeft) return 'Arrow Left';
|
||||
if (k == LogicalKeyboardKey.arrowRight) return 'Arrow Right';
|
||||
// Pure modifier keys are not bindable on their own (user can
|
||||
// still chord by pressing a non-modifier while holding them).
|
||||
if (k == LogicalKeyboardKey.shift ||
|
||||
k == LogicalKeyboardKey.shiftLeft ||
|
||||
k == LogicalKeyboardKey.shiftRight ||
|
||||
k == LogicalKeyboardKey.control ||
|
||||
k == LogicalKeyboardKey.controlLeft ||
|
||||
k == LogicalKeyboardKey.controlRight ||
|
||||
k == LogicalKeyboardKey.alt ||
|
||||
k == LogicalKeyboardKey.altLeft ||
|
||||
k == LogicalKeyboardKey.altRight ||
|
||||
k == LogicalKeyboardKey.meta ||
|
||||
k == LogicalKeyboardKey.metaLeft ||
|
||||
k == LogicalKeyboardKey.metaRight ||
|
||||
k == LogicalKeyboardKey.capsLock ||
|
||||
k == LogicalKeyboardKey.numLock ||
|
||||
k == LogicalKeyboardKey.scrollLock) {
|
||||
return null;
|
||||
}
|
||||
// Fall back to keyLabel for letters, digits, function keys,
|
||||
// numpad digits, and punctuation. Trim whitespace as a final
|
||||
// belt-and-braces guard.
|
||||
final fallback = k.keyLabel.trim();
|
||||
if (fallback.isEmpty) return null;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
void _captureMouseSideButton(int button) {
|
||||
setState(() {
|
||||
_captured = 'mouse-side-button:$button';
|
||||
|
||||
Reference in New Issue
Block a user