Crate pipewire

source ·
Expand description

Rust bindings for pipewire

pipewire is a crate offering a rustic bindings for libpipewire, the library for interacting with the pipewire server.

Programs that interact with pipewire usually react to events from the server by registering callbacks and invoke methods on objects on the server by calling methods on local proxy objects.

Getting started

Most programs that interact with pipewire will need the same few basic objects:

  • A MainLoop that drives the program, reacting to any incoming events and dispatching method calls. Most of a time, the program/thread will sit idle in this loop, waiting on events to occur.
  • A Context that keeps track of any pipewire resources.
  • A Core that is a proxy for the remote pipewire instance, used to send messages to and receive events from the remote server.
  • Optionally, a Registry that can be used to manage and track available objects on the server.

This is how they can be created:

use pipewire::{main_loop::MainLoop, context::Context};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mainloop = MainLoop::new(None)?;
    let context = Context::new(&mainloop)?;
    let core = context.connect(None)?;
    let registry = core.get_registry()?;

    Ok(())
}

Now you can start hooking up different kinds of callbacks to the objects to react to events, and call methods on objects to change the state of the remote.

use pipewire::{main_loop::MainLoop, context::Context};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mainloop = MainLoop::new(None)?;
    let context = Context::new(&mainloop)?;
    let core = context.connect(None)?;
    let registry = core.get_registry()?;

    // Register a callback to the `global` event on the registry, which notifies of any new global objects
    // appearing on the remote.
    // The callback will only get called as long as we keep the returned listener alive.
    let _listener = registry
        .add_listener_local()
        .global(|global| println!("New global: {:?}", global))
        .register();

    // Calling the `destroy_global` method on the registry will destroy the object with the specified id on the remote.
    // We don't have a specific object to destroy now, so this is commented out.
    // registry.destroy_global(313).into_result()?;

    mainloop.run();

    Ok(())
}

Note that registering any callback requires the closure to have the 'static lifetime, so if you need to capture any variables, use move || closures, and use std::rc::Rcs to access shared variables and some std::cell variant if you need to mutate them.

Also note that we called mainloop.run() at the end. This will enter the loop, and won’t return until we call mainloop.quit() from some event. If we didn’t run the loop, events and method invocations would not be processed, so the program would terminate without doing much.

The main loop

Sometimes, other stuff needs to be done even though we are waiting inside the main loop.
This can be done by adding sources to the loop.

For example, we can call a function on an interval:

use pipewire::main_loop::MainLoop;
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mainloop = MainLoop::new(None)?;

    let timer = mainloop.loop_().add_timer(|_| println!("Hello"));
    // Call the first time in half a second, and then in a one second interval.
    timer.update_timer(Some(Duration::from_millis(500)), Some(Duration::from_secs(1))).into_result()?;

    mainloop.run();

    Ok(())
}

This program will print out “Hello” every second forever.

Using similar methods, you can also react to IO or Signals, or call a callback whenever the loop is idle.

Multithreading

The pipewire library is not really thread-safe, so pipewire objects do not implement Send or Sync.

However, you can spawn a MainLoop in another thread and do bidirectional communication using two channels.

To send messages to the main thread, we can easily use a std::sync::mpsc. Because we are stuck in the main loop in the pipewire thread and can’t just block on receiving a message, we use a pipewire::channel instead.

See the pipewire::channel module for details.

Re-exports

Modules

Macros

  • A macro for creating a new Properties struct with predefined key-value pairs.

Enums

Functions

  • Deinitialize PipeWire
  • Initialize PipeWire