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