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 version → Haskell 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
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