detached acd608d

feat: generate PipeWire playback stream bindings

commit
acd608d467b5e5237806bf87dad5ad1a6a9b81b6
author
brice · · 2026-04-09 14:44 UTC
parent
04b2aa7
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
6 files · +239 -23
M cmd/pipewiregen/internal/emitter/emitter.go +53 -18
@@ -50,13 +50,13 @@ func Emit(m *model.Model, root string) (map[string][]byte, error) {
5050 capiPath := filepath.Join("internal", "capi", group.Name+"_gen.go")
5151 portPath := filepath.Join("internal", "ports", "out", group.Name+"_gen.go")
5252
53- capiContent, err := renderCAPI(group, m.Symbols, m.Libraries)
53+ capiContent, err := renderCAPI(group, m.Symbols, m.Libraries, m.Callbacks, m.EventStructs)
5454 if err != nil {
5555 return nil, fmt.Errorf("render capi for group %q: %w", group.Name, err)
5656 }
5757 out[capiPath] = capiContent
5858
59- portContent, err := renderPort(group, m.Symbols)
59+ portContent, err := renderPort(group, m.Symbols, m.EventStructs)
6060 if err != nil {
6161 return nil, fmt.Errorf("render port for group %q: %w", group.Name, err)
6262 }
@@ -66,7 +66,7 @@ func Emit(m *model.Model, root string) (map[string][]byte, error) {
6666 }
6767
6868 // renderCAPI generates the raw C API function types and registration helpers.
69-func renderCAPI(group model.Group, symbols []model.Symbol, libraries []model.Library) ([]byte, error) {
69+func renderCAPI(group model.Group, symbols []model.Symbol, libraries []model.Library, callbacks []model.Callback, eventStructs []model.EventStruct) ([]byte, error) {
7070 // Find symbols belonging to this group
7171 groupSymbols := filterSymbolsByGroup(symbols, group.Name)
7272
@@ -85,16 +85,37 @@ func renderCAPI(group model.Group, symbols []model.Symbol, libraries []model.Lib
8585 }
8686 }
8787
88+ // Filter callbacks and event structs for stream_playback group
89+ var groupCallbacks []model.Callback
90+ var groupEventStructs []model.EventStruct
91+ if group.Name == "stream_playback" {
92+ groupCallbacks = callbacks
93+ groupEventStructs = eventStructs
94+ // Check if callbacks need unsafe
95+ for _, cb := range callbacks {
96+ if strings.Contains(cb.Signature, "unsafe.Pointer") {
97+ needsUnsafe = true
98+ break
99+ }
100+ }
101+ }
102+
88103 data := struct {
89- Group model.Group
90- Symbols []model.Symbol
91- LibraryMap map[string]string
92- NeedsUnsafe bool
104+ Group model.Group
105+ Symbols []model.Symbol
106+ LibraryMap map[string]string
107+ NeedsUnsafe bool
108+ Callbacks []model.Callback
109+ EventStructs []model.EventStruct
110+ IsStreamPlayback bool
93111 }{
94- Group: group,
95- Symbols: groupSymbols,
96- LibraryMap: libMap,
97- NeedsUnsafe: needsUnsafe,
112+ Group: group,
113+ Symbols: groupSymbols,
114+ LibraryMap: libMap,
115+ NeedsUnsafe: needsUnsafe,
116+ Callbacks: groupCallbacks,
117+ EventStructs: groupEventStructs,
118+ IsStreamPlayback: group.Name == "stream_playback",
98119 }
99120
100121 var buf bytes.Buffer
@@ -105,7 +126,7 @@ func renderCAPI(group model.Group, symbols []model.Symbol, libraries []model.Lib
105126 }
106127
107128 // renderPort generates the outbound interface definitions.
108-func renderPort(group model.Group, symbols []model.Symbol) ([]byte, error) {
129+func renderPort(group model.Group, symbols []model.Symbol, eventStructs []model.EventStruct) ([]byte, error) {
109130 groupSymbols := filterSymbolsByGroup(symbols, group.Name)
110131
111132 // Convert symbols to methods with proper Go names
@@ -134,14 +155,22 @@ func renderPort(group model.Group, symbols []model.Symbol) ([]byte, error) {
134155 }
135156 }
136157
158+ // Filter event structs for stream_playback group
159+ var groupEventStructs []model.EventStruct
160+ if group.Name == "stream_playback" {
161+ groupEventStructs = eventStructs
162+ }
163+
137164 data := struct {
138- Group model.Group
139- Methods []Method
140- NeedsUnsafe bool
165+ Group model.Group
166+ Methods []Method
167+ NeedsUnsafe bool
168+ EventStructs []model.EventStruct
141169 }{
142- Group: group,
143- Methods: methods,
144- NeedsUnsafe: needsUnsafe,
170+ Group: group,
171+ Methods: methods,
172+ NeedsUnsafe: needsUnsafe,
173+ EventStructs: groupEventStructs,
145174 }
146175
147176 var buf bytes.Buffer
@@ -248,6 +277,12 @@ import "unsafe"
248277 // {{.Name}}Func is the function type for {{.Name}}.
249278 type {{.Name}}Func {{.Signature}}
250279 {{end}}
280+{{if .IsStreamPlayback}}
281+{{range .Callbacks}}
282+// {{.Name}} represents the callback struct for {{.Name}}.
283+type {{.Name}} {{.Signature}}
284+{{end}}
285+{{end}}
251286
252287 var (
253288 {{range .Symbols}}
@@ -50,13 +50,13 @@ func Emit(m *model.Model, root string) (map[string][]byte, error) {
50 capiPath := filepath.Join("internal", "capi", group.Name+"_gen.go")50 capiPath := filepath.Join("internal", "capi", group.Name+"_gen.go")
51 portPath := filepath.Join("internal", "ports", "out", group.Name+"_gen.go")51 portPath := filepath.Join("internal", "ports", "out", group.Name+"_gen.go")
52 52
53- capiContent, err := renderCAPI(group, m.Symbols, m.Libraries)53+ capiContent, err := renderCAPI(group, m.Symbols, m.Libraries, m.Callbacks, m.EventStructs)
54 if err != nil {54 if err != nil {
55 return nil, fmt.Errorf("render capi for group %q: %w", group.Name, err)55 return nil, fmt.Errorf("render capi for group %q: %w", group.Name, err)
56 }56 }
57 out[capiPath] = capiContent57 out[capiPath] = capiContent
58 58
59- portContent, err := renderPort(group, m.Symbols)59+ portContent, err := renderPort(group, m.Symbols, m.EventStructs)
60 if err != nil {60 if err != nil {
61 return nil, fmt.Errorf("render port for group %q: %w", group.Name, err)61 return nil, fmt.Errorf("render port for group %q: %w", group.Name, err)
62 }62 }
@@ -66,7 +66,7 @@ func Emit(m *model.Model, root string) (map[string][]byte, error) {
66 }66 }
67 67
68 // renderCAPI generates the raw C API function types and registration helpers.68 // renderCAPI generates the raw C API function types and registration helpers.
69-func renderCAPI(group model.Group, symbols []model.Symbol, libraries []model.Library) ([]byte, error) {69+func renderCAPI(group model.Group, symbols []model.Symbol, libraries []model.Library, callbacks []model.Callback, eventStructs []model.EventStruct) ([]byte, error) {
70 // Find symbols belonging to this group70 // Find symbols belonging to this group
71 groupSymbols := filterSymbolsByGroup(symbols, group.Name)71 groupSymbols := filterSymbolsByGroup(symbols, group.Name)
72 72
@@ -85,16 +85,37 @@ func renderCAPI(group model.Group, symbols []model.Symbol, libraries []model.Lib
85 }85 }
86 }86 }
87 87
88+ // Filter callbacks and event structs for stream_playback group
89+ var groupCallbacks []model.Callback
90+ var groupEventStructs []model.EventStruct
91+ if group.Name == "stream_playback" {
92+ groupCallbacks = callbacks
93+ groupEventStructs = eventStructs
94+ // Check if callbacks need unsafe
95+ for _, cb := range callbacks {
96+ if strings.Contains(cb.Signature, "unsafe.Pointer") {
97+ needsUnsafe = true
98+ break
99+ }
100+ }
101+ }
102+
88 data := struct {103 data := struct {
89- Group model.Group104+ Group model.Group
90- Symbols []model.Symbol105+ Symbols []model.Symbol
91- LibraryMap map[string]string106+ LibraryMap map[string]string
92- NeedsUnsafe bool107+ NeedsUnsafe bool
108+ Callbacks []model.Callback
109+ EventStructs []model.EventStruct
110+ IsStreamPlayback bool
93 }{111 }{
94- Group: group,112+ Group: group,
95- Symbols: groupSymbols,113+ Symbols: groupSymbols,
96- LibraryMap: libMap,114+ LibraryMap: libMap,
97- NeedsUnsafe: needsUnsafe,115+ NeedsUnsafe: needsUnsafe,
116+ Callbacks: groupCallbacks,
117+ EventStructs: groupEventStructs,
118+ IsStreamPlayback: group.Name == "stream_playback",
98 }119 }
99 120
100 var buf bytes.Buffer121 var buf bytes.Buffer
@@ -105,7 +126,7 @@ func renderCAPI(group model.Group, symbols []model.Symbol, libraries []model.Lib
105 }126 }
106 127
107 // renderPort generates the outbound interface definitions.128 // renderPort generates the outbound interface definitions.
108-func renderPort(group model.Group, symbols []model.Symbol) ([]byte, error) {129+func renderPort(group model.Group, symbols []model.Symbol, eventStructs []model.EventStruct) ([]byte, error) {
109 groupSymbols := filterSymbolsByGroup(symbols, group.Name)130 groupSymbols := filterSymbolsByGroup(symbols, group.Name)
110 131
111 // Convert symbols to methods with proper Go names132 // Convert symbols to methods with proper Go names
@@ -134,14 +155,22 @@ func renderPort(group model.Group, symbols []model.Symbol) ([]byte, error) {
134 }155 }
135 }156 }
136 157
158+ // Filter event structs for stream_playback group
159+ var groupEventStructs []model.EventStruct
160+ if group.Name == "stream_playback" {
161+ groupEventStructs = eventStructs
162+ }
163+
137 data := struct {164 data := struct {
138- Group model.Group165+ Group model.Group
139- Methods []Method166+ Methods []Method
140- NeedsUnsafe bool167+ NeedsUnsafe bool
168+ EventStructs []model.EventStruct
141 }{169 }{
142- Group: group,170+ Group: group,
143- Methods: methods,171+ Methods: methods,
144- NeedsUnsafe: needsUnsafe,172+ NeedsUnsafe: needsUnsafe,
173+ EventStructs: groupEventStructs,
145 }174 }
146 175
147 var buf bytes.Buffer176 var buf bytes.Buffer
@@ -248,6 +277,12 @@ import "unsafe"
248 // {{.Name}}Func is the function type for {{.Name}}.277 // {{.Name}}Func is the function type for {{.Name}}.
249 type {{.Name}}Func {{.Signature}}278 type {{.Name}}Func {{.Signature}}
250 {{end}}279 {{end}}
280+{{if .IsStreamPlayback}}
281+{{range .Callbacks}}
282+// {{.Name}} represents the callback struct for {{.Name}}.
283+type {{.Name}} {{.Signature}}
284+{{end}}
285+{{end}}
251 286
252 var (287 var (
253 {{range .Symbols}}288 {{range .Symbols}}
M cmd/pipewiregen/internal/emitter/emitter_test.go +65 -0
@@ -1,6 +1,7 @@
11 package emitter
22
33 import (
4+ "strings"
45 "testing"
56
67 "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/model"
@@ -21,6 +22,45 @@ func testModel() *model.Model {
2122 }
2223 }
2324
25+func testPlayerModel() *model.Model {
26+ return &model.Model{
27+ Libraries: []model.Library{
28+ {Name: "pipewire", SOName: "libpipewire-0.3.so.0"},
29+ },
30+ Groups: []model.Group{
31+ {
32+ Name: "stream_playback",
33+ Interface: "StreamPlaybackAPI",
34+ Package: "out",
35+ Symbols: []string{
36+ "pw_stream_new_simple",
37+ "pw_stream_connect",
38+ "pw_stream_set_active",
39+ "pw_stream_disconnect",
40+ "pw_stream_dequeue_buffer",
41+ "pw_stream_queue_buffer",
42+ "pw_stream_add_listener",
43+ },
44+ },
45+ },
46+ Symbols: []model.Symbol{
47+ {Name: "pw_stream_new_simple", Library: "pipewire", Group: "stream_playback", Signature: "func(context unsafe.Pointer, name *byte, props unsafe.Pointer, events unsafe.Pointer, data unsafe.Pointer) unsafe.Pointer"},
48+ {Name: "pw_stream_connect", Library: "pipewire", Group: "stream_playback", Signature: "func(stream unsafe.Pointer, direction int32, id uint32, flags uint32, ports unsafe.Pointer, n_ports uint32) int32"},
49+ {Name: "pw_stream_set_active", Library: "pipewire", Group: "stream_playback", Signature: "func(stream unsafe.Pointer, active bool) int32"},
50+ {Name: "pw_stream_disconnect", Library: "pipewire", Group: "stream_playback", Signature: "func(stream unsafe.Pointer) int32"},
51+ {Name: "pw_stream_dequeue_buffer", Library: "pipewire", Group: "stream_playback", Signature: "func(stream unsafe.Pointer) unsafe.Pointer"},
52+ {Name: "pw_stream_queue_buffer", Library: "pipewire", Group: "stream_playback", Signature: "func(stream unsafe.Pointer, buffer unsafe.Pointer) int32"},
53+ {Name: "pw_stream_add_listener", Library: "pipewire", Group: "stream_playback", Signature: "func(stream unsafe.Pointer, listener unsafe.Pointer, events unsafe.Pointer, data unsafe.Pointer) int32"},
54+ },
55+ Callbacks: []model.Callback{
56+ {Name: "pw_stream_events", Signature: "struct{version uint32; process *func(stream unsafe.Pointer); param_changed *func(stream unsafe.Pointer, id uint32, param unsafe.Pointer); add_buffer *func(stream unsafe.Pointer, buffer unsafe.Pointer); remove_buffer *func(stream unsafe.Pointer, buffer unsafe.Pointer); flush *func(stream unsafe.Pointer, drain bool); drained *func(stream unsafe.Pointer); io_changed *func(stream unsafe.Pointer, id uint32, area unsafe.Pointer, size uint32)}"},
57+ },
58+ EventStructs: []model.EventStruct{
59+ {Name: "pw_stream_events", Callbacks: []string{"process", "param_changed", "add_buffer", "remove_buffer", "flush", "drained", "io_changed"}},
60+ },
61+ }
62+}
63+
2464 func TestEmitWritesCAPIAndPortFiles(t *testing.T) {
2565 paths, err := Emit(testModel(), t.TempDir())
2666 if err != nil {
@@ -33,3 +73,28 @@ func TestEmitWritesCAPIAndPortFiles(t *testing.T) {
3373 t.Fatalf("missing port output")
3474 }
3575 }
76+
77+func TestEmitWritesPlayerStreamFiles(t *testing.T) {
78+ paths, err := Emit(testPlayerModel(), t.TempDir())
79+ if err != nil {
80+ t.Fatalf("Emit returned error: %v", err)
81+ }
82+ if _, ok := paths["internal/capi/stream_playback_gen.go"]; !ok {
83+ t.Fatalf("missing stream_playback capi output")
84+ }
85+ if _, ok := paths["internal/ports/out/stream_playback_gen.go"]; !ok {
86+ t.Fatalf("missing stream_playback port output")
87+ }
88+
89+ // Verify callback typedefs are in capi output
90+ capiContent := string(paths["internal/capi/stream_playback_gen.go"])
91+ if !strings.Contains(capiContent, "pw_stream_events") {
92+ t.Fatalf("capi output missing pw_stream_events callback typedef")
93+ }
94+
95+ // Verify event struct callbacks are in port output
96+ portContent := string(paths["internal/ports/out/stream_playback_gen.go"])
97+ if !strings.Contains(portContent, "StreamPlaybackAPI") {
98+ t.Fatalf("port output missing StreamPlaybackAPI interface")
99+ }
100+}
@@ -1,6 +1,7 @@
1 package emitter1 package emitter
2 2
3 import (3 import (
4+ "strings"
4 "testing"5 "testing"
5 6
6 "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/model"7 "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/model"
@@ -21,6 +22,45 @@ func testModel() *model.Model {
21 }22 }
22 }23 }
23 24
25+func testPlayerModel() *model.Model {
26+ return &model.Model{
27+ Libraries: []model.Library{
28+ {Name: "pipewire", SOName: "libpipewire-0.3.so.0"},
29+ },
30+ Groups: []model.Group{
31+ {
32+ Name: "stream_playback",
33+ Interface: "StreamPlaybackAPI",
34+ Package: "out",
35+ Symbols: []string{
36+ "pw_stream_new_simple",
37+ "pw_stream_connect",
38+ "pw_stream_set_active",
39+ "pw_stream_disconnect",
40+ "pw_stream_dequeue_buffer",
41+ "pw_stream_queue_buffer",
42+ "pw_stream_add_listener",
43+ },
44+ },
45+ },
46+ Symbols: []model.Symbol{
47+ {Name: "pw_stream_new_simple", Library: "pipewire", Group: "stream_playback", Signature: "func(context unsafe.Pointer, name *byte, props unsafe.Pointer, events unsafe.Pointer, data unsafe.Pointer) unsafe.Pointer"},
48+ {Name: "pw_stream_connect", Library: "pipewire", Group: "stream_playback", Signature: "func(stream unsafe.Pointer, direction int32, id uint32, flags uint32, ports unsafe.Pointer, n_ports uint32) int32"},
49+ {Name: "pw_stream_set_active", Library: "pipewire", Group: "stream_playback", Signature: "func(stream unsafe.Pointer, active bool) int32"},
50+ {Name: "pw_stream_disconnect", Library: "pipewire", Group: "stream_playback", Signature: "func(stream unsafe.Pointer) int32"},
51+ {Name: "pw_stream_dequeue_buffer", Library: "pipewire", Group: "stream_playback", Signature: "func(stream unsafe.Pointer) unsafe.Pointer"},
52+ {Name: "pw_stream_queue_buffer", Library: "pipewire", Group: "stream_playback", Signature: "func(stream unsafe.Pointer, buffer unsafe.Pointer) int32"},
53+ {Name: "pw_stream_add_listener", Library: "pipewire", Group: "stream_playback", Signature: "func(stream unsafe.Pointer, listener unsafe.Pointer, events unsafe.Pointer, data unsafe.Pointer) int32"},
54+ },
55+ Callbacks: []model.Callback{
56+ {Name: "pw_stream_events", Signature: "struct{version uint32; process *func(stream unsafe.Pointer); param_changed *func(stream unsafe.Pointer, id uint32, param unsafe.Pointer); add_buffer *func(stream unsafe.Pointer, buffer unsafe.Pointer); remove_buffer *func(stream unsafe.Pointer, buffer unsafe.Pointer); flush *func(stream unsafe.Pointer, drain bool); drained *func(stream unsafe.Pointer); io_changed *func(stream unsafe.Pointer, id uint32, area unsafe.Pointer, size uint32)}"},
57+ },
58+ EventStructs: []model.EventStruct{
59+ {Name: "pw_stream_events", Callbacks: []string{"process", "param_changed", "add_buffer", "remove_buffer", "flush", "drained", "io_changed"}},
60+ },
61+ }
62+}
63+
24 func TestEmitWritesCAPIAndPortFiles(t *testing.T) {64 func TestEmitWritesCAPIAndPortFiles(t *testing.T) {
25 paths, err := Emit(testModel(), t.TempDir())65 paths, err := Emit(testModel(), t.TempDir())
26 if err != nil {66 if err != nil {
@@ -33,3 +73,28 @@ func TestEmitWritesCAPIAndPortFiles(t *testing.T) {
33 t.Fatalf("missing port output")73 t.Fatalf("missing port output")
34 }74 }
35 }75 }
76+
77+func TestEmitWritesPlayerStreamFiles(t *testing.T) {
78+ paths, err := Emit(testPlayerModel(), t.TempDir())
79+ if err != nil {
80+ t.Fatalf("Emit returned error: %v", err)
81+ }
82+ if _, ok := paths["internal/capi/stream_playback_gen.go"]; !ok {
83+ t.Fatalf("missing stream_playback capi output")
84+ }
85+ if _, ok := paths["internal/ports/out/stream_playback_gen.go"]; !ok {
86+ t.Fatalf("missing stream_playback port output")
87+ }
88+
89+ // Verify callback typedefs are in capi output
90+ capiContent := string(paths["internal/capi/stream_playback_gen.go"])
91+ if !strings.Contains(capiContent, "pw_stream_events") {
92+ t.Fatalf("capi output missing pw_stream_events callback typedef")
93+ }
94+
95+ // Verify event struct callbacks are in port output
96+ portContent := string(paths["internal/ports/out/stream_playback_gen.go"])
97+ if !strings.Contains(portContent, "StreamPlaybackAPI") {
98+ t.Fatalf("port output missing StreamPlaybackAPI interface")
99+ }
100+}
M cmd/pipewiregen/internal/model/model.go +17 -3
@@ -2,9 +2,11 @@ package model
22
33 // Model represents the checked-in binding model for PipeWire/SPA code generation.
44 type Model struct {
5- Libraries []Library `json:"libraries"`
6- Groups []Group `json:"groups"`
7- Symbols []Symbol `json:"symbols"`
5+ Libraries []Library `json:"libraries"`
6+ Groups []Group `json:"groups"`
7+ Symbols []Symbol `json:"symbols"`
8+ Callbacks []Callback `json:"callbacks,omitempty"`
9+ EventStructs []EventStruct `json:"event_structs,omitempty"`
810 }
911
1012 // Library represents a shared library to load at runtime.
@@ -29,3 +31,15 @@ type Symbol struct {
2931 Optional bool `json:"optional,omitempty"`
3032 Group string `json:"group"`
3133 }
34+
35+// Callback represents a C callback typedef to generate.
36+type Callback struct {
37+ Name string `json:"name"`
38+ Signature string `json:"signature"`
39+}
40+
41+// EventStruct represents an event struct with its associated callbacks.
42+type EventStruct struct {
43+ Name string `json:"name"`
44+ Callbacks []string `json:"callbacks"`
45+}
@@ -2,9 +2,11 @@ package model
2 2
3 // Model represents the checked-in binding model for PipeWire/SPA code generation.3 // Model represents the checked-in binding model for PipeWire/SPA code generation.
4 type Model struct {4 type Model struct {
5- Libraries []Library `json:"libraries"`5+ Libraries []Library `json:"libraries"`
6- Groups []Group `json:"groups"`6+ Groups []Group `json:"groups"`
7- Symbols []Symbol `json:"symbols"`7+ Symbols []Symbol `json:"symbols"`
8+ Callbacks []Callback `json:"callbacks,omitempty"`
9+ EventStructs []EventStruct `json:"event_structs,omitempty"`
8 }10 }
9 11
10 // Library represents a shared library to load at runtime.12 // Library represents a shared library to load at runtime.
@@ -29,3 +31,15 @@ type Symbol struct {
29 Optional bool `json:"optional,omitempty"`31 Optional bool `json:"optional,omitempty"`
30 Group string `json:"group"`32 Group string `json:"group"`
31 }33 }
34+
35+// Callback represents a C callback typedef to generate.
36+type Callback struct {
37+ Name string `json:"name"`
38+ Signature string `json:"signature"`
39+}
40+
41+// EventStruct represents an event struct with its associated callbacks.
42+type EventStruct struct {
43+ Name string `json:"name"`
44+ Callbacks []string `json:"callbacks"`
45+}
M gen/pipewire.json +16 -2
@@ -5,13 +5,27 @@
55 ],
66 "groups": [
77 {"name": "init", "interface": "InitAPI", "package": "out", "symbols": ["pw_init", "pw_deinit"]},
8- {"name": "loop", "interface": "LoopAPI", "package": "out", "symbols": ["pw_main_loop_new", "pw_main_loop_destroy", "pw_main_loop_run"]}
8+ {"name": "loop", "interface": "LoopAPI", "package": "out", "symbols": ["pw_main_loop_new", "pw_main_loop_destroy", "pw_main_loop_run"]},
9+ {"name": "stream_playback", "interface": "StreamPlaybackAPI", "package": "out", "symbols": ["pw_stream_new_simple", "pw_stream_connect", "pw_stream_set_active", "pw_stream_disconnect", "pw_stream_dequeue_buffer", "pw_stream_queue_buffer", "pw_stream_add_listener"]}
910 ],
1011 "symbols": [
1112 {"name": "pw_init", "library": "pipewire", "group": "init", "signature": "func(argc *int32, argv ***byte)"},
1213 {"name": "pw_deinit", "library": "pipewire", "group": "init", "signature": "func()"},
1314 {"name": "pw_main_loop_new", "library": "pipewire", "group": "loop", "signature": "func(props unsafe.Pointer) unsafe.Pointer"},
1415 {"name": "pw_main_loop_destroy", "library": "pipewire", "group": "loop", "signature": "func(loop unsafe.Pointer)"},
15- {"name": "pw_main_loop_run", "library": "pipewire", "group": "loop", "signature": "func(loop unsafe.Pointer) int32"}
16+ {"name": "pw_main_loop_run", "library": "pipewire", "group": "loop", "signature": "func(loop unsafe.Pointer) int32"},
17+ {"name": "pw_stream_new_simple", "library": "pipewire", "group": "stream_playback", "signature": "func(context unsafe.Pointer, name *byte, props unsafe.Pointer, events unsafe.Pointer, data unsafe.Pointer) unsafe.Pointer"},
18+ {"name": "pw_stream_connect", "library": "pipewire", "group": "stream_playback", "signature": "func(stream unsafe.Pointer, direction int32, id uint32, flags uint32, ports unsafe.Pointer, n_ports uint32) int32"},
19+ {"name": "pw_stream_set_active", "library": "pipewire", "group": "stream_playback", "signature": "func(stream unsafe.Pointer, active bool) int32"},
20+ {"name": "pw_stream_disconnect", "library": "pipewire", "group": "stream_playback", "signature": "func(stream unsafe.Pointer) int32"},
21+ {"name": "pw_stream_dequeue_buffer", "library": "pipewire", "group": "stream_playback", "signature": "func(stream unsafe.Pointer) unsafe.Pointer"},
22+ {"name": "pw_stream_queue_buffer", "library": "pipewire", "group": "stream_playback", "signature": "func(stream unsafe.Pointer, buffer unsafe.Pointer) int32"},
23+ {"name": "pw_stream_add_listener", "library": "pipewire", "group": "stream_playback", "signature": "func(stream unsafe.Pointer, listener unsafe.Pointer, events unsafe.Pointer, data unsafe.Pointer) int32"}
24+ ],
25+ "callbacks": [
26+ {"name": "pw_stream_events", "signature": "struct{version uint32; process *func(stream unsafe.Pointer); param_changed *func(stream unsafe.Pointer, id uint32, param unsafe.Pointer); add_buffer *func(stream unsafe.Pointer, buffer unsafe.Pointer); remove_buffer *func(stream unsafe.Pointer, buffer unsafe.Pointer); flush *func(stream unsafe.Pointer, drain bool); drained *func(stream unsafe.Pointer); io_changed *func(stream unsafe.Pointer, id uint32, area unsafe.Pointer, size uint32)}"}
27+ ],
28+ "event_structs": [
29+ {"name": "pw_stream_events", "callbacks": ["process", "param_changed", "add_buffer", "remove_buffer", "flush", "drained", "io_changed"]}
1630 ]
1731 }
@@ -5,13 +5,27 @@
5 ],5 ],
6 "groups": [6 "groups": [
7 {"name": "init", "interface": "InitAPI", "package": "out", "symbols": ["pw_init", "pw_deinit"]},7 {"name": "init", "interface": "InitAPI", "package": "out", "symbols": ["pw_init", "pw_deinit"]},
8- {"name": "loop", "interface": "LoopAPI", "package": "out", "symbols": ["pw_main_loop_new", "pw_main_loop_destroy", "pw_main_loop_run"]}8+ {"name": "loop", "interface": "LoopAPI", "package": "out", "symbols": ["pw_main_loop_new", "pw_main_loop_destroy", "pw_main_loop_run"]},
9+ {"name": "stream_playback", "interface": "StreamPlaybackAPI", "package": "out", "symbols": ["pw_stream_new_simple", "pw_stream_connect", "pw_stream_set_active", "pw_stream_disconnect", "pw_stream_dequeue_buffer", "pw_stream_queue_buffer", "pw_stream_add_listener"]}
9 ],10 ],
10 "symbols": [11 "symbols": [
11 {"name": "pw_init", "library": "pipewire", "group": "init", "signature": "func(argc *int32, argv ***byte)"},12 {"name": "pw_init", "library": "pipewire", "group": "init", "signature": "func(argc *int32, argv ***byte)"},
12 {"name": "pw_deinit", "library": "pipewire", "group": "init", "signature": "func()"},13 {"name": "pw_deinit", "library": "pipewire", "group": "init", "signature": "func()"},
13 {"name": "pw_main_loop_new", "library": "pipewire", "group": "loop", "signature": "func(props unsafe.Pointer) unsafe.Pointer"},14 {"name": "pw_main_loop_new", "library": "pipewire", "group": "loop", "signature": "func(props unsafe.Pointer) unsafe.Pointer"},
14 {"name": "pw_main_loop_destroy", "library": "pipewire", "group": "loop", "signature": "func(loop unsafe.Pointer)"},15 {"name": "pw_main_loop_destroy", "library": "pipewire", "group": "loop", "signature": "func(loop unsafe.Pointer)"},
15- {"name": "pw_main_loop_run", "library": "pipewire", "group": "loop", "signature": "func(loop unsafe.Pointer) int32"}16+ {"name": "pw_main_loop_run", "library": "pipewire", "group": "loop", "signature": "func(loop unsafe.Pointer) int32"},
17+ {"name": "pw_stream_new_simple", "library": "pipewire", "group": "stream_playback", "signature": "func(context unsafe.Pointer, name *byte, props unsafe.Pointer, events unsafe.Pointer, data unsafe.Pointer) unsafe.Pointer"},
18+ {"name": "pw_stream_connect", "library": "pipewire", "group": "stream_playback", "signature": "func(stream unsafe.Pointer, direction int32, id uint32, flags uint32, ports unsafe.Pointer, n_ports uint32) int32"},
19+ {"name": "pw_stream_set_active", "library": "pipewire", "group": "stream_playback", "signature": "func(stream unsafe.Pointer, active bool) int32"},
20+ {"name": "pw_stream_disconnect", "library": "pipewire", "group": "stream_playback", "signature": "func(stream unsafe.Pointer) int32"},
21+ {"name": "pw_stream_dequeue_buffer", "library": "pipewire", "group": "stream_playback", "signature": "func(stream unsafe.Pointer) unsafe.Pointer"},
22+ {"name": "pw_stream_queue_buffer", "library": "pipewire", "group": "stream_playback", "signature": "func(stream unsafe.Pointer, buffer unsafe.Pointer) int32"},
23+ {"name": "pw_stream_add_listener", "library": "pipewire", "group": "stream_playback", "signature": "func(stream unsafe.Pointer, listener unsafe.Pointer, events unsafe.Pointer, data unsafe.Pointer) int32"}
24+ ],
25+ "callbacks": [
26+ {"name": "pw_stream_events", "signature": "struct{version uint32; process *func(stream unsafe.Pointer); param_changed *func(stream unsafe.Pointer, id uint32, param unsafe.Pointer); add_buffer *func(stream unsafe.Pointer, buffer unsafe.Pointer); remove_buffer *func(stream unsafe.Pointer, buffer unsafe.Pointer); flush *func(stream unsafe.Pointer, drain bool); drained *func(stream unsafe.Pointer); io_changed *func(stream unsafe.Pointer, id uint32, area unsafe.Pointer, size uint32)}"}
27+ ],
28+ "event_structs": [
29+ {"name": "pw_stream_events", "callbacks": ["process", "param_changed", "add_buffer", "remove_buffer", "flush", "drained", "io_changed"]}
16 ]30 ]
17 }31 }
A internal/capi/stream_playback_gen.go +72 -0 generated file: collapsed
A internal/ports/out/stream_playback_gen.go +16 -0 generated file: collapsed

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