fix(gen) fix 4 confirmed template generator bugs
- glong/gulong mapped to int/uint for LP64 correctness - vector property getters read GByteArray/GPtrArray struct layout - signal disconnect cleans callback registry via handler mapping - string params with transfer-full use g_strdup for safe g_free WIP: bindings not yet regenerated, needs go generate + build verification
- commit
- 53bd89c3db79330691e0d12a6d47f9da62368e0a
- author
- bnema · · 2026-02-06 22:40 UTC
- parent
- 7393052
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
8 files · +125 -27
M internal/core/core.go +33 -0
| @@ -10,6 +10,7 @@ import ( | |||
| 10 | 10 | "path/filepath" | |
| 11 | 11 | "runtime" | |
| 12 | 12 | "strings" | |
| 13 | + | "sync" | |
| 13 | 14 | "unsafe" | |
| 14 | 15 | ||
| 15 | 16 | "github.com/jwijenbergh/purego" | |
| @@ -227,6 +228,38 @@ func GoString(c uintptr) string { | |||
| 227 | 228 | return string(unsafe.Slice((*byte)(ptr), length)) | |
| 228 | 229 | } | |
| 229 | 230 | ||
| 231 | + | var ( | |
| 232 | + | xGStrdup func(string) uintptr | |
| 233 | + | gstrdupOnce sync.Once | |
| 234 | + | ) | |
| 235 | + | ||
| 236 | + | // GStrdup allocates a C-owned copy of a Go string using g_strdup. | |
| 237 | + | // The returned pointer must be freed with g_free (typically by the callee | |
| 238 | + | // when transfer-ownership="full"). | |
| 239 | + | func GStrdup(s string) uintptr { | |
| 240 | + | gstrdupOnce.Do(func() { | |
| 241 | + | var libs []uintptr | |
| 242 | + | for _, libPath := range GetPaths("GLIB") { | |
| 243 | + | lib, err := purego.Dlopen(libPath, purego.RTLD_NOW|purego.RTLD_GLOBAL) | |
| 244 | + | if err != nil { | |
| 245 | + | continue | |
| 246 | + | } | |
| 247 | + | libs = append(libs, lib) | |
| 248 | + | } | |
| 249 | + | PuregoSafeRegister(&xGStrdup, libs, "g_strdup") | |
| 250 | + | }) | |
| 251 | + | return xGStrdup(s) | |
| 252 | + | } | |
| 253 | + | ||
| 254 | + | // GStrdupNullable is like GStrdup but accepts a nullable *string. | |
| 255 | + | // Returns 0 for nil, or a g_strdup'd copy for non-nil. | |
| 256 | + | func GStrdupNullable(s *string) uintptr { | |
| 257 | + | if s == nil { | |
| 258 | + | return 0 | |
| 259 | + | } | |
| 260 | + | return GStrdup(*s) | |
| 261 | + | } | |
| 262 | + | ||
| 230 | 263 | // NullableStringToPtr converts a nullable Go string to a uintptr suitable for C calls. | |
| 231 | 264 | // Returns both the pointer and the backing byte slice. The caller MUST call | |
| 232 | 265 | // runtime.KeepAlive(bytes) after the C call completes to prevent GC from collecting | |
| @@ -10,6 +10,7 @@ import ( | |||||
| 10 | "path/filepath" | 10 | "path/filepath" | ||
| 11 | "runtime" | 11 | "runtime" | ||
| 12 | "strings" | 12 | "strings" | ||
| 13 | + | "sync" | |||
| 13 | "unsafe" | 14 | "unsafe" | ||
| 14 | 15 | ||||
| 15 | "github.com/jwijenbergh/purego" | 16 | "github.com/jwijenbergh/purego" | ||
| @@ -227,6 +228,38 @@ func GoString(c uintptr) string { | |||||
| 227 | return string(unsafe.Slice((*byte)(ptr), length)) | 228 | return string(unsafe.Slice((*byte)(ptr), length)) | ||
| 228 | } | 229 | } | ||
| 229 | 230 | ||||
| 231 | + | var ( | |||
| 232 | + | xGStrdup func(string) uintptr | |||
| 233 | + | gstrdupOnce sync.Once | |||
| 234 | + | ) | |||
| 235 | + | ||||
| 236 | + | // GStrdup allocates a C-owned copy of a Go string using g_strdup. | |||
| 237 | + | // The returned pointer must be freed with g_free (typically by the callee | |||
| 238 | + | // when transfer-ownership="full"). | |||
| 239 | + | func GStrdup(s string) uintptr { | |||
| 240 | + | gstrdupOnce.Do(func() { | |||
| 241 | + | var libs []uintptr | |||
| 242 | + | for _, libPath := range GetPaths("GLIB") { | |||
| 243 | + | lib, err := purego.Dlopen(libPath, purego.RTLD_NOW|purego.RTLD_GLOBAL) | |||
| 244 | + | if err != nil { | |||
| 245 | + | continue | |||
| 246 | + | } | |||
| 247 | + | libs = append(libs, lib) | |||
| 248 | + | } | |||
| 249 | + | PuregoSafeRegister(&xGStrdup, libs, "g_strdup") | |||
| 250 | + | }) | |||
| 251 | + | return xGStrdup(s) | |||
| 252 | + | } | |||
| 253 | + | ||||
| 254 | + | // GStrdupNullable is like GStrdup but accepts a nullable *string. | |||
| 255 | + | // Returns 0 for nil, or a g_strdup'd copy for non-nil. | |||
| 256 | + | func GStrdupNullable(s *string) uintptr { | |||
| 257 | + | if s == nil { | |||
| 258 | + | return 0 | |||
| 259 | + | } | |||
| 260 | + | return GStrdup(*s) | |||
| 261 | + | } | |||
| 262 | + | ||||
| 230 | // NullableStringToPtr converts a nullable Go string to a uintptr suitable for C calls. | 263 | // NullableStringToPtr converts a nullable Go string to a uintptr suitable for C calls. | ||
| 231 | // Returns both the pointer and the backing byte slice. The caller MUST call | 264 | // Returns both the pointer and the backing byte slice. The caller MUST call | ||
| 232 | // runtime.KeepAlive(bytes) after the C call completes to prevent GC from collecting | 265 | // runtime.KeepAlive(bytes) after the C call completes to prevent GC from collecting | ||
M internal/gir/types/template.go +31 -10
| @@ -62,6 +62,9 @@ type funcArgsTemplate struct { | |||
| 62 | 62 | ||
| 63 | 63 | // UsesNullableHelper indicates nullable string handling that needs core import. | |
| 64 | 64 | UsesNullableHelper bool | |
| 65 | + | ||
| 66 | + | // UsesGStrdup indicates transfer-full string handling that needs core import. | |
| 67 | + | UsesGStrdup bool | |
| 65 | 68 | } | |
| 66 | 69 | ||
| 67 | 70 | // ArgContext indicates where the arguments are flowing so we can handle | |
| @@ -84,7 +87,7 @@ func isStringType(t string) bool { | |||
| 84 | 87 | ||
| 85 | 88 | // NeedsCore reports whether this argument set requires core helpers. | |
| 86 | 89 | func (f funcArgsTemplate) NeedsCore() bool { | |
| 87 | - | return f.UsesNullableHelper | |
| 90 | + | return f.UsesNullableHelper || f.UsesGStrdup | |
| 88 | 91 | } | |
| 89 | 92 | ||
| 90 | 93 | func qualifyCallbackType(t string, callbackNS string, currentNS string) string { | |
| @@ -140,7 +143,7 @@ func qualifyCallbackTypes(types []string, callbackNS string, currentNS string) [ | |||
| 140 | 143 | return qualified | |
| 141 | 144 | } | |
| 142 | 145 | ||
| 143 | - | func (f *funcArgsTemplate) AddAPI(t string, n string, k Kind, ns string, nullable bool, isOut bool, ctx ArgContext) { | |
| 146 | + | func (f *funcArgsTemplate) AddAPI(t string, n string, k Kind, ns string, nullable bool, isOut bool, ctx ArgContext, transferFull bool) { | |
| 144 | 147 | c := n | |
| 145 | 148 | cRef := n // For CallWithRefs, defaults to same as Call | |
| 146 | 149 | stars := strings.Count(t, "*") | |
| @@ -162,10 +165,23 @@ func (f *funcArgsTemplate) AddAPI(t string, n string, k Kind, ns string, nullabl | |||
| 162 | 165 | c = n | |
| 163 | 166 | cRef = n | |
| 164 | 167 | } else { | |
| 165 | - | // Nullable strings differ based on direction. For Go->C we need a *string API type | |
| 166 | - | // and pass a pointer (or nil) to C. For C->Go we keep the string as-is. | |
| 167 | - | // We track these parameters for runtime.KeepAlive to prevent GC issues. | |
| 168 | - | if ctx == ArgsFromGoToC && nullable && isStringType(t) { | |
| 168 | + | // For transfer-full strings, use g_strdup so GTK can safely g_free() the copy. | |
| 169 | + | // This must be checked before nullable handling to avoid conflicting conversions. | |
| 170 | + | if ctx == ArgsFromGoToC && transferFull && isStringType(t) { | |
| 171 | + | f.UsesGStrdup = true | |
| 172 | + | if nullable { | |
| 173 | + | // Nullable transfer-full: API type is *string, call uses GStrdupNullable | |
| 174 | + | t = "*string" | |
| 175 | + | c = fmt.Sprintf("core.GStrdupNullable(%s)", n) | |
| 176 | + | cRef = c | |
| 177 | + | } else { | |
| 178 | + | c = fmt.Sprintf("core.GStrdup(%s)", n) | |
| 179 | + | cRef = c | |
| 180 | + | } | |
| 181 | + | } else if ctx == ArgsFromGoToC && nullable && isStringType(t) { | |
| 182 | + | // Nullable strings differ based on direction. For Go->C we need a *string API type | |
| 183 | + | // and pass a pointer (or nil) to C. For C->Go we keep the string as-is. | |
| 184 | + | // We track these parameters for runtime.KeepAlive to prevent GC issues. | |
| 169 | 185 | f.UsesNullableHelper = true | |
| 170 | 186 | t = "*string" | |
| 171 | 187 | c = fmt.Sprintf("%sPtr", n) | |
| @@ -225,7 +241,7 @@ func (f *funcArgsTemplate) AddAPI(t string, n string, k Kind, ns string, nullabl | |||
| 225 | 241 | f.API.Full = append(f.API.Full, n+" "+t) | |
| 226 | 242 | } | |
| 227 | 243 | ||
| 228 | - | func (f *funcArgsTemplate) AddPure(t string, n string, k Kind, isOut bool, nullable bool, ctx ArgContext) { | |
| 244 | + | func (f *funcArgsTemplate) AddPure(t string, n string, k Kind, isOut bool, nullable bool, ctx ArgContext, transferFull bool) { | |
| 229 | 245 | n += "p" | |
| 230 | 246 | c := n | |
| 231 | 247 | stars := strings.Count(t, "*") | |
| @@ -238,7 +254,11 @@ func (f *funcArgsTemplate) AddPure(t string, n string, k Kind, isOut bool, nulla | |||
| 238 | 254 | } | |
| 239 | 255 | c = n | |
| 240 | 256 | } else { | |
| 241 | - | if ctx == ArgsFromGoToC && nullable && isStringType(t) { | |
| 257 | + | // For transfer-full strings, the pure type must be uintptr to accept | |
| 258 | + | // the g_strdup'd pointer from the API call | |
| 259 | + | if ctx == ArgsFromGoToC && transferFull && isStringType(t) { | |
| 260 | + | t = "uintptr" | |
| 261 | + | } else if ctx == ArgsFromGoToC && nullable && isStringType(t) { | |
| 242 | 262 | f.UsesNullableHelper = true | |
| 243 | 263 | t = "uintptr" | |
| 244 | 264 | // Use the pointer variable that will be set up by the template | |
| @@ -313,8 +333,9 @@ func (f *funcArgsTemplate) Add(p Parameter, ins string, ns string, kinds KindMap | |||
| 313 | 333 | ||
| 314 | 334 | isOut := p.Direction == "out" | |
| 315 | 335 | ||
| 316 | - | f.AddAPI(goType, varName, kind, ns, p.Nullable, isOut, ctx) | |
| 317 | - | f.AddPure(goType, varName, kind, isOut, p.Nullable, ctx) | |
| 336 | + | transferFull := p.TransferOwnership.TransferOwnership == "full" | |
| 337 | + | f.AddAPI(goType, varName, kind, ns, p.Nullable, isOut, ctx, transferFull) | |
| 338 | + | f.AddPure(goType, varName, kind, isOut, p.Nullable, ctx, transferFull) | |
| 318 | 339 | ||
| 319 | 340 | // For callback parameters (not out parameters), populate callback metadata | |
| 320 | 341 | // This enables the template to generate proper closure wrapping | |
| @@ -62,6 +62,9 @@ type funcArgsTemplate struct { | |||||
| 62 | 62 | ||||
| 63 | // UsesNullableHelper indicates nullable string handling that needs core import. | 63 | // UsesNullableHelper indicates nullable string handling that needs core import. | ||
| 64 | UsesNullableHelper bool | 64 | UsesNullableHelper bool | ||
| 65 | + | ||||
| 66 | + | // UsesGStrdup indicates transfer-full string handling that needs core import. | |||
| 67 | + | UsesGStrdup bool | |||
| 65 | } | 68 | } | ||
| 66 | 69 | ||||
| 67 | // ArgContext indicates where the arguments are flowing so we can handle | 70 | // ArgContext indicates where the arguments are flowing so we can handle | ||
| @@ -84,7 +87,7 @@ func isStringType(t string) bool { | |||||
| 84 | 87 | ||||
| 85 | // NeedsCore reports whether this argument set requires core helpers. | 88 | // NeedsCore reports whether this argument set requires core helpers. | ||
| 86 | func (f funcArgsTemplate) NeedsCore() bool { | 89 | func (f funcArgsTemplate) NeedsCore() bool { | ||
| 87 | - | return f.UsesNullableHelper | 90 | + | return f.UsesNullableHelper || f.UsesGStrdup |
| 88 | } | 91 | } | ||
| 89 | 92 | ||||
| 90 | func qualifyCallbackType(t string, callbackNS string, currentNS string) string { | 93 | func qualifyCallbackType(t string, callbackNS string, currentNS string) string { | ||
| @@ -140,7 +143,7 @@ func qualifyCallbackTypes(types []string, callbackNS string, currentNS string) [ | |||||
| 140 | return qualified | 143 | return qualified | ||
| 141 | } | 144 | } | ||
| 142 | 145 | ||||
| 143 | - | func (f *funcArgsTemplate) AddAPI(t string, n string, k Kind, ns string, nullable bool, isOut bool, ctx ArgContext) { | 146 | + | func (f *funcArgsTemplate) AddAPI(t string, n string, k Kind, ns string, nullable bool, isOut bool, ctx ArgContext, transferFull bool) { |
| 144 | c := n | 147 | c := n | ||
| 145 | cRef := n // For CallWithRefs, defaults to same as Call | 148 | cRef := n // For CallWithRefs, defaults to same as Call | ||
| 146 | stars := strings.Count(t, "*") | 149 | stars := strings.Count(t, "*") | ||
| @@ -162,10 +165,23 @@ func (f *funcArgsTemplate) AddAPI(t string, n string, k Kind, ns string, nullabl | |||||
| 162 | c = n | 165 | c = n | ||
| 163 | cRef = n | 166 | cRef = n | ||
| 164 | } else { | 167 | } else { | ||
| 165 | - | // Nullable strings differ based on direction. For Go->C we need a *string API type | 168 | + | // For transfer-full strings, use g_strdup so GTK can safely g_free() the copy. |
| 166 | - | // and pass a pointer (or nil) to C. For C->Go we keep the string as-is. | 169 | + | // This must be checked before nullable handling to avoid conflicting conversions. |
| 167 | - | // We track these parameters for runtime.KeepAlive to prevent GC issues. | 170 | + | if ctx == ArgsFromGoToC && transferFull && isStringType(t) { |
| 168 | - | if ctx == ArgsFromGoToC && nullable && isStringType(t) { | 171 | + | f.UsesGStrdup = true |
| 172 | + | if nullable { | |||
| 173 | + | // Nullable transfer-full: API type is *string, call uses GStrdupNullable | |||
| 174 | + | t = "*string" | |||
| 175 | + | c = fmt.Sprintf("core.GStrdupNullable(%s)", n) | |||
| 176 | + | cRef = c | |||
| 177 | + | } else { | |||
| 178 | + | c = fmt.Sprintf("core.GStrdup(%s)", n) | |||
| 179 | + | cRef = c | |||
| 180 | + | } | |||
| 181 | + | } else if ctx == ArgsFromGoToC && nullable && isStringType(t) { | |||
| 182 | + | // Nullable strings differ based on direction. For Go->C we need a *string API type | |||
| 183 | + | // and pass a pointer (or nil) to C. For C->Go we keep the string as-is. | |||
| 184 | + | // We track these parameters for runtime.KeepAlive to prevent GC issues. | |||
| 169 | f.UsesNullableHelper = true | 185 | f.UsesNullableHelper = true | ||
| 170 | t = "*string" | 186 | t = "*string" | ||
| 171 | c = fmt.Sprintf("%sPtr", n) | 187 | c = fmt.Sprintf("%sPtr", n) | ||
| @@ -225,7 +241,7 @@ func (f *funcArgsTemplate) AddAPI(t string, n string, k Kind, ns string, nullabl | |||||
| 225 | f.API.Full = append(f.API.Full, n+" "+t) | 241 | f.API.Full = append(f.API.Full, n+" "+t) | ||
| 226 | } | 242 | } | ||
| 227 | 243 | ||||
| 228 | - | func (f *funcArgsTemplate) AddPure(t string, n string, k Kind, isOut bool, nullable bool, ctx ArgContext) { | 244 | + | func (f *funcArgsTemplate) AddPure(t string, n string, k Kind, isOut bool, nullable bool, ctx ArgContext, transferFull bool) { |
| 229 | n += "p" | 245 | n += "p" | ||
| 230 | c := n | 246 | c := n | ||
| 231 | stars := strings.Count(t, "*") | 247 | stars := strings.Count(t, "*") | ||
| @@ -238,7 +254,11 @@ func (f *funcArgsTemplate) AddPure(t string, n string, k Kind, isOut bool, nulla | |||||
| 238 | } | 254 | } | ||
| 239 | c = n | 255 | c = n | ||
| 240 | } else { | 256 | } else { | ||
| 241 | - | if ctx == ArgsFromGoToC && nullable && isStringType(t) { | 257 | + | // For transfer-full strings, the pure type must be uintptr to accept |
| 258 | + | // the g_strdup'd pointer from the API call | |||
| 259 | + | if ctx == ArgsFromGoToC && transferFull && isStringType(t) { | |||
| 260 | + | t = "uintptr" | |||
| 261 | + | } else if ctx == ArgsFromGoToC && nullable && isStringType(t) { | |||
| 242 | f.UsesNullableHelper = true | 262 | f.UsesNullableHelper = true | ||
| 243 | t = "uintptr" | 263 | t = "uintptr" | ||
| 244 | // Use the pointer variable that will be set up by the template | 264 | // Use the pointer variable that will be set up by the template | ||
| @@ -313,8 +333,9 @@ func (f *funcArgsTemplate) Add(p Parameter, ins string, ns string, kinds KindMap | |||||
| 313 | 333 | ||||
| 314 | isOut := p.Direction == "out" | 334 | isOut := p.Direction == "out" | ||
| 315 | 335 | ||||
| 316 | - | f.AddAPI(goType, varName, kind, ns, p.Nullable, isOut, ctx) | 336 | + | transferFull := p.TransferOwnership.TransferOwnership == "full" |
| 317 | - | f.AddPure(goType, varName, kind, isOut, p.Nullable, ctx) | 337 | + | f.AddAPI(goType, varName, kind, ns, p.Nullable, isOut, ctx, transferFull) |
| 338 | + | f.AddPure(goType, varName, kind, isOut, p.Nullable, ctx, transferFull) | |||
| 318 | 339 | ||||
| 319 | // For callback parameters (not out parameters), populate callback metadata | 340 | // For callback parameters (not out parameters), populate callback metadata | ||
| 320 | // This enables the template to generate proper closure wrapping | 341 | // This enables the template to generate proper closure wrapping | ||
M internal/gir/types/types.go +2 -2
| @@ -24,7 +24,7 @@ var convList = map[string]string{ | |||
| 24 | 24 | "gint16": "int16", | |
| 25 | 25 | "gshort": "int16", | |
| 26 | 26 | "gint32": "int32", | |
| 27 | - | "glong": "int32", | |
| 27 | + | "glong": "int", | |
| 28 | 28 | "int32": "int32", | |
| 29 | 29 | "gint64": "int64", | |
| 30 | 30 | "guint": "uint", | |
| @@ -35,7 +35,7 @@ var convList = map[string]string{ | |||
| 35 | 35 | "guint16": "uint16", | |
| 36 | 36 | "gushort": "uint16", | |
| 37 | 37 | "guint32": "uint32", | |
| 38 | - | "gulong": "uint32", | |
| 38 | + | "gulong": "uint", | |
| 39 | 39 | "gunichar": "uint32", | |
| 40 | 40 | "guint64": "uint64", | |
| 41 | 41 | "guintptr": "uintptr", | |
| @@ -24,7 +24,7 @@ var convList = map[string]string{ | |||||
| 24 | "gint16": "int16", | 24 | "gint16": "int16", | ||
| 25 | "gshort": "int16", | 25 | "gshort": "int16", | ||
| 26 | "gint32": "int32", | 26 | "gint32": "int32", | ||
| 27 | - | "glong": "int32", | 27 | + | "glong": "int", |
| 28 | "int32": "int32", | 28 | "int32": "int32", | ||
| 29 | "gint64": "int64", | 29 | "gint64": "int64", | ||
| 30 | "guint": "uint", | 30 | "guint": "uint", | ||
| @@ -35,7 +35,7 @@ var convList = map[string]string{ | |||||
| 35 | "guint16": "uint16", | 35 | "guint16": "uint16", | ||
| 36 | "gushort": "uint16", | 36 | "gushort": "uint16", | ||
| 37 | "guint32": "uint32", | 37 | "guint32": "uint32", | ||
| 38 | - | "gulong": "uint32", | 38 | + | "gulong": "uint", |
| 39 | "gunichar": "uint32", | 39 | "gunichar": "uint32", | ||
| 40 | "guint64": "uint64", | 40 | "guint64": "uint64", | ||
| 41 | "guintptr": "uintptr", | 41 | "guintptr": "uintptr", | ||
M internal/gir/util/util.go +14 -2
| @@ -296,13 +296,25 @@ func PropertyVectorGet(goType string) string { | |||
| 296 | 296 | if ptr == 0 { | |
| 297 | 297 | return nil | |
| 298 | 298 | } | |
| 299 | - | return unsafe.Slice((*byte)(unsafe.Pointer(ptr)), 0)[:0]` | |
| 299 | + | // GByteArray layout: { guint8 *data; guint len; } | |
| 300 | + | dataPtr := *(*uintptr)(unsafe.Pointer(ptr)) | |
| 301 | + | length := *(*uint32)(unsafe.Pointer(ptr + unsafe.Sizeof(uintptr(0)))) | |
| 302 | + | if length == 0 || dataPtr == 0 { | |
| 303 | + | return nil | |
| 304 | + | } | |
| 305 | + | return unsafe.Slice((*byte)(unsafe.Pointer(dataPtr)), length)` | |
| 300 | 306 | case "[]uintptr": | |
| 301 | 307 | return `ptr := v.GetBoxed() | |
| 302 | 308 | if ptr == 0 { | |
| 303 | 309 | return nil | |
| 304 | 310 | } | |
| 305 | - | return unsafe.Slice((*uintptr)(unsafe.Pointer(ptr)), 0)[:0]` | |
| 311 | + | // GPtrArray layout: { gpointer *pdata; guint len; } | |
| 312 | + | dataPtr := *(*uintptr)(unsafe.Pointer(ptr)) | |
| 313 | + | length := *(*uint32)(unsafe.Pointer(ptr + unsafe.Sizeof(uintptr(0)))) | |
| 314 | + | if length == 0 || dataPtr == 0 { | |
| 315 | + | return nil | |
| 316 | + | } | |
| 317 | + | return unsafe.Slice((*uintptr)(unsafe.Pointer(dataPtr)), length)` | |
| 306 | 318 | default: | |
| 307 | 319 | return `return core.GoStringSlice(v.GetBoxed())` | |
| 308 | 320 | } | |
| @@ -296,13 +296,25 @@ func PropertyVectorGet(goType string) string { | |||||
| 296 | if ptr == 0 { | 296 | if ptr == 0 { | ||
| 297 | return nil | 297 | return nil | ||
| 298 | } | 298 | } | ||
| 299 | - | return unsafe.Slice((*byte)(unsafe.Pointer(ptr)), 0)[:0]` | 299 | + | // GByteArray layout: { guint8 *data; guint len; } |
| 300 | + | dataPtr := *(*uintptr)(unsafe.Pointer(ptr)) | |||
| 301 | + | length := *(*uint32)(unsafe.Pointer(ptr + unsafe.Sizeof(uintptr(0)))) | |||
| 302 | + | if length == 0 || dataPtr == 0 { | |||
| 303 | + | return nil | |||
| 304 | + | } | |||
| 305 | + | return unsafe.Slice((*byte)(unsafe.Pointer(dataPtr)), length)` | |||
| 300 | case "[]uintptr": | 306 | case "[]uintptr": | ||
| 301 | return `ptr := v.GetBoxed() | 307 | return `ptr := v.GetBoxed() | ||
| 302 | if ptr == 0 { | 308 | if ptr == 0 { | ||
| 303 | return nil | 309 | return nil | ||
| 304 | } | 310 | } | ||
| 305 | - | return unsafe.Slice((*uintptr)(unsafe.Pointer(ptr)), 0)[:0]` | 311 | + | // GPtrArray layout: { gpointer *pdata; guint len; } |
| 312 | + | dataPtr := *(*uintptr)(unsafe.Pointer(ptr)) | |||
| 313 | + | length := *(*uint32)(unsafe.Pointer(ptr + unsafe.Sizeof(uintptr(0)))) | |||
| 314 | + | if length == 0 || dataPtr == 0 { | |||
| 315 | + | return nil | |||
| 316 | + | } | |||
| 317 | + | return unsafe.Slice((*uintptr)(unsafe.Pointer(dataPtr)), length)` | |||
| 306 | default: | 318 | default: | ||
| 307 | return `return core.GoStringSlice(v.GetBoxed())` | 319 | return `return core.GoStringSlice(v.GetBoxed())` | ||
| 308 | } | 320 | } | ||
M pkg/core/core.go +2 -0
| @@ -7,6 +7,8 @@ var ( | |||
| 7 | 7 | ByteSlice = core.ByteSlice | |
| 8 | 8 | GoStringSlice = core.GoStringSlice | |
| 9 | 9 | GoString = core.GoString | |
| 10 | + | GStrdup = core.GStrdup | |
| 11 | + | GStrdupNullable = core.GStrdupNullable | |
| 10 | 12 | NullableStringToPtr = core.NullableStringToPtr | |
| 11 | 13 | PtrToNullableString = core.PtrToNullableString | |
| 12 | 14 | SetPackageName = core.SetPackageName | |
| @@ -7,6 +7,8 @@ var ( | |||||
| 7 | ByteSlice = core.ByteSlice | 7 | ByteSlice = core.ByteSlice | ||
| 8 | GoStringSlice = core.GoStringSlice | 8 | GoStringSlice = core.GoStringSlice | ||
| 9 | GoString = core.GoString | 9 | GoString = core.GoString | ||
| 10 | + | GStrdup = core.GStrdup | |||
| 11 | + | GStrdupNullable = core.GStrdupNullable | |||
| 10 | NullableStringToPtr = core.NullableStringToPtr | 12 | NullableStringToPtr = core.NullableStringToPtr | ||
| 11 | PtrToNullableString = core.PtrToNullableString | 13 | PtrToNullableString = core.PtrToNullableString | ||
| 12 | SetPackageName = core.SetPackageName | 14 | SetPackageName = core.SetPackageName | ||
M templates/glib +25 -4
| @@ -11,11 +11,13 @@ import ( | |||
| 11 | 11 | ||
| 12 | 12 | var callbacks = struct { | |
| 13 | 13 | sync.RWMutex | |
| 14 | - | refs map[uintptr]uintptr | |
| 15 | - | closures map[uintptr]interface{} | |
| 14 | + | refs map[uintptr]uintptr | |
| 15 | + | closures map[uintptr]interface{} | |
| 16 | + | handlerToCallback map[uint]uintptr | |
| 16 | 17 | }{ | |
| 17 | - | refs: make(map[uintptr]uintptr), | |
| 18 | - | closures: make(map[uintptr]interface{}), | |
| 18 | + | refs: make(map[uintptr]uintptr), | |
| 19 | + | closures: make(map[uintptr]interface{}), | |
| 20 | + | handlerToCallback: make(map[uint]uintptr), | |
| 19 | 21 | } | |
| 20 | 22 | ||
| 21 | 23 | // GetCallback retrives a callback reference by value. | |
| @@ -55,6 +57,25 @@ func RemoveCallback(cbPtr uintptr) { | |||
| 55 | 57 | callbacks.Unlock() | |
| 56 | 58 | } | |
| 57 | 59 | ||
| 60 | + | // SaveHandlerMapping records a signal handler ID → callback pointer mapping | |
| 61 | + | // so that DisconnectSignal can clean up the callback registry. | |
| 62 | + | func SaveHandlerMapping(handlerID uint, cbPtr uintptr) { | |
| 63 | + | callbacks.Lock() | |
| 64 | + | callbacks.handlerToCallback[handlerID] = cbPtr | |
| 65 | + | callbacks.Unlock() | |
| 66 | + | } | |
| 67 | + | ||
| 68 | + | // RemoveCallbackByHandler removes a callback from the registry using a signal handler ID. | |
| 69 | + | func RemoveCallbackByHandler(handlerID uint) { | |
| 70 | + | callbacks.Lock() | |
| 71 | + | if cbPtr, ok := callbacks.handlerToCallback[handlerID]; ok { | |
| 72 | + | delete(callbacks.refs, cbPtr) | |
| 73 | + | delete(callbacks.closures, cbPtr) | |
| 74 | + | delete(callbacks.handlerToCallback, handlerID) | |
| 75 | + | } | |
| 76 | + | callbacks.Unlock() | |
| 77 | + | } | |
| 78 | + | ||
| 58 | 79 | // UnrefCallbackValue unreferences the provided callback by reflect.value to free a purego slot | |
| 59 | 80 | // | |
| 60 | 81 | // NOTE: Windows does not support unreferencing callbacks, so on that platform this operation is | |
| @@ -11,11 +11,13 @@ import ( | |||||
| 11 | 11 | ||||
| 12 | var callbacks = struct { | 12 | var callbacks = struct { | ||
| 13 | sync.RWMutex | 13 | sync.RWMutex | ||
| 14 | - | refs map[uintptr]uintptr | 14 | + | refs map[uintptr]uintptr |
| 15 | - | closures map[uintptr]interface{} | 15 | + | closures map[uintptr]interface{} |
| 16 | + | handlerToCallback map[uint]uintptr | |||
| 16 | }{ | 17 | }{ | ||
| 17 | - | refs: make(map[uintptr]uintptr), | 18 | + | refs: make(map[uintptr]uintptr), |
| 18 | - | closures: make(map[uintptr]interface{}), | 19 | + | closures: make(map[uintptr]interface{}), |
| 20 | + | handlerToCallback: make(map[uint]uintptr), | |||
| 19 | } | 21 | } | ||
| 20 | 22 | ||||
| 21 | // GetCallback retrives a callback reference by value. | 23 | // GetCallback retrives a callback reference by value. | ||
| @@ -55,6 +57,25 @@ func RemoveCallback(cbPtr uintptr) { | |||||
| 55 | callbacks.Unlock() | 57 | callbacks.Unlock() | ||
| 56 | } | 58 | } | ||
| 57 | 59 | ||||
| 60 | + | // SaveHandlerMapping records a signal handler ID → callback pointer mapping | |||
| 61 | + | // so that DisconnectSignal can clean up the callback registry. | |||
| 62 | + | func SaveHandlerMapping(handlerID uint, cbPtr uintptr) { | |||
| 63 | + | callbacks.Lock() | |||
| 64 | + | callbacks.handlerToCallback[handlerID] = cbPtr | |||
| 65 | + | callbacks.Unlock() | |||
| 66 | + | } | |||
| 67 | + | ||||
| 68 | + | // RemoveCallbackByHandler removes a callback from the registry using a signal handler ID. | |||
| 69 | + | func RemoveCallbackByHandler(handlerID uint) { | |||
| 70 | + | callbacks.Lock() | |||
| 71 | + | if cbPtr, ok := callbacks.handlerToCallback[handlerID]; ok { | |||
| 72 | + | delete(callbacks.refs, cbPtr) | |||
| 73 | + | delete(callbacks.closures, cbPtr) | |||
| 74 | + | delete(callbacks.handlerToCallback, handlerID) | |||
| 75 | + | } | |||
| 76 | + | callbacks.Unlock() | |||
| 77 | + | } | |||
| 78 | + | ||||
| 58 | // UnrefCallbackValue unreferences the provided callback by reflect.value to free a purego slot | 79 | // UnrefCallbackValue unreferences the provided callback by reflect.value to free a purego slot | ||
| 59 | // | 80 | // | ||
| 60 | // NOTE: Windows does not support unreferencing callbacks, so on that platform this operation is | 81 | // NOTE: Windows does not support unreferencing callbacks, so on that platform this operation is | ||
M templates/go +14 -6
| @@ -457,10 +457,12 @@ func (c *{{.Name}}) SetGoPointer(ptr uintptr) { | |||
| 457 | 457 | {{range .Signals -}} | |
| 458 | 458 | ||
| 459 | 459 | {{.Doc}} | |
| 460 | - | func (x *{{$outer.Name}}) Connect{{.Name}}(cb *func({{$outer.Name}} {{convc .Args.API.Types}}) {{.Ret.Value}}) uint32 { | |
| 460 | + | func (x *{{$outer.Name}}) Connect{{.Name}}(cb *func({{$outer.Name}} {{convc .Args.API.Types}}) {{.Ret.Value}}) uint { | |
| 461 | 461 | cbPtr := uintptr(unsafe.Pointer(cb)) | |
| 462 | 462 | if cbRefPtr, ok := {{if $NotGLib}}glib.{{end}}GetCallback(cbPtr); ok { | |
| 463 | - | return {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), "{{.CName}}", cbRefPtr) | |
| 463 | + | handlerID := {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), "{{.CName}}", cbRefPtr) | |
| 464 | + | {{if $NotGLib}}glib.{{end}}SaveHandlerMapping(handlerID, cbPtr) | |
| 465 | + | return handlerID | |
| 464 | 466 | } | |
| 465 | 467 | ||
| 466 | 468 | fcb := func(clsPtr uintptr {{convc .Args.Pure.Full}}) {{.Ret.Raw}} { | |
| @@ -478,16 +480,20 @@ func (x *{{$outer.Name}}) Connect{{.Name}}(cb *func({{$outer.Name}} {{convc .Arg | |||
| 478 | 480 | } | |
| 479 | 481 | cbRefPtr := purego.NewCallback(fcb) | |
| 480 | 482 | {{if $NotGLib}}glib.{{end}}SaveCallbackWithClosure(cbPtr, cbRefPtr, cb) | |
| 481 | - | return {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), "{{.CName}}", cbRefPtr) | |
| 483 | + | handlerID := {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), "{{.CName}}", cbRefPtr) | |
| 484 | + | {{if $NotGLib}}glib.{{end}}SaveHandlerMapping(handlerID, cbPtr) | |
| 485 | + | return handlerID | |
| 482 | 486 | } | |
| 483 | 487 | {{if .Detailed}} | |
| 484 | 488 | // Connect{{.Name}}WithDetail connects to the "{{.CName}}" signal with a detail string. | |
| 485 | 489 | // The detail is appended as "{{.CName}}::<detail>". | |
| 486 | - | func (x *{{$outer.Name}}) Connect{{.Name}}WithDetail(detail string, cb *func({{$outer.Name}} {{convc .Args.API.Types}}) {{.Ret.Value}}) uint32 { | |
| 490 | + | func (x *{{$outer.Name}}) Connect{{.Name}}WithDetail(detail string, cb *func({{$outer.Name}} {{convc .Args.API.Types}}) {{.Ret.Value}}) uint { | |
| 487 | 491 | cbPtr := uintptr(unsafe.Pointer(cb)) | |
| 488 | 492 | signalName := fmt.Sprintf("{{.CName}}::%s", detail) | |
| 489 | 493 | if cbRefPtr, ok := {{if $NotGLib}}glib.{{end}}GetCallback(cbPtr); ok { | |
| 490 | - | return {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), signalName, cbRefPtr) | |
| 494 | + | handlerID := {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), signalName, cbRefPtr) | |
| 495 | + | {{if $NotGLib}}glib.{{end}}SaveHandlerMapping(handlerID, cbPtr) | |
| 496 | + | return handlerID | |
| 491 | 497 | } | |
| 492 | 498 | ||
| 493 | 499 | fcb := func(clsPtr uintptr {{convc .Args.Pure.Full}}) {{.Ret.Raw}} { | |
| @@ -505,7 +511,9 @@ func (x *{{$outer.Name}}) Connect{{.Name}}WithDetail(detail string, cb *func({{$ | |||
| 505 | 511 | } | |
| 506 | 512 | cbRefPtr := purego.NewCallback(fcb) | |
| 507 | 513 | {{if $NotGLib}}glib.{{end}}SaveCallbackWithClosure(cbPtr, cbRefPtr, cb) | |
| 508 | - | return {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), signalName, cbRefPtr) | |
| 514 | + | handlerID := {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), signalName, cbRefPtr) | |
| 515 | + | {{if $NotGLib}}glib.{{end}}SaveHandlerMapping(handlerID, cbPtr) | |
| 516 | + | return handlerID | |
| 509 | 517 | } | |
| 510 | 518 | {{end}} | |
| 511 | 519 | {{end}} | |
| @@ -457,10 +457,12 @@ func (c *{{.Name}}) SetGoPointer(ptr uintptr) { | |||||
| 457 | {{range .Signals -}} | 457 | {{range .Signals -}} | ||
| 458 | 458 | ||||
| 459 | {{.Doc}} | 459 | {{.Doc}} | ||
| 460 | - | func (x *{{$outer.Name}}) Connect{{.Name}}(cb *func({{$outer.Name}} {{convc .Args.API.Types}}) {{.Ret.Value}}) uint32 { | 460 | + | func (x *{{$outer.Name}}) Connect{{.Name}}(cb *func({{$outer.Name}} {{convc .Args.API.Types}}) {{.Ret.Value}}) uint { |
| 461 | cbPtr := uintptr(unsafe.Pointer(cb)) | 461 | cbPtr := uintptr(unsafe.Pointer(cb)) | ||
| 462 | if cbRefPtr, ok := {{if $NotGLib}}glib.{{end}}GetCallback(cbPtr); ok { | 462 | if cbRefPtr, ok := {{if $NotGLib}}glib.{{end}}GetCallback(cbPtr); ok { | ||
| 463 | - | return {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), "{{.CName}}", cbRefPtr) | 463 | + | handlerID := {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), "{{.CName}}", cbRefPtr) |
| 464 | + | {{if $NotGLib}}glib.{{end}}SaveHandlerMapping(handlerID, cbPtr) | |||
| 465 | + | return handlerID | |||
| 464 | } | 466 | } | ||
| 465 | 467 | ||||
| 466 | fcb := func(clsPtr uintptr {{convc .Args.Pure.Full}}) {{.Ret.Raw}} { | 468 | fcb := func(clsPtr uintptr {{convc .Args.Pure.Full}}) {{.Ret.Raw}} { | ||
| @@ -478,16 +480,20 @@ func (x *{{$outer.Name}}) Connect{{.Name}}(cb *func({{$outer.Name}} {{convc .Arg | |||||
| 478 | } | 480 | } | ||
| 479 | cbRefPtr := purego.NewCallback(fcb) | 481 | cbRefPtr := purego.NewCallback(fcb) | ||
| 480 | {{if $NotGLib}}glib.{{end}}SaveCallbackWithClosure(cbPtr, cbRefPtr, cb) | 482 | {{if $NotGLib}}glib.{{end}}SaveCallbackWithClosure(cbPtr, cbRefPtr, cb) | ||
| 481 | - | return {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), "{{.CName}}", cbRefPtr) | 483 | + | handlerID := {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), "{{.CName}}", cbRefPtr) |
| 484 | + | {{if $NotGLib}}glib.{{end}}SaveHandlerMapping(handlerID, cbPtr) | |||
| 485 | + | return handlerID | |||
| 482 | } | 486 | } | ||
| 483 | {{if .Detailed}} | 487 | {{if .Detailed}} | ||
| 484 | // Connect{{.Name}}WithDetail connects to the "{{.CName}}" signal with a detail string. | 488 | // Connect{{.Name}}WithDetail connects to the "{{.CName}}" signal with a detail string. | ||
| 485 | // The detail is appended as "{{.CName}}::<detail>". | 489 | // The detail is appended as "{{.CName}}::<detail>". | ||
| 486 | - | func (x *{{$outer.Name}}) Connect{{.Name}}WithDetail(detail string, cb *func({{$outer.Name}} {{convc .Args.API.Types}}) {{.Ret.Value}}) uint32 { | 490 | + | func (x *{{$outer.Name}}) Connect{{.Name}}WithDetail(detail string, cb *func({{$outer.Name}} {{convc .Args.API.Types}}) {{.Ret.Value}}) uint { |
| 487 | cbPtr := uintptr(unsafe.Pointer(cb)) | 491 | cbPtr := uintptr(unsafe.Pointer(cb)) | ||
| 488 | signalName := fmt.Sprintf("{{.CName}}::%s", detail) | 492 | signalName := fmt.Sprintf("{{.CName}}::%s", detail) | ||
| 489 | if cbRefPtr, ok := {{if $NotGLib}}glib.{{end}}GetCallback(cbPtr); ok { | 493 | if cbRefPtr, ok := {{if $NotGLib}}glib.{{end}}GetCallback(cbPtr); ok { | ||
| 490 | - | return {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), signalName, cbRefPtr) | 494 | + | handlerID := {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), signalName, cbRefPtr) |
| 495 | + | {{if $NotGLib}}glib.{{end}}SaveHandlerMapping(handlerID, cbPtr) | |||
| 496 | + | return handlerID | |||
| 491 | } | 497 | } | ||
| 492 | 498 | ||||
| 493 | fcb := func(clsPtr uintptr {{convc .Args.Pure.Full}}) {{.Ret.Raw}} { | 499 | fcb := func(clsPtr uintptr {{convc .Args.Pure.Full}}) {{.Ret.Raw}} { | ||
| @@ -505,7 +511,9 @@ func (x *{{$outer.Name}}) Connect{{.Name}}WithDetail(detail string, cb *func({{$ | |||||
| 505 | } | 511 | } | ||
| 506 | cbRefPtr := purego.NewCallback(fcb) | 512 | cbRefPtr := purego.NewCallback(fcb) | ||
| 507 | {{if $NotGLib}}glib.{{end}}SaveCallbackWithClosure(cbPtr, cbRefPtr, cb) | 513 | {{if $NotGLib}}glib.{{end}}SaveCallbackWithClosure(cbPtr, cbRefPtr, cb) | ||
| 508 | - | return {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), signalName, cbRefPtr) | 514 | + | handlerID := {{if $NotGObject}}gobject.{{end}}SignalConnect(x.GoPointer(), signalName, cbRefPtr) |
| 515 | + | {{if $NotGLib}}glib.{{end}}SaveHandlerMapping(handlerID, cbPtr) | |||
| 516 | + | return handlerID | |||
| 509 | } | 517 | } | ||
| 510 | {{end}} | 518 | {{end}} | ||
| 511 | {{end}} | 519 | {{end}} | ||
M templates/gobject +4 -3
| @@ -28,7 +28,7 @@ func IncreaseRef(a uintptr) { | |||
| 28 | 28 | xObjectRefSink(a) | |
| 29 | 29 | } | |
| 30 | 30 | ||
| 31 | - | func SignalConnect(a uintptr, b string, c uintptr) uint32 { | |
| 31 | + | func SignalConnect(a uintptr, b string, c uintptr) uint { | |
| 32 | 32 | return xSignalConnectData(a, b, c, 0, 0, 0) | |
| 33 | 33 | } | |
| 34 | 34 | ||
| @@ -36,12 +36,13 @@ func (o Object) Cast(v Ptr) { | |||
| 36 | 36 | v.SetGoPointer(o.GoPointer()) | |
| 37 | 37 | } | |
| 38 | 38 | ||
| 39 | - | func (o Object) ConnectSignal(signal string, cb *func()) uint32 { | |
| 39 | + | func (o Object) ConnectSignal(signal string, cb *func()) uint { | |
| 40 | 40 | return SignalConnect(o.GoPointer(), signal, glib.NewCallback(cb)) | |
| 41 | 41 | } | |
| 42 | 42 | ||
| 43 | - | func (o Object) DisconnectSignal(handler uint32) { | |
| 43 | + | func (o Object) DisconnectSignal(handler uint) { | |
| 44 | 44 | SignalHandlerDisconnect(&o, handler) | |
| 45 | + | glib.RemoveCallbackByHandler(handler) | |
| 45 | 46 | } | |
| 46 | 47 | ||
| 47 | 48 | // types | |
| @@ -28,7 +28,7 @@ func IncreaseRef(a uintptr) { | |||||
| 28 | xObjectRefSink(a) | 28 | xObjectRefSink(a) | ||
| 29 | } | 29 | } | ||
| 30 | 30 | ||||
| 31 | - | func SignalConnect(a uintptr, b string, c uintptr) uint32 { | 31 | + | func SignalConnect(a uintptr, b string, c uintptr) uint { |
| 32 | return xSignalConnectData(a, b, c, 0, 0, 0) | 32 | return xSignalConnectData(a, b, c, 0, 0, 0) | ||
| 33 | } | 33 | } | ||
| 34 | 34 | ||||
| @@ -36,12 +36,13 @@ func (o Object) Cast(v Ptr) { | |||||
| 36 | v.SetGoPointer(o.GoPointer()) | 36 | v.SetGoPointer(o.GoPointer()) | ||
| 37 | } | 37 | } | ||
| 38 | 38 | ||||
| 39 | - | func (o Object) ConnectSignal(signal string, cb *func()) uint32 { | 39 | + | func (o Object) ConnectSignal(signal string, cb *func()) uint { |
| 40 | return SignalConnect(o.GoPointer(), signal, glib.NewCallback(cb)) | 40 | return SignalConnect(o.GoPointer(), signal, glib.NewCallback(cb)) | ||
| 41 | } | 41 | } | ||
| 42 | 42 | ||||
| 43 | - | func (o Object) DisconnectSignal(handler uint32) { | 43 | + | func (o Object) DisconnectSignal(handler uint) { |
| 44 | SignalHandlerDisconnect(&o, handler) | 44 | SignalHandlerDisconnect(&o, handler) | ||
| 45 | + | glib.RemoveCallbackByHandler(handler) | |||
| 45 | } | 46 | } | ||
| 46 | 47 | ||||
| 47 | // types | 48 | // types | ||