detached 7d5e644

feat: add public PipeWire and SPA wrappers

commit
7d5e644e81678fb4a44bc02a6edd59ca0f24c703
author
brice · · 2026-04-09 06:20 UTC
parent
67ff6c8
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
11 files · +319 -2
M internal/capi/register.go +50 -2
@@ -1,11 +1,59 @@
11 package capi
22
3-import "github.com/bnema/purego-pipewire/internal/loader"
3+import (
4+ "unsafe"
45
6+ "github.com/bnema/purego-pipewire/internal/loader"
7+ portout "github.com/bnema/purego-pipewire/internal/ports/out"
8+)
9+
10+// defaultImpl implements portout.CAPI by wrapping the registered functions.
11+type defaultImpl struct{}
12+
13+func (d defaultImpl) PWInit(argc *int32, argv ***byte) {
14+ pw_init(argc, argv)
15+}
16+
17+func (d defaultImpl) PWDeinit() {
18+ pw_deinit()
19+}
20+
21+func (d defaultImpl) PWMainLoopNew(props unsafe.Pointer) unsafe.Pointer {
22+ return pw_main_loop_new(props)
23+}
24+
25+func (d defaultImpl) PWMainLoopDestroy(loop unsafe.Pointer) {
26+ pw_main_loop_destroy(loop)
27+}
28+
29+func (d defaultImpl) PWMainLoopRun(loop unsafe.Pointer) int32 {
30+ return pw_main_loop_run(loop)
31+}
32+
33+var (
34+ defaultAPI portout.CAPI
35+ registered bool
36+)
37+
38+// Register loads the PipeWire libraries and registers all function bindings.
539 func Register() error {
40+ if registered {
41+ return nil
42+ }
643 h, err := loader.Open()
744 if err != nil {
845 return err
946 }
10- return registerAll(h)
47+ if err := registerAll(h); err != nil {
48+ return err
49+ }
50+ defaultAPI = defaultImpl{}
51+ registered = true
52+ return nil
53+}
54+
55+// Default returns the default CAPI instance after Register() has been called.
56+// Returns nil if Register() has not been called successfully.
57+func Default() portout.CAPI {
58+ return defaultAPI
1159 }
@@ -1,11 +1,59 @@
1 package capi1 package capi
2 2
3-import "github.com/bnema/purego-pipewire/internal/loader"3+import (
4+ "unsafe"
4 5
6+ "github.com/bnema/purego-pipewire/internal/loader"
7+ portout "github.com/bnema/purego-pipewire/internal/ports/out"
8+)
9+
10+// defaultImpl implements portout.CAPI by wrapping the registered functions.
11+type defaultImpl struct{}
12+
13+func (d defaultImpl) PWInit(argc *int32, argv ***byte) {
14+ pw_init(argc, argv)
15+}
16+
17+func (d defaultImpl) PWDeinit() {
18+ pw_deinit()
19+}
20+
21+func (d defaultImpl) PWMainLoopNew(props unsafe.Pointer) unsafe.Pointer {
22+ return pw_main_loop_new(props)
23+}
24+
25+func (d defaultImpl) PWMainLoopDestroy(loop unsafe.Pointer) {
26+ pw_main_loop_destroy(loop)
27+}
28+
29+func (d defaultImpl) PWMainLoopRun(loop unsafe.Pointer) int32 {
30+ return pw_main_loop_run(loop)
31+}
32+
33+var (
34+ defaultAPI portout.CAPI
35+ registered bool
36+)
37+
38+// Register loads the PipeWire libraries and registers all function bindings.
5 func Register() error {39 func Register() error {
40+ if registered {
41+ return nil
42+ }
6 h, err := loader.Open()43 h, err := loader.Open()
7 if err != nil {44 if err != nil {
8 return err45 return err
9 }46 }
10- return registerAll(h)47+ if err := registerAll(h); err != nil {
48+ return err
49+ }
50+ defaultAPI = defaultImpl{}
51+ registered = true
52+ return nil
53+}
54+
55+// Default returns the default CAPI instance after Register() has been called.
56+// Returns nil if Register() has not been called successfully.
57+func Default() portout.CAPI {
58+ return defaultAPI
11 }59 }
A pipewire/context.go +29 -0
@@ -0,0 +1,29 @@
1+package pipewire
2+
3+import (
4+ "github.com/bnema/purego-pipewire/internal/core"
5+)
6+
7+// Context is the public interface for PipeWire context operations.
8+// Contexts manage resources and connections to the PipeWire server.
9+type Context interface {
10+ Destroy()
11+ Connect() (Core, error)
12+}
13+
14+// contextWrapper wraps the internal core.Context to implement the public Context interface.
15+type contextWrapper struct {
16+ inner *core.Context
17+}
18+
19+func (cw contextWrapper) Destroy() {
20+ cw.inner.Destroy()
21+}
22+
23+func (cw contextWrapper) Connect() (Core, error) {
24+ c, err := cw.inner.Connect()
25+ if err != nil {
26+ return nil, err
27+ }
28+ return coreWrapper{inner: c}, nil
29+}
@@ -0,0 +1,29 @@
1+package pipewire
2+
3+import (
4+ "github.com/bnema/purego-pipewire/internal/core"
5+)
6+
7+// Context is the public interface for PipeWire context operations.
8+// Contexts manage resources and connections to the PipeWire server.
9+type Context interface {
10+ Destroy()
11+ Connect() (Core, error)
12+}
13+
14+// contextWrapper wraps the internal core.Context to implement the public Context interface.
15+type contextWrapper struct {
16+ inner *core.Context
17+}
18+
19+func (cw contextWrapper) Destroy() {
20+ cw.inner.Destroy()
21+}
22+
23+func (cw contextWrapper) Connect() (Core, error) {
24+ c, err := cw.inner.Connect()
25+ if err != nil {
26+ return nil, err
27+ }
28+ return coreWrapper{inner: c}, nil
29+}
A pipewire/core.go +40 -0
@@ -0,0 +1,40 @@
1+package pipewire
2+
3+import (
4+ "unsafe"
5+
6+ "github.com/bnema/purego-pipewire/internal/core"
7+)
8+
9+// Core is the public interface for PipeWire core connection operations.
10+// A Core represents a connection to the PipeWire server.
11+type Core interface {
12+ Disconnect()
13+ Registry() (Registry, error)
14+ NewStream(name string, props unsafe.Pointer) (Stream, error)
15+}
16+
17+// coreWrapper wraps the internal core.Core to implement the public Core interface.
18+type coreWrapper struct {
19+ inner *core.Core
20+}
21+
22+func (cw coreWrapper) Disconnect() {
23+ cw.inner.Disconnect()
24+}
25+
26+func (cw coreWrapper) Registry() (Registry, error) {
27+ r, err := cw.inner.Registry()
28+ if err != nil {
29+ return nil, err
30+ }
31+ return registryWrapper{inner: r}, nil
32+}
33+
34+func (cw coreWrapper) NewStream(name string, props unsafe.Pointer) (Stream, error) {
35+ s, err := cw.inner.NewStream(name, props)
36+ if err != nil {
37+ return nil, err
38+ }
39+ return streamWrapper{inner: s}, nil
40+}
@@ -0,0 +1,40 @@
1+package pipewire
2+
3+import (
4+ "unsafe"
5+
6+ "github.com/bnema/purego-pipewire/internal/core"
7+)
8+
9+// Core is the public interface for PipeWire core connection operations.
10+// A Core represents a connection to the PipeWire server.
11+type Core interface {
12+ Disconnect()
13+ Registry() (Registry, error)
14+ NewStream(name string, props unsafe.Pointer) (Stream, error)
15+}
16+
17+// coreWrapper wraps the internal core.Core to implement the public Core interface.
18+type coreWrapper struct {
19+ inner *core.Core
20+}
21+
22+func (cw coreWrapper) Disconnect() {
23+ cw.inner.Disconnect()
24+}
25+
26+func (cw coreWrapper) Registry() (Registry, error) {
27+ r, err := cw.inner.Registry()
28+ if err != nil {
29+ return nil, err
30+ }
31+ return registryWrapper{inner: r}, nil
32+}
33+
34+func (cw coreWrapper) NewStream(name string, props unsafe.Pointer) (Stream, error) {
35+ s, err := cw.inner.NewStream(name, props)
36+ if err != nil {
37+ return nil, err
38+ }
39+ return streamWrapper{inner: s}, nil
40+}
A pipewire/interfaces.go +14 -0
@@ -0,0 +1,14 @@
1+package pipewire
2+
3+// Runtime is the public interface for PipeWire runtime lifecycle management.
4+type Runtime interface {
5+ Init() error
6+ Deinit()
7+ NewMainLoop() (MainLoop, error)
8+}
9+
10+// MainLoop is the public interface for PipeWire main loop operations.
11+type MainLoop interface {
12+ Run()
13+ Destroy()
14+}
@@ -0,0 +1,14 @@
1+package pipewire
2+
3+// Runtime is the public interface for PipeWire runtime lifecycle management.
4+type Runtime interface {
5+ Init() error
6+ Deinit()
7+ NewMainLoop() (MainLoop, error)
8+}
9+
10+// MainLoop is the public interface for PipeWire main loop operations.
11+type MainLoop interface {
12+ Run()
13+ Destroy()
14+}
A pipewire/loop.go +4 -0
@@ -0,0 +1,4 @@
1+package pipewire
2+
3+// Loop provides additional main loop related types and constants.
4+// This file exists for future loop-related public API expansion.
@@ -0,0 +1,4 @@
1+package pipewire
2+
3+// Loop provides additional main loop related types and constants.
4+// This file exists for future loop-related public API expansion.
A pipewire/registry.go +27 -0
@@ -0,0 +1,27 @@
1+package pipewire
2+
3+import (
4+ "unsafe"
5+
6+ "github.com/bnema/purego-pipewire/internal/core"
7+)
8+
9+// Registry is the public interface for PipeWire registry operations.
10+// The registry is used to discover and bind to global objects.
11+type Registry interface {
12+ Destroy()
13+ Bind(name uint32, t string, version uint32) (unsafe.Pointer, error)
14+}
15+
16+// registryWrapper wraps the internal core.Registry to implement the public Registry interface.
17+type registryWrapper struct {
18+ inner *core.Registry
19+}
20+
21+func (rw registryWrapper) Destroy() {
22+ rw.inner.Destroy()
23+}
24+
25+func (rw registryWrapper) Bind(name uint32, t string, version uint32) (unsafe.Pointer, error) {
26+ return rw.inner.Bind(name, t, version)
27+}
@@ -0,0 +1,27 @@
1+package pipewire
2+
3+import (
4+ "unsafe"
5+
6+ "github.com/bnema/purego-pipewire/internal/core"
7+)
8+
9+// Registry is the public interface for PipeWire registry operations.
10+// The registry is used to discover and bind to global objects.
11+type Registry interface {
12+ Destroy()
13+ Bind(name uint32, t string, version uint32) (unsafe.Pointer, error)
14+}
15+
16+// registryWrapper wraps the internal core.Registry to implement the public Registry interface.
17+type registryWrapper struct {
18+ inner *core.Registry
19+}
20+
21+func (rw registryWrapper) Destroy() {
22+ rw.inner.Destroy()
23+}
24+
25+func (rw registryWrapper) Bind(name uint32, t string, version uint32) (unsafe.Pointer, error) {
26+ return rw.inner.Bind(name, t, version)
27+}
A pipewire/runtime.go +60 -0
@@ -0,0 +1,60 @@
1+package pipewire
2+
3+import (
4+ "errors"
5+
6+ "github.com/bnema/purego-pipewire/internal/capi"
7+ "github.com/bnema/purego-pipewire/internal/core"
8+)
9+
10+// runtimeWrapper wraps the internal core.Runtime to implement the public Runtime interface.
11+type runtimeWrapper struct {
12+ inner *core.Runtime
13+}
14+
15+func (rw runtimeWrapper) Init() error {
16+ return rw.inner.Init()
17+}
18+
19+func (rw runtimeWrapper) Deinit() {
20+ rw.inner.Deinit()
21+}
22+
23+func (rw runtimeWrapper) NewMainLoop() (MainLoop, error) {
24+ ml, err := rw.inner.NewMainLoop()
25+ if err != nil {
26+ return nil, err
27+ }
28+ return mainLoopWrapper{inner: ml}, nil
29+}
30+
31+// mainLoopWrapper wraps the internal core.MainLoop to implement the public MainLoop interface.
32+type mainLoopWrapper struct {
33+ inner *core.MainLoop
34+}
35+
36+func (mlw mainLoopWrapper) Run() {
37+ mlw.inner.Run()
38+}
39+
40+func (mlw mainLoopWrapper) Destroy() {
41+ mlw.inner.Destroy()
42+}
43+
44+// Init initializes the PipeWire runtime and returns a Runtime instance.
45+// This function must be called before using any other PipeWire functionality.
46+// Returns an error if the PipeWire libraries cannot be loaded.
47+func Init() (Runtime, error) {
48+ if err := capi.Register(); err != nil {
49+ return nil, err
50+ }
51+ capiInstance := capi.Default()
52+ if capiInstance == nil {
53+ return nil, errors.New("failed to get default CAPI instance")
54+ }
55+ r := core.NewRuntime(capiInstance)
56+ if err := r.Init(); err != nil {
57+ return nil, err
58+ }
59+ return runtimeWrapper{inner: r}, nil
60+}
@@ -0,0 +1,60 @@
1+package pipewire
2+
3+import (
4+ "errors"
5+
6+ "github.com/bnema/purego-pipewire/internal/capi"
7+ "github.com/bnema/purego-pipewire/internal/core"
8+)
9+
10+// runtimeWrapper wraps the internal core.Runtime to implement the public Runtime interface.
11+type runtimeWrapper struct {
12+ inner *core.Runtime
13+}
14+
15+func (rw runtimeWrapper) Init() error {
16+ return rw.inner.Init()
17+}
18+
19+func (rw runtimeWrapper) Deinit() {
20+ rw.inner.Deinit()
21+}
22+
23+func (rw runtimeWrapper) NewMainLoop() (MainLoop, error) {
24+ ml, err := rw.inner.NewMainLoop()
25+ if err != nil {
26+ return nil, err
27+ }
28+ return mainLoopWrapper{inner: ml}, nil
29+}
30+
31+// mainLoopWrapper wraps the internal core.MainLoop to implement the public MainLoop interface.
32+type mainLoopWrapper struct {
33+ inner *core.MainLoop
34+}
35+
36+func (mlw mainLoopWrapper) Run() {
37+ mlw.inner.Run()
38+}
39+
40+func (mlw mainLoopWrapper) Destroy() {
41+ mlw.inner.Destroy()
42+}
43+
44+// Init initializes the PipeWire runtime and returns a Runtime instance.
45+// This function must be called before using any other PipeWire functionality.
46+// Returns an error if the PipeWire libraries cannot be loaded.
47+func Init() (Runtime, error) {
48+ if err := capi.Register(); err != nil {
49+ return nil, err
50+ }
51+ capiInstance := capi.Default()
52+ if capiInstance == nil {
53+ return nil, errors.New("failed to get default CAPI instance")
54+ }
55+ r := core.NewRuntime(capiInstance)
56+ if err := r.Init(); err != nil {
57+ return nil, err
58+ }
59+ return runtimeWrapper{inner: r}, nil
60+}
A pipewire/runtime_test.go +13 -0
@@ -0,0 +1,13 @@
1+package pipewire
2+
3+import "testing"
4+
5+func TestInitReturnsRuntime(t *testing.T) {
6+ r, err := Init()
7+ if err != nil {
8+ t.Fatalf("Init returned error: %v", err)
9+ }
10+ if r == nil {
11+ t.Fatal("Init returned nil runtime")
12+ }
13+}
@@ -0,0 +1,13 @@
1+package pipewire
2+
3+import "testing"
4+
5+func TestInitReturnsRuntime(t *testing.T) {
6+ r, err := Init()
7+ if err != nil {
8+ t.Fatalf("Init returned error: %v", err)
9+ }
10+ if r == nil {
11+ t.Fatal("Init returned nil runtime")
12+ }
13+}
A pipewire/stream.go +32 -0
@@ -0,0 +1,32 @@
1+package pipewire
2+
3+import (
4+ "unsafe"
5+
6+ "github.com/bnema/purego-pipewire/internal/core"
7+)
8+
9+// Stream is the public interface for PipeWire stream operations.
10+// Streams are used to transfer audio/video data to and from PipeWire.
11+type Stream interface {
12+ Destroy()
13+ Connect(direction int, id uint32, flags uint32, params unsafe.Pointer) error
14+ Disconnect()
15+}
16+
17+// streamWrapper wraps the internal core.Stream to implement the public Stream interface.
18+type streamWrapper struct {
19+ inner *core.Stream
20+}
21+
22+func (sw streamWrapper) Destroy() {
23+ sw.inner.Destroy()
24+}
25+
26+func (sw streamWrapper) Connect(direction int, id uint32, flags uint32, params unsafe.Pointer) error {
27+ return sw.inner.Connect(direction, id, flags, params)
28+}
29+
30+func (sw streamWrapper) Disconnect() {
31+ sw.inner.Disconnect()
32+}
@@ -0,0 +1,32 @@
1+package pipewire
2+
3+import (
4+ "unsafe"
5+
6+ "github.com/bnema/purego-pipewire/internal/core"
7+)
8+
9+// Stream is the public interface for PipeWire stream operations.
10+// Streams are used to transfer audio/video data to and from PipeWire.
11+type Stream interface {
12+ Destroy()
13+ Connect(direction int, id uint32, flags uint32, params unsafe.Pointer) error
14+ Disconnect()
15+}
16+
17+// streamWrapper wraps the internal core.Stream to implement the public Stream interface.
18+type streamWrapper struct {
19+ inner *core.Stream
20+}
21+
22+func (sw streamWrapper) Destroy() {
23+ sw.inner.Destroy()
24+}
25+
26+func (sw streamWrapper) Connect(direction int, id uint32, flags uint32, params unsafe.Pointer) error {
27+ return sw.inner.Connect(direction, id, flags, params)
28+}
29+
30+func (sw streamWrapper) Disconnect() {
31+ sw.inner.Disconnect()
32+}
A spa/constants.go +18 -0
@@ -0,0 +1,18 @@
1+package spa
2+
3+// Media type constants for SPA media types.
4+const (
5+ MediaTypeUnknown = 0
6+ MediaTypeAudio = 1
7+ MediaTypeVideo = 2
8+ MediaTypeImage = 3
9+ MediaTypeBinary = 4
10+ MediaTypeStream = 5
11+ MediaTypeApplication = 6
12+)
13+
14+// Direction constants for port directions.
15+const (
16+ DirectionInput = 0
17+ DirectionOutput = 1
18+)
@@ -0,0 +1,18 @@
1+package spa
2+
3+// Media type constants for SPA media types.
4+const (
5+ MediaTypeUnknown = 0
6+ MediaTypeAudio = 1
7+ MediaTypeVideo = 2
8+ MediaTypeImage = 3
9+ MediaTypeBinary = 4
10+ MediaTypeStream = 5
11+ MediaTypeApplication = 6
12+)
13+
14+// Direction constants for port directions.
15+const (
16+ DirectionInput = 0
17+ DirectionOutput = 1
18+)
A spa/formats.go +32 -0
@@ -0,0 +1,32 @@
1+package spa
2+
3+// Format represents a SPA media format identifier.
4+type Format uint32
5+
6+// Standard SPA audio/video formats.
7+const (
8+ FormatUnknown Format = 0
9+ FormatEncoded Format = 1
10+ FormatS8 Format = 2
11+ FormatU8 Format = 3
12+ FormatS16LE Format = 4
13+ FormatS16BE Format = 5
14+ FormatU16LE Format = 6
15+ FormatU16BE Format = 7
16+ FormatS24LE Format = 8
17+ FormatS24BE Format = 9
18+ FormatU24LE Format = 10
19+ FormatU24BE Format = 11
20+ FormatS32LE Format = 12
21+ FormatS32BE Format = 13
22+ FormatU32LE Format = 14
23+ FormatU32BE Format = 15
24+ FormatF32LE Format = 16
25+ FormatF32BE Format = 17
26+ FormatF64LE Format = 18
27+ FormatF64BE Format = 19
28+ FormatS24_32LE Format = 20
29+ FormatS24_32BE Format = 21
30+ FormatU24_32LE Format = 22
31+ FormatU24_32BE Format = 23
32+)
@@ -0,0 +1,32 @@
1+package spa
2+
3+// Format represents a SPA media format identifier.
4+type Format uint32
5+
6+// Standard SPA audio/video formats.
7+const (
8+ FormatUnknown Format = 0
9+ FormatEncoded Format = 1
10+ FormatS8 Format = 2
11+ FormatU8 Format = 3
12+ FormatS16LE Format = 4
13+ FormatS16BE Format = 5
14+ FormatU16LE Format = 6
15+ FormatU16BE Format = 7
16+ FormatS24LE Format = 8
17+ FormatS24BE Format = 9
18+ FormatU24LE Format = 10
19+ FormatU24BE Format = 11
20+ FormatS32LE Format = 12
21+ FormatS32BE Format = 13
22+ FormatU32LE Format = 14
23+ FormatU32BE Format = 15
24+ FormatF32LE Format = 16
25+ FormatF32BE Format = 17
26+ FormatF64LE Format = 18
27+ FormatF64BE Format = 19
28+ FormatS24_32LE Format = 20
29+ FormatS24_32BE Format = 21
30+ FormatU24_32LE Format = 22
31+ FormatU24_32BE Format = 23
32+)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 8

↑↓ moveenter openesc close