feat: generate PipeWire adapter and composite glue
- commit
- fcdec78c92ee8ad7aef44fa6dfaf2f2e64cc42d1
- author
- brice · · 2026-04-10 12:38 UTC
- parent
- 28a2e52
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
7 files · +274 -34
M cmd/pipewiregen/internal/emitter/emitter.go +192 -30
| @@ -14,8 +14,10 @@ import ( | |||
| 14 | 14 | ) | |
| 15 | 15 | ||
| 16 | 16 | var ( | |
| 17 | - | capiTmpl *template.Template | |
| 18 | - | portTmpl *template.Template | |
| 17 | + | capiTmpl *template.Template | |
| 18 | + | portTmpl *template.Template | |
| 19 | + | adapterTmpl *template.Template | |
| 20 | + | compositeTmpl *template.Template | |
| 19 | 21 | ) | |
| 20 | 22 | ||
| 21 | 23 | func init() { | |
| @@ -26,6 +28,14 @@ func init() { | |||
| 26 | 28 | portTmpl = template.Must(template.New("port").Funcs(template.FuncMap{ | |
| 27 | 29 | "title": titleCase, | |
| 28 | 30 | }).Parse(portTemplate)) | |
| 31 | + | ||
| 32 | + | adapterTmpl = template.Must(template.New("adapter").Funcs(template.FuncMap{ | |
| 33 | + | "title": titleCase, | |
| 34 | + | }).Parse(adapterTemplate)) | |
| 35 | + | ||
| 36 | + | compositeTmpl = template.Must(template.New("composite").Funcs(template.FuncMap{ | |
| 37 | + | "title": titleCase, | |
| 38 | + | }).Parse(compositeTemplate)) | |
| 29 | 39 | } | |
| 30 | 40 | ||
| 31 | 41 | // titleCase converts a string to title case (first letter uppercase, rest lowercase). | |
| @@ -62,6 +72,21 @@ func Emit(m *model.Model, root string) (map[string][]byte, error) { | |||
| 62 | 72 | } | |
| 63 | 73 | out[portPath] = portContent | |
| 64 | 74 | } | |
| 75 | + | ||
| 76 | + | // Generate adapters file | |
| 77 | + | adapterContent, err := renderAdapters(m) | |
| 78 | + | if err != nil { | |
| 79 | + | return nil, fmt.Errorf("render adapters: %w", err) | |
| 80 | + | } | |
| 81 | + | out[filepath.Join("internal", "capi", "adapters_gen.go")] = adapterContent | |
| 82 | + | ||
| 83 | + | // Generate composite port file | |
| 84 | + | compositeContent, err := renderComposite(m) | |
| 85 | + | if err != nil { | |
| 86 | + | return nil, fmt.Errorf("render composite: %w", err) | |
| 87 | + | } | |
| 88 | + | out[filepath.Join("internal", "ports", "out", "capi_gen.go")] = compositeContent | |
| 89 | + | ||
| 65 | 90 | return out, writeAll(root, out) | |
| 66 | 91 | } | |
| 67 | 92 | ||
| @@ -85,37 +110,43 @@ func renderCAPI(group model.Group, symbols []model.Symbol, libraries []model.Lib | |||
| 85 | 110 | } | |
| 86 | 111 | } | |
| 87 | 112 | ||
| 88 | - | // Filter callbacks and event structs for stream_playback group | |
| 113 | + | // Filter callbacks and event structs for this group | |
| 89 | 114 | var groupCallbacks []model.Callback | |
| 90 | 115 | 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 | - | } | |
| 116 | + | for _, cb := range callbacks { | |
| 117 | + | if cb.Group == group.Name { | |
| 118 | + | groupCallbacks = append(groupCallbacks, cb) | |
| 119 | + | } | |
| 120 | + | } | |
| 121 | + | for _, es := range eventStructs { | |
| 122 | + | if es.Group == group.Name { | |
| 123 | + | groupEventStructs = append(groupEventStructs, es) | |
| 124 | + | } | |
| 125 | + | } | |
| 126 | + | // Check if callbacks need unsafe | |
| 127 | + | for _, cb := range groupCallbacks { | |
| 128 | + | if strings.Contains(cb.Signature, "unsafe.Pointer") { | |
| 129 | + | needsUnsafe = true | |
| 130 | + | break | |
| 100 | 131 | } | |
| 101 | 132 | } | |
| 102 | 133 | ||
| 103 | 134 | data := struct { | |
| 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 | |
| 135 | + | Group model.Group | |
| 136 | + | Symbols []model.Symbol | |
| 137 | + | LibraryMap map[string]string | |
| 138 | + | NeedsUnsafe bool | |
| 139 | + | Callbacks []model.Callback | |
| 140 | + | EventStructs []model.EventStruct | |
| 141 | + | HasCallbacks bool | |
| 111 | 142 | }{ | |
| 112 | - | Group: group, | |
| 113 | - | Symbols: groupSymbols, | |
| 114 | - | LibraryMap: libMap, | |
| 115 | - | NeedsUnsafe: needsUnsafe, | |
| 116 | - | Callbacks: groupCallbacks, | |
| 117 | - | EventStructs: groupEventStructs, | |
| 118 | - | IsStreamPlayback: group.Name == "stream_playback", | |
| 143 | + | Group: group, | |
| 144 | + | Symbols: groupSymbols, | |
| 145 | + | LibraryMap: libMap, | |
| 146 | + | NeedsUnsafe: needsUnsafe, | |
| 147 | + | Callbacks: groupCallbacks, | |
| 148 | + | EventStructs: groupEventStructs, | |
| 149 | + | HasCallbacks: len(groupCallbacks) > 0, | |
| 119 | 150 | } | |
| 120 | 151 | ||
| 121 | 152 | var buf bytes.Buffer | |
| @@ -155,10 +186,12 @@ func renderPort(group model.Group, symbols []model.Symbol, eventStructs []model. | |||
| 155 | 186 | } | |
| 156 | 187 | } | |
| 157 | 188 | ||
| 158 | - | // Filter event structs for stream_playback group | |
| 189 | + | // Filter event structs for this group | |
| 159 | 190 | var groupEventStructs []model.EventStruct | |
| 160 | - | if group.Name == "stream_playback" { | |
| 161 | - | groupEventStructs = eventStructs | |
| 191 | + | for _, es := range eventStructs { | |
| 192 | + | if es.Group == group.Name { | |
| 193 | + | groupEventStructs = append(groupEventStructs, es) | |
| 194 | + | } | |
| 162 | 195 | } | |
| 163 | 196 | ||
| 164 | 197 | data := struct { | |
| @@ -250,6 +283,100 @@ func toGoName(cName string) string { | |||
| 250 | 283 | return strings.Join(parts, "") | |
| 251 | 284 | } | |
| 252 | 285 | ||
| 286 | + | // toCamelCase converts a snake_case name like stream_playback to StreamPlayback. | |
| 287 | + | func toCamelCase(s string) string { | |
| 288 | + | parts := strings.Split(s, "_") | |
| 289 | + | for i, p := range parts { | |
| 290 | + | parts[i] = titleCase(p) | |
| 291 | + | } | |
| 292 | + | return strings.Join(parts, "") | |
| 293 | + | } | |
| 294 | + | ||
| 295 | + | // renderAdapters generates one thin adapter struct per group with forwarding methods. | |
| 296 | + | func renderAdapters(m *model.Model) ([]byte, error) { | |
| 297 | + | type AdapterMethod struct { | |
| 298 | + | CName string | |
| 299 | + | GoName string | |
| 300 | + | Params string | |
| 301 | + | Results string | |
| 302 | + | } | |
| 303 | + | ||
| 304 | + | type AdapterGroup struct { | |
| 305 | + | AdapterName string | |
| 306 | + | Methods []AdapterMethod | |
| 307 | + | } | |
| 308 | + | ||
| 309 | + | var groups []AdapterGroup | |
| 310 | + | needsUnsafe := false | |
| 311 | + | for _, group := range m.Groups { | |
| 312 | + | groupSyms := filterSymbolsByGroup(m.Symbols, group.Name) | |
| 313 | + | methods := make([]AdapterMethod, 0, len(groupSyms)) | |
| 314 | + | ||
| 315 | + | for _, sym := range groupSyms { | |
| 316 | + | params, results := parseSignature(sym.Signature) | |
| 317 | + | methods = append(methods, AdapterMethod{ | |
| 318 | + | CName: sym.Name, | |
| 319 | + | GoName: toGoName(sym.Name), | |
| 320 | + | Params: params, | |
| 321 | + | Results: results, | |
| 322 | + | }) | |
| 323 | + | if strings.Contains(sym.Signature, "unsafe.Pointer") { | |
| 324 | + | needsUnsafe = true | |
| 325 | + | } | |
| 326 | + | } | |
| 327 | + | ||
| 328 | + | groups = append(groups, AdapterGroup{ | |
| 329 | + | AdapterName: toCamelCase(group.Name) + "CAPIAdapter", | |
| 330 | + | Methods: methods, | |
| 331 | + | }) | |
| 332 | + | } | |
| 333 | + | ||
| 334 | + | data := struct { | |
| 335 | + | Groups []AdapterGroup | |
| 336 | + | NeedsUnsafe bool | |
| 337 | + | }{ | |
| 338 | + | Groups: groups, | |
| 339 | + | NeedsUnsafe: needsUnsafe, | |
| 340 | + | } | |
| 341 | + | ||
| 342 | + | var buf bytes.Buffer | |
| 343 | + | if err := adapterTmpl.Execute(&buf, data); err != nil { | |
| 344 | + | return nil, fmt.Errorf("execute adapter template: %w", err) | |
| 345 | + | } | |
| 346 | + | return buf.Bytes(), nil | |
| 347 | + | } | |
| 348 | + | ||
| 349 | + | // renderComposite generates the composite CAPI interface that embeds all group interfaces. | |
| 350 | + | func renderComposite(m *model.Model) ([]byte, error) { | |
| 351 | + | // Determine if any group uses unsafe.Pointer | |
| 352 | + | needsUnsafe := false | |
| 353 | + | for _, group := range m.Groups { | |
| 354 | + | for _, sym := range filterSymbolsByGroup(m.Symbols, group.Name) { | |
| 355 | + | if strings.Contains(sym.Signature, "unsafe.Pointer") { | |
| 356 | + | needsUnsafe = true | |
| 357 | + | break | |
| 358 | + | } | |
| 359 | + | } | |
| 360 | + | if needsUnsafe { | |
| 361 | + | break | |
| 362 | + | } | |
| 363 | + | } | |
| 364 | + | ||
| 365 | + | data := struct { | |
| 366 | + | Groups []model.Group | |
| 367 | + | NeedsUnsafe bool | |
| 368 | + | }{ | |
| 369 | + | Groups: m.Groups, | |
| 370 | + | NeedsUnsafe: needsUnsafe, | |
| 371 | + | } | |
| 372 | + | ||
| 373 | + | var buf bytes.Buffer | |
| 374 | + | if err := compositeTmpl.Execute(&buf, data); err != nil { | |
| 375 | + | return nil, fmt.Errorf("execute composite template: %w", err) | |
| 376 | + | } | |
| 377 | + | return buf.Bytes(), nil | |
| 378 | + | } | |
| 379 | + | ||
| 253 | 380 | // writeAll writes all generated files to disk. | |
| 254 | 381 | func writeAll(root string, files map[string][]byte) error { | |
| 255 | 382 | for path, content := range files { | |
| @@ -277,7 +404,7 @@ import "unsafe" | |||
| 277 | 404 | // {{.Name}}Func is the function type for {{.Name}}. | |
| 278 | 405 | type {{.Name}}Func {{.Signature}} | |
| 279 | 406 | {{end}} | |
| 280 | - | {{if .IsStreamPlayback}} | |
| 407 | + | {{if .HasCallbacks}} | |
| 281 | 408 | {{range .Callbacks}} | |
| 282 | 409 | // {{.Name}} represents the callback struct for {{.Name}}. | |
| 283 | 410 | type {{.Name}} {{.Signature}} | |
| @@ -310,3 +437,38 @@ type {{.Group.Interface}} interface { | |||
| 310 | 437 | {{- end}} | |
| 311 | 438 | } | |
| 312 | 439 | ` | |
| 440 | + | ||
| 441 | + | const adapterTemplate = `// Code generated by pipewiregen. DO NOT EDIT. | |
| 442 | + | ||
| 443 | + | package capi | |
| 444 | + | {{if .NeedsUnsafe}} | |
| 445 | + | import "unsafe" | |
| 446 | + | {{end}} | |
| 447 | + | {{range $group := .Groups}} | |
| 448 | + | // {{$group.AdapterName}} forwards calls to the generated CAPI variables. | |
| 449 | + | type {{$group.AdapterName}} struct{} | |
| 450 | + | {{range $group.Methods}} | |
| 451 | + | func (a *{{$group.AdapterName}}) {{.GoName}}{{.Params}}{{if .Results}} {{.Results}}{{end}} { | |
| 452 | + | {{- if .Results}} | |
| 453 | + | return {{.CName}}{{.Params}} | |
| 454 | + | {{- else}} | |
| 455 | + | {{.CName}}{{.Params}} | |
| 456 | + | {{- end}} | |
| 457 | + | } | |
| 458 | + | {{end}} | |
| 459 | + | {{end}} | |
| 460 | + | ` | |
| 461 | + | ||
| 462 | + | const compositeTemplate = `// Code generated by pipewiregen. DO NOT EDIT. | |
| 463 | + | ||
| 464 | + | package out | |
| 465 | + | {{if .NeedsUnsafe}} | |
| 466 | + | import "unsafe" | |
| 467 | + | {{end}} | |
| 468 | + | // CAPI is the composite interface that embeds all generated group interfaces. | |
| 469 | + | type CAPI interface { | |
| 470 | + | {{- range .Groups}} | |
| 471 | + | {{.Interface}} | |
| 472 | + | {{- end}} | |
| 473 | + | } | |
| 474 | + | ` | |
| @@ -14,8 +14,10 @@ import ( | |||||
| 14 | ) | 14 | ) | ||
| 15 | 15 | ||||
| 16 | var ( | 16 | var ( | ||
| 17 | - | capiTmpl *template.Template | 17 | + | capiTmpl *template.Template |
| 18 | - | portTmpl *template.Template | 18 | + | portTmpl *template.Template |
| 19 | + | adapterTmpl *template.Template | |||
| 20 | + | compositeTmpl *template.Template | |||
| 19 | ) | 21 | ) | ||
| 20 | 22 | ||||
| 21 | func init() { | 23 | func init() { | ||
| @@ -26,6 +28,14 @@ func init() { | |||||
| 26 | portTmpl = template.Must(template.New("port").Funcs(template.FuncMap{ | 28 | portTmpl = template.Must(template.New("port").Funcs(template.FuncMap{ | ||
| 27 | "title": titleCase, | 29 | "title": titleCase, | ||
| 28 | }).Parse(portTemplate)) | 30 | }).Parse(portTemplate)) | ||
| 31 | + | ||||
| 32 | + | adapterTmpl = template.Must(template.New("adapter").Funcs(template.FuncMap{ | |||
| 33 | + | "title": titleCase, | |||
| 34 | + | }).Parse(adapterTemplate)) | |||
| 35 | + | ||||
| 36 | + | compositeTmpl = template.Must(template.New("composite").Funcs(template.FuncMap{ | |||
| 37 | + | "title": titleCase, | |||
| 38 | + | }).Parse(compositeTemplate)) | |||
| 29 | } | 39 | } | ||
| 30 | 40 | ||||
| 31 | // titleCase converts a string to title case (first letter uppercase, rest lowercase). | 41 | // titleCase converts a string to title case (first letter uppercase, rest lowercase). | ||
| @@ -62,6 +72,21 @@ func Emit(m *model.Model, root string) (map[string][]byte, error) { | |||||
| 62 | } | 72 | } | ||
| 63 | out[portPath] = portContent | 73 | out[portPath] = portContent | ||
| 64 | } | 74 | } | ||
| 75 | + | ||||
| 76 | + | // Generate adapters file | |||
| 77 | + | adapterContent, err := renderAdapters(m) | |||
| 78 | + | if err != nil { | |||
| 79 | + | return nil, fmt.Errorf("render adapters: %w", err) | |||
| 80 | + | } | |||
| 81 | + | out[filepath.Join("internal", "capi", "adapters_gen.go")] = adapterContent | |||
| 82 | + | ||||
| 83 | + | // Generate composite port file | |||
| 84 | + | compositeContent, err := renderComposite(m) | |||
| 85 | + | if err != nil { | |||
| 86 | + | return nil, fmt.Errorf("render composite: %w", err) | |||
| 87 | + | } | |||
| 88 | + | out[filepath.Join("internal", "ports", "out", "capi_gen.go")] = compositeContent | |||
| 89 | + | ||||
| 65 | return out, writeAll(root, out) | 90 | return out, writeAll(root, out) | ||
| 66 | } | 91 | } | ||
| 67 | 92 | ||||
| @@ -85,37 +110,43 @@ func renderCAPI(group model.Group, symbols []model.Symbol, libraries []model.Lib | |||||
| 85 | } | 110 | } | ||
| 86 | } | 111 | } | ||
| 87 | 112 | ||||
| 88 | - | // Filter callbacks and event structs for stream_playback group | 113 | + | // Filter callbacks and event structs for this group |
| 89 | var groupCallbacks []model.Callback | 114 | var groupCallbacks []model.Callback | ||
| 90 | var groupEventStructs []model.EventStruct | 115 | var groupEventStructs []model.EventStruct | ||
| 91 | - | if group.Name == "stream_playback" { | 116 | + | for _, cb := range callbacks { |
| 92 | - | groupCallbacks = callbacks | 117 | + | if cb.Group == group.Name { |
| 93 | - | groupEventStructs = eventStructs | 118 | + | groupCallbacks = append(groupCallbacks, cb) |
| 94 | - | // Check if callbacks need unsafe | 119 | + | } |
| 95 | - | for _, cb := range callbacks { | 120 | + | } |
| 96 | - | if strings.Contains(cb.Signature, "unsafe.Pointer") { | 121 | + | for _, es := range eventStructs { |
| 97 | - | needsUnsafe = true | 122 | + | if es.Group == group.Name { |
| 98 | - | break | 123 | + | groupEventStructs = append(groupEventStructs, es) |
| 99 | - | } | 124 | + | } |
| 125 | + | } | |||
| 126 | + | // Check if callbacks need unsafe | |||
| 127 | + | for _, cb := range groupCallbacks { | |||
| 128 | + | if strings.Contains(cb.Signature, "unsafe.Pointer") { | |||
| 129 | + | needsUnsafe = true | |||
| 130 | + | break | |||
| 100 | } | 131 | } | ||
| 101 | } | 132 | } | ||
| 102 | 133 | ||||
| 103 | data := struct { | 134 | data := struct { | ||
| 104 | - | Group model.Group | 135 | + | Group model.Group |
| 105 | - | Symbols []model.Symbol | 136 | + | Symbols []model.Symbol |
| 106 | - | LibraryMap map[string]string | 137 | + | LibraryMap map[string]string |
| 107 | - | NeedsUnsafe bool | 138 | + | NeedsUnsafe bool |
| 108 | - | Callbacks []model.Callback | 139 | + | Callbacks []model.Callback |
| 109 | - | EventStructs []model.EventStruct | 140 | + | EventStructs []model.EventStruct |
| 110 | - | IsStreamPlayback bool | 141 | + | HasCallbacks bool |
| 111 | }{ | 142 | }{ | ||
| 112 | - | Group: group, | 143 | + | Group: group, |
| 113 | - | Symbols: groupSymbols, | 144 | + | Symbols: groupSymbols, |
| 114 | - | LibraryMap: libMap, | 145 | + | LibraryMap: libMap, |
| 115 | - | NeedsUnsafe: needsUnsafe, | 146 | + | NeedsUnsafe: needsUnsafe, |
| 116 | - | Callbacks: groupCallbacks, | 147 | + | Callbacks: groupCallbacks, |
| 117 | - | EventStructs: groupEventStructs, | 148 | + | EventStructs: groupEventStructs, |
| 118 | - | IsStreamPlayback: group.Name == "stream_playback", | 149 | + | HasCallbacks: len(groupCallbacks) > 0, |
| 119 | } | 150 | } | ||
| 120 | 151 | ||||
| 121 | var buf bytes.Buffer | 152 | var buf bytes.Buffer | ||
| @@ -155,10 +186,12 @@ func renderPort(group model.Group, symbols []model.Symbol, eventStructs []model. | |||||
| 155 | } | 186 | } | ||
| 156 | } | 187 | } | ||
| 157 | 188 | ||||
| 158 | - | // Filter event structs for stream_playback group | 189 | + | // Filter event structs for this group |
| 159 | var groupEventStructs []model.EventStruct | 190 | var groupEventStructs []model.EventStruct | ||
| 160 | - | if group.Name == "stream_playback" { | 191 | + | for _, es := range eventStructs { |
| 161 | - | groupEventStructs = eventStructs | 192 | + | if es.Group == group.Name { |
| 193 | + | groupEventStructs = append(groupEventStructs, es) | |||
| 194 | + | } | |||
| 162 | } | 195 | } | ||
| 163 | 196 | ||||
| 164 | data := struct { | 197 | data := struct { | ||
| @@ -250,6 +283,100 @@ func toGoName(cName string) string { | |||||
| 250 | return strings.Join(parts, "") | 283 | return strings.Join(parts, "") | ||
| 251 | } | 284 | } | ||
| 252 | 285 | ||||
| 286 | + | // toCamelCase converts a snake_case name like stream_playback to StreamPlayback. | |||
| 287 | + | func toCamelCase(s string) string { | |||
| 288 | + | parts := strings.Split(s, "_") | |||
| 289 | + | for i, p := range parts { | |||
| 290 | + | parts[i] = titleCase(p) | |||
| 291 | + | } | |||
| 292 | + | return strings.Join(parts, "") | |||
| 293 | + | } | |||
| 294 | + | ||||
| 295 | + | // renderAdapters generates one thin adapter struct per group with forwarding methods. | |||
| 296 | + | func renderAdapters(m *model.Model) ([]byte, error) { | |||
| 297 | + | type AdapterMethod struct { | |||
| 298 | + | CName string | |||
| 299 | + | GoName string | |||
| 300 | + | Params string | |||
| 301 | + | Results string | |||
| 302 | + | } | |||
| 303 | + | ||||
| 304 | + | type AdapterGroup struct { | |||
| 305 | + | AdapterName string | |||
| 306 | + | Methods []AdapterMethod | |||
| 307 | + | } | |||
| 308 | + | ||||
| 309 | + | var groups []AdapterGroup | |||
| 310 | + | needsUnsafe := false | |||
| 311 | + | for _, group := range m.Groups { | |||
| 312 | + | groupSyms := filterSymbolsByGroup(m.Symbols, group.Name) | |||
| 313 | + | methods := make([]AdapterMethod, 0, len(groupSyms)) | |||
| 314 | + | ||||
| 315 | + | for _, sym := range groupSyms { | |||
| 316 | + | params, results := parseSignature(sym.Signature) | |||
| 317 | + | methods = append(methods, AdapterMethod{ | |||
| 318 | + | CName: sym.Name, | |||
| 319 | + | GoName: toGoName(sym.Name), | |||
| 320 | + | Params: params, | |||
| 321 | + | Results: results, | |||
| 322 | + | }) | |||
| 323 | + | if strings.Contains(sym.Signature, "unsafe.Pointer") { | |||
| 324 | + | needsUnsafe = true | |||
| 325 | + | } | |||
| 326 | + | } | |||
| 327 | + | ||||
| 328 | + | groups = append(groups, AdapterGroup{ | |||
| 329 | + | AdapterName: toCamelCase(group.Name) + "CAPIAdapter", | |||
| 330 | + | Methods: methods, | |||
| 331 | + | }) | |||
| 332 | + | } | |||
| 333 | + | ||||
| 334 | + | data := struct { | |||
| 335 | + | Groups []AdapterGroup | |||
| 336 | + | NeedsUnsafe bool | |||
| 337 | + | }{ | |||
| 338 | + | Groups: groups, | |||
| 339 | + | NeedsUnsafe: needsUnsafe, | |||
| 340 | + | } | |||
| 341 | + | ||||
| 342 | + | var buf bytes.Buffer | |||
| 343 | + | if err := adapterTmpl.Execute(&buf, data); err != nil { | |||
| 344 | + | return nil, fmt.Errorf("execute adapter template: %w", err) | |||
| 345 | + | } | |||
| 346 | + | return buf.Bytes(), nil | |||
| 347 | + | } | |||
| 348 | + | ||||
| 349 | + | // renderComposite generates the composite CAPI interface that embeds all group interfaces. | |||
| 350 | + | func renderComposite(m *model.Model) ([]byte, error) { | |||
| 351 | + | // Determine if any group uses unsafe.Pointer | |||
| 352 | + | needsUnsafe := false | |||
| 353 | + | for _, group := range m.Groups { | |||
| 354 | + | for _, sym := range filterSymbolsByGroup(m.Symbols, group.Name) { | |||
| 355 | + | if strings.Contains(sym.Signature, "unsafe.Pointer") { | |||
| 356 | + | needsUnsafe = true | |||
| 357 | + | break | |||
| 358 | + | } | |||
| 359 | + | } | |||
| 360 | + | if needsUnsafe { | |||
| 361 | + | break | |||
| 362 | + | } | |||
| 363 | + | } | |||
| 364 | + | ||||
| 365 | + | data := struct { | |||
| 366 | + | Groups []model.Group | |||
| 367 | + | NeedsUnsafe bool | |||
| 368 | + | }{ | |||
| 369 | + | Groups: m.Groups, | |||
| 370 | + | NeedsUnsafe: needsUnsafe, | |||
| 371 | + | } | |||
| 372 | + | ||||
| 373 | + | var buf bytes.Buffer | |||
| 374 | + | if err := compositeTmpl.Execute(&buf, data); err != nil { | |||
| 375 | + | return nil, fmt.Errorf("execute composite template: %w", err) | |||
| 376 | + | } | |||
| 377 | + | return buf.Bytes(), nil | |||
| 378 | + | } | |||
| 379 | + | ||||
| 253 | // writeAll writes all generated files to disk. | 380 | // writeAll writes all generated files to disk. | ||
| 254 | func writeAll(root string, files map[string][]byte) error { | 381 | func writeAll(root string, files map[string][]byte) error { | ||
| 255 | for path, content := range files { | 382 | for path, content := range files { | ||
| @@ -277,7 +404,7 @@ import "unsafe" | |||||
| 277 | // {{.Name}}Func is the function type for {{.Name}}. | 404 | // {{.Name}}Func is the function type for {{.Name}}. | ||
| 278 | type {{.Name}}Func {{.Signature}} | 405 | type {{.Name}}Func {{.Signature}} | ||
| 279 | {{end}} | 406 | {{end}} | ||
| 280 | - | {{if .IsStreamPlayback}} | 407 | + | {{if .HasCallbacks}} |
| 281 | {{range .Callbacks}} | 408 | {{range .Callbacks}} | ||
| 282 | // {{.Name}} represents the callback struct for {{.Name}}. | 409 | // {{.Name}} represents the callback struct for {{.Name}}. | ||
| 283 | type {{.Name}} {{.Signature}} | 410 | type {{.Name}} {{.Signature}} | ||
| @@ -310,3 +437,38 @@ type {{.Group.Interface}} interface { | |||||
| 310 | {{- end}} | 437 | {{- end}} | ||
| 311 | } | 438 | } | ||
| 312 | ` | 439 | ` | ||
| 440 | + | ||||
| 441 | + | const adapterTemplate = `// Code generated by pipewiregen. DO NOT EDIT. | |||
| 442 | + | ||||
| 443 | + | package capi | |||
| 444 | + | {{if .NeedsUnsafe}} | |||
| 445 | + | import "unsafe" | |||
| 446 | + | {{end}} | |||
| 447 | + | {{range $group := .Groups}} | |||
| 448 | + | // {{$group.AdapterName}} forwards calls to the generated CAPI variables. | |||
| 449 | + | type {{$group.AdapterName}} struct{} | |||
| 450 | + | {{range $group.Methods}} | |||
| 451 | + | func (a *{{$group.AdapterName}}) {{.GoName}}{{.Params}}{{if .Results}} {{.Results}}{{end}} { | |||
| 452 | + | {{- if .Results}} | |||
| 453 | + | return {{.CName}}{{.Params}} | |||
| 454 | + | {{- else}} | |||
| 455 | + | {{.CName}}{{.Params}} | |||
| 456 | + | {{- end}} | |||
| 457 | + | } | |||
| 458 | + | {{end}} | |||
| 459 | + | {{end}} | |||
| 460 | + | ` | |||
| 461 | + | ||||
| 462 | + | const compositeTemplate = `// Code generated by pipewiregen. DO NOT EDIT. | |||
| 463 | + | ||||
| 464 | + | package out | |||
| 465 | + | {{if .NeedsUnsafe}} | |||
| 466 | + | import "unsafe" | |||
| 467 | + | {{end}} | |||
| 468 | + | // CAPI is the composite interface that embeds all generated group interfaces. | |||
| 469 | + | type CAPI interface { | |||
| 470 | + | {{- range .Groups}} | |||
| 471 | + | {{.Interface}} | |||
| 472 | + | {{- end}} | |||
| 473 | + | } | |||
| 474 | + | ` | |||
M cmd/pipewiregen/internal/emitter/emitter_test.go +39 -2
| @@ -55,10 +55,10 @@ func testPlayerModel() *model.Model { | |||
| 55 | 55 | {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"}, | |
| 56 | 56 | }, | |
| 57 | 57 | Callbacks: []model.Callback{ | |
| 58 | - | {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)}"}, | |
| 58 | + | {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)}", Group: "stream_playback"}, | |
| 59 | 59 | }, | |
| 60 | 60 | EventStructs: []model.EventStruct{ | |
| 61 | - | {Name: "pw_stream_events", Callbacks: []string{"process", "param_changed", "add_buffer", "remove_buffer", "flush", "drained", "io_changed"}}, | |
| 61 | + | {Name: "pw_stream_events", Callbacks: []string{"process", "param_changed", "add_buffer", "remove_buffer", "flush", "drained", "io_changed"}, Group: "stream_playback"}, | |
| 62 | 62 | }, | |
| 63 | 63 | } | |
| 64 | 64 | } | |
| @@ -101,6 +101,43 @@ func TestEmitWritesPlayerStreamFiles(t *testing.T) { | |||
| 101 | 101 | } | |
| 102 | 102 | } | |
| 103 | 103 | ||
| 104 | + | func TestEmitWritesAdaptersAndCompositeFiles(t *testing.T) { | |
| 105 | + | paths, err := Emit(testPlayerModel(), t.TempDir()) | |
| 106 | + | if err != nil { | |
| 107 | + | t.Fatalf("Emit returned error: %v", err) | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | // Verify adapters file exists | |
| 111 | + | adapterContent, ok := paths["internal/capi/adapters_gen.go"] | |
| 112 | + | if !ok { | |
| 113 | + | t.Fatal("missing adapters_gen.go output") | |
| 114 | + | } | |
| 115 | + | ||
| 116 | + | // Verify adapter contains streamPlaybackCAPIAdapter with PWStreamConnect method | |
| 117 | + | adapterStr := string(adapterContent) | |
| 118 | + | if !strings.Contains(adapterStr, "StreamPlaybackCAPIAdapter") { | |
| 119 | + | t.Fatal("adapters output missing StreamPlaybackCAPIAdapter struct") | |
| 120 | + | } | |
| 121 | + | if !strings.Contains(adapterStr, "PWStreamConnect") { | |
| 122 | + | t.Fatal("adapters output missing PWStreamConnect method") | |
| 123 | + | } | |
| 124 | + | ||
| 125 | + | // Verify composite port file exists | |
| 126 | + | compositeContent, ok := paths["internal/ports/out/capi_gen.go"] | |
| 127 | + | if !ok { | |
| 128 | + | t.Fatal("missing capi_gen.go composite port output") | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | // Verify composite interface embeds StreamPlaybackAPI | |
| 132 | + | compositeStr := string(compositeContent) | |
| 133 | + | if !strings.Contains(compositeStr, "CAPI") { | |
| 134 | + | t.Fatal("composite output missing CAPI type") | |
| 135 | + | } | |
| 136 | + | if !strings.Contains(compositeStr, "StreamPlaybackAPI") { | |
| 137 | + | t.Fatal("composite output missing StreamPlaybackAPI embed") | |
| 138 | + | } | |
| 139 | + | } | |
| 140 | + | ||
| 104 | 141 | // testCleanupModel returns a model with cleanup symbols that should be generated. | |
| 105 | 142 | func testCleanupModel() *model.Model { | |
| 106 | 143 | return &model.Model{ | |
| @@ -55,10 +55,10 @@ func testPlayerModel() *model.Model { | |||||
| 55 | {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"}, | 55 | {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"}, | ||
| 56 | }, | 56 | }, | ||
| 57 | Callbacks: []model.Callback{ | 57 | Callbacks: []model.Callback{ | ||
| 58 | - | {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)}"}, | 58 | + | {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)}", Group: "stream_playback"}, |
| 59 | }, | 59 | }, | ||
| 60 | EventStructs: []model.EventStruct{ | 60 | EventStructs: []model.EventStruct{ | ||
| 61 | - | {Name: "pw_stream_events", Callbacks: []string{"process", "param_changed", "add_buffer", "remove_buffer", "flush", "drained", "io_changed"}}, | 61 | + | {Name: "pw_stream_events", Callbacks: []string{"process", "param_changed", "add_buffer", "remove_buffer", "flush", "drained", "io_changed"}, Group: "stream_playback"}, |
| 62 | }, | 62 | }, | ||
| 63 | } | 63 | } | ||
| 64 | } | 64 | } | ||
| @@ -101,6 +101,43 @@ func TestEmitWritesPlayerStreamFiles(t *testing.T) { | |||||
| 101 | } | 101 | } | ||
| 102 | } | 102 | } | ||
| 103 | 103 | ||||
| 104 | + | func TestEmitWritesAdaptersAndCompositeFiles(t *testing.T) { | |||
| 105 | + | paths, err := Emit(testPlayerModel(), t.TempDir()) | |||
| 106 | + | if err != nil { | |||
| 107 | + | t.Fatalf("Emit returned error: %v", err) | |||
| 108 | + | } | |||
| 109 | + | ||||
| 110 | + | // Verify adapters file exists | |||
| 111 | + | adapterContent, ok := paths["internal/capi/adapters_gen.go"] | |||
| 112 | + | if !ok { | |||
| 113 | + | t.Fatal("missing adapters_gen.go output") | |||
| 114 | + | } | |||
| 115 | + | ||||
| 116 | + | // Verify adapter contains streamPlaybackCAPIAdapter with PWStreamConnect method | |||
| 117 | + | adapterStr := string(adapterContent) | |||
| 118 | + | if !strings.Contains(adapterStr, "StreamPlaybackCAPIAdapter") { | |||
| 119 | + | t.Fatal("adapters output missing StreamPlaybackCAPIAdapter struct") | |||
| 120 | + | } | |||
| 121 | + | if !strings.Contains(adapterStr, "PWStreamConnect") { | |||
| 122 | + | t.Fatal("adapters output missing PWStreamConnect method") | |||
| 123 | + | } | |||
| 124 | + | ||||
| 125 | + | // Verify composite port file exists | |||
| 126 | + | compositeContent, ok := paths["internal/ports/out/capi_gen.go"] | |||
| 127 | + | if !ok { | |||
| 128 | + | t.Fatal("missing capi_gen.go composite port output") | |||
| 129 | + | } | |||
| 130 | + | ||||
| 131 | + | // Verify composite interface embeds StreamPlaybackAPI | |||
| 132 | + | compositeStr := string(compositeContent) | |||
| 133 | + | if !strings.Contains(compositeStr, "CAPI") { | |||
| 134 | + | t.Fatal("composite output missing CAPI type") | |||
| 135 | + | } | |||
| 136 | + | if !strings.Contains(compositeStr, "StreamPlaybackAPI") { | |||
| 137 | + | t.Fatal("composite output missing StreamPlaybackAPI embed") | |||
| 138 | + | } | |||
| 139 | + | } | |||
| 140 | + | ||||
| 104 | // testCleanupModel returns a model with cleanup symbols that should be generated. | 141 | // testCleanupModel returns a model with cleanup symbols that should be generated. | ||
| 105 | func testCleanupModel() *model.Model { | 142 | func testCleanupModel() *model.Model { | ||
| 106 | return &model.Model{ | 143 | return &model.Model{ | ||
M cmd/pipewiregen/internal/model/model.go +2 -0
| @@ -36,10 +36,12 @@ type Symbol struct { | |||
| 36 | 36 | type Callback struct { | |
| 37 | 37 | Name string `json:"name"` | |
| 38 | 38 | Signature string `json:"signature"` | |
| 39 | + | Group string `json:"group"` | |
| 39 | 40 | } | |
| 40 | 41 | ||
| 41 | 42 | // EventStruct represents an event struct with its associated callbacks. | |
| 42 | 43 | type EventStruct struct { | |
| 43 | 44 | Name string `json:"name"` | |
| 44 | 45 | Callbacks []string `json:"callbacks"` | |
| 46 | + | Group string `json:"group"` | |
| 45 | 47 | } | |
| @@ -36,10 +36,12 @@ type Symbol struct { | |||||
| 36 | type Callback struct { | 36 | type Callback struct { | ||
| 37 | Name string `json:"name"` | 37 | Name string `json:"name"` | ||
| 38 | Signature string `json:"signature"` | 38 | Signature string `json:"signature"` | ||
| 39 | + | Group string `json:"group"` | |||
| 39 | } | 40 | } | ||
| 40 | 41 | ||||
| 41 | // EventStruct represents an event struct with its associated callbacks. | 42 | // EventStruct represents an event struct with its associated callbacks. | ||
| 42 | type EventStruct struct { | 43 | type EventStruct struct { | ||
| 43 | Name string `json:"name"` | 44 | Name string `json:"name"` | ||
| 44 | Callbacks []string `json:"callbacks"` | 45 | Callbacks []string `json:"callbacks"` | ||
| 46 | + | Group string `json:"group"` | |||
| 45 | } | 47 | } | ||
M cmd/pipewiregen/internal/parser/parser.go +12 -0
| @@ -79,6 +79,12 @@ func validate(m *model.Model) error { | |||
| 79 | 79 | if cb.Signature == "" { | |
| 80 | 80 | return fmt.Errorf("callback %q has empty signature", cb.Name) | |
| 81 | 81 | } | |
| 82 | + | if cb.Group == "" { | |
| 83 | + | return fmt.Errorf("callback %q has empty group", cb.Name) | |
| 84 | + | } | |
| 85 | + | if !groupNames[cb.Group] { | |
| 86 | + | return fmt.Errorf("callback %q references unknown group %q", cb.Name, cb.Group) | |
| 87 | + | } | |
| 82 | 88 | } | |
| 83 | 89 | ||
| 84 | 90 | // Validate event structs | |
| @@ -86,6 +92,12 @@ func validate(m *model.Model) error { | |||
| 86 | 92 | if es.Name == "" { | |
| 87 | 93 | return fmt.Errorf("event struct at index %d has empty name", i) | |
| 88 | 94 | } | |
| 95 | + | if es.Group == "" { | |
| 96 | + | return fmt.Errorf("event struct %q has empty group", es.Name) | |
| 97 | + | } | |
| 98 | + | if !groupNames[es.Group] { | |
| 99 | + | return fmt.Errorf("event struct %q references unknown group %q", es.Name, es.Group) | |
| 100 | + | } | |
| 89 | 101 | } | |
| 90 | 102 | ||
| 91 | 103 | return nil | |
| @@ -79,6 +79,12 @@ func validate(m *model.Model) error { | |||||
| 79 | if cb.Signature == "" { | 79 | if cb.Signature == "" { | ||
| 80 | return fmt.Errorf("callback %q has empty signature", cb.Name) | 80 | return fmt.Errorf("callback %q has empty signature", cb.Name) | ||
| 81 | } | 81 | } | ||
| 82 | + | if cb.Group == "" { | |||
| 83 | + | return fmt.Errorf("callback %q has empty group", cb.Name) | |||
| 84 | + | } | |||
| 85 | + | if !groupNames[cb.Group] { | |||
| 86 | + | return fmt.Errorf("callback %q references unknown group %q", cb.Name, cb.Group) | |||
| 87 | + | } | |||
| 82 | } | 88 | } | ||
| 83 | 89 | ||||
| 84 | // Validate event structs | 90 | // Validate event structs | ||
| @@ -86,6 +92,12 @@ func validate(m *model.Model) error { | |||||
| 86 | if es.Name == "" { | 92 | if es.Name == "" { | ||
| 87 | return fmt.Errorf("event struct at index %d has empty name", i) | 93 | return fmt.Errorf("event struct at index %d has empty name", i) | ||
| 88 | } | 94 | } | ||
| 95 | + | if es.Group == "" { | |||
| 96 | + | return fmt.Errorf("event struct %q has empty group", es.Name) | |||
| 97 | + | } | |||
| 98 | + | if !groupNames[es.Group] { | |||
| 99 | + | return fmt.Errorf("event struct %q references unknown group %q", es.Name, es.Group) | |||
| 100 | + | } | |||
| 89 | } | 101 | } | ||
| 90 | 102 | ||||
| 91 | return nil | 103 | return nil | ||
M cmd/pipewiregen/internal/parser/parser_test.go +13 -0
| @@ -151,3 +151,16 @@ func TestLoadEmptyEventStructNameReturnsError(t *testing.T) { | |||
| 151 | 151 | t.Fatalf("error message should mention event struct empty name, got: %v", err) | |
| 152 | 152 | } | |
| 153 | 153 | } | |
| 154 | + | ||
| 155 | + | func TestLoadCallbackWithUnknownGroupReturnsError(t *testing.T) { | |
| 156 | + | _, err := Load("testdata/callback_unknown_group.json") | |
| 157 | + | if err == nil { | |
| 158 | + | t.Fatal("expected error for callback with unknown group, got nil") | |
| 159 | + | } | |
| 160 | + | if !strings.Contains(err.Error(), "validation failed") { | |
| 161 | + | t.Fatalf("error message should contain 'validation failed', got: %v", err) | |
| 162 | + | } | |
| 163 | + | if !strings.Contains(err.Error(), "unknown group") { | |
| 164 | + | t.Fatalf("error message should contain 'unknown group', got: %v", err) | |
| 165 | + | } | |
| 166 | + | } | |
| @@ -151,3 +151,16 @@ func TestLoadEmptyEventStructNameReturnsError(t *testing.T) { | |||||
| 151 | t.Fatalf("error message should mention event struct empty name, got: %v", err) | 151 | t.Fatalf("error message should mention event struct empty name, got: %v", err) | ||
| 152 | } | 152 | } | ||
| 153 | } | 153 | } | ||
| 154 | + | ||||
| 155 | + | func TestLoadCallbackWithUnknownGroupReturnsError(t *testing.T) { | |||
| 156 | + | _, err := Load("testdata/callback_unknown_group.json") | |||
| 157 | + | if err == nil { | |||
| 158 | + | t.Fatal("expected error for callback with unknown group, got nil") | |||
| 159 | + | } | |||
| 160 | + | if !strings.Contains(err.Error(), "validation failed") { | |||
| 161 | + | t.Fatalf("error message should contain 'validation failed', got: %v", err) | |||
| 162 | + | } | |||
| 163 | + | if !strings.Contains(err.Error(), "unknown group") { | |||
| 164 | + | t.Fatalf("error message should contain 'unknown group', got: %v", err) | |||
| 165 | + | } | |||
| 166 | + | } | |||
A cmd/pipewiregen/internal/parser/testdata/callback_unknown_group.json +14 -0
| @@ -0,0 +1,14 @@ | |||
| 1 | + | { | |
| 2 | + | "libraries": [ | |
| 3 | + | {"name": "pipewire", "soname": "libpipewire-0.3.so.0"} | |
| 4 | + | ], | |
| 5 | + | "groups": [ | |
| 6 | + | {"name": "init", "interface": "InitAPI", "package": "out", "symbols": ["pw_init"]} | |
| 7 | + | ], | |
| 8 | + | "symbols": [ | |
| 9 | + | {"name": "pw_init", "library": "pipewire", "group": "init", "signature": "func()"} | |
| 10 | + | ], | |
| 11 | + | "callbacks": [ | |
| 12 | + | {"name": "pw_stream_events", "signature": "struct{}", "group": "nonexistent"} | |
| 13 | + | ] | |
| 14 | + | }⏎̸ | |
| @@ -0,0 +1,14 @@ | |||||
| 1 | + | { | |||
| 2 | + | "libraries": [ | |||
| 3 | + | {"name": "pipewire", "soname": "libpipewire-0.3.so.0"} | |||
| 4 | + | ], | |||
| 5 | + | "groups": [ | |||
| 6 | + | {"name": "init", "interface": "InitAPI", "package": "out", "symbols": ["pw_init"]} | |||
| 7 | + | ], | |||
| 8 | + | "symbols": [ | |||
| 9 | + | {"name": "pw_init", "library": "pipewire", "group": "init", "signature": "func()"} | |||
| 10 | + | ], | |||
| 11 | + | "callbacks": [ | |||
| 12 | + | {"name": "pw_stream_events", "signature": "struct{}", "group": "nonexistent"} | |||
| 13 | + | ] | |||
| 14 | + | }⏎̸ | |||
M gen/pipewire.json +2 -2
| @@ -25,9 +25,9 @@ | |||
| 25 | 25 | {"name": "pw_stream_destroy", "library": "pipewire", "group": "stream_playback", "signature": "func(stream unsafe.Pointer)"} | |
| 26 | 26 | ], | |
| 27 | 27 | "callbacks": [ | |
| 28 | - | {"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)}"} | |
| 28 | + | {"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)}", "group": "stream_playback"} | |
| 29 | 29 | ], | |
| 30 | 30 | "event_structs": [ | |
| 31 | - | {"name": "pw_stream_events", "callbacks": ["process", "param_changed", "add_buffer", "remove_buffer", "flush", "drained", "io_changed"]} | |
| 31 | + | {"name": "pw_stream_events", "callbacks": ["process", "param_changed", "add_buffer", "remove_buffer", "flush", "drained", "io_changed"], "group": "stream_playback"} | |
| 32 | 32 | ] | |
| 33 | 33 | } | |
| @@ -25,9 +25,9 @@ | |||||
| 25 | {"name": "pw_stream_destroy", "library": "pipewire", "group": "stream_playback", "signature": "func(stream unsafe.Pointer)"} | 25 | {"name": "pw_stream_destroy", "library": "pipewire", "group": "stream_playback", "signature": "func(stream unsafe.Pointer)"} | ||
| 26 | ], | 26 | ], | ||
| 27 | "callbacks": [ | 27 | "callbacks": [ | ||
| 28 | - | {"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)}"} | 28 | + | {"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)}", "group": "stream_playback"} |
| 29 | ], | 29 | ], | ||
| 30 | "event_structs": [ | 30 | "event_structs": [ | ||
| 31 | - | {"name": "pw_stream_events", "callbacks": ["process", "param_changed", "add_buffer", "remove_buffer", "flush", "drained", "io_changed"]} | 31 | + | {"name": "pw_stream_events", "callbacks": ["process", "param_changed", "add_buffer", "remove_buffer", "flush", "drained", "io_changed"], "group": "stream_playback"} |
| 32 | ] | 32 | ] | ||
| 33 | } | 33 | } | ||