1package glib 2 3// #include <glib.h> 4// #include <glib-object.h> 5// #include "glib.go.h" 6import "C" 7import ( 8 "reflect" 9 "unsafe" 10 11 "github.com/gotk3/gotk3/internal/closure" 12) 13 14/* 15 * Events 16 */ 17 18// SignalHandle is the ID of a signal handler. 19type SignalHandle uint 20 21// Connect is a wrapper around g_signal_connect_closure(). f must be a function 22// with at least one parameter matching the type it is connected to. 23// 24// It is optional to list the rest of the required types from Gtk, as values 25// that don't fit into the function parameter will simply be ignored; however, 26// extraneous types will trigger a runtime panic. Arguments for f must be a 27// matching Go equivalent type for the C callback, or an interface type which 28// the value may be packed in. If the type is not suitable, a runtime panic will 29// occur when the signal is emitted. 30// 31// Circular References 32// 33// To prevent circular references, prefer declaring Connect functions like so: 34// 35// obj.Connect(func(obj *ObjType) { obj.Do() }) 36// 37// Instead of directly referencing the object from outside like so: 38// 39// obj.Connect(func() { obj.Do() }) 40// 41// When using Connect, beware of referencing variables outside the closure that 42// may cause a circular reference that prevents both Go from garbage collecting 43// the callback and GTK from successfully unreferencing its values. 44// 45// Below is an example piece of code that is considered "leaky": 46// 47// type ChatBox struct { 48// gtk.TextView 49// Loader *gdk.PixbufLoader 50// 51// State State 52// } 53// 54// func (box *ChatBox) Method() { 55// box.Loader.Connect("size-allocate", func(loader *gdk.PixbufLoader) { 56// // Here, we're dereferencing box to get the state, which might 57// // keep box alive along with the PixbufLoader, causing a circular 58// // reference. 59// loader.SetSize(box.State.Width, box.State.Height) 60// }) 61// } 62// 63// There are many solutions to fix the above piece of code. For example, 64// box.Loader could be discarded manually immediately after it's done by setting 65// it to nil, or the signal handle could be disconnected manually, or box could 66// be set to nil after its first call in the callback. 67func (v *Object) Connect(detailedSignal string, f interface{}) SignalHandle { 68 return v.connectClosure(false, detailedSignal, f) 69} 70 71// ConnectAfter is a wrapper around g_signal_connect_closure(). The difference 72// between Connect and ConnectAfter is that the latter will be invoked after the 73// default handler, not before. For more information, refer to Connect. 74func (v *Object) ConnectAfter(detailedSignal string, f interface{}) SignalHandle { 75 return v.connectClosure(true, detailedSignal, f) 76} 77 78// ClosureCheckReceiver, if true, will make GLib check for every single 79// closure's first argument to ensure that it is correct, otherwise it will 80// panic with a message warning about the possible circular references. The 81// receiver in this case is most often the first argument of the callback. 82// 83// This constant can be changed by using go.mod's replace directive for 84// debugging purposes. 85const ClosureCheckReceiver = false 86 87func (v *Object) connectClosure(after bool, detailedSignal string, f interface{}) SignalHandle { 88 fs := closure.NewFuncStack(f, 2) 89 90 if ClosureCheckReceiver { 91 // This is a bit slow, but we could be careful. 92 objValue, err := v.goValue() 93 if err == nil { 94 fsType := fs.Func.Type() 95 if fsType.NumIn() < 1 { 96 fs.Panicf("callback should have the object receiver to avoid circular references") 97 } 98 objType := reflect.TypeOf(objValue) 99 if first := fsType.In(0); !objType.ConvertibleTo(first) { 100 fs.Panicf("receiver not convertible to expected type %s, got %s", objType, first) 101 } 102 } 103 104 // Allow the type check to fail if we can't get a value marshaler. This 105 // rarely happens, but it might, and we want to at least allow working 106 // around it. 107 } 108 109 cstr := C.CString(detailedSignal) 110 defer C.free(unsafe.Pointer(cstr)) 111 112 gclosure := ClosureNewFunc(fs) 113 c := C.g_signal_connect_closure(C.gpointer(v.native()), (*C.gchar)(cstr), gclosure, gbool(after)) 114 115 // TODO: There's a slight race condition here, where 116 // g_signal_connect_closure may trigger signal callbacks before the signal 117 // is registered. It is therefore ideal to have another intermediate ID to 118 // pass into the connect function. This is not a big issue though, since 119 // there isn't really any guarantee that signals should arrive until after 120 // the Connect functions return successfully. 121 closure.RegisterSignal(uint(c), unsafe.Pointer(gclosure)) 122 123 return SignalHandle(c) 124} 125 126// ClosureNew creates a new GClosure and adds its callback function to the 127// internal registry. It's exported for visibility to other gotk3 packages and 128// should not be used in a regular application. 129func ClosureNew(f interface{}) *C.GClosure { 130 return ClosureNewFunc(closure.NewFuncStack(f, 2)) 131} 132 133// ClosureNewFunc creates a new GClosure and adds its callback function to the 134// internal registry. It's exported for visibility to other gotk3 packages; it 135// cannot be used in application code, as package closure is part of the 136// internals. 137func ClosureNewFunc(funcStack closure.FuncStack) *C.GClosure { 138 gclosure := C._g_closure_new() 139 closure.Assign(unsafe.Pointer(gclosure), funcStack) 140 141 return gclosure 142} 143 144// removeClosure removes a closure from the internal closures map. This is 145// needed to prevent a leak where Go code can access the closure context 146// (along with rf and userdata) even after an object has been destroyed and 147// the GClosure is invalidated and will never run. 148// 149//export removeClosure 150func removeClosure(_ C.gpointer, gclosure *C.GClosure) { 151 closure.Delete(unsafe.Pointer(gclosure)) 152} 153