feat: generate PipeWire playback stream bindings
- commit
- acd608d467b5e5237806bf87dad5ad1a6a9b81b6
- author
- brice · · 2026-04-09 14:44 UTC
- parent
- 04b2aa7
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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) { | |||
| 50 | 50 | capiPath := filepath.Join("internal", "capi", group.Name+"_gen.go") | |
| 51 | 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 | 54 | if err != nil { | |
| 55 | 55 | return nil, fmt.Errorf("render capi for group %q: %w", group.Name, err) | |
| 56 | 56 | } | |
| 57 | 57 | out[capiPath] = capiContent | |
| 58 | 58 | ||
| 59 | - | portContent, err := renderPort(group, m.Symbols) | |
| 59 | + | portContent, err := renderPort(group, m.Symbols, m.EventStructs) | |
| 60 | 60 | if err != nil { | |
| 61 | 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 | 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 | 70 | // Find symbols belonging to this group | |
| 71 | 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 | 103 | 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 | |
| 93 | 111 | }{ | |
| 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", | |
| 98 | 119 | } | |
| 99 | 120 | ||
| 100 | 121 | var buf bytes.Buffer | |
| @@ -105,7 +126,7 @@ func renderCAPI(group model.Group, symbols []model.Symbol, libraries []model.Lib | |||
| 105 | 126 | } | |
| 106 | 127 | ||
| 107 | 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 | 130 | groupSymbols := filterSymbolsByGroup(symbols, group.Name) | |
| 110 | 131 | ||
| 111 | 132 | // 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 | 164 | 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 | |
| 141 | 169 | }{ | |
| 142 | - | Group: group, | |
| 143 | - | Methods: methods, | |
| 144 | - | NeedsUnsafe: needsUnsafe, | |
| 170 | + | Group: group, | |
| 171 | + | Methods: methods, | |
| 172 | + | NeedsUnsafe: needsUnsafe, | |
| 173 | + | EventStructs: groupEventStructs, | |
| 145 | 174 | } | |
| 146 | 175 | ||
| 147 | 176 | var buf bytes.Buffer | |
| @@ -248,6 +277,12 @@ import "unsafe" | |||
| 248 | 277 | // {{.Name}}Func is the function type for {{.Name}}. | |
| 249 | 278 | type {{.Name}}Func {{.Signature}} | |
| 250 | 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 | 287 | var ( | |
| 253 | 288 | {{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] = capiContent | 57 | 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 group | 70 | // 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.Group | 104 | + | Group model.Group |
| 90 | - | Symbols []model.Symbol | 105 | + | Symbols []model.Symbol |
| 91 | - | LibraryMap map[string]string | 106 | + | LibraryMap map[string]string |
| 92 | - | NeedsUnsafe bool | 107 | + | 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.Buffer | 121 | 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 names | 132 | // 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.Group | 165 | + | Group model.Group |
| 139 | - | Methods []Method | 166 | + | Methods []Method |
| 140 | - | NeedsUnsafe bool | 167 | + | 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.Buffer | 176 | 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 @@ | |||
| 1 | 1 | package emitter | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "strings" | |
| 4 | 5 | "testing" | |
| 5 | 6 | ||
| 6 | 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 | 64 | func TestEmitWritesCAPIAndPortFiles(t *testing.T) { | |
| 25 | 65 | paths, err := Emit(testModel(), t.TempDir()) | |
| 26 | 66 | if err != nil { | |
| @@ -33,3 +73,28 @@ func TestEmitWritesCAPIAndPortFiles(t *testing.T) { | |||
| 33 | 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 | + | } | |
| @@ -1,6 +1,7 @@ | |||||
| 1 | package emitter | 1 | 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 | |||
| 2 | 2 | ||
| 3 | 3 | // Model represents the checked-in binding model for PipeWire/SPA code generation. | |
| 4 | 4 | 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"` | |
| 8 | 10 | } | |
| 9 | 11 | ||
| 10 | 12 | // Library represents a shared library to load at runtime. | |
| @@ -29,3 +31,15 @@ type Symbol struct { | |||
| 29 | 31 | Optional bool `json:"optional,omitempty"` | |
| 30 | 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 | + | } | |
| @@ -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 @@ | |||
| 5 | 5 | ], | |
| 6 | 6 | "groups": [ | |
| 7 | 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 | 11 | "symbols": [ | |
| 11 | 12 | {"name": "pw_init", "library": "pipewire", "group": "init", "signature": "func(argc *int32, argv ***byte)"}, | |
| 12 | 13 | {"name": "pw_deinit", "library": "pipewire", "group": "init", "signature": "func()"}, | |
| 13 | 14 | {"name": "pw_main_loop_new", "library": "pipewire", "group": "loop", "signature": "func(props unsafe.Pointer) unsafe.Pointer"}, | |
| 14 | 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 | } | |
| @@ -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 | } | ||