mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
source code
This commit is contained in:
4
libs/pulsectl/.gitignore
vendored
Normal file
4
libs/pulsectl/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
**/target
|
||||
/target
|
||||
**/*.rs.bk
|
||||
.idea/
|
||||
129
libs/pulsectl/Cargo.lock
generated
Normal file
129
libs/pulsectl/Cargo.lock
generated
Normal file
@@ -0,0 +1,129 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8"
|
||||
|
||||
[[package]]
|
||||
name = "libpulse-binding"
|
||||
version = "2.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe925a4d3a96961316c9c1488f97a95938a6093f0d4691eec888776057ce965e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libpulse-sys",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libpulse-sys"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d9073c83dda6aff9b611dc368e8db6e0aa29027546d8800a18b4417e182b4d5"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"pkg-config",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-pulsectl"
|
||||
version = "0.2.9"
|
||||
dependencies = [
|
||||
"libpulse-binding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b4f34193997d92804d359ed09953e25d5138df6bcc055a71bf68ee89fdf9223"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
19
libs/pulsectl/Cargo.toml
Normal file
19
libs/pulsectl/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "rust-pulsectl"
|
||||
version = "0.2.10"
|
||||
authors = ["Kristopher Ruzic <krruzic@gmail.com>"]
|
||||
edition = "2018"
|
||||
license = "GPL-3.0+"
|
||||
description = "A higher level API for libpulse_binding"
|
||||
readme = "README.md"
|
||||
keywords = ["pulse", "pulseaudio", "binding", "audio", "api"]
|
||||
categories = ["api-bindings", "multimedia::audio"]
|
||||
homepage = "https://github.com/krruzic/pulsectl"
|
||||
repository = "https://github.com/krruzic/pulsectl"
|
||||
|
||||
[lib]
|
||||
name = "pulsectl"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
libpulse-binding = "2.21"
|
||||
13
libs/pulsectl/LICENSE.md
Normal file
13
libs/pulsectl/LICENSE.md
Normal file
@@ -0,0 +1,13 @@
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
43
libs/pulsectl/README.md
Normal file
43
libs/pulsectl/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
Rust PulsecAudio API
|
||||
====================
|
||||
|
||||
`pulsectl-rust` is a API wrapper for `libpulse_binding` to make pulseaudio application development easier.
|
||||
This is a wrapper around the introspector, and thus this library is only capable of modifying PulseAudio data (changing volume, routing applications and muting right now).
|
||||
|
||||
### Usage
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
```toml
|
||||
[dependencies]
|
||||
rust-pulsectl = "0.2.6"
|
||||
```
|
||||
|
||||
Then, connect to PulseAudio by creating a `SinkController` for audio playback devices and apps or a `SourceController` for audio recording devices and apps.
|
||||
|
||||
```rust
|
||||
// Simple application that lists all playback devices and their status
|
||||
// See examples/change_device_vol.rs for a more complete example
|
||||
extern crate pulsectl;
|
||||
|
||||
use std::io;
|
||||
|
||||
use pulsectl::controllers::SinkController;
|
||||
use pulsectl::controllers::DeviceControl;
|
||||
fn main() {
|
||||
// create handler that calls functions on playback devices and apps
|
||||
let mut handler = SinkController::create();
|
||||
let devices = handler
|
||||
.list_devices()
|
||||
.expect("Could not get list of playback devices");
|
||||
println!("Playback Devices");
|
||||
for dev in devices.clone() {
|
||||
println!(
|
||||
"[{}] {}, [Volume: {}]",
|
||||
dev.index,
|
||||
dev.description.as_ref().unwrap(),
|
||||
dev.volume.print()
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
34
libs/pulsectl/examples/change_device_vol.rs
Normal file
34
libs/pulsectl/examples/change_device_vol.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
extern crate pulsectl;
|
||||
|
||||
use std::io;
|
||||
|
||||
use pulsectl::controllers::DeviceControl;
|
||||
use pulsectl::controllers::SinkController;
|
||||
|
||||
fn main() {
|
||||
// create handler that calls functions on playback devices and apps
|
||||
let mut handler = SinkController::create().unwrap();
|
||||
let devices = handler
|
||||
.list_devices()
|
||||
.expect("Could not get list of playback devices");
|
||||
|
||||
println!("Playback Devices");
|
||||
for dev in devices.clone() {
|
||||
println!(
|
||||
"[{}] {}, [Volume: {}]",
|
||||
dev.index,
|
||||
dev.description.as_ref().unwrap(),
|
||||
dev.volume.print()
|
||||
);
|
||||
}
|
||||
let mut selection = String::new();
|
||||
|
||||
io::stdin()
|
||||
.read_line(&mut selection)
|
||||
.expect("error: unable to read user input");
|
||||
for dev in devices.clone() {
|
||||
if let true = selection.trim() == dev.index.to_string() {
|
||||
handler.increase_device_volume_by_percent(dev.index, 0.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
51
libs/pulsectl/src/controllers/errors.rs
Normal file
51
libs/pulsectl/src/controllers/errors.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::PulseCtlError;
|
||||
|
||||
/// if the error occurs within the Mainloop, we bubble up the error with
|
||||
/// this conversion
|
||||
impl From<PulseCtlError> for ControllerError {
|
||||
fn from(error: super::errors::PulseCtlError) -> Self {
|
||||
ControllerError {
|
||||
error: ControllerErrorType::PulseCtlError,
|
||||
message: format!("{:?}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ControllerError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut error_string = String::new();
|
||||
match self.error {
|
||||
ControllerErrorType::PulseCtlError => {
|
||||
error_string.push_str("PulseCtlError");
|
||||
}
|
||||
ControllerErrorType::GetInfoError => {
|
||||
error_string.push_str("GetInfoError");
|
||||
}
|
||||
}
|
||||
write!(f, "[{}]: {}", error_string, self.message)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum ControllerErrorType {
|
||||
PulseCtlError,
|
||||
GetInfoError,
|
||||
}
|
||||
|
||||
/// Error thrown while fetching data from pulseaudio,
|
||||
/// has two variants: PulseCtlError for when PulseAudio returns an error code
|
||||
/// and GetInfoError when a request for data fails for whatever reason
|
||||
pub struct ControllerError {
|
||||
error: ControllerErrorType,
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl ControllerError {
|
||||
pub(crate) fn new(err: ControllerErrorType, msg: &str) -> Self {
|
||||
ControllerError {
|
||||
error: err,
|
||||
message: msg.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
595
libs/pulsectl/src/controllers/mod.rs
Normal file
595
libs/pulsectl/src/controllers/mod.rs
Normal file
@@ -0,0 +1,595 @@
|
||||
/// Source = microphone etc. something that takes in audio
|
||||
/// Source Output = application consuming that audio
|
||||
///
|
||||
/// Sink = headphones etc. something that plays out audio
|
||||
/// Sink Input = application producing that audio
|
||||
/// When you create a `SinkController`, you are working with audio playback devices and applications
|
||||
/// if you want to manipulate recording devices such as microphone volume,
|
||||
/// you'll need to use a `SourceController`. Both of these implement the same api, defined by
|
||||
/// the traits DeviceControl and AppControl
|
||||
use std::cell::RefCell;
|
||||
use std::clone::Clone;
|
||||
use std::rc::Rc;
|
||||
|
||||
use pulse::{
|
||||
callbacks::ListResult,
|
||||
context::introspect,
|
||||
volume::{ChannelVolumes, Volume},
|
||||
};
|
||||
|
||||
use errors::{ControllerError, ControllerErrorType::*};
|
||||
use types::{ApplicationInfo, DeviceInfo, ServerInfo};
|
||||
|
||||
use crate::Handler;
|
||||
|
||||
pub(crate) mod errors;
|
||||
pub mod types;
|
||||
|
||||
pub trait DeviceControl<T> {
|
||||
fn get_default_device(&mut self) -> Result<T, ControllerError>;
|
||||
fn set_default_device(&mut self, name: &str) -> Result<bool, ControllerError>;
|
||||
|
||||
fn list_devices(&mut self) -> Result<Vec<T>, ControllerError>;
|
||||
fn get_device_by_index(&mut self, index: u32) -> Result<T, ControllerError>;
|
||||
fn get_device_by_name(&mut self, name: &str) -> Result<T, ControllerError>;
|
||||
fn set_device_volume_by_index(&mut self, index: u32, volume: &ChannelVolumes);
|
||||
fn set_device_volume_by_name(&mut self, name: &str, volume: &ChannelVolumes);
|
||||
fn increase_device_volume_by_percent(&mut self, index: u32, delta: f64);
|
||||
fn decrease_device_volume_by_percent(&mut self, index: u32, delta: f64);
|
||||
}
|
||||
|
||||
pub trait AppControl<T> {
|
||||
fn list_applications(&mut self) -> Result<Vec<T>, ControllerError>;
|
||||
|
||||
fn get_app_by_index(&mut self, index: u32) -> Result<T, ControllerError>;
|
||||
fn increase_app_volume_by_percent(&mut self, index: u32, delta: f64);
|
||||
fn decrease_app_volume_by_percent(&mut self, index: u32, delta: f64);
|
||||
|
||||
fn move_app_by_index(
|
||||
&mut self,
|
||||
stream_index: u32,
|
||||
device_index: u32,
|
||||
) -> Result<bool, ControllerError>;
|
||||
fn move_app_by_name(
|
||||
&mut self,
|
||||
stream_index: u32,
|
||||
device_name: &str,
|
||||
) -> Result<bool, ControllerError>;
|
||||
fn set_app_mute(&mut self, index: u32, mute: bool) -> Result<bool, ControllerError>;
|
||||
}
|
||||
|
||||
fn volume_from_percent(volume: f64) -> f64 {
|
||||
(volume * 100.0) * (f64::from(pulse::volume::VOLUME_NORM.0) / 100.0)
|
||||
}
|
||||
|
||||
pub struct SinkController {
|
||||
pub handler: Handler,
|
||||
}
|
||||
|
||||
impl SinkController {
|
||||
pub fn create() -> Result<Self, ControllerError> {
|
||||
let handler = Handler::connect("SinkController")?;
|
||||
Ok(SinkController { handler })
|
||||
}
|
||||
|
||||
pub fn get_server_info(&mut self) -> Result<ServerInfo, ControllerError> {
|
||||
let server = Rc::new(RefCell::new(Some(None)));
|
||||
let server_ref = server.clone();
|
||||
|
||||
let op = self.handler.introspect.get_server_info(move |res| {
|
||||
server_ref
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.replace(res.into());
|
||||
});
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let mut result = server.borrow_mut();
|
||||
result.take().unwrap().ok_or(ControllerError::new(
|
||||
GetInfoError,
|
||||
"Error getting information about the server",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceControl<DeviceInfo> for SinkController {
|
||||
fn get_default_device(&mut self) -> Result<DeviceInfo, ControllerError> {
|
||||
let server_info = self.get_server_info();
|
||||
match server_info {
|
||||
Ok(info) => self.get_device_by_name(info.default_sink_name.unwrap().as_ref()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
fn set_default_device(&mut self, name: &str) -> Result<bool, ControllerError> {
|
||||
let success = Rc::new(RefCell::new(false));
|
||||
let success_ref = success.clone();
|
||||
|
||||
let op = self
|
||||
.handler
|
||||
.context
|
||||
.borrow_mut()
|
||||
.set_default_sink(name, move |res| success_ref.borrow_mut().clone_from(&res));
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let result = success.borrow_mut().clone();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn list_devices(&mut self) -> Result<Vec<DeviceInfo>, ControllerError> {
|
||||
let list = Rc::new(RefCell::new(Some(Vec::new())));
|
||||
let list_ref = list.clone();
|
||||
|
||||
let op = self.handler.introspect.get_sink_info_list(
|
||||
move |sink_list: ListResult<&introspect::SinkInfo>| {
|
||||
if let ListResult::Item(item) = sink_list {
|
||||
list_ref.borrow_mut().as_mut().unwrap().push(item.into());
|
||||
}
|
||||
},
|
||||
);
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let mut result = list.borrow_mut();
|
||||
result.take().ok_or(ControllerError::new(
|
||||
GetInfoError,
|
||||
"Error getting device list",
|
||||
))
|
||||
}
|
||||
fn get_device_by_index(&mut self, index: u32) -> Result<DeviceInfo, ControllerError> {
|
||||
let device = Rc::new(RefCell::new(Some(None)));
|
||||
let dev_ref = device.clone();
|
||||
let op = self.handler.introspect.get_sink_info_by_index(
|
||||
index,
|
||||
move |sink_list: ListResult<&introspect::SinkInfo>| {
|
||||
if let ListResult::Item(item) = sink_list {
|
||||
dev_ref.borrow_mut().as_mut().unwrap().replace(item.into());
|
||||
}
|
||||
},
|
||||
);
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let mut result = device.borrow_mut();
|
||||
result.take().unwrap().ok_or(ControllerError::new(
|
||||
GetInfoError,
|
||||
"Error getting requested device",
|
||||
))
|
||||
}
|
||||
fn get_device_by_name(&mut self, name: &str) -> Result<DeviceInfo, ControllerError> {
|
||||
let device = Rc::new(RefCell::new(Some(None)));
|
||||
let dev_ref = device.clone();
|
||||
let op = self.handler.introspect.get_sink_info_by_name(
|
||||
name,
|
||||
move |sink_list: ListResult<&introspect::SinkInfo>| {
|
||||
if let ListResult::Item(item) = sink_list {
|
||||
dev_ref.borrow_mut().as_mut().unwrap().replace(item.into());
|
||||
}
|
||||
},
|
||||
);
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let mut result = device.borrow_mut();
|
||||
result.take().unwrap().ok_or(ControllerError::new(
|
||||
GetInfoError,
|
||||
"Error getting requested device",
|
||||
))
|
||||
}
|
||||
|
||||
fn set_device_volume_by_index(&mut self, index: u32, volume: &ChannelVolumes) {
|
||||
let op = self
|
||||
.handler
|
||||
.introspect
|
||||
.set_sink_volume_by_index(index, volume, None);
|
||||
self.handler.wait_for_operation(op).ok();
|
||||
}
|
||||
fn set_device_volume_by_name(&mut self, name: &str, volume: &ChannelVolumes) {
|
||||
let op = self
|
||||
.handler
|
||||
.introspect
|
||||
.set_sink_volume_by_name(name, volume, None);
|
||||
self.handler.wait_for_operation(op).ok();
|
||||
}
|
||||
fn increase_device_volume_by_percent(&mut self, index: u32, delta: f64) {
|
||||
if let Ok(mut dev_ref) = self.get_device_by_index(index) {
|
||||
let new_vol = Volume::from(Volume(volume_from_percent(delta) as u32));
|
||||
if let Some(volumes) = dev_ref.volume.increase(new_vol) {
|
||||
let op = self
|
||||
.handler
|
||||
.introspect
|
||||
.set_sink_volume_by_index(index, &volumes, None);
|
||||
self.handler.wait_for_operation(op).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
fn decrease_device_volume_by_percent(&mut self, index: u32, delta: f64) {
|
||||
if let Ok(mut dev_ref) = self.get_device_by_index(index) {
|
||||
let new_vol = Volume::from(Volume(volume_from_percent(delta) as u32));
|
||||
if let Some(volumes) = dev_ref.volume.decrease(new_vol) {
|
||||
let op = self
|
||||
.handler
|
||||
.introspect
|
||||
.set_sink_volume_by_index(index, &volumes, None);
|
||||
self.handler.wait_for_operation(op).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppControl<ApplicationInfo> for SinkController {
|
||||
fn list_applications(&mut self) -> Result<Vec<ApplicationInfo>, ControllerError> {
|
||||
let list = Rc::new(RefCell::new(Some(Vec::new())));
|
||||
let list_ref = list.clone();
|
||||
|
||||
let op = self.handler.introspect.get_sink_input_info_list(
|
||||
move |sink_list: ListResult<&introspect::SinkInputInfo>| {
|
||||
if let ListResult::Item(item) = sink_list {
|
||||
list_ref.borrow_mut().as_mut().unwrap().push(item.into());
|
||||
}
|
||||
},
|
||||
);
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let mut result = list.borrow_mut();
|
||||
result.take().ok_or(ControllerError::new(
|
||||
GetInfoError,
|
||||
"Error getting application list",
|
||||
))
|
||||
}
|
||||
|
||||
fn get_app_by_index(&mut self, index: u32) -> Result<ApplicationInfo, ControllerError> {
|
||||
let app = Rc::new(RefCell::new(Some(None)));
|
||||
let app_ref = app.clone();
|
||||
let op = self.handler.introspect.get_sink_input_info(
|
||||
index,
|
||||
move |sink_list: ListResult<&introspect::SinkInputInfo>| {
|
||||
if let ListResult::Item(item) = sink_list {
|
||||
app_ref.borrow_mut().as_mut().unwrap().replace(item.into());
|
||||
}
|
||||
},
|
||||
);
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let mut result = app.borrow_mut();
|
||||
result.take().unwrap().ok_or(ControllerError::new(
|
||||
GetInfoError,
|
||||
"Error getting requested app",
|
||||
))
|
||||
}
|
||||
|
||||
fn increase_app_volume_by_percent(&mut self, index: u32, delta: f64) {
|
||||
if let Ok(mut app_ref) = self.get_app_by_index(index) {
|
||||
let new_vol = Volume::from(Volume(volume_from_percent(delta) as u32));
|
||||
if let Some(volumes) = app_ref.volume.increase(new_vol) {
|
||||
let op = self
|
||||
.handler
|
||||
.introspect
|
||||
.set_sink_input_volume(index, &volumes, None);
|
||||
self.handler.wait_for_operation(op).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decrease_app_volume_by_percent(&mut self, index: u32, delta: f64) {
|
||||
if let Ok(mut app_ref) = self.get_app_by_index(index) {
|
||||
let new_vol = Volume::from(Volume(volume_from_percent(delta) as u32));
|
||||
if let Some(volumes) = app_ref.volume.decrease(new_vol) {
|
||||
let op = self
|
||||
.handler
|
||||
.introspect
|
||||
.set_sink_input_volume(index, &volumes, None);
|
||||
self.handler.wait_for_operation(op).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn move_app_by_index(
|
||||
&mut self,
|
||||
stream_index: u32,
|
||||
device_index: u32,
|
||||
) -> Result<bool, ControllerError> {
|
||||
let success = Rc::new(RefCell::new(false));
|
||||
let success_ref = success.clone();
|
||||
let op = self.handler.introspect.move_sink_input_by_index(
|
||||
stream_index,
|
||||
device_index,
|
||||
Some(Box::new(move |res| {
|
||||
success_ref.borrow_mut().clone_from(&res)
|
||||
})),
|
||||
);
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let result = success.borrow_mut().clone();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn move_app_by_name(
|
||||
&mut self,
|
||||
stream_index: u32,
|
||||
device_name: &str,
|
||||
) -> Result<bool, ControllerError> {
|
||||
let success = Rc::new(RefCell::new(false));
|
||||
let success_ref = success.clone();
|
||||
let op = self.handler.introspect.move_sink_input_by_name(
|
||||
stream_index,
|
||||
device_name,
|
||||
Some(Box::new(move |res| {
|
||||
success_ref.borrow_mut().clone_from(&res)
|
||||
})),
|
||||
);
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let result = success.borrow_mut().clone();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn set_app_mute(&mut self, index: u32, mute: bool) -> Result<bool, ControllerError> {
|
||||
let success = Rc::new(RefCell::new(false));
|
||||
let success_ref = success.clone();
|
||||
let op = self.handler.introspect.set_sink_input_mute(
|
||||
index,
|
||||
mute,
|
||||
Some(Box::new(move |res| {
|
||||
success_ref.borrow_mut().clone_from(&res)
|
||||
})),
|
||||
);
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let result = success.borrow_mut().clone();
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SourceController {
|
||||
pub handler: Handler,
|
||||
}
|
||||
|
||||
impl SourceController {
|
||||
pub fn create() -> Result<Self, ControllerError> {
|
||||
let handler = Handler::connect("SourceController")?;
|
||||
Ok(SourceController { handler })
|
||||
}
|
||||
|
||||
pub fn get_server_info(&mut self) -> Result<ServerInfo, ControllerError> {
|
||||
let server = Rc::new(RefCell::new(Some(None)));
|
||||
let server_ref = server.clone();
|
||||
|
||||
let op = self.handler.introspect.get_server_info(move |res| {
|
||||
server_ref
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.replace(res.into());
|
||||
});
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let mut result = server.borrow_mut();
|
||||
result.take().unwrap().ok_or(ControllerError::new(
|
||||
GetInfoError,
|
||||
"Error getting application list",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceControl<DeviceInfo> for SourceController {
|
||||
fn get_default_device(&mut self) -> Result<DeviceInfo, ControllerError> {
|
||||
let server_info = self.get_server_info();
|
||||
match server_info {
|
||||
Ok(info) => self.get_device_by_name(info.default_sink_name.unwrap().as_ref()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
fn set_default_device(&mut self, name: &str) -> Result<bool, ControllerError> {
|
||||
let success = Rc::new(RefCell::new(false));
|
||||
let success_ref = success.clone();
|
||||
|
||||
let op = self
|
||||
.handler
|
||||
.context
|
||||
.borrow_mut()
|
||||
.set_default_source(name, move |res| success_ref.borrow_mut().clone_from(&res));
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let result = success.borrow_mut().clone();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn list_devices(&mut self) -> Result<Vec<DeviceInfo>, ControllerError> {
|
||||
let list = Rc::new(RefCell::new(Some(Vec::new())));
|
||||
let list_ref = list.clone();
|
||||
|
||||
let op = self.handler.introspect.get_source_info_list(
|
||||
move |sink_list: ListResult<&introspect::SourceInfo>| {
|
||||
if let ListResult::Item(item) = sink_list {
|
||||
list_ref.borrow_mut().as_mut().unwrap().push(item.into());
|
||||
}
|
||||
},
|
||||
);
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let mut result = list.borrow_mut();
|
||||
result.take().ok_or(ControllerError::new(
|
||||
GetInfoError,
|
||||
"Error getting application list",
|
||||
))
|
||||
}
|
||||
fn get_device_by_index(&mut self, index: u32) -> Result<DeviceInfo, ControllerError> {
|
||||
let device = Rc::new(RefCell::new(Some(None)));
|
||||
let dev_ref = device.clone();
|
||||
let op = self.handler.introspect.get_source_info_by_index(
|
||||
index,
|
||||
move |sink_list: ListResult<&introspect::SourceInfo>| {
|
||||
if let ListResult::Item(item) = sink_list {
|
||||
dev_ref.borrow_mut().as_mut().unwrap().replace(item.into());
|
||||
}
|
||||
},
|
||||
);
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let mut result = device.borrow_mut();
|
||||
result.take().unwrap().ok_or(ControllerError::new(
|
||||
GetInfoError,
|
||||
"Error getting application list",
|
||||
))
|
||||
}
|
||||
fn get_device_by_name(&mut self, name: &str) -> Result<DeviceInfo, ControllerError> {
|
||||
let device = Rc::new(RefCell::new(Some(None)));
|
||||
let dev_ref = device.clone();
|
||||
let op = self.handler.introspect.get_source_info_by_name(
|
||||
name,
|
||||
move |sink_list: ListResult<&introspect::SourceInfo>| {
|
||||
if let ListResult::Item(item) = sink_list {
|
||||
dev_ref.borrow_mut().as_mut().unwrap().replace(item.into());
|
||||
}
|
||||
},
|
||||
);
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let mut result = device.borrow_mut();
|
||||
result.take().unwrap().ok_or(ControllerError::new(
|
||||
GetInfoError,
|
||||
"Error getting application list",
|
||||
))
|
||||
}
|
||||
|
||||
fn set_device_volume_by_index(&mut self, index: u32, volume: &ChannelVolumes) {
|
||||
let op = self
|
||||
.handler
|
||||
.introspect
|
||||
.set_source_volume_by_index(index, volume, None);
|
||||
self.handler.wait_for_operation(op).ok();
|
||||
}
|
||||
fn set_device_volume_by_name(&mut self, name: &str, volume: &ChannelVolumes) {
|
||||
let op = self
|
||||
.handler
|
||||
.introspect
|
||||
.set_source_volume_by_name(name, volume, None);
|
||||
self.handler.wait_for_operation(op).ok();
|
||||
}
|
||||
fn increase_device_volume_by_percent(&mut self, index: u32, delta: f64) {
|
||||
if let Ok(mut dev_ref) = self.get_device_by_index(index) {
|
||||
let new_vol = Volume::from(Volume(volume_from_percent(delta) as u32));
|
||||
if let Some(volumes) = dev_ref.volume.increase(new_vol) {
|
||||
let op = self
|
||||
.handler
|
||||
.introspect
|
||||
.set_source_volume_by_index(index, &volumes, None);
|
||||
self.handler.wait_for_operation(op).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
fn decrease_device_volume_by_percent(&mut self, index: u32, delta: f64) {
|
||||
if let Ok(mut dev_ref) = self.get_device_by_index(index) {
|
||||
let new_vol = Volume::from(Volume(volume_from_percent(delta) as u32));
|
||||
if let Some(volumes) = dev_ref.volume.decrease(new_vol) {
|
||||
let op = self
|
||||
.handler
|
||||
.introspect
|
||||
.set_source_volume_by_index(index, &volumes, None);
|
||||
self.handler.wait_for_operation(op).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppControl<ApplicationInfo> for SourceController {
|
||||
fn list_applications(&mut self) -> Result<Vec<ApplicationInfo>, ControllerError> {
|
||||
let list = Rc::new(RefCell::new(Some(Vec::new())));
|
||||
let list_ref = list.clone();
|
||||
|
||||
let op = self.handler.introspect.get_source_output_info_list(
|
||||
move |sink_list: ListResult<&introspect::SourceOutputInfo>| {
|
||||
if let ListResult::Item(item) = sink_list {
|
||||
list_ref.borrow_mut().as_mut().unwrap().push(item.into());
|
||||
}
|
||||
},
|
||||
);
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let mut result = list.borrow_mut();
|
||||
result.take().ok_or(ControllerError::new(
|
||||
GetInfoError,
|
||||
"Error getting application list",
|
||||
))
|
||||
}
|
||||
|
||||
fn get_app_by_index(&mut self, index: u32) -> Result<ApplicationInfo, ControllerError> {
|
||||
let app = Rc::new(RefCell::new(Some(None)));
|
||||
let app_ref = app.clone();
|
||||
let op = self.handler.introspect.get_source_output_info(
|
||||
index,
|
||||
move |sink_list: ListResult<&introspect::SourceOutputInfo>| {
|
||||
if let ListResult::Item(item) = sink_list {
|
||||
app_ref.borrow_mut().as_mut().unwrap().replace(item.into());
|
||||
}
|
||||
},
|
||||
);
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let mut result = app.borrow_mut();
|
||||
result.take().unwrap().ok_or(ControllerError::new(
|
||||
GetInfoError,
|
||||
"Error getting application list",
|
||||
))
|
||||
}
|
||||
|
||||
fn increase_app_volume_by_percent(&mut self, index: u32, delta: f64) {
|
||||
if let Ok(mut app_ref) = self.get_app_by_index(index) {
|
||||
let new_vol = Volume::from(Volume(volume_from_percent(delta) as u32));
|
||||
if let Some(volumes) = app_ref.volume.increase(new_vol) {
|
||||
let op = self
|
||||
.handler
|
||||
.introspect
|
||||
.set_source_output_volume(index, &volumes, None);
|
||||
self.handler.wait_for_operation(op).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decrease_app_volume_by_percent(&mut self, index: u32, delta: f64) {
|
||||
if let Ok(mut app_ref) = self.get_app_by_index(index) {
|
||||
let new_vol = Volume::from(Volume(volume_from_percent(delta) as u32));
|
||||
if let Some(volumes) = app_ref.volume.decrease(new_vol) {
|
||||
let op = self
|
||||
.handler
|
||||
.introspect
|
||||
.set_source_output_volume(index, &volumes, None);
|
||||
self.handler.wait_for_operation(op).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn move_app_by_index(
|
||||
&mut self,
|
||||
stream_index: u32,
|
||||
device_index: u32,
|
||||
) -> Result<bool, ControllerError> {
|
||||
let success = Rc::new(RefCell::new(false));
|
||||
let success_ref = success.clone();
|
||||
let op = self.handler.introspect.move_source_output_by_index(
|
||||
stream_index,
|
||||
device_index,
|
||||
Some(Box::new(move |res| {
|
||||
success_ref.borrow_mut().clone_from(&res)
|
||||
})),
|
||||
);
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let result = success.borrow_mut().clone();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn move_app_by_name(
|
||||
&mut self,
|
||||
stream_index: u32,
|
||||
device_name: &str,
|
||||
) -> Result<bool, ControllerError> {
|
||||
let success = Rc::new(RefCell::new(false));
|
||||
let success_ref = success.clone();
|
||||
let op = self.handler.introspect.move_source_output_by_name(
|
||||
stream_index,
|
||||
device_name,
|
||||
Some(Box::new(move |res| {
|
||||
success_ref.borrow_mut().clone_from(&res)
|
||||
})),
|
||||
);
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let result = success.borrow_mut().clone();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn set_app_mute(&mut self, index: u32, mute: bool) -> Result<bool, ControllerError> {
|
||||
let success = Rc::new(RefCell::new(false));
|
||||
let success_ref = success.clone();
|
||||
let op = self.handler.introspect.set_source_mute_by_index(
|
||||
index,
|
||||
mute,
|
||||
Some(Box::new(move |res| {
|
||||
success_ref.borrow_mut().clone_from(&res)
|
||||
})),
|
||||
);
|
||||
self.handler.wait_for_operation(op)?;
|
||||
let result = success.borrow_mut().clone();
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
354
libs/pulsectl/src/controllers/types.rs
Normal file
354
libs/pulsectl/src/controllers/types.rs
Normal file
@@ -0,0 +1,354 @@
|
||||
use pulse::{
|
||||
channelmap,
|
||||
context::introspect,
|
||||
def,
|
||||
def::PortAvailable,
|
||||
format,
|
||||
proplist::Proplist,
|
||||
sample,
|
||||
time::MicroSeconds,
|
||||
volume::{ChannelVolumes, Volume},
|
||||
};
|
||||
|
||||
/// These structs are direct representations of what libpulse_binding gives
|
||||
/// created to be copyable / cloneable for use in and out of callbacks
|
||||
|
||||
/// This is a wrapper around SinkPortInfo and SourcePortInfo as they have the same members
|
||||
#[derive(Clone)]
|
||||
pub struct DevicePortInfo {
|
||||
/// Name of the sink.
|
||||
pub name: Option<String>,
|
||||
/// Description of this sink.
|
||||
pub description: Option<String>,
|
||||
/// The higher this value is, the more useful this port is as a default.
|
||||
pub priority: u32,
|
||||
/// A flag indicating availability status of this port.
|
||||
pub available: PortAvailable,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Box<introspect::SinkPortInfo<'a>>> for DevicePortInfo {
|
||||
fn from(item: &'a Box<introspect::SinkPortInfo<'a>>) -> Self {
|
||||
DevicePortInfo {
|
||||
name: item.name.as_ref().map(|cow| cow.to_string()),
|
||||
description: item.description.as_ref().map(|cow| cow.to_string()),
|
||||
priority: item.priority,
|
||||
available: item.available,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a introspect::SinkPortInfo<'a>> for DevicePortInfo {
|
||||
fn from(item: &'a introspect::SinkPortInfo<'a>) -> Self {
|
||||
DevicePortInfo {
|
||||
name: item.name.as_ref().map(|cow| cow.to_string()),
|
||||
description: item.description.as_ref().map(|cow| cow.to_string()),
|
||||
priority: item.priority,
|
||||
available: item.available,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Box<introspect::SourcePortInfo<'a>>> for DevicePortInfo {
|
||||
fn from(item: &'a Box<introspect::SourcePortInfo<'a>>) -> Self {
|
||||
DevicePortInfo {
|
||||
name: item.name.as_ref().map(|cow| cow.to_string()),
|
||||
description: item.description.as_ref().map(|cow| cow.to_string()),
|
||||
priority: item.priority,
|
||||
available: item.available,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a introspect::SourcePortInfo<'a>> for DevicePortInfo {
|
||||
fn from(item: &'a introspect::SourcePortInfo<'a>) -> Self {
|
||||
DevicePortInfo {
|
||||
name: item.name.as_ref().map(|cow| cow.to_string()),
|
||||
description: item.description.as_ref().map(|cow| cow.to_string()),
|
||||
priority: item.priority,
|
||||
available: item.available,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a wrapper around SinkState and SourceState as they have the same values
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum DevState {
|
||||
/// This state is used when the server does not support sink state introspection.
|
||||
Invalid = -1,
|
||||
/// Running, sink is playing and used by at least one non-corked sink-input.
|
||||
Running = 0,
|
||||
/// When idle, the sink is playing but there is no non-corked sink-input attached to it.
|
||||
Idle = 1,
|
||||
/// When suspended, actual sink access can be closed, for instance.
|
||||
Suspended = 2,
|
||||
}
|
||||
|
||||
impl<'a> From<def::SourceState> for DevState {
|
||||
fn from(s: def::SourceState) -> Self {
|
||||
match s {
|
||||
def::SourceState::Idle => DevState::Idle,
|
||||
def::SourceState::Invalid => DevState::Invalid,
|
||||
def::SourceState::Running => DevState::Running,
|
||||
def::SourceState::Suspended => DevState::Suspended,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<def::SinkState> for DevState {
|
||||
fn from(s: def::SinkState) -> Self {
|
||||
match s {
|
||||
def::SinkState::Idle => DevState::Idle,
|
||||
def::SinkState::Invalid => DevState::Invalid,
|
||||
def::SinkState::Running => DevState::Running,
|
||||
def::SinkState::Suspended => DevState::Suspended,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Flags {
|
||||
SourceFLags(def::SourceFlagSet),
|
||||
SinkFlags(def::SinkFlagSet),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DeviceInfo {
|
||||
/// Index of the sink.
|
||||
pub index: u32,
|
||||
/// Name of the sink.
|
||||
pub name: Option<String>,
|
||||
/// Description of this sink.
|
||||
pub description: Option<String>,
|
||||
/// Sample spec of this sink.
|
||||
pub sample_spec: sample::Spec,
|
||||
/// Channel map.
|
||||
pub channel_map: channelmap::Map,
|
||||
/// Index of the owning module of this sink, or `None` if is invalid.
|
||||
pub owner_module: Option<u32>,
|
||||
/// Volume of the sink.
|
||||
pub volume: ChannelVolumes,
|
||||
/// Mute switch of the sink.
|
||||
pub mute: bool,
|
||||
/// Index of the monitor source connected to this sink.
|
||||
pub monitor: Option<u32>,
|
||||
/// The name of the monitor source.
|
||||
pub monitor_name: Option<String>,
|
||||
/// Length of queued audio in the output buffer.
|
||||
pub latency: MicroSeconds,
|
||||
/// Driver name.
|
||||
pub driver: Option<String>,
|
||||
/// Flags.
|
||||
pub flags: Flags,
|
||||
/// Property list.
|
||||
pub proplist: Proplist,
|
||||
/// The latency this device has been configured to.
|
||||
pub configured_latency: MicroSeconds,
|
||||
/// Some kind of “base” volume that refers to unamplified/unattenuated volume in the context of
|
||||
/// the output device.
|
||||
pub base_volume: Volume,
|
||||
/// State.
|
||||
pub state: DevState,
|
||||
/// Number of volume steps for sinks which do not support arbitrary volumes.
|
||||
pub n_volume_steps: u32,
|
||||
/// Card index, or `None` if invalid.
|
||||
pub card: Option<u32>,
|
||||
/// Set of available ports.
|
||||
pub ports: Vec<DevicePortInfo>,
|
||||
// Pointer to active port in the set, or None.
|
||||
pub active_port: Option<DevicePortInfo>,
|
||||
/// Set of formats supported by the sink.
|
||||
pub formats: Vec<format::Info>,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a introspect::SinkInfo<'a>> for DeviceInfo {
|
||||
fn from(item: &'a introspect::SinkInfo<'a>) -> Self {
|
||||
DeviceInfo {
|
||||
name: item.name.as_ref().map(|cow| cow.to_string()),
|
||||
index: item.index,
|
||||
description: item.description.as_ref().map(|cow| cow.to_string()),
|
||||
sample_spec: item.sample_spec,
|
||||
channel_map: item.channel_map,
|
||||
owner_module: item.owner_module,
|
||||
volume: item.volume,
|
||||
mute: item.mute,
|
||||
monitor: Some(item.monitor_source),
|
||||
monitor_name: item.monitor_source_name.as_ref().map(|cow| cow.to_string()),
|
||||
latency: item.latency,
|
||||
driver: item.driver.as_ref().map(|cow| cow.to_string()),
|
||||
flags: Flags::SinkFlags(item.flags),
|
||||
proplist: item.proplist.clone(),
|
||||
configured_latency: item.configured_latency,
|
||||
base_volume: item.base_volume,
|
||||
state: DevState::from(item.state),
|
||||
n_volume_steps: item.n_volume_steps,
|
||||
card: item.card,
|
||||
ports: item.ports.iter().map(From::from).collect(),
|
||||
active_port: item.active_port.as_ref().map(From::from),
|
||||
formats: item.formats.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a introspect::SourceInfo<'a>> for DeviceInfo {
|
||||
fn from(item: &'a introspect::SourceInfo<'a>) -> Self {
|
||||
DeviceInfo {
|
||||
name: item.name.as_ref().map(|cow| cow.to_string()),
|
||||
index: item.index,
|
||||
description: item.description.as_ref().map(|cow| cow.to_string()),
|
||||
sample_spec: item.sample_spec,
|
||||
channel_map: item.channel_map,
|
||||
owner_module: item.owner_module,
|
||||
volume: item.volume,
|
||||
mute: item.mute,
|
||||
monitor: item.monitor_of_sink,
|
||||
monitor_name: item
|
||||
.monitor_of_sink_name
|
||||
.as_ref()
|
||||
.map(|cow| cow.to_string()),
|
||||
latency: item.latency,
|
||||
driver: item.driver.as_ref().map(|cow| cow.to_string()),
|
||||
flags: Flags::SourceFLags(item.flags),
|
||||
proplist: item.proplist.clone(),
|
||||
configured_latency: item.configured_latency,
|
||||
base_volume: item.base_volume,
|
||||
state: DevState::from(item.state),
|
||||
n_volume_steps: item.n_volume_steps,
|
||||
card: item.card,
|
||||
ports: item.ports.iter().map(From::from).collect(),
|
||||
active_port: item.active_port.as_ref().map(From::from),
|
||||
formats: item.formats.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ApplicationInfo {
|
||||
/// Index of the sink input.
|
||||
pub index: u32,
|
||||
/// Name of the sink input.
|
||||
pub name: Option<String>,
|
||||
/// Index of the module this sink input belongs to, or `None` when it does not belong to any
|
||||
/// module.
|
||||
pub owner_module: Option<u32>,
|
||||
/// Index of the client this sink input belongs to, or invalid when it does not belong to any
|
||||
/// client.
|
||||
pub client: Option<u32>,
|
||||
/// Index of the connected sink/source.
|
||||
pub connection_id: u32,
|
||||
/// The sample specification of the sink input.
|
||||
pub sample_spec: sample::Spec,
|
||||
/// Channel map.
|
||||
pub channel_map: channelmap::Map,
|
||||
/// The volume of this sink input.
|
||||
pub volume: ChannelVolumes,
|
||||
/// Latency due to buffering in sink input, see
|
||||
/// [`def::TimingInfo`](../../def/struct.TimingInfo.html) for details.
|
||||
pub buffer_usec: MicroSeconds,
|
||||
/// Latency of the sink device, see
|
||||
/// [`def::TimingInfo`](../../def/struct.TimingInfo.html) for details.
|
||||
pub connection_usec: MicroSeconds,
|
||||
/// The resampling method used by this sink input.
|
||||
pub resample_method: Option<String>,
|
||||
/// Driver name.
|
||||
pub driver: Option<String>,
|
||||
/// Stream muted.
|
||||
pub mute: bool,
|
||||
/// Property list.
|
||||
pub proplist: Proplist,
|
||||
/// Stream corked.
|
||||
pub corked: bool,
|
||||
/// Stream has volume. If not set, then the meaning of this struct’s volume member is unspecified.
|
||||
pub has_volume: bool,
|
||||
/// The volume can be set. If not set, the volume can still change even though clients can’t
|
||||
/// control the volume.
|
||||
pub volume_writable: bool,
|
||||
/// Stream format information.
|
||||
pub format: format::Info,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a introspect::SinkInputInfo<'a>> for ApplicationInfo {
|
||||
fn from(item: &'a introspect::SinkInputInfo<'a>) -> Self {
|
||||
ApplicationInfo {
|
||||
index: item.index,
|
||||
name: item.name.as_ref().map(|cow| cow.to_string()),
|
||||
owner_module: item.owner_module,
|
||||
client: item.client,
|
||||
connection_id: item.sink,
|
||||
sample_spec: item.sample_spec,
|
||||
channel_map: item.channel_map,
|
||||
volume: item.volume,
|
||||
buffer_usec: item.buffer_usec,
|
||||
connection_usec: item.sink_usec,
|
||||
resample_method: item.resample_method.as_ref().map(|cow| cow.to_string()),
|
||||
driver: item.driver.as_ref().map(|cow| cow.to_string()),
|
||||
mute: item.mute,
|
||||
proplist: item.proplist.clone(),
|
||||
corked: item.corked,
|
||||
has_volume: item.has_volume,
|
||||
volume_writable: item.volume_writable,
|
||||
format: item.format.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a introspect::SourceOutputInfo<'a>> for ApplicationInfo {
|
||||
fn from(item: &'a introspect::SourceOutputInfo<'a>) -> Self {
|
||||
ApplicationInfo {
|
||||
index: item.index,
|
||||
name: item.name.as_ref().map(|cow| cow.to_string()),
|
||||
owner_module: item.owner_module,
|
||||
client: item.client,
|
||||
connection_id: item.source,
|
||||
sample_spec: item.sample_spec,
|
||||
channel_map: item.channel_map,
|
||||
volume: item.volume,
|
||||
buffer_usec: item.buffer_usec,
|
||||
connection_usec: item.source_usec,
|
||||
resample_method: item.resample_method.as_ref().map(|cow| cow.to_string()),
|
||||
driver: item.driver.as_ref().map(|cow| cow.to_string()),
|
||||
mute: item.mute,
|
||||
proplist: item.proplist.clone(),
|
||||
corked: item.corked,
|
||||
has_volume: item.has_volume,
|
||||
volume_writable: item.volume_writable,
|
||||
format: item.format.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServerInfo {
|
||||
/// User name of the daemon process.
|
||||
pub user_name: Option<String>,
|
||||
/// Host name the daemon is running on.
|
||||
pub host_name: Option<String>,
|
||||
/// Version string of the daemon.
|
||||
pub server_version: Option<String>,
|
||||
/// Server package name (usually “pulseaudio”).
|
||||
pub server_name: Option<String>,
|
||||
/// Default sample specification.
|
||||
pub sample_spec: sample::Spec,
|
||||
/// Name of default sink.
|
||||
pub default_sink_name: Option<String>,
|
||||
/// Name of default source.
|
||||
pub default_source_name: Option<String>,
|
||||
/// A random cookie for identifying this instance of PulseAudio.
|
||||
pub cookie: u32,
|
||||
/// Default channel map.
|
||||
pub channel_map: channelmap::Map,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a introspect::ServerInfo<'a>> for ServerInfo {
|
||||
fn from(info: &'a introspect::ServerInfo<'a>) -> Self {
|
||||
ServerInfo {
|
||||
user_name: info.user_name.as_ref().map(|cow| cow.to_string()),
|
||||
host_name: info.host_name.as_ref().map(|cow| cow.to_string()),
|
||||
server_version: info.server_version.as_ref().map(|cow| cow.to_string()),
|
||||
server_name: info.server_name.as_ref().map(|cow| cow.to_string()),
|
||||
sample_spec: info.sample_spec,
|
||||
default_sink_name: info.default_sink_name.as_ref().map(|cow| cow.to_string()),
|
||||
default_source_name: info.default_source_name.as_ref().map(|cow| cow.to_string()),
|
||||
cookie: info.cookie,
|
||||
channel_map: info.channel_map,
|
||||
}
|
||||
}
|
||||
}
|
||||
54
libs/pulsectl/src/errors.rs
Normal file
54
libs/pulsectl/src/errors.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use std::fmt;
|
||||
|
||||
use pulse::error::{PAErr};
|
||||
|
||||
impl From<PAErr> for PulseCtlError {
|
||||
fn from(error: PAErr) -> Self {
|
||||
PulseCtlError {
|
||||
error: PulseCtlErrorType::PulseAudioError,
|
||||
message: format!("PulseAudio returned error: {}", error.to_string().unwrap_or("Unknown".to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for PulseCtlError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut error_string = String::new();
|
||||
match self.error {
|
||||
PulseCtlErrorType::ConnectError => {
|
||||
error_string.push_str("ConnectError");
|
||||
}
|
||||
PulseCtlErrorType::OperationError => {
|
||||
error_string.push_str("OperationError");
|
||||
}
|
||||
PulseCtlErrorType::PulseAudioError => {
|
||||
error_string.push_str("PulseAudioError");
|
||||
}
|
||||
}
|
||||
write!(f, "[{}]: {}", error_string, self.message)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum PulseCtlErrorType {
|
||||
ConnectError,
|
||||
OperationError,
|
||||
PulseAudioError,
|
||||
}
|
||||
|
||||
/// Error thrown when PulseAudio throws an error code, there are 3 variants
|
||||
/// `PulseCtlErrorType::ConnectError` when there's an error establishing a connection
|
||||
/// `PulseCtlErrorType::OperationError` when the requested operation quis unexpecdatly or is cancelled
|
||||
/// `PulseCtlErrorType::PulseAudioError` when PulseAudio returns an error code in any circumstance
|
||||
pub struct PulseCtlError {
|
||||
error: PulseCtlErrorType,
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl PulseCtlError {
|
||||
pub(crate) fn new(err: PulseCtlErrorType, msg: &str) -> Self {
|
||||
PulseCtlError {
|
||||
error: err,
|
||||
message: msg.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
166
libs/pulsectl/src/lib.rs
Normal file
166
libs/pulsectl/src/lib.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
/// `pulsectl` is a high level wrapper around the PulseAudio bindings supplied by
|
||||
/// `libpulse_binding`. It provides simple access to sinks, inputs, sources and outputs allowing
|
||||
/// one to write audio control programs with ease.
|
||||
///
|
||||
/// ## Quick Example
|
||||
///
|
||||
/// The following example demonstrates listing all of the playback devices currently connected
|
||||
///
|
||||
/// See examples/change_device_vol.rs for a more complete example
|
||||
/// ```no_run
|
||||
/// extern crate pulsectl;
|
||||
///
|
||||
/// use std::io;
|
||||
///
|
||||
/// use pulsectl::controllers::SinkController;
|
||||
/// use pulsectl::controllers::DeviceControl;
|
||||
/// fn main() {
|
||||
/// // create handler that calls functions on playback devices and apps
|
||||
/// let mut handler = SinkController::create().unwrap();
|
||||
/// let devices = handler
|
||||
/// .list_devices()
|
||||
/// .expect("Could not get list of playback devices");
|
||||
///
|
||||
/// println!("Playback Devices");
|
||||
/// for dev in devices.clone() {
|
||||
/// println!(
|
||||
/// "[{}] {}, Volume: {}",
|
||||
/// dev.index,
|
||||
/// dev.description.as_ref().unwrap(),
|
||||
/// dev.volume.print()
|
||||
/// );
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
extern crate libpulse_binding as pulse;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use pulse::{
|
||||
context::{introspect, Context},
|
||||
mainloop::standard::{IterateResult, Mainloop},
|
||||
operation::{Operation, State},
|
||||
proplist::Proplist,
|
||||
};
|
||||
|
||||
use crate::errors::{PulseCtlError, PulseCtlErrorType::*};
|
||||
|
||||
pub mod controllers;
|
||||
mod errors;
|
||||
|
||||
pub struct Handler {
|
||||
pub mainloop: Rc<RefCell<Mainloop>>,
|
||||
pub context: Rc<RefCell<Context>>,
|
||||
pub introspect: introspect::Introspector,
|
||||
}
|
||||
|
||||
fn connect_error(err: &str) -> PulseCtlError {
|
||||
PulseCtlError::new(ConnectError, err)
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
pub fn connect(name: &str) -> Result<Handler, PulseCtlError> {
|
||||
let mut proplist = Proplist::new().unwrap();
|
||||
proplist
|
||||
.set_str(pulse::proplist::properties::APPLICATION_NAME, name)
|
||||
.unwrap();
|
||||
|
||||
let mainloop;
|
||||
if let Some(m) = Mainloop::new() {
|
||||
mainloop = Rc::new(RefCell::new(m));
|
||||
} else {
|
||||
return Err(connect_error("Failed to create mainloop"));
|
||||
}
|
||||
|
||||
let context;
|
||||
if let Some(c) =
|
||||
Context::new_with_proplist(mainloop.borrow().deref(), "MainConn", &proplist)
|
||||
{
|
||||
context = Rc::new(RefCell::new(c));
|
||||
} else {
|
||||
return Err(connect_error("Failed to create new context"));
|
||||
}
|
||||
|
||||
context
|
||||
.borrow_mut()
|
||||
.connect(None, pulse::context::flags::NOFLAGS, None)
|
||||
.map_err(|_| connect_error("Failed to connect context"))?;
|
||||
|
||||
loop {
|
||||
match mainloop.borrow_mut().iterate(false) {
|
||||
IterateResult::Err(e) => {
|
||||
eprintln!("iterate state was not success, quitting...");
|
||||
return Err(e.into());
|
||||
}
|
||||
IterateResult::Success(_) => {}
|
||||
IterateResult::Quit(_) => {
|
||||
eprintln!("iterate state was not success, quitting...");
|
||||
return Err(PulseCtlError::new(
|
||||
ConnectError,
|
||||
"Iterate state quit without an error",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
match context.borrow().get_state() {
|
||||
pulse::context::State::Ready => break,
|
||||
pulse::context::State::Failed | pulse::context::State::Terminated => {
|
||||
eprintln!("context state failed/terminated, quitting...");
|
||||
return Err(PulseCtlError::new(
|
||||
ConnectError,
|
||||
"Context state failed/terminated without an error",
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let introspect = context.borrow_mut().introspect();
|
||||
Ok(Handler {
|
||||
mainloop,
|
||||
context,
|
||||
introspect,
|
||||
})
|
||||
}
|
||||
|
||||
// loop until the passed operation is completed
|
||||
pub fn wait_for_operation<G: ?Sized>(
|
||||
&mut self,
|
||||
op: Operation<G>,
|
||||
) -> Result<(), errors::PulseCtlError> {
|
||||
loop {
|
||||
match self.mainloop.borrow_mut().iterate(false) {
|
||||
IterateResult::Err(e) => return Err(e.into()),
|
||||
IterateResult::Success(_) => {}
|
||||
IterateResult::Quit(_) => {
|
||||
return Err(PulseCtlError::new(
|
||||
OperationError,
|
||||
"Iterate state quit without an error",
|
||||
));
|
||||
}
|
||||
}
|
||||
match op.get_state() {
|
||||
State::Done => {
|
||||
break;
|
||||
}
|
||||
State::Running => {}
|
||||
State::Cancelled => {
|
||||
return Err(PulseCtlError::new(
|
||||
OperationError,
|
||||
"Operation cancelled without an error",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Handler {
|
||||
fn drop(&mut self) {
|
||||
self.context.borrow_mut().disconnect();
|
||||
self.mainloop.borrow_mut().quit(pulse::def::Retval(0));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user