The v1.0.0-rc.7 Windows backends were thread::sleep stubs that
reported optimistic L2GlobalHoldToTalk / L3GlobalWithMouseButtons
descriptors without actually registering for any global key events.
Surfaced on Windows verification as:
* 'even press to talk key was set, still only the hold to talk
button is work for talk'
* 'and displayed as L2GlobalHoldToTalk(raw-input)'
* 'cannot continuous transmission'
This commit implements the real backends:
WindowsRawInputBackend (preferred Windows rung, SDD-083):
* Hidden message-only window via
CreateWindowExW(..., HWND_MESSAGE, ...).
* RegisterRawInputDevices with RIDEV_INPUTSINK on
Usage Page 0x01 / Usage 0x06 (keyboard) and 0x02 (mouse) so
events fire globally — including when Chanora is unfocused.
* WndProc handling WM_INPUT: GetRawInputData ->
keyboard.VKey vs bound vk, or mouse.usButtonFlags vs bound
side-button index. Down -> gate.set(true); up -> gate.set(false).
* Dedicated chanora-rawinput thread runs GetMessageW /
TranslateMessage / DispatchMessageW until stop() posts
WM_QUIT via PostThreadMessageW.
WindowsHookBackend (fallback rung, SDD-084):
* SetWindowsHookExW(WH_KEYBOARD_LL) + WH_MOUSE_LL on a
dedicated chanora-llhook thread.
* Hook procs translate KBDLLHOOKSTRUCT.vkCode and
MSLLHOOKSTRUCT.mouseData against the same shared
AtomicBinding.
* UnhookWindowsHookEx on teardown.
Both backends:
* Honest descriptor() reporting: backends start reporting
L0Focused; level upgrades to L2 / L3 only after a real
arming success (RegisterRawInputDevices or SetWindowsHookEx
returning Ok). This fixes the 'L2 reported but doesn't fire'
complaint by making the badge tell the truth — if Raw Input
registration fails at runtime the user sees the L0Focused
info-icon explanation sheet instead of being told L2 works.
* AtomicBinding (class / vk / mouse_btn) for lock-free hot
path. Translation lives in
crates/chanora_audio/src/ptt_backends/windows_keymap.rs
which maps Flutter LogicalKeyboardKey.keyLabel strings
(e.g. 'Space', 'F10', 'A') to Win32 VK_* codes; mouse
side-button bitmask strings ('mouse-side-button:8' /
':16') to RawInput button indices (4 / 5).
* Per-thread context (thread_local RefCell) carries the
gate + binding to the WndProc / hook proc without needing
raw-pointer user-data plumbing.
Diagnostic logging:
* AudioEngine::start now logs default_input_config and
default_output_config explicitly with the channels /
sample_rate / sample_format that cpal reports, so a
build_*_stream failure on locale-specific Windows hosts
(reported on ko-KR Windows 11 as 'Start Audio Button not
work') becomes diagnosable from the stderr log alone.
* build_output_stream surfaces the requested config in the
tracing::error! record on failure.
Privacy (DEC-027 / SDD-090): the windows.rs and
windows_keymap.rs hot paths NEVER log raw VKs, scan codes,
keysyms, key labels, or button identifiers. Only the
platform-neutral input class ('keyboard' /
'mouse-side-button') and the backend id appear in the tracing
stream. The SDD-090 PttSanitizer Layer is the defence-in-depth
net but this code does not rely on it.
Tests: 4 new windows-only unit tests in windows_keymap (ASCII
letters / digits / Space + Fn / unknown / mouse button index).
They compile only under cfg(target_os = "windows") so the
Linux workspace test count is unchanged at 59/0/3.
Cargo deps: adds windows = '0.54' (target_os = windows) with
the feature set needed for RawInput + hooks. 0.54 matches
the version already transitive through the workspace.
Verified on Linux: cargo check --workspace clean, cargo test
--workspace 59/0/3 (windows-gated tests skip on Linux). The
real exercise of this commit will happen on the Korean Windows
11 host (100.84.219.45) at the next build.
73 lines
3.1 KiB
TOML
73 lines
3.1 KiB
TOML
[package]
|
|
name = "chanora_audio"
|
|
description = "Chanora audio subsystem — cpal-based capture/playback, audiopus encode, tsclientlib AudioHandler for decode + jitter buffer + mix. DEC-011, DEC-011.1."
|
|
version.workspace = true
|
|
edition.workspace = true
|
|
rust-version.workspace = true
|
|
authors.workspace = true
|
|
license.workspace = true
|
|
repository.workspace = true
|
|
publish.workspace = true
|
|
|
|
[dependencies]
|
|
chanora_protocol = { path = "../chanora_protocol" }
|
|
thiserror.workspace = true
|
|
tracing.workspace = true
|
|
|
|
# Cross-platform audio I/O (DEC-011.1).
|
|
cpal = "0.16"
|
|
# Opus encoder. tsclientlib already pulls this; we depend explicitly so
|
|
# this crate can compile against it without going through tsclientlib.
|
|
audiopus = "0.3.0-rc.0"
|
|
|
|
# AudioHandler lives in the tsclientlib crate behind the `audio`
|
|
# feature. We import the crate just for the AudioHandler type; the
|
|
# Connection type stays inside chanora_protocol.
|
|
tsclientlib = { git = "https://github.com/ReSpeak/tsclientlib.git", rev = "04aa2491", default-features = false, features = ["default-tls", "audio"] }
|
|
|
|
tokio = { version = "1", features = ["sync", "rt", "macros", "time"] }
|
|
|
|
[target.'cfg(target_os = "android")'.dependencies]
|
|
# JNI bindings to flip Android's AudioManager into MODE_IN_COMMUNICATION
|
|
# when the voice-comm preset is requested. ndk_context is initialised
|
|
# by the bridge crate's android_init shim.
|
|
jni = { version = "0.21", default-features = false }
|
|
ndk-context = "0.1"
|
|
|
|
[target.'cfg(target_os = "windows")'.dependencies]
|
|
# Real Windows global PTT (SDD-083 / SDD-084): RegisterRawInputDevices
|
|
# + WM_INPUT translation backed by a hidden message-only window, and
|
|
# SetWindowsHookExW(WH_KEYBOARD_LL / WH_MOUSE_LL) fallback. Both
|
|
# require a per-backend OS thread that owns a message pump.
|
|
windows = { version = "0.54", features = [
|
|
"Win32_Foundation",
|
|
"Win32_System_LibraryLoader",
|
|
"Win32_UI_Input",
|
|
"Win32_UI_Input_KeyboardAndMouse",
|
|
"Win32_UI_WindowsAndMessaging",
|
|
] }
|
|
|
|
[dev-dependencies]
|
|
# `test-util` enables `start_paused` / virtual-clock tests used by
|
|
# the missed-key-up watchdog unit tests.
|
|
tokio = { version = "1", features = ["sync", "rt", "macros", "time", "test-util"] }
|
|
|
|
[target.'cfg(target_os = "linux")'.dependencies]
|
|
# GNOME-on-Wayland Global Push-to-Talk uses the freedesktop
|
|
# `org.freedesktop.portal.GlobalShortcuts` interface over D-Bus.
|
|
# `zbus` is the standard async D-Bus crate; the `tokio` runtime
|
|
# selector is mandatory in zbus 5; we share the tokio runtime
|
|
# the rest of the audio + core crates already depend on. The
|
|
# `blocking-api` feature is retained so the audio-engine
|
|
# probe path can do a synchronous portal-version read without
|
|
# starting an async runtime; the live session flow uses the
|
|
# async surface.
|
|
zbus = { version = "5", default-features = false, features = ["tokio", "blocking-api"] }
|
|
# Stream / sink utilities for consuming portal signals on the
|
|
# async path.
|
|
futures-util = { version = "0.3", default-features = false, features = ["std"] }
|
|
# Random token bytes for the portal handle_token / session_handle_token
|
|
# options. The portal recommends fresh tokens to scope its own
|
|
# object paths per call.
|
|
rand = "0.8"
|