pulsectl/
lib.rs

1//! `pulsectl` is a high level wrapper around the PulseAudio bindings supplied by
2//! `libpulse-binding` to make application development easier. It provides simple access to sinks,
3//! inputs, sources and outputs allowing one to write audio control programs with ease. This
4//! library is only capable of modifying PulseAudio data (e.g., changing volume, routing
5//! applications, and muting).
6//!
7//! This is a fork of [` JojiiOfficial/pulsectl-rust-fork`](https://github.com/JojiiOfficial/pulsectl)
8//! which is then a fork of [`krruzic/pulsectl-rust`](https://github.com/krruzic/pulsectl).
9//!
10//! # Example
11//!
12//! List all currently connected playback devices.
13//!
14//! ```no_run
15//! use pulsectl::controllers::SinkController;
16//! use pulsectl::controllers::DeviceControl;
17//!
18//! // create handler that calls functions on playback devices and apps
19//! let mut handler = SinkController::create().unwrap();
20//!
21//! let devices = handler
22//!     .list_devices()
23//!     .expect("Could not get list of playback devices.");
24//!
25//! println!("Playback Devices: ");
26//! for dev in devices.clone() {
27//!     println!(
28//!         "[{}] {}, Volume: {}",
29//!         dev.index,
30//!         dev.description.as_ref().unwrap(),
31//!         dev.volume.print()
32//!     );
33//! }
34//! ```
35//!
36//! For more detailed examples, check the `examples` directory.
37
38pub mod controllers;
39mod error;
40
41use pulse::{
42    context::{introspect::Introspector, Context},
43    mainloop::standard::{IterateResult, Mainloop},
44    operation::{Operation, State},
45    proplist::Proplist,
46};
47
48use std::cell::RefCell;
49use std::ops::Deref;
50use std::rc::Rc;
51
52pub use crate::controllers::error::ControllerError;
53pub use crate::error::Error;
54
55/// The main wrapper of libpulse-binding API.
56pub struct Handler {
57    /// Safe interface to the internal PA Mainloop.
58    pub mainloop: Rc<RefCell<Mainloop>>,
59    /// An opaque connection context to a daemon.
60    pub context: Rc<RefCell<Context>>,
61    /// A wrapper object providing introspection routines to a context.
62    pub introspect: Introspector,
63}
64
65impl Handler {
66    pub fn connect(name: &str) -> Result<Handler, Error> {
67        let mut proplist = Proplist::new().unwrap();
68        proplist
69            .set_str(pulse::proplist::properties::APPLICATION_NAME, name)
70            .unwrap();
71
72        let mainloop = match Mainloop::new() {
73            Some(mainloop) => Rc::new(RefCell::new(mainloop)),
74            None => return Err(Error::Connect("Failed to create mainloop".to_string())),
75        };
76
77        let context =
78            match Context::new_with_proplist(mainloop.borrow().deref(), "MainConn", &proplist) {
79                Some(context) => Rc::new(RefCell::new(context)),
80                None => return Err(Error::Connect("Failed to create new context".to_string())),
81            };
82
83        context
84            .borrow_mut()
85            .connect(None, pulse::context::FlagSet::NOFLAGS, None)
86            .map_err(|_| Error::Connect("Failed to connect context".to_string()))?;
87
88        loop {
89            match mainloop.borrow_mut().iterate(false) {
90                IterateResult::Err(e) => {
91                    eprintln!("iterate state was not success, quitting...");
92                    return Err(e.into());
93                }
94                IterateResult::Success(_) => {}
95                IterateResult::Quit(_) => {
96                    eprintln!("iterate state was not success, quitting...");
97                    return Err(Error::Connect(
98                        "Iterate state quit without an error".to_string(),
99                    ));
100                }
101            }
102
103            match context.borrow().get_state() {
104                pulse::context::State::Ready => break,
105                pulse::context::State::Failed | pulse::context::State::Terminated => {
106                    eprintln!("context state failed/terminated, quitting...");
107                    return Err(Error::Connect(
108                        "Context state failed/terminated without an error".to_string(),
109                    ));
110                }
111                _ => {}
112            }
113        }
114
115        let introspect = context.borrow_mut().introspect();
116        Ok(Handler {
117            mainloop,
118            context,
119            introspect,
120        })
121    }
122
123    /// loops until the passed operation is completed
124    pub fn wait_for_operation<G: ?Sized>(&mut self, op: Operation<G>) -> Result<(), Error> {
125        loop {
126            match self.mainloop.borrow_mut().iterate(false) {
127                IterateResult::Err(e) => return Err(e.into()),
128                IterateResult::Success(_) => {}
129                IterateResult::Quit(_) => {
130                    return Err(Error::Operation(
131                        "Iterate state quit without an error".to_string(),
132                    ));
133                }
134            }
135            match op.get_state() {
136                State::Done => {
137                    break;
138                }
139                State::Running => {}
140                State::Cancelled => {
141                    return Err(Error::Operation(
142                        "Operation cancelled without an error".to_string(),
143                    ));
144                }
145            }
146        }
147        Ok(())
148    }
149}
150
151impl Drop for Handler {
152    fn drop(&mut self) {
153        self.context.borrow_mut().disconnect();
154        self.mainloop.borrow_mut().quit(pulse::def::Retval(0));
155    }
156}