feat(ptt): code-side initial split — transmit_active / capability badge / sanitizer
Implements the gen2 v0.9.3 doc baseline's first slice of code work:
* SRS-201: split the audio engine's `ptt` AtomicBool into the
authoritative `transmit_active` flag. The legacy `set_ptt` /
`ptt` accessors are retained as `#[doc(hidden)]` thin wrappers
so the existing bridge command and the existing Flutter
hold-to-talk UI keep compiling.
* SAD-075 / SDD-089 acknowledged at the type level: only
`AudioEngine::set_transmit_active` (or its legacy alias)
mutates the flag; the encoder feed reads it once per outbound
frame and never writes.
* SDD-082: new `chanora_audio::ptt` module ships the
`PttCapabilityLevel` enum (`L0Focused`, `L1GlobalShortcut`,
`L2GlobalHoldToTalk`, `L3GlobalWithMouseButtons`,
`L4DeviceAware` reserved) with a stable `as_str` mapping and
an `is_global` classifier.
* SDD-087: `PttBackendDescriptor::focused()` constant value for
the universal Focused-PTT fallback. The struct shape carries
only privacy-safe fields (`level`, `backend_id`,
`bound_input_class`) — a key code cannot fit through this
surface by construction (DEC-027).
* SAD-077 / SDD-090: `RedactingLogLayer` now hosts the
`PttBanCheckVisitor` and the `PTT_BANNED_FIELDS` constant
(`key_code`, `scan_code`, `virtual_key`, `vk`, `keysym`,
`keysym_string`, `key_sequence`, `key_press_history`,
`key_timing`). Any record whose field set names a banned key
is dropped before reaching the in-memory log sink or the
user-initiated diagnostic export. The check is structural and
runs ahead of formatting / redaction.
* `SessionEvent::PttCapability` carries the diagnostics-safe
descriptor through the broadcast event stream;
`chanora_core::ChanoraSession::start_audio` publishes the
Focused-PTT descriptor when the audio engine starts (SRS-196
/ SDD-091).
* `BridgeEvent::PttCapability` mirrors the event across the
FFI boundary. flutter_rust_bridge codegen regenerated.
* Flutter `_AudioControls` renders a capability badge above the
PTT button: a globe icon for Global levels, a focus-frame
icon for `L0Focused`, plus a Tooltip exposing the bound input
class. New ARB key `pttCapabilityBadge(level, backend)` in
`app_en.arb` and `app_zh.arb`.
Per-platform global PTT backends (`WindowsRawInputBackend`,
`MacOSEventTapBackend`, `LinuxGnomeWaylandBackend`) and the
`MissedKeyUpWatchdog` task land in a separate follow-up commit;
this milestone ships only PTT-L0 universally so the application's
runtime capability reporting is honest from day one.
Tests
-----
* `chanora_audio` rises from 1 to 4 unit tests covering
`PttCapabilityLevel::as_str`, `is_global`, and the
`PttBackendDescriptor::focused()` shape contract.
* `chanora_diagnostics` rises from 9 to 11 unit tests covering
the new `PttBanCheckVisitor` over every banned field name and
the `PTT_BANNED_FIELDS` stability assertion.
* Workspace total: 53 unit + integration tests, all green with
`CHANORA_DISABLE_KEYRING=1` (was 49 at v1.0.0-rc.2).
* `flutter analyze`: clean.
* `cargo deny check`: advisories ok, bans ok, licenses ok,
sources ok.
* `cargo about generate`: zero warnings (license inventory
regenerated).
* `tools/dump_flutter_licenses.sh`: 94 packages, zero without
LICENSE.
* Linux x86_64 release bundle builds clean.
No Android live verification in this commit per the user's note
that the test device was removed. Android arm64-v8a continues to
build via the same `cargo ndk` path; runtime reporting on Android
is `L0Focused` for the foreseeable future.
This commit is contained in:
@@ -107,6 +107,14 @@ class _BetaHomeState extends State<_BetaHome> {
|
||||
bool _outputMuted = false;
|
||||
double _outputGain = 1.0;
|
||||
|
||||
// Desktop PTT capability badge state (gen2 v0.9.3 / SDD-091).
|
||||
// Populated by `BridgeEvent.PttCapability`. Defaults match the
|
||||
// universal `FocusedPttBackend::focused()` descriptor so the UI
|
||||
// shows an honest baseline even before the first event arrives.
|
||||
String _pttLevel = 'L0Focused';
|
||||
String _pttBackendId = 'focused';
|
||||
String _pttBoundInputClass = 'keyboard';
|
||||
|
||||
List<rust.BridgeBookmark> _bookmarks = const [];
|
||||
|
||||
@override
|
||||
@@ -160,6 +168,16 @@ class _BetaHomeState extends State<_BetaHome> {
|
||||
setState(() => _audioStarted = false);
|
||||
case rust.BridgeEvent_SnapshotChanged():
|
||||
unawaited(_onRefresh());
|
||||
case rust.BridgeEvent_PttCapability(
|
||||
:final level,
|
||||
:final backendId,
|
||||
:final boundInputClass,
|
||||
):
|
||||
setState(() {
|
||||
_pttLevel = level;
|
||||
_pttBackendId = backendId;
|
||||
_pttBoundInputClass = boundInputClass;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,6 +662,9 @@ class _BetaHomeState extends State<_BetaHome> {
|
||||
inputMuted: _inputMuted,
|
||||
outputMuted: _outputMuted,
|
||||
outputGain: _outputGain,
|
||||
pttLevel: _pttLevel,
|
||||
pttBackendId: _pttBackendId,
|
||||
pttBoundInputClass: _pttBoundInputClass,
|
||||
onPttDown: () => _setPtt(true),
|
||||
onPttUp: () => _setPtt(false),
|
||||
onToggleInputMute: _toggleInputMute,
|
||||
@@ -798,6 +819,9 @@ class _AudioControls extends StatefulWidget {
|
||||
required this.inputMuted,
|
||||
required this.outputMuted,
|
||||
required this.outputGain,
|
||||
required this.pttLevel,
|
||||
required this.pttBackendId,
|
||||
required this.pttBoundInputClass,
|
||||
required this.onPttDown,
|
||||
required this.onPttUp,
|
||||
required this.onToggleInputMute,
|
||||
@@ -809,6 +833,9 @@ class _AudioControls extends StatefulWidget {
|
||||
final bool inputMuted;
|
||||
final bool outputMuted;
|
||||
final double outputGain;
|
||||
final String pttLevel;
|
||||
final String pttBackendId;
|
||||
final String pttBoundInputClass;
|
||||
final VoidCallback onPttDown;
|
||||
final VoidCallback onPttUp;
|
||||
final VoidCallback onToggleInputMute;
|
||||
@@ -835,9 +862,44 @@ class _AudioControlsState extends State<_AudioControls> {
|
||||
stats.pttActive ? 'on' : 'off',
|
||||
);
|
||||
|
||||
final isGlobal = widget.pttLevel != 'L0Focused';
|
||||
final badgeLabel = l10n.pttCapabilityBadge(
|
||||
widget.pttLevel,
|
||||
widget.pttBackendId,
|
||||
);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Capability badge (gen2 v0.9.3 / SDD-091). Renders the
|
||||
// active PTT level + backend so the user understands when
|
||||
// Global PTT has fallen back to Focused PTT.
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: Tooltip(
|
||||
message: widget.pttBoundInputClass.isEmpty
|
||||
? badgeLabel
|
||||
: '$badgeLabel\n(${widget.pttBoundInputClass})',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
isGlobal ? Icons.public : Icons.crop_free,
|
||||
size: 14,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Text(
|
||||
badgeLabel,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Listener(
|
||||
onPointerDown: (_) {
|
||||
setState(() => _pressed = true);
|
||||
|
||||
Reference in New Issue
Block a user