A cross-platform Rust library for querying and controlling system audio volume.
volumecontrol exposes a single, unified API that works on Linux (PulseAudio), Windows (WASAPI), and macOS (CoreAudio). The correct backend is selected automatically at compile time — no feature flags or platform-specific imports are needed in your code.
- Repository Structure
- Getting Started
- Installation
- Usage
- Node.js Bindings
- How to Collaborate
- License
- Contact / Further Information
volumecontrol/ ← workspace root
├── Cargo.toml ← workspace manifest & cross-compilation config
├── Cargo.lock ← locked dependency versions
├── LICENSE ← MIT license
├── README.md ← this file
├── AGENTS.md ← AI agent contribution instructions
│
├── volumecontrol-core/ ← platform-independent traits & error types
│ └── src/
│ ├── lib.rs ← public re-exports
│ ├── traits.rs ← AudioDevice trait definition
│ └── error.rs ← AudioError enum
│
├── volumecontrol-linux/ ← PulseAudio backend (feature: pulseaudio)
├── volumecontrol-windows/ ← WASAPI backend (feature: wasapi)
├── volumecontrol-macos/ ← CoreAudio backend (feature: coreaudio)
│
├── volumecontrol/ ← cross-platform wrapper crate
│ └── src/lib.rs ← selects the right backend via #[cfg(target_os)]
│
└── volumecontrol-napi/ ← Node.js bindings via napi-rs
├── src/lib.rs ← #[napi] annotated Rust ↔ JS bridge
└── package.json ← npm package configuration
| Crate | Purpose |
|---|---|
volumecontrol-core |
AudioDevice trait, AudioError enum, shared utilities |
volumecontrol-linux |
AudioDevice impl using PulseAudio |
volumecontrol-windows |
AudioDevice impl using WASAPI |
volumecontrol-macos |
AudioDevice impl using CoreAudio |
volumecontrol |
Selects the right backend at compile time via #[cfg(target_os)] |
volumecontrol-napi |
Node.js native addon via napi-rs; wraps volumecontrol |
volumecontrol lets you read and change the system audio volume from Rust with a single, cross-platform API:
use volumecontrol::AudioDevice;
fn main() -> Result<(), volumecontrol::AudioError> {
let device = AudioDevice::from_default()?;
println!("Current volume: {}%", device.get_vol()?);
Ok(())
}| Platform | Backend | System library required |
|---|---|---|
| Linux | PulseAudio | libpulse-dev (e.g. via apt) |
| Windows | WASAPI | built into Windows — nothing extra |
| macOS | CoreAudio | built into macOS — nothing extra |
Add volumecontrol to your Cargo.toml:
[dependencies]
volumecontrol = "0.1"Linux users: install the PulseAudio development headers before building:
# Debian / Ubuntu sudo apt-get install libpulse-dev # Fedora / RHEL sudo dnf install pulseaudio-libs-devel # Arch Linux sudo pacman -S libpulse
Once the system library is in place, build as usual:
cargo builduse volumecontrol::AudioDevice;
let device = AudioDevice::from_default()?;// By exact device identifier returned from list()
let device = AudioDevice::from_id("alsa_output.pci-0000_00_1f.3.analog-stereo")?;
// By a partial name match (case-insensitive substring search)
let device = AudioDevice::from_name("Speakers")?;let devices = AudioDevice::list()?;
for info in &devices {
// DeviceInfo implements Display as "name (id)"
println!("{info}");
}// Display shows "name (id)" — useful for logs and CLI output
println!("{device}");
// id() returns the opaque platform identifier used by from_id() and list()
println!("Device id: {}", device.id());
// name() returns the human-readable label used by from_name() and list()
println!("Device name: {}", device.name());Both values are guaranteed to be non-empty.
// Volume is always in the range 0..=100
let vol = device.get_vol()?;
println!("Volume: {vol}%");
device.set_vol(50)?; // set to 50 %if device.is_mute()? {
println!("Device is muted");
}
device.set_mute(true)?; // mute
device.set_mute(false)?; // unmuteAll methods return Result<_, AudioError>. The error variants are:
| Variant | Meaning |
|---|---|
DeviceNotFound |
No device matched the given id or name |
InitializationFailed |
The audio subsystem could not be initialised |
ListFailed |
Listing available devices failed |
GetVolumeFailed |
Could not read the current volume |
SetVolumeFailed |
Could not change the volume |
GetMuteFailed |
Could not read the mute state |
SetMuteFailed |
Could not change the mute state |
Unsupported |
Operation not supported on this platform |
EndpointLockPoisoned |
A thread panicked while holding the audio endpoint lock (Windows) |
The volumecontrol-napi package exposes the same cross-platform API to Node.js as a native addon powered by napi-rs. The correct system backend (PulseAudio, WASAPI, or CoreAudio) is selected automatically at compile time.
npm install @touchifyapp/volumecontrolconst { AudioDevice } = require('@touchifyapp/volumecontrol');
const device = AudioDevice.fromDefault();
console.log(`${device.name} (${device.id})`);
console.log(`Volume: ${device.getVol()}%`);
device.setVol(50);
console.log(`Muted: ${device.isMute()}`);
const devices = AudioDevice.list();
devices.forEach(d => console.log(`${d.name} (${d.id})`));- Node.js >= 18
- Linux:
libpulse-dev(PulseAudio development headers) — same requirement as the Rust crate - Windows / macOS: no extra system libraries required
For full details, building from source, and the complete API reference, see volumecontrol-napi/README.md.
Contributions are very welcome! Please follow these steps:
For non-trivial changes (new features, API changes, new platform backends) open a GitHub issue to discuss the idea before investing time in a pull request.
git clone https://github.com/<your-username>/volumecontrol.git
cd volumecontrolgit checkout -b feature/my-improvement- Follow the Rust API Guidelines.
- Document every public item with a
///doc comment. - Add
# Errorssections to fallible functions. - Never use
unwrap()orexpect()— propagate errors with?. - All
unsafecode must be hidden behind a safe wrapper and annotated with a// SAFETY:comment. - Use
thiserrorfor any new error types.
# Check the whole workspace
cargo check --workspace
# Run all tests
cargo test --workspace
# Lint (no warnings allowed)
cargo clippy --workspace --all-targets --all-features -- -D warnings
# Format
cargo fmt --allAll four commands must pass before submitting a pull request.
Push your branch and open a pull request against main. Describe what you changed and why. Link the relevant issue if one exists.
| Topic | Rule |
|---|---|
| Formatting | cargo fmt defaults (enforced by CI) |
| Naming | snake_case for files and modules |
| Errors | thiserror-derived enums; no unwrap/expect |
| Unsafe code | Private helpers only; // SAFETY: required |
| Tests | #[cfg(test)] mod tests block in the same file |
| Documentation | /// for every public item; //! for crates |
This project was built 100% with GitHub Copilot (Claude Opus & Claude Sonnet models) as an experiment in driving an AI to produce a production-ready Rust crate — from architecture and API design to platform-specific backends, CI pipelines, and documentation.
No human wrote any Rust source code directly; every implementation decision, refactor, and fix was produced by the AI under human direction.
This project is licensed under the MIT License.
See the LICENSE file for the full text.
- Issues & feature requests: GitHub Issues
- Pull requests: GitHub Pull Requests
- Maintainer: @SomaticIT
If you have a question that is not suited for a public issue, you can reach the maintainer through their GitHub profile.