Change architecture to controller-centered arch

struct PipewireConnection is now decoupled from any other components, another component (the controller)
can receive updates by registering a callback.

struct PipewireState has been refactored to a struct Controller.
It still keeps state and manages the view, but now also actively requests updates from the pipewire connection via callback.
This commit is contained in:
Tom A. Wagner
2021-03-28 12:10:13 +02:00
parent d75dee5ea8
commit 9519eefa6e
5 changed files with 188 additions and 98 deletions

View File

@@ -1,7 +1,9 @@
use crate::pipewire_state::PipewireState;
use gtk::glib::{self, clone};
use libspa::ForeignDict;
use log::trace;
use once_cell::unsync::OnceCell;
use pipewire as pw;
use pw::registry::GlobalObject;
use std::{
cell::{Cell, RefCell},
@@ -9,59 +11,79 @@ use std::{
};
/// This struct is responsible for communication with the pipewire server.
/// It handles new globals appearing as well as globals being removed.
/// The owner of this struct can subscribe to notifications for globals added or removed.
///
/// It's `roundtrip` function must be called regularly to receive updates.
pub struct PipewireConnection {
mainloop: pw::MainLoop,
_context: pw::Context<pw::MainLoop>,
core: Rc<pw::Core>,
_registry: pw::registry::Registry,
_listeners: pw::registry::Listener,
_state: Rc<RefCell<PipewireState>>,
core: pw::Core,
registry: pw::registry::Registry,
listeners: OnceCell<pw::registry::Listener>,
on_global_add: Option<Box<dyn Fn(&GlobalObject<ForeignDict>)>>,
on_global_remove: Option<Box<dyn Fn(u32)>>,
}
impl PipewireConnection {
pub fn new(state: PipewireState) -> Result<Self, String> {
/// Create a new Pipewire Connection.
///
/// This returns an `Rc`, because weak references to the result are needed inside closures set up during creation.
pub fn new() -> Result<Rc<RefCell<Self>>, pw::Error> {
// Initialize pipewire lib and obtain needed pipewire objects.
pw::init();
let mainloop = pw::MainLoop::new().map_err(|_| "Failed to create pipewire mainloop!")?;
let context =
pw::Context::new(&mainloop).map_err(|_| "Failed to create pipewire context")?;
let core = Rc::new(
context
.connect(None)
.map_err(|_| "Failed to connect to pipewire core")?,
);
let registry = core
.get_registry()
.map_err(|_| "Failed to get pipewire registry")?;
let mainloop = pw::MainLoop::new()?;
let context = pw::Context::new(&mainloop)?;
let core = context.connect(None)?;
let registry = core.get_registry()?;
let state = Rc::new(RefCell::new(state));
// Notify state on globals added / removed
let _listeners = registry
.add_listener_local()
.global(clone!(@weak state => @default-panic, move |global| {
state.borrow_mut().global(global);
}))
.global_remove(clone!(@weak state => @default-panic, move |id| {
state.borrow_mut().global_remove(id);
}))
.register();
Ok(Self {
let result = Rc::new(RefCell::new(Self {
mainloop,
_context: context,
core,
_registry: registry,
_listeners,
_state: state,
})
registry,
listeners: OnceCell::new(),
on_global_add: None,
on_global_remove: None,
}));
// Notify state on globals added / removed
let listeners = result
.borrow()
.registry
.add_listener_local()
.global(clone!(@weak result as this => move |global| {
trace!("Global is added: {}", global.id);
let con = this.borrow();
if let Some(callback) = con.on_global_add.as_ref() {
callback(global)
} else {
trace!("No on_global_add callback registered");
}
}))
.global_remove(clone!(@weak result as this => move |id| {
trace!("Global is removed: {}", id);
let con = this.borrow();
if let Some(callback) = con.on_global_remove.as_ref() {
callback(id)
} else {
trace!("No on_global_remove callback registered");
}
}))
.register();
// Makeshift `expect()`: listeners does not implement `Debug`, so we can not use `expect`.
assert!(
result.borrow_mut().listeners.set(listeners).is_ok(),
"PipewireConnection.listeners field already set"
);
Ok(result)
}
/// Receive all events from the pipewire server, sending them to the `pipewire_state` struct for processing.
pub fn roundtrip(&self) {
trace!("Starting roundtrip");
let done = Rc::new(Cell::new(false));
let pending = self
.core
@@ -85,5 +107,17 @@ impl PipewireConnection {
while !done.get() {
self.mainloop.run();
}
trace!("Roundtrip finished");
}
/// Set or unset a callback that gets called when a new global is added.
pub fn on_global_add(&mut self, callback: Option<Box<dyn Fn(&GlobalObject<ForeignDict>)>>) {
self.on_global_add = callback;
}
/// Set or unset a callback that gets called when a global is removed.
pub fn on_global_remove(&mut self, callback: Option<Box<dyn Fn(u32)>>) {
self.on_global_remove = callback;
}
}