detached 53bd89c

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 VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
8 files · +125 -27
M internal/core/core.go +33 -0
@@ -10,6 +10,7 @@ import (
1010 "path/filepath"
1111 "runtime"
1212 "strings"
13+ "sync"
1314 "unsafe"
1415
1516 "github.com/jwijenbergh/purego"
@@ -227,6 +228,38 @@ func GoString(c uintptr) string {
227228 return string(unsafe.Slice((*byte)(ptr), length))
228229 }
229230
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+
230263 // NullableStringToPtr converts a nullable Go string to a uintptr suitable for C calls.
231264 // Returns both the pointer and the backing byte slice. The caller MUST call
232265 // 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 call264 // 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 collecting265 // 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 {
6262
6363 // UsesNullableHelper indicates nullable string handling that needs core import.
6464 UsesNullableHelper bool
65+
66+ // UsesGStrdup indicates transfer-full string handling that needs core import.
67+ UsesGStrdup bool
6568 }
6669
6770 // ArgContext indicates where the arguments are flowing so we can handle
@@ -84,7 +87,7 @@ func isStringType(t string) bool {
8487
8588 // NeedsCore reports whether this argument set requires core helpers.
8689 func (f funcArgsTemplate) NeedsCore() bool {
87- return f.UsesNullableHelper
90+ return f.UsesNullableHelper || f.UsesGStrdup
8891 }
8992
9093 func qualifyCallbackType(t string, callbackNS string, currentNS string) string {
@@ -140,7 +143,7 @@ func qualifyCallbackTypes(types []string, callbackNS string, currentNS string) [
140143 return qualified
141144 }
142145
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) {
144147 c := n
145148 cRef := n // For CallWithRefs, defaults to same as Call
146149 stars := strings.Count(t, "*")
@@ -162,10 +165,23 @@ func (f *funcArgsTemplate) AddAPI(t string, n string, k Kind, ns string, nullabl
162165 c = n
163166 cRef = n
164167 } 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.
169185 f.UsesNullableHelper = true
170186 t = "*string"
171187 c = fmt.Sprintf("%sPtr", n)
@@ -225,7 +241,7 @@ func (f *funcArgsTemplate) AddAPI(t string, n string, k Kind, ns string, nullabl
225241 f.API.Full = append(f.API.Full, n+" "+t)
226242 }
227243
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) {
229245 n += "p"
230246 c := n
231247 stars := strings.Count(t, "*")
@@ -238,7 +254,11 @@ func (f *funcArgsTemplate) AddPure(t string, n string, k Kind, isOut bool, nulla
238254 }
239255 c = n
240256 } 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) {
242262 f.UsesNullableHelper = true
243263 t = "uintptr"
244264 // 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
313333
314334 isOut := p.Direction == "out"
315335
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)
318339
319340 // For callback parameters (not out parameters), populate callback metadata
320341 // 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 bool64 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 handle70 // 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.UsesNullableHelper90+ 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 qualified143 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 := n147 c := n
145 cRef := n // For CallWithRefs, defaults to same as Call148 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 = n165 c = n
163 cRef = n166 cRef = n
164 } else {167 } else {
165- // Nullable strings differ based on direction. For Go->C we need a *string API type168+ // 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 = true185 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 := n246 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 = n255 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 = true262 f.UsesNullableHelper = true
243 t = "uintptr"263 t = "uintptr"
244 // Use the pointer variable that will be set up by the template264 // 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 metadata340 // For callback parameters (not out parameters), populate callback metadata
320 // This enables the template to generate proper closure wrapping341 // 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{
2424 "gint16": "int16",
2525 "gshort": "int16",
2626 "gint32": "int32",
27- "glong": "int32",
27+ "glong": "int",
2828 "int32": "int32",
2929 "gint64": "int64",
3030 "guint": "uint",
@@ -35,7 +35,7 @@ var convList = map[string]string{
3535 "guint16": "uint16",
3636 "gushort": "uint16",
3737 "guint32": "uint32",
38- "gulong": "uint32",
38+ "gulong": "uint",
3939 "gunichar": "uint32",
4040 "guint64": "uint64",
4141 "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 {
296296 if ptr == 0 {
297297 return nil
298298 }
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)`
300306 case "[]uintptr":
301307 return `ptr := v.GetBoxed()
302308 if ptr == 0 {
303309 return nil
304310 }
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)`
306318 default:
307319 return `return core.GoStringSlice(v.GetBoxed())`
308320 }
@@ -296,13 +296,25 @@ func PropertyVectorGet(goType string) string {
296 if ptr == 0 {296 if ptr == 0 {
297 return nil297 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 nil309 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 (
77 ByteSlice = core.ByteSlice
88 GoStringSlice = core.GoStringSlice
99 GoString = core.GoString
10+ GStrdup = core.GStrdup
11+ GStrdupNullable = core.GStrdupNullable
1012 NullableStringToPtr = core.NullableStringToPtr
1113 PtrToNullableString = core.PtrToNullableString
1214 SetPackageName = core.SetPackageName
@@ -7,6 +7,8 @@ var (
7 ByteSlice = core.ByteSlice7 ByteSlice = core.ByteSlice
8 GoStringSlice = core.GoStringSlice8 GoStringSlice = core.GoStringSlice
9 GoString = core.GoString9 GoString = core.GoString
10+ GStrdup = core.GStrdup
11+ GStrdupNullable = core.GStrdupNullable
10 NullableStringToPtr = core.NullableStringToPtr12 NullableStringToPtr = core.NullableStringToPtr
11 PtrToNullableString = core.PtrToNullableString13 PtrToNullableString = core.PtrToNullableString
12 SetPackageName = core.SetPackageName14 SetPackageName = core.SetPackageName
M templates/glib +25 -4
@@ -11,11 +11,13 @@ import (
1111
1212 var callbacks = struct {
1313 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
1617 }{
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),
1921 }
2022
2123 // GetCallback retrives a callback reference by value.
@@ -55,6 +57,25 @@ func RemoveCallback(cbPtr uintptr) {
5557 callbacks.Unlock()
5658 }
5759
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+
5879 // UnrefCallbackValue unreferences the provided callback by reflect.value to free a purego slot
5980 //
6081 // 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.RWMutex13 sync.RWMutex
14- refs map[uintptr]uintptr14+ 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 slot79 // 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 is81 // 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) {
457457 {{range .Signals -}}
458458
459459 {{.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 {
461461 cbPtr := uintptr(unsafe.Pointer(cb))
462462 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
464466 }
465467
466468 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
478480 }
479481 cbRefPtr := purego.NewCallback(fcb)
480482 {{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
482486 }
483487 {{if .Detailed}}
484488 // Connect{{.Name}}WithDetail connects to the "{{.CName}}" signal with a detail string.
485489 // 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 {
487491 cbPtr := uintptr(unsafe.Pointer(cb))
488492 signalName := fmt.Sprintf("{{.CName}}::%s", detail)
489493 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
491497 }
492498
493499 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({{$
505511 }
506512 cbRefPtr := purego.NewCallback(fcb)
507513 {{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
509517 }
510518 {{end}}
511519 {{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) {
2828 xObjectRefSink(a)
2929 }
3030
31-func SignalConnect(a uintptr, b string, c uintptr) uint32 {
31+func SignalConnect(a uintptr, b string, c uintptr) uint {
3232 return xSignalConnectData(a, b, c, 0, 0, 0)
3333 }
3434
@@ -36,12 +36,13 @@ func (o Object) Cast(v Ptr) {
3636 v.SetGoPointer(o.GoPointer())
3737 }
3838
39-func (o Object) ConnectSignal(signal string, cb *func()) uint32 {
39+func (o Object) ConnectSignal(signal string, cb *func()) uint {
4040 return SignalConnect(o.GoPointer(), signal, glib.NewCallback(cb))
4141 }
4242
43-func (o Object) DisconnectSignal(handler uint32) {
43+func (o Object) DisconnectSignal(handler uint) {
4444 SignalHandlerDisconnect(&o, handler)
45+ glib.RemoveCallbackByHandler(handler)
4546 }
4647
4748 // 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 // types48 // types

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close