detached 2a9d816

docs: rewrite README and add MIT licence

commit
2a9d8168696f3c5517d0ba4ad36f944e8bb30ea4
author
brice · · 2026-04-11 06:16 UTC
parent
8b5fe8b
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +195 -24
A LICENCE +21 -0
@@ -0,0 +1,21 @@
1+MIT License
2+
3+Copyright (c) 2026 bnema
4+
5+Permission is hereby granted, free of charge, to any person obtaining a copy
6+of this software and associated documentation files (the "Software"), to deal
7+in the Software without restriction, including without limitation the rights
8+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+copies of the Software, and to permit persons to whom the Software is
10+furnished to do so, subject to the following conditions:
11+
12+The above copyright notice and this permission notice shall be included in all
13+copies or substantial portions of the Software.
14+
15+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+SOFTWARE.
@@ -0,0 +1,21 @@
1+MIT License
2+
3+Copyright (c) 2026 bnema
4+
5+Permission is hereby granted, free of charge, to any person obtaining a copy
6+of this software and associated documentation files (the "Software"), to deal
7+in the Software without restriction, including without limitation the rights
8+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+copies of the Software, and to permit persons to whom the Software is
10+furnished to do so, subject to the following conditions:
11+
12+The above copyright notice and this permission notice shall be included in all
13+copies or substantial portions of the Software.
14+
15+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+SOFTWARE.
M README.md +174 -24
@@ -1,48 +1,198 @@
11 # purego-pipewire
22
3-Pure Go bindings for PipeWire using [purego](https://github.com/ebitengine/purego).
3+Go bindings for [PipeWire](https://pipewire.org/) via [purego](https://github.com/ebitengine/purego) instead of cgo.
4+
5+Linux only. `CGO_ENABLED=0`.
6+
7+> [!WARNING]
8+> This is early stage. The generated binding layer is in place, but the handwritten API is still focused on runtime lifecycle and float32 playback. API may change.
49
510 ## Architecture
611
7-This package follows the same layer split as other purego-* projects:
12+The binding layer is **generated** from the checked-in model in `gen/pipewire.json`. The generator produces:
813
9-- `internal/capi` — generated raw function types and registration helpers
10-- `internal/ports/{in,out}` — generated port interfaces
11-- `internal/core` — handwritten lifecycle logic
12-- `internal/loader` — `dlopen` for PipeWire and SPA libs
13-- `pipewire/` — public Go-facing wrappers
14-- `spa/` — public constants and helpers
14+- **purego function bindings** for PipeWire init, loop, and stream entry points
15+- **Outbound port interfaces** grouped by API surface (`InitAPI`, `LoopAPI`, `StreamOps`)
16+- **A composite CAPI interface** that embeds the generated port groups
17+- **Inbound port interfaces** for the handwritten core layer
18+- **Public API wrappers** in `pipewire/` and SPA constants/helpers in `spa/`
1519
16-## Regenerating bindings
20+A small hand-written core implements runtime init/deinit, player lifecycle, stream setup, and PCM buffer handoff for playback.
21+
22+Mocks for public interfaces and outbound ports are generated by [Mockery v3](https://vektra.github.io/mockery/) and shipped in `pipewire/mocks/` and `internal/ports/out/mocks/`.
23+
24+## Usage
25+
26+### Runtime lifecycle
27+
28+```go
29+import "github.com/bnema/purego-pipewire/pipewire"
30+
31+rt, err := pipewire.Init()
32+if err != nil {
33+ return err
34+}
35+defer rt.Deinit()
36+
37+loop, err := rt.NewMainLoop()
38+if err != nil {
39+ return err
40+}
41+defer loop.Destroy()
42+
43+go loop.Run()
44+```
45+
46+### Player playback
47+
48+```go
49+import "github.com/bnema/purego-pipewire/pipewire"
50+
51+rt, err := pipewire.Init()
52+if err != nil {
53+ return err
54+}
55+defer rt.Deinit()
56+
57+player, err := pipewire.NewPlayer(pipewire.PlayerConfig{
58+ SampleRate: 48000,
59+ Channels: 2,
60+ FramesPerBuffer: 256,
61+ SampleFormat: pipewire.SampleFormatF32,
62+ UnderrunPolicy: pipewire.UnderrunFillSilence,
63+}, pipewire.PlayerCallbacks{
64+ Fill: func(buf *pipewire.PCMBuffer) (int, error) {
65+ for ch := range buf.Samples {
66+ for i := range buf.Samples[ch] {
67+ buf.Samples[ch][i] = 0
68+ }
69+ }
70+ return buf.Frames, nil
71+ },
72+})
73+if err != nil {
74+ return err
75+}
76+defer player.Close()
77+
78+if err := player.Start(); err != nil {
79+ return err
80+}
81+```
82+
83+### Testing with mocks
84+
85+The package ships pre-generated [Mockery v3](https://vektra.github.io/mockery/) mocks for the public interfaces in `github.com/bnema/purego-pipewire/pipewire/mocks`.
1786
18-Normal contributors should run:
87+**Step 1: Accept the interface in your code**
1988
20- go generate ./...
89+```go
90+package output
2191
22-This uses checked-in model files under `gen/` (e.g. `gen/pipewire.json`).
92+import "github.com/bnema/purego-pipewire/pipewire"
2393
24-Mocks are generated by [mockery](https://github.com/vektra/mockery):
94+type Service struct {
95+ player pipewire.Player
96+}
2597
26- mockery
98+func New(player pipewire.Player) *Service {
99+ return &Service{player: player}
100+}
27101
28-### Maintainer workflow
102+func (s *Service) Start() error {
103+ return s.player.Start()
104+}
105+```
29106
30-Maintainers can refresh the model from local PipeWire and SPA headers with:
107+**Step 2: Mock it in tests**
31108
32- ./scripts/sync-headers.sh
33- go generate ./...
109+```go
110+package output_test
34111
35-The sync script is currently a placeholder for the planned header-import workflow.
112+import (
113+ "testing"
114+
115+ "github.com/bnema/purego-pipewire/pipewire/mocks"
116+ "github.com/stretchr/testify/require"
117+)
118+
119+func TestServiceStart(t *testing.T) {
120+ mockPlayer := mocks.NewMockPlayer(t)
121+ mockPlayer.EXPECT().Start().Return(nil)
122+
123+ svc := output.New(mockPlayer)
124+ require.NoError(t, svc.Start())
125+}
126+```
127+
128+**Available mocks** in `github.com/bnema/purego-pipewire/pipewire/mocks`:
129+
130+| Mock | Interface | Key methods |
131+|---|---|---|
132+| `MockRuntime` | `pipewire.Runtime` | `Init`, `Deinit`, `NewMainLoop` |
133+| `MockMainLoop` | `pipewire.MainLoop` | `Run`, `Destroy` |
134+| `MockPlayer` | `pipewire.Player` | `Start`, `Pause`, `Stop`, `Close`, `State` |
135+
136+All mocks support `.EXPECT()` for type-safe expectations and `.On()`/`.Return()` for classic testify style.
36137
37138 ## Requirements
38139
39140 - Go 1.26.1+
40-- Linux with PipeWire 0.3 runtime libraries
141+- Linux with `libpipewire-0.3.so.0`
142+- A running PipeWire daemon for integration tests and real playback
143+- Override path: `PIPEWIRE_LIB_PATH=/custom/path/libpipewire-0.3.so.0`
144+
145+## Building
146+
147+```sh
148+CGO_ENABLED=0 go build ./...
149+CGO_ENABLED=0 go test ./...
150+```
151+
152+Integration tests need a real PipeWire daemon:
153+
154+```sh
155+go test -tags integration ./integration ./pipewire -v
156+```
157+
158+The player integration suite checks public lifecycle transitions and proves that a live PipeWire process callback invokes the user-supplied `Fill` callback.
159+
160+## Regenerating bindings
161+
162+```sh
163+go generate ./...
164+```
165+
166+Or manually:
167+
168+```sh
169+go run ./cmd/pipewiregen
170+mockery
171+```
172+
173+Maintainers refreshing the checked-in model can run:
174+
175+```sh
176+./scripts/sync-headers.sh
177+go generate ./...
178+```
41179
42-## Integration tests
180+## Project layout
43181
44-Tagged integration tests require a **real PipeWire runtime/daemon** and verify the public lifecycle API against a live runtime:
182+```text
183+pipewire/ public API + mocks
184+spa/ public PipeWire and SPA constants/helpers
185+internal/
186+ ports/in/ inbound port interfaces
187+ ports/out/ outbound port interfaces + mocks
188+ core/ runtime and playback logic
189+ capi/ purego bindings + bridge
190+ loader/ dlopen libpipewire-0.3.so.0
191+cmd/pipewiregen/ binding generator (parser, model, emitter, templates)
192+gen/ checked-in model data
193+integration/ integration tests (require a real PipeWire daemon)
194+```
45195
46- go test -tags integration ./integration ./pipewire -v
196+## License
47197
48-The player integration suite exercises state transitions (Idle → Playing → Stopped → Closed) and proves that the real start path reaches the PipeWire process callback, invoking the user-supplied `Fill` callback under a live daemon. Deeper cleanup internals are primarily covered by unit tests that use mocked dependencies.
198+MIT
@@ -1,48 +1,198 @@
1 # purego-pipewire1 # purego-pipewire
2 2
3-Pure Go bindings for PipeWire using [purego](https://github.com/ebitengine/purego).3+Go bindings for [PipeWire](https://pipewire.org/) via [purego](https://github.com/ebitengine/purego) instead of cgo.
4+
5+Linux only. `CGO_ENABLED=0`.
6+
7+> [!WARNING]
8+> This is early stage. The generated binding layer is in place, but the handwritten API is still focused on runtime lifecycle and float32 playback. API may change.
4 9
5 ## Architecture10 ## Architecture
6 11
7-This package follows the same layer split as other purego-* projects:12+The binding layer is **generated** from the checked-in model in `gen/pipewire.json`. The generator produces:
8 13
9-- `internal/capi` — generated raw function types and registration helpers14+- **purego function bindings** for PipeWire init, loop, and stream entry points
10-- `internal/ports/{in,out}` — generated port interfaces15+- **Outbound port interfaces** grouped by API surface (`InitAPI`, `LoopAPI`, `StreamOps`)
11-- `internal/core` — handwritten lifecycle logic16+- **A composite CAPI interface** that embeds the generated port groups
12-- `internal/loader` — `dlopen` for PipeWire and SPA libs17+- **Inbound port interfaces** for the handwritten core layer
13-- `pipewire/` — public Go-facing wrappers18+- **Public API wrappers** in `pipewire/` and SPA constants/helpers in `spa/`
14-- `spa/` — public constants and helpers
15 19
16-## Regenerating bindings20+A small hand-written core implements runtime init/deinit, player lifecycle, stream setup, and PCM buffer handoff for playback.
21+
22+Mocks for public interfaces and outbound ports are generated by [Mockery v3](https://vektra.github.io/mockery/) and shipped in `pipewire/mocks/` and `internal/ports/out/mocks/`.
23+
24+## Usage
25+
26+### Runtime lifecycle
27+
28+```go
29+import "github.com/bnema/purego-pipewire/pipewire"
30+
31+rt, err := pipewire.Init()
32+if err != nil {
33+ return err
34+}
35+defer rt.Deinit()
36+
37+loop, err := rt.NewMainLoop()
38+if err != nil {
39+ return err
40+}
41+defer loop.Destroy()
42+
43+go loop.Run()
44+```
45+
46+### Player playback
47+
48+```go
49+import "github.com/bnema/purego-pipewire/pipewire"
50+
51+rt, err := pipewire.Init()
52+if err != nil {
53+ return err
54+}
55+defer rt.Deinit()
56+
57+player, err := pipewire.NewPlayer(pipewire.PlayerConfig{
58+ SampleRate: 48000,
59+ Channels: 2,
60+ FramesPerBuffer: 256,
61+ SampleFormat: pipewire.SampleFormatF32,
62+ UnderrunPolicy: pipewire.UnderrunFillSilence,
63+}, pipewire.PlayerCallbacks{
64+ Fill: func(buf *pipewire.PCMBuffer) (int, error) {
65+ for ch := range buf.Samples {
66+ for i := range buf.Samples[ch] {
67+ buf.Samples[ch][i] = 0
68+ }
69+ }
70+ return buf.Frames, nil
71+ },
72+})
73+if err != nil {
74+ return err
75+}
76+defer player.Close()
77+
78+if err := player.Start(); err != nil {
79+ return err
80+}
81+```
82+
83+### Testing with mocks
84+
85+The package ships pre-generated [Mockery v3](https://vektra.github.io/mockery/) mocks for the public interfaces in `github.com/bnema/purego-pipewire/pipewire/mocks`.
17 86
18-Normal contributors should run:87+**Step 1: Accept the interface in your code**
19 88
20- go generate ./...89+```go
90+package output
21 91
22-This uses checked-in model files under `gen/` (e.g. `gen/pipewire.json`).92+import "github.com/bnema/purego-pipewire/pipewire"
23 93
24-Mocks are generated by [mockery](https://github.com/vektra/mockery):94+type Service struct {
95+ player pipewire.Player
96+}
25 97
26- mockery98+func New(player pipewire.Player) *Service {
99+ return &Service{player: player}
100+}
27 101
28-### Maintainer workflow102+func (s *Service) Start() error {
103+ return s.player.Start()
104+}
105+```
29 106
30-Maintainers can refresh the model from local PipeWire and SPA headers with:107+**Step 2: Mock it in tests**
31 108
32- ./scripts/sync-headers.sh109+```go
33- go generate ./...110+package output_test
34 111
35-The sync script is currently a placeholder for the planned header-import workflow.112+import (
113+ "testing"
114+
115+ "github.com/bnema/purego-pipewire/pipewire/mocks"
116+ "github.com/stretchr/testify/require"
117+)
118+
119+func TestServiceStart(t *testing.T) {
120+ mockPlayer := mocks.NewMockPlayer(t)
121+ mockPlayer.EXPECT().Start().Return(nil)
122+
123+ svc := output.New(mockPlayer)
124+ require.NoError(t, svc.Start())
125+}
126+```
127+
128+**Available mocks** in `github.com/bnema/purego-pipewire/pipewire/mocks`:
129+
130+| Mock | Interface | Key methods |
131+|---|---|---|
132+| `MockRuntime` | `pipewire.Runtime` | `Init`, `Deinit`, `NewMainLoop` |
133+| `MockMainLoop` | `pipewire.MainLoop` | `Run`, `Destroy` |
134+| `MockPlayer` | `pipewire.Player` | `Start`, `Pause`, `Stop`, `Close`, `State` |
135+
136+All mocks support `.EXPECT()` for type-safe expectations and `.On()`/`.Return()` for classic testify style.
36 137
37 ## Requirements138 ## Requirements
38 139
39 - Go 1.26.1+140 - Go 1.26.1+
40-- Linux with PipeWire 0.3 runtime libraries141+- Linux with `libpipewire-0.3.so.0`
142+- A running PipeWire daemon for integration tests and real playback
143+- Override path: `PIPEWIRE_LIB_PATH=/custom/path/libpipewire-0.3.so.0`
144+
145+## Building
146+
147+```sh
148+CGO_ENABLED=0 go build ./...
149+CGO_ENABLED=0 go test ./...
150+```
151+
152+Integration tests need a real PipeWire daemon:
153+
154+```sh
155+go test -tags integration ./integration ./pipewire -v
156+```
157+
158+The player integration suite checks public lifecycle transitions and proves that a live PipeWire process callback invokes the user-supplied `Fill` callback.
159+
160+## Regenerating bindings
161+
162+```sh
163+go generate ./...
164+```
165+
166+Or manually:
167+
168+```sh
169+go run ./cmd/pipewiregen
170+mockery
171+```
172+
173+Maintainers refreshing the checked-in model can run:
174+
175+```sh
176+./scripts/sync-headers.sh
177+go generate ./...
178+```
41 179
42-## Integration tests180+## Project layout
43 181
44-Tagged integration tests require a **real PipeWire runtime/daemon** and verify the public lifecycle API against a live runtime:182+```text
183+pipewire/ public API + mocks
184+spa/ public PipeWire and SPA constants/helpers
185+internal/
186+ ports/in/ inbound port interfaces
187+ ports/out/ outbound port interfaces + mocks
188+ core/ runtime and playback logic
189+ capi/ purego bindings + bridge
190+ loader/ dlopen libpipewire-0.3.so.0
191+cmd/pipewiregen/ binding generator (parser, model, emitter, templates)
192+gen/ checked-in model data
193+integration/ integration tests (require a real PipeWire daemon)
194+```
45 195
46- go test -tags integration ./integration ./pipewire -v196+## License
47 197
48-The player integration suite exercises state transitions (Idle → Playing → Stopped → Closed) and proves that the real start path reaches the PipeWire process callback, invoking the user-supplied `Fill` callback under a live daemon. Deeper cleanup internals are primarily covered by unit tests that use mocked dependencies.198+MIT

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