Windows port Phase 0: platform-select the audio backend
Make the tree compile for Windows without touching core logic, by confining all Linux/PipeWire assumptions behind cfg gates and a single platform-selected backend alias. No new dependencies — the cpal/WASAPI backend lands in Phase 1; this ships a no-op stub. - Cargo.toml: move pipewire + rfd(xdg-portal) under cfg(unix); add a cfg(windows) rfd using the Win32 dialog backend. - audio: gate pipewire_impl to unix, add a cpal_impl stub for windows, and select between them via the new PlatformAudioBackend alias. - core: use PlatformAudioBackend instead of the concrete PipeWireBackend. - lib: gate the unix-only 0o600 log-file mode code (+ its test); Windows logs inherit the directory ACL. - audio_probe: gate this PipeWire diagnostic to unix with a stub main. - app: open URLs via rundll32 on windows, xdg-open on unix (shell-free). - ci: add .gitea/workflows/windows-build.yml (M1) — build + lib tests for x86_64-pc-windows-msvc, with CMAKE_POLICY_VERSION_MINIMUM=3.5 for the vendored libopus build. Needs a windows act_runner to actually run. Linux build/clippy/tests green (316/316). The Windows path is verified by inspection only (no local Windows toolchain); CI is the real gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
//! Windows audio backend (cpal/WASAPI) — **Phase 0 stub**.
|
||||
//!
|
||||
//! This is a compile-and-run placeholder so the Windows build links and the app
|
||||
//! starts up (networking, UI, and text chat all functional) while the real
|
||||
//! capture/playback implementation lands in Phase 1. Every method satisfies the
|
||||
//! [`AudioBackend`] contract as a no-op: no microphone is captured and nothing is
|
||||
//! played. It deliberately pulls in no extra dependency — `cpal` is added only
|
||||
//! when the real implementation arrives.
|
||||
//!
|
||||
//! Phase 1 will replace this with cpal streams on the WASAPI host, mapping:
|
||||
//! - `start_capture` → input stream, f32→i16, mono 48 kHz, into `tx`;
|
||||
//! - `start_playback` → output stream draining a `ringbuf`, keeping `ring_fill`
|
||||
//! updated so the existing hardware-clock pacing in the mixer keeps working;
|
||||
//! - `stop` → drop the streams.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
|
||||
use super::{AudioBackend, AudioError};
|
||||
|
||||
/// No-op Windows audio backend (Phase 0). See module docs.
|
||||
pub struct CpalBackend;
|
||||
|
||||
impl CpalBackend {
|
||||
pub fn new() -> Self {
|
||||
crate::log_msg("CpalBackend: Phase 0 stub active (no audio I/O yet)");
|
||||
CpalBackend
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CpalBackend {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioBackend for CpalBackend {
|
||||
fn start_capture(
|
||||
&self,
|
||||
_tx: Sender<Vec<i16>>,
|
||||
_target_node: Option<String>,
|
||||
) -> Result<(), AudioError> {
|
||||
// No capture stream yet: dropping `_tx` simply means no samples are ever
|
||||
// produced (silent mic), which is the intended Phase 0 behaviour.
|
||||
crate::log_msg("CpalBackend::start_capture: not yet implemented (Phase 1) — capturing silence");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_playback(
|
||||
&self,
|
||||
rx: Receiver<Vec<i16>>,
|
||||
_target_node: Option<String>,
|
||||
_ring_fill: Arc<AtomicUsize>,
|
||||
) -> Result<(), AudioError> {
|
||||
// Drain and discard incoming audio on a detached thread so the mixer's
|
||||
// producer never blocks or sees a closed channel. This keeps the rest of
|
||||
// the pipeline running normally while output is silent.
|
||||
std::thread::spawn(move || while rx.recv().is_ok() {});
|
||||
crate::log_msg("CpalBackend::start_playback: not yet implemented (Phase 1) — discarding output");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), AudioError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,24 @@ pub mod gate;
|
||||
pub mod limiter;
|
||||
pub mod multitrack;
|
||||
pub mod pan;
|
||||
#[cfg(unix)]
|
||||
pub mod pipewire_impl;
|
||||
#[cfg(windows)]
|
||||
pub mod cpal_impl;
|
||||
pub mod pw_cli;
|
||||
pub mod recorder;
|
||||
|
||||
/// The audio backend implementation for the current platform.
|
||||
///
|
||||
/// The whole app constructs and threads this alias (via
|
||||
/// `PlatformAudioBackend::new()`) rather than any concrete backend type, so
|
||||
/// platform selection lives entirely here. Both implementations satisfy the
|
||||
/// [`AudioBackend`] trait, which is the only interface the core talks to.
|
||||
///
|
||||
/// - Linux/Unix → PipeWire ([`pipewire_impl::PipeWireBackend`]).
|
||||
/// - Windows → cpal/WASAPI ([`cpal_impl::CpalBackend`]); a no-op stub until the
|
||||
/// Phase 1 capture/playback implementation lands.
|
||||
#[cfg(unix)]
|
||||
pub type PlatformAudioBackend = pipewire_impl::PipeWireBackend;
|
||||
#[cfg(windows)]
|
||||
pub type PlatformAudioBackend = cpal_impl::CpalBackend;
|
||||
|
||||
Reference in New Issue
Block a user