Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,34 @@ jobs:
submodules: recursive

- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libdbus-1-dev
run: sudo apt-get update && sudo apt-get install libdbus-1-dev libpulse-dev pulseaudio
if: matrix.os == 'ubuntu-24.04'

- name: Start Sound Server (Linux)
run: pulseaudio -D --start --exit-idle-time=-1
if: matrix.os == 'ubuntu-24.04'

- name: Install virtual audio devices (Windows)
run: git clone https://github.com/LABSN/sound-ci-helpers && powershell sound-ci-helpers/windows/setup_sound.ps1
if: matrix.os == 'windows-2025'

- name: Allow microphone access to all apps (Windows)
shell: pwsh
run: |
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy\"
New-ItemProperty -Path "HKLM:\SOFTWARE\policies\microsoft\windows\appprivacy" -Name "LetAppsAccessMicrophone" -Value "0x00000001" -PropertyType "dword"
if: matrix.os == 'windows-2025'

- name: Install virtual audio devices (macOS)
if: matrix.os == 'macos-14'
run: |
brew install switchaudio-osx
brew install blackhole-2ch
sudo kill -9 `pgrep coreaudiod`
sleep 10
SwitchAudioSource -s "BlackHole 2ch" -t input
SwitchAudioSource -s "BlackHole 2ch" -t output

- name: Install Rust
run: |
rustup toolchain install ${{ matrix.rust }} --profile minimal
Expand Down
2 changes: 1 addition & 1 deletion audioipc/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ pub struct RegisterDeviceCollectionChanged {
// ServerConn::process_msg doesn't have a catch-all case.
#[derive(Debug, Serialize, Deserialize)]
pub enum ServerMessage {
ClientConnect(u32),
ClientConnect,
ClientDisconnect,

ContextGetBackendId,
Expand Down
2 changes: 1 addition & 1 deletion client/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ impl ContextOps for ClientContext {

// Don't let errors bubble from here. Later calls against this context
// will return errors the caller expects to handle.
let _ = send_recv!(rpc, ClientConnect(std::process::id()) => ClientConnected);
let _ = send_recv!(rpc, ClientConnect => ClientConnected);

let backend_id = send_recv!(rpc, ContextGetBackendId => ContextBackendId())
.unwrap_or_else(|_| "(remote error)".to_string());
Expand Down
4 changes: 4 additions & 0 deletions ipctest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ authors = ["Dan Glastonbury <dan.glastonbury@gmail.com>"]
license = "ISC"
edition = "2018"

[lib]
name = "ipctest"
path = "src/lib.rs"

[dependencies]
audioipc = { package = "audioipc2", path = "../audioipc" }
audioipc-client = { package = "audioipc2-client", path = "../client" }
Expand Down
128 changes: 128 additions & 0 deletions ipctest/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright © 2026 Mozilla Foundation
//
// This program is made available under an ISC-style license. See the
// accompanying file LICENSE for details.

pub mod client;

use std::os::raw::c_void;

use audioipc::PlatformHandleType;

/// RAII wrapper around the audioipc server lifecycle.
pub struct TestServer {
handle: *mut c_void,
}

impl Default for TestServer {
fn default() -> Self {
Self::new()
}
}

impl TestServer {
pub fn new() -> Self {
let init_params = audioipc_server::AudioIpcServerInitParams {
thread_create_callback: None,
thread_destroy_callback: None,
};
let handle = unsafe {
audioipc_server::audioipc2_server_start(
std::ptr::null(),
std::ptr::null(),
&init_params,
)
};
assert!(!handle.is_null(), "audioipc2_server_start failed");
TestServer { handle }
}

pub fn new_client(&self, remote_pid: u32) -> PlatformHandleType {
let fd = audioipc_server::audioipc2_server_new_client(self.handle, remote_pid, 0);
assert!(
fd != audioipc::INVALID_HANDLE_VALUE,
"audioipc2_server_new_client failed"
);
fd
}
}

impl Drop for TestServer {
fn drop(&mut self) {
audioipc_server::audioipc2_server_stop(self.handle);
}
}

#[cfg(unix)]
pub fn send_fd(socket: i32, fd: i32) {
unsafe {
let iov = libc::iovec {
iov_base: &[0u8; 1] as *const _ as *mut _,
iov_len: 1,
};

let cmsg_space = libc::CMSG_SPACE(std::mem::size_of::<i32>() as u32) as usize;
let mut cmsg_buf = vec![0u8; cmsg_space];

let mut msg: libc::msghdr = std::mem::zeroed();
msg.msg_iov = &iov as *const _ as *mut _;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf.as_mut_ptr() as *mut _;
msg.msg_controllen = cmsg_space as _;

let cmsg = libc::CMSG_FIRSTHDR(&msg);
(*cmsg).cmsg_level = libc::SOL_SOCKET;
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
(*cmsg).cmsg_len = libc::CMSG_LEN(std::mem::size_of::<i32>() as u32) as _;
std::ptr::copy_nonoverlapping(
&fd as *const _ as *const u8,
libc::CMSG_DATA(cmsg),
std::mem::size_of::<i32>(),
);

let result = libc::sendmsg(socket, &msg, 0);
assert!(
result >= 0,
"sendmsg failed: {}",
std::io::Error::last_os_error()
);
}
}

#[cfg(unix)]
pub fn recv_fd(socket: i32) -> i32 {
unsafe {
let mut buf = [0u8; 1];
let iov = libc::iovec {
iov_base: buf.as_mut_ptr() as *mut _,
iov_len: 1,
};

let cmsg_space = libc::CMSG_SPACE(std::mem::size_of::<i32>() as u32) as usize;
let mut cmsg_buf = vec![0u8; cmsg_space];

let mut msg: libc::msghdr = std::mem::zeroed();
msg.msg_iov = &iov as *const _ as *mut _;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf.as_mut_ptr() as *mut _;
msg.msg_controllen = cmsg_space as _;

let result = libc::recvmsg(socket, &mut msg, 0);
assert!(
result >= 0,
"recvmsg failed: {}",
std::io::Error::last_os_error()
);

let cmsg = libc::CMSG_FIRSTHDR(&msg);
assert!(!cmsg.is_null());

let mut fd: i32 = -1;
std::ptr::copy_nonoverlapping(
libc::CMSG_DATA(cmsg),
&mut fd as *mut _ as *mut u8,
std::mem::size_of::<i32>(),
);
fd
}
}
Loading
Loading