Bindings to Pipewire Multimedia Framework

Hello folks,

I made a little tool to apply rules to a pipewire system:

GitHub - TristanCacqueray/pw-controller: Apply rules to pipewire links

The current implementation is processing the pw-mon command’s output, but I also started making real bindings using inline-c similar to the dear-imgui.hs bindings. I was able to implement the second tutorial: C versionHaskell version which demonstrates setting up a registry handler with a main loop that call a haskell function from a C callback.

The bindings are very simple and I think we could get a decent coverage without much efforts, so please let me know if you are interested!

16 Likes

Also, since this is my first C bindings, your feedback is much welcomed.

For example, to keep things simple, all the C structs are presently opaque pointers on the Haskell side without any Storable instances. Instead I’m using inline-c quasi quotes to implement custom readers. Here is how it looks for the spa_dict data (which are provided to the client callback as a struct pointer).

import Data.Vector qualified as V
import Data.Vector.Storable.Mutable qualified as VM
import Language.C.Inline qualified as C
import Pipewire.Context

C.context pwContext
C.include "<pipewire/pipewire.h>"

newtype SpaDict = SpaDict (Ptr SpaDictStruct)
data SpaDictStruct

-- | Read 'SpaDict' keys/values
spaDictRead :: SpaDict -> IO (V.Vector (Text, Text))
spaDictRead (SpaDict spaDict) = do
    -- Read the dictionary size
    propSize <-
        fromIntegral
            <$> [C.exp| int{$(struct spa_dict* spaDict)->n_items}|]

    -- Create two mutable vectors to store the key and value char* pointer.
    vecKey <- VM.new propSize
    vecValue <- VM.new propSize

    -- Fill the vectors
    [C.block| void {
      // The Haskell spaDict
      struct spa_dict *spa_dict = $(struct spa_dict* spaDict);

      // The Haskell vectors
      const char** keys = $vec-ptr:(const char **vecKey);
      const char** values = $vec-ptr:(const char **vecValue);

      // Use the provided 'spa_dict_for_each' macro to traverse the c struct
      const struct spa_dict_item *item;
      int item_pos = 0;
      spa_dict_for_each(item, spa_dict) {
        keys[item_pos] = item->key;
        values[item_pos] = item->value;
        item_pos += 1;
      };
    }|]

    -- Convert the CString vectors into a vector of Text (key,value) tuples
    let readCString pos = do
            key <- peekCString =<< VM.read vecKey pos
            val <- peekCString =<< VM.read vecValue pos
            pure (key, val)
    V.generateM propSize readCString

I think the bindings can go really far using such a custom API, and I wonder if this is a viable design.

Nice! I’ve got at least one project that currently shells out to pw-link and would benefit from a proper library.

Alright, I’ve implemented the third tutorial which demonstrates a server roundtrip. I’ll try to implement the pw-link features next.

3 Likes

The project now features a PwLink.hs demo that demonstrates how to list, create and delete links. The API is very much in progress as I am learning how to use the libpipewire, nevertheless, here is the current haddocks: Pipewire.hs module.

Based on this progress, I renamed the repository pipewire.hs,
Cheers :slight_smile:

3 Likes

I managed to implement a proof of concept binding for the real-time callbacks with these two demos:

This wasn’t supposed to work because memory allocations are forbidden in such callbacks, but it seems to run surprisingly well.

Based on this new knowledge, I am now looking forward to improving the API. In particular, I’m presently using the withWrapper pattern to ensure de-allocations, but this isn’t working well because complex actions need to setup event handlers during a callback. So, I think it’s time to look into using resourcet or a custom system to make this more ergonomic.

As always, your feedback is welcomed.
-Tristan

1 Like