diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index b1c1113f0..ff81d4634 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -47,6 +47,8 @@ jobs: name: "${{ github.event_name }} / test" runs-on: ubuntu-latest steps: + - name: Install xfvb + run: sudo apt-get install -y xvfb x11-xserver-utils - uses: actions/checkout@v3 - uses: diamondburned/cache-install@main with: @@ -65,7 +67,7 @@ jobs: - run: go test ./... - - run: cd pkg && go test ./... + - run: cd pkg && xvfb-run -a go test ./... docker: name: "${{ github.event_name }} / docker" diff --git a/go.work b/go.work new file mode 100644 index 000000000..6502fe888 --- /dev/null +++ b/go.work @@ -0,0 +1,6 @@ +go 1.22.0 + +use ( + . + ./pkg +) diff --git a/pkg/core/glib/bind.go b/pkg/core/glib/bind.go new file mode 100644 index 000000000..dbdbe8898 --- /dev/null +++ b/pkg/core/glib/bind.go @@ -0,0 +1,74 @@ +package glib + +import ( + "reflect" + "sync" + "unsafe" + + "github.com/diamondburned/gotk4/pkg/core/gbox" +) + +// #include +// #include +// #include "glib.go.h" +import "C" + +var bindingNames sync.Map // map[reflect.Type]C.GQuark + +// Associate value with object +func Bind[T any](obj Objector, value T) { + object := BaseObject(obj) + name := bindingName[T]() + + ptr := C.gpointer(gbox.Assign(value)) + + C.g_object_set_data_full(object.native(), (*C.gchar)(name), ptr, (*[0]byte)(C._gotk4_data_destroy)) +} + +// Disassociate value from object +func Unbind[T any](obj Objector) { + name := bindingName[T]() + + ptr := C.g_object_steal_data(BaseObject(obj).native(), (*C.gchar)(name)) + defer gbox.Delete(uintptr(ptr)) +} + +// Obtain value associated with object +func Bounded[T any](obj Objector) *T { + name := bindingName[T]() + + ptr := C.g_object_get_data(BaseObject(obj).native(), name) + + value, ok := gbox.Get(uintptr(ptr)).(T) + if !ok { + return nil + } + + return &value +} + +func bindingName[T any]() *C.gchar { + t := reflect.TypeFor[T]() + + if v, ok := bindingNames.Load(t); ok { + quark := v.(C.GQuark) + return C.g_quark_to_string(quark) + } + + name := "_gotk4_" + t.String() + + nameC := C.CString(name) + defer C.free(unsafe.Pointer(nameC)) + + quark := C.g_quark_from_string(nameC) + if v, lost := bindingNames.LoadOrStore(t, quark); lost { + quark = v.(C.GQuark) + } + + return C.g_quark_to_string(quark) +} + +//export _gotk4_data_destroy +func _gotk4_data_destroy(ptr C.gpointer) { + gbox.Delete(uintptr(ptr)) +} diff --git a/pkg/core/glib/glib.go.h b/pkg/core/glib/glib.go.h index 62ce6ba11..ad0a2a2e2 100644 --- a/pkg/core/glib/glib.go.h +++ b/pkg/core/glib/glib.go.h @@ -109,4 +109,6 @@ static void init_i18n(const char *domain, const char *dir) { static const char *localize(const char *string) { return _(string); } +extern void _gotk4_data_destroy(gpointer ptr); + #endif diff --git a/pkg/core/test/glib_test.go b/pkg/core/test/glib_test.go new file mode 100644 index 000000000..1938d6e99 --- /dev/null +++ b/pkg/core/test/glib_test.go @@ -0,0 +1,27 @@ +package test + +import ( + "runtime" + "testing" + + "github.com/diamondburned/gotk4/pkg/core/glib" + "github.com/diamondburned/gotk4/pkg/gio/v2" +) + +func TestObjectData(t *testing.T) { + testObjectData(t) + + runtime.GC() +} + +func testObjectData(t *testing.T) { + app := gio.NewApplication("foo.bar", gio.ApplicationFlagsNone) + + glib.Bind(app, "foo") + + if value := glib.Bounded[string](app); value == nil || *value != "foo" { + t.Fatal("returned data did not match expected data") + } + + glib.Unbind[string](app) +}