Lands SDD-081..088 + SDD-092 implementations on top of v1.0.0-rc.3.
The cross-platform pieces — `AudioTransmitGate`, the per-platform
backend ladder, and the missed-key-up watchdog — are wired into the
audio engine lifecycle. Per-platform live verification on Windows
/ macOS / GNOME-Wayland reference hosts is the remaining work
(RR-PTT-001..006/008 in `release-readiness-go-nogo-record.md`).
`chanora_audio::ptt`
--------------------
* `AudioTransmitGate` now owns an `Arc<AtomicBool>` plus a
`tokio::sync::watch::Sender<bool>` (SAD-075 / SDD-089). The
encoder feed reads the atomic on the hot path; the watchdog
subscribes to the watch channel.
* `MissedKeyUpWatchdog::spawn(gate, timeout)` watches the gate
transitions and self-clears `transmit_active` if the
`false -> true` lifetime exceeds the configured ceiling
(DEC-028, default 30s). Two unit tests cover the timeout-fires
and the no-fire-on-normal-release paths.
`chanora_audio::ptt_backends`
-----------------------------
* `DesktopPttBackend` trait + `PttBinding` value type + `PttInputClass`
enum + `PttBackendError` (SDD-081). `PttBinding` deliberately
carries only `input_class` and an opaque `platform_key`
string; raw key codes never appear in the type surface.
* `select()` factory (SAD-071): runtime ladder evaluation per
OS. Windows → Raw Input → low-level hook → Focused; macOS →
Event Tap → Focused; Linux → GNOME-Wayland portal probe →
Focused.
* `FocusedPttBackend` (SDD-087): universal terminal fallback;
integrates with the existing Flutter Listener-driven PTT.
* `WindowsRawInputBackend` + `WindowsHookBackend` (SDD-083 /
SDD-084): three-rung ladder evaluated once at engine start.
Each backend runs a dedicated worker thread that holds the
OS-level handle; `start`/`stop` lifecycle is honest. Live
`RegisterRawInputDevices` / `SetWindowsHookEx` wiring is
platform-verification work — the scaffolding lets the
descriptor + watchdog + capability event be exercised
end-to-end now.
* `MacOSEventTapBackend` (SDD-085): two-rung ladder with
explicit `PermissionState` (Granted / Denied / Undetermined).
`Undetermined` resolves to `L0Focused` so capability
advertising matches actual runtime behaviour even before
Input Monitoring is granted. Live `CGEventTap` + `IOHIDCheckAccess`
wiring is platform-verification work.
* `LinuxGnomeWaylandBackend` (SDD-086): probes GNOME-on-Wayland
via `XDG_SESSION_TYPE` + `XDG_CURRENT_DESKTOP`, then verifies
the `org.freedesktop.portal.GlobalShortcuts` D-Bus interface
is reachable by reading the `version` property over a
blocking zbus session. Reports `gnome-wayland-portal` /
`L2GlobalHoldToTalk`. Other Linux environments fall through
to the universal Focused backend (DEC-025).
`chanora_audio::engine`
-----------------------
* Engine now owns `transmit_gate: AudioTransmitGate` and
threads a `flag_arc()` clone into the existing capture
state for the cheap hot-path read. `set_transmit_active` /
`transmit_active()` go through the gate so subscribers see
every transition.
* `start_audio` selects the highest-capability backend via
`ptt_backends::select()`, calls `backend.start(gate, none())`,
and spawns the watchdog. Both are released in `stop()` and
on Drop.
* New `engine.rebind_ptt(binding) -> PttBackendDescriptor`
drives the binding-capture flow without restarting the engine.
* New `engine.ptt_descriptor()` returns the privacy-safe
descriptor for the initial UI render before the first
capability event arrives.
`chanora_core`
--------------
* Re-exports `PttBinding` + `PttInputClass`.
* New `ChanoraSession::set_ptt_binding(binding)` — calls
`audio.rebind_ptt` and broadcasts the freshly-published
`SessionEvent::PttCapability` so the UI badge updates live.
* New `ChanoraSession::ptt_descriptor()` for the initial render.
`chanora_bridge`
----------------
* New `BridgePttInputClass` enum + `set_ptt_binding(input_class,
platform_key)` async function. The `platform_key` string is
opaque to the bridge and never logged.
* New `ptt_descriptor()` async accessor returning the
`(level, backend_id, bound_input_class)` triple.
Flutter
-------
* `_AudioControls` now has a "Configure" button next to the
capability badge; `_PttBindingCaptureDialog` captures the
next key press (via `Focus.onKeyEvent`) or mouse side button
(via `Listener.onPointerDown` filtered to button bitmasks
`0x08` / `0x10`). The captured value is the platform-neutral
`LogicalKeyboardKey.keyLabel` or `mouse-side-button:{button}`.
* The dialog explicitly tells the user that the actual key
value never leaves it (DEC-027).
* New ARB keys: `pttConfigureAction`, `pttConfigureTitle`,
`pttConfigurePrompt`, `pttConfigureWaiting`,
`pttConfigureCaptured`, `pttConfigurePrivacyNote`,
`pttConfigureSaveAction` (en + zh-Hans).
Dependencies
------------
* `chanora_audio` adds (Linux only) `zbus = "5"` with the
`tokio` runtime selector + `blocking-api` feature for the
GlobalShortcuts portal probe.
* `chanora_audio` adds `tokio` `test-util` to dev-deps for
`start_paused` watchdog tests (the live watchdog tests use
multi-threaded real time).
Verification
------------
* `cargo test --workspace` with `CHANORA_DISABLE_KEYRING=1`:
57 tests green (was 53). chanora_audio rises from 4 to 8.
* `cargo deny check`: advisories ok, bans ok, licenses ok,
sources ok.
* `cargo about generate --offline`: regenerates
`docs/security/license-inventory.{md,html}`. The crate count
rises from 364 to 383 with the addition of the zbus tree.
* `tools/dump_flutter_licenses.sh`: 94 packages, zero without
LICENSE (unchanged).
* `flutter analyze`: clean.
* `cargo build -p chanora_bridge --release` + `flutter build
linux --release`: clean Linux x86_64 bundle.
Documentation
-------------
* `docs/release/release-readiness-go-nogo-record.md` flips
RR-PTT-007 (missed-key-up watchdog) to Done with a pointer
to the two passing unit tests; bumps to v0.9.4. Live
per-platform traces (RR-PTT-001..005, RR-PTT-008) remain
open and are blocked only on platform reference hosts.
Per-platform live verification (Raw Input registration, Event Tap
creation under granted permission, GlobalShortcuts CreateSession +
BindShortcuts) is queued for the platform owners' reference hosts
per `staged-release-plan.md`.
65 lines
3.0 KiB
Plaintext
65 lines
3.0 KiB
Plaintext
{
|
||
"@@locale": "zh",
|
||
"@@x-source-of-truth": "DEC-015 (register v0.9.5). Simplified Chinese for the Beta key set.",
|
||
|
||
"appTitle": "Chanora",
|
||
"homeNotProductionReadyBanner": "Beta 版本——已接通语音收发;尚未达到生产环境质量。",
|
||
|
||
"fieldServerHost": "服务器地址",
|
||
"fieldNickname": "昵称",
|
||
"fieldServerPassword": "服务器密码",
|
||
"fieldServerPasswordHelp": "如果服务器无需密码,留空即可。",
|
||
"fieldPassword": "密码",
|
||
"fieldDisplayName": "显示名称",
|
||
|
||
"connectAction": "连接",
|
||
"disconnectAction": "断开连接",
|
||
"refreshAction": "刷新",
|
||
"diagnosticsAction": "诊断信息",
|
||
"aboutAction": "关于",
|
||
"aboutVersion": "版本 {version}",
|
||
"aboutNonAffiliation": "Chanora 是独立项目,与 TeamSpeak 之间不存在任何附属、认可、赞助或官方关联关系。",
|
||
"aboutLicenseHeading": "许可协议",
|
||
"aboutLicenseBody": "Chanora 采用 Apache License 2.0 或 MIT License 双协议授权,使用者可任选其一。完整协议文本以 LICENSE-APACHE 与 LICENSE-MIT 形式随仓库一同分发。",
|
||
"aboutThirdPartyHeading": "第三方组件",
|
||
"aboutThirdPartyBody": "Chanora 基于 tsclientlib、flutter_rust_bridge、cpal、rusqlite、Flutter 框架等开源组件构建。完整归属信息请参阅仓库根目录的 NOTICE 文件。",
|
||
"copyAction": "复制",
|
||
"closeAction": "关闭",
|
||
"startAudioAction": "启动语音",
|
||
"pttHoldToTalk": "按住说话",
|
||
"pttTransmitting": "正在发送…",
|
||
"pttCapabilityBadge": "对讲能力:{level}({backend})",
|
||
"pttConfigureAction": "配置",
|
||
"pttConfigureTitle": "配置对讲按键",
|
||
"pttConfigurePrompt": "按下您希望用于对讲的按键或鼠标侧键。",
|
||
"pttConfigureWaiting": "(等待输入…)",
|
||
"pttConfigureCaptured": "已捕获",
|
||
"pttConfigurePrivacyNote": "Chanora 不会记录具体的按键值。本对话框只会向应用提交输入类别(键盘 / 鼠标侧键)以及一个跨平台标签。",
|
||
"pttConfigureSaveAction": "保存",
|
||
"inputMuteAction": "静音麦克风",
|
||
"inputUnmuteAction": "取消麦克风静音",
|
||
"outputMuteAction": "静音扬声器",
|
||
"outputUnmuteAction": "取消扬声器静音",
|
||
"joinChannelAction": "加入频道",
|
||
"channelPasswordTitle": "频道密码",
|
||
"bookmarksHeading": "书签",
|
||
"bookmarksEmpty": "尚无书签。先在上方填写服务器,然后点击“保存书签”。",
|
||
"bookmarkAddAction": "保存书签",
|
||
"bookmarkAddTitle": "保存书签",
|
||
"bookmarkDeleteAction": "删除书签",
|
||
|
||
"statusIdle": "未连接",
|
||
"statusConnecting": "正在连接…",
|
||
"statusConnected": "已连接到 {server}",
|
||
"statusError": "错误:{message}",
|
||
|
||
"statusReconnecting": "正在重新连接……第 {attempt} 次尝试,{delay} 秒后",
|
||
"statusConnectionLost": "连接已断开:{reason}",
|
||
|
||
"audioStatsLine": "发送 {sent} 帧 • 接收 {received} 帧 • PTT {ptt}",
|
||
|
||
"channelsHeading": "频道",
|
||
"clientsHeading": "在线用户",
|
||
"countChannelsAndClients": "{channels} 个频道 • {clients} 在线"
|
||
}
|