From 0526bb3d90803dd120545d6fe18e7ebef31fe993 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Mon, 28 Sep 2026 14:13:49 +0900 Subject: [PATCH 1/2] feat: add standalone XPC runtime Transplant the XPC runtime from PR #205. Cherry-picked the runtime content from 634188f, 9f673da, 4a942c7, and d34115c without committing their vmnet integration or tests. Aligned cgohandler.go, array.go, and object.go with the PR branch final versions after 43fded6. Keep XPC tests and vmnet adapter changes for later slices. --- internal/cgohandler/cgohandler.go | 40 ++ xpc/array.go | 267 +++++++++++ xpc/dictionary.go | 370 ++++++++++++++++ xpc/error.go | 48 ++ xpc/listener.go | 100 +++++ xpc/object.go | 350 +++++++++++++++ xpc/osversion_alias.go | 7 + xpc/peer_requirement.go | 62 +++ xpc/session.go | 228 ++++++++++ xpc/type.go | 46 ++ xpc/xpc_darwin.h | 212 +++++++++ xpc/xpc_darwin.m | 713 ++++++++++++++++++++++++++++++ xpc/xpc_object.go | 64 +++ 13 files changed, 2507 insertions(+) create mode 100644 internal/cgohandler/cgohandler.go create mode 100644 xpc/array.go create mode 100644 xpc/dictionary.go create mode 100644 xpc/error.go create mode 100644 xpc/listener.go create mode 100644 xpc/object.go create mode 100644 xpc/osversion_alias.go create mode 100644 xpc/peer_requirement.go create mode 100644 xpc/session.go create mode 100644 xpc/type.go create mode 100644 xpc/xpc_darwin.h create mode 100644 xpc/xpc_darwin.m create mode 100644 xpc/xpc_object.go diff --git a/internal/cgohandler/cgohandler.go b/internal/cgohandler/cgohandler.go new file mode 100644 index 00000000..495cd79c --- /dev/null +++ b/internal/cgohandler/cgohandler.go @@ -0,0 +1,40 @@ +package cgohandler + +import ( + "runtime" + "runtime/cgo" +) + +// Handler holds a cgo.Handle for an Object. +// It provides methods to hold and release the handle. +// handle will released when Handler is cleaned up. +type Handler struct { + handle cgo.Handle +} + +// releaseOnCleanup registers a cleanup function to delete the cgo.Handle when cleaned up. +func (h *Handler) releaseOnCleanup() { + runtime.AddCleanup(h, func(h cgo.Handle) { + h.Delete() + }, h.handle) +} + +// New creates a new [Handler] and holds the given value. +func New(v any) (*Handler, uintptr) { + if v == nil { + return nil, 0 + } + h := &Handler{cgo.NewHandle(v)} + h.releaseOnCleanup() + return h, uintptr(h.handle) +} + +// Unwrap unwraps the cgo.Handle from the given uintptr and returns the associated value. +// It does NOT delete the handle; it expects the handle to be managed by [Handler] or caller. +func Unwrap[T any](handle uintptr) T { + if handle == 0 { + var zero T + return zero + } + return cgo.Handle(handle).Value().(T) +} diff --git a/xpc/array.go b/xpc/array.go new file mode 100644 index 00000000..aa741cc9 --- /dev/null +++ b/xpc/array.go @@ -0,0 +1,267 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +# include "xpc_darwin.h" +*/ +import "C" +import ( + "iter" + "runtime/cgo" + "time" + "unsafe" + + "github.com/Code-Hex/vz/v3/internal/cgohandler" + "github.com/Code-Hex/vz/v3/internal/objc" +) + +// Array represents an XPC array([XPC_TYPE_ARRAY]) object. [TypeArray] +// +// [XPC_TYPE_ARRAY]: https://developer.apple.com/documentation/xpc/xpc_type_array-c.macro?language=objc +type Array struct { + *xpcObject +} + +var _ Object = &Array{} + +// MARK: - Constructor + +// NewArray creates a new [Array] from the given [Object]s. +// - https://developer.apple.com/documentation/xpc/xpc_array_create(_:_:)?language=objc +func NewArray(objects ...Object) *Array { + n := len(objects) + if n == 0 { + return ReleaseOnCleanup(&Array{newXpcObject(C.xpcArrayCreate(nil, 0))}) + } + cObjects := make([]unsafe.Pointer, n) + for i, obj := range objects { + cObjects[i] = objc.Ptr(obj) + } + return ReleaseOnCleanup(&Array{newXpcObject(C.xpcArrayCreate( + (*unsafe.Pointer)(unsafe.Pointer(&cObjects[0])), + C.size_t(len(cObjects)), + ))}) +} + +// MARK: - Value Accessors + +// GetValue retrieves the [Object] at the given index in the [Array]. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_value(_:_:)?language=objc +func (a *Array) GetValue(index int) Object { + ptr := C.xpcArrayGetValue(objc.Ptr(a), C.size_t(index)) + return NewObject(ptr) +} + +// SetValue sets the [Object] at the given index in the [Array]. +// The value can not be nil. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_value(_:_:_:)?language=objc +func (a *Array) SetValue(index int, value Object) { + C.xpcArraySetValue(objc.Ptr(a), C.size_t(index), objc.Ptr(value)) +} + +// AppendValue appends the given [Object] to the end of the [Array]. +// - https://developer.apple.com/documentation/xpc/xpc_array_append_value(_:_:)?language=objc +func (a *Array) AppendValue(value Object) { + C.xpcArrayAppendValue(objc.Ptr(a), objc.Ptr(value)) +} + +// MARK: - Iteration + +// Count returns the number of elements in the [Array]. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_count(_:)?language=objc +func (a *Array) Count() int { + return int(C.xpcArrayGetCount(objc.Ptr(a))) +} + +// ArrayApplier is a function type for applying to each element in the Array. +type ArrayApplier func(uint64, Object) bool + +// callArrayApplier is called from C to apply a function to each element in the Array. +// +//export callArrayApplier +func callArrayApplier(cgoApplier uintptr, index C.size_t, cgoValue uintptr) C.bool { + applier := cgohandler.Unwrap[ArrayApplier](cgoApplier) + value := unwrapObject[Object](cgoValue) + result := applier(uint64(index), value) + return C.bool(result) +} + +// All iterates over all elements in the [Array]. +// - https://developer.apple.com/documentation/xpc/xpc_array_apply(_:_:)?language=objc +func (a *Array) All() iter.Seq2[uint64, Object] { + return func(yieald func(uint64, Object) bool) { + cgoApplier := cgo.NewHandle(ArrayApplier(yieald)) + defer cgoApplier.Delete() + _ = C.xpcArrayApply( + objc.Ptr(a), + C.uintptr_t(cgoApplier), + ) + } +} + +// Values iterates over all values in the [Array] using [Array.All]. +func (a *Array) Values() iter.Seq[Object] { + return func(yieald func(Object) bool) { + for _, value := range a.All() { + if !yieald(value) { + return + } + } + } +} + +// MARK: - Typed Getters + +// DupFd retrieves duplicated file descriptors from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_dup_fd(_:_:)?language=objc +func (a *Array) DupFd(index int) uintptr { + return uintptr(C.xpcArrayDupFd(objc.Ptr(a), C.size_t(index))) +} + +// GetArray retrieves an [Array] value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_array(_:_:)?language=objc +func (a *Array) GetArray(index int) *Array { + ptr := C.xpcArrayGetArray(objc.Ptr(a), C.size_t(index)) + return &Array{newXpcObject(ptr)} +} + +// GetBool retrieves a boolean value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_bool(_:_:)?language=objc +func (a *Array) GetBool(index int) bool { + return bool(C.xpcArrayGetBool(objc.Ptr(a), C.size_t(index))) +} + +// GetData retrieves a byte slice from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_data(_:_:_:)?language=objc +func (a *Array) GetData(index int) []byte { + var length C.size_t + dataPtr := C.xpcArrayGetData(objc.Ptr(a), C.size_t(index), &length) + if dataPtr == nil || length == 0 { + return nil + } + return C.GoBytes(unsafe.Pointer(dataPtr), C.int(length)) +} + +// GetDate retrieves a date value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_date(_:_:)?language=objc +func (a *Array) GetDate(index int) time.Time { + unixNano := C.xpcArrayGetDate(objc.Ptr(a), C.size_t(index)) + return time.Unix(0, int64(unixNano)) +} + +// GetDictionary retrieves a [Dictionary] value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_dictionary(_:_:)?language=objc +func (a *Array) GetDictionary(index int) *Dictionary { + ptr := C.xpcArrayGetDictionary(objc.Ptr(a), C.size_t(index)) + return &Dictionary{newXpcObject(ptr)} +} + +// GetDouble retrieves a double value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_double(_:_:)?language=objc +func (a *Array) GetDouble(index int) float64 { + return float64(C.xpcArrayGetDouble(objc.Ptr(a), C.size_t(index))) +} + +// GetInt64 retrieves an int64 value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_int64(_:_:)?language=objc +func (a *Array) GetInt64(index int) int64 { + return int64(C.xpcArrayGetInt64(objc.Ptr(a), C.size_t(index))) +} + +// GetString retrieves a string value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_string(_:_:)?language=objc +func (a *Array) GetString(index int) string { + cstr := C.xpcArrayGetString(objc.Ptr(a), C.size_t(index)) + return C.GoString(cstr) +} + +// GetUInt64 retrieves a uint64 value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_uint64(_:_:)?language=objc +func (a *Array) GetUInt64(index int) uint64 { + return uint64(C.xpcArrayGetUInt64(objc.Ptr(a), C.size_t(index))) +} + +// GetUUID retrieves a UUID value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_uuid(_:_:)?language=objc +func (a *Array) GetUUID(index int) [16]byte { + var uuid [16]byte + ptr := C.xpcArrayGetUUID(objc.Ptr(a), C.size_t(index)) + if ptr == nil { + return uuid + } + copy(uuid[:], unsafe.Slice((*byte)(ptr), 16)) + return uuid +} + +// MARK: - Typed Setters + +// SetBool sets a boolean value in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_bool(_:_:_:)?language=objc +func (a *Array) SetBool(index int, value bool) { + cvalue := C.bool(value) + C.xpcArraySetBool(objc.Ptr(a), C.size_t(index), cvalue) +} + +// SetData sets a byte slice in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_data(_:_:_:_:)?language=objc +func (a *Array) SetData(index int, value []byte) { + var ptr unsafe.Pointer + var length C.size_t + if len(value) > 0 { + ptr = unsafe.Pointer(&value[0]) + length = C.size_t(len(value)) + } + C.xpcArraySetData(objc.Ptr(a), C.size_t(index), ptr, length) +} + +// SetDate sets a date value in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_date(_:_:_:)?language=objc +func (a *Array) SetDate(index int, value time.Time) { + unixNano := C.int64_t(value.UnixNano()) + C.xpcArraySetDate(objc.Ptr(a), C.size_t(index), unixNano) +} + +// SetDouble sets a double value in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_double(_:_:_:)?language=objc +func (a *Array) SetDouble(index int, value float64) { + cvalue := C.double(value) + C.xpcArraySetDouble(objc.Ptr(a), C.size_t(index), cvalue) +} + +// SetFd sets a file descriptor in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_fd(_:_:_:)?language=objc +func (a *Array) SetFd(index int, fd uintptr) { + cfd := C.int(fd) + C.xpcArraySetFd(objc.Ptr(a), C.size_t(index), cfd) +} + +// SetInt64 sets an int64 value in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_int64(_:_:_:)?language=objc +func (a *Array) SetInt64(index int, value int64) { + cvalue := C.int64_t(value) + C.xpcArraySetInt64(objc.Ptr(a), C.size_t(index), cvalue) +} + +// SetString sets a string value in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_string(_:_:_:)?language=objc +func (a *Array) SetString(index int, value string) { + cstr := C.CString(value) + defer C.free(unsafe.Pointer(cstr)) + C.xpcArraySetString(objc.Ptr(a), C.size_t(index), cstr) +} + +// SetUInt64 sets a uint64 value in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_uint64(_:_:_:)?language=objc +func (a *Array) SetUInt64(index int, value uint64) { + cvalue := C.uint64_t(value) + C.xpcArraySetUInt64(objc.Ptr(a), C.size_t(index), cvalue) +} + +// SetUUID sets a UUID value in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_uuid(_:_:_:)?language=objc +func (a *Array) SetUUID(index int, value [16]byte) { + C.xpcArraySetUUID(objc.Ptr(a), C.size_t(index), (*C.uint8_t)(unsafe.Pointer(&value[0]))) +} + +// ArrayApppendIndex is a constant to append an element to the end of the [Array]. +const ArrayApppendIndex = C.XPC_ARRAY_APPEND diff --git a/xpc/dictionary.go b/xpc/dictionary.go new file mode 100644 index 00000000..d06d02be --- /dev/null +++ b/xpc/dictionary.go @@ -0,0 +1,370 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +# include "xpc_darwin.h" +*/ +import "C" +import ( + "fmt" + "iter" + "runtime/cgo" + "time" + "unsafe" + + "github.com/Code-Hex/vz/v3/internal/cgohandler" + "github.com/Code-Hex/vz/v3/internal/objc" +) + +// Dictionary represents an XPC dictionary ([XPC_TYPE_DICTIONARY]) object. [TypeDictionary] +// +// [XPC_TYPE_DICTIONARY]: https://developer.apple.com/documentation/xpc/xpc_type_dictionary-c.macro?language=objc +type Dictionary struct { + *xpcObject +} + +var _ Object = &Dictionary{} + +// MARK: - Constructor + +// NewDictionary creates a new empty [Dictionary] object and applies the given entries. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_create_empty()?language=objc +// +// The entries can be created using [DictionaryEntry] functions such as [KeyValue]. +func NewDictionary(entries ...DictionaryEntry) *Dictionary { + d := ReleaseOnCleanup(&Dictionary{newXpcObject(C.xpcDictionaryCreateEmpty())}) + for _, e := range entries { + e(d) + } + return d +} + +// DictionaryEntry defines a function type for customizing [NewDictionary] or [Dictionary.CreateReply]. +type DictionaryEntry func(*Dictionary) + +// KeyValue sets a [Object] value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_value(_:_:_:)?language=objc +func KeyValue(key string, value Object) DictionaryEntry { + return func(o *Dictionary) { + o.SetValue(key, value) + } +} + +// MARK: - CreateReply + +// DictionaryCreateReply creates a new reply [Dictionary] based on the current [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_create_reply(_:)?language=objc +// +// The entries can be created using [DictionaryEntry] functions such as [KeyValue]. +func (o *Dictionary) CreateReply(entries ...DictionaryEntry) *Dictionary { + // Do not use ReleaseOnCleanup here because the reply dictionary will be released in C after sending. + d := &Dictionary{newXpcObject(C.xpcDictionaryCreateReply(objc.Ptr(o)))} + for _, entry := range entries { + entry(d) + } + return d +} + +// MARK: - Value Accessors + +// SetValue sets an [Object] value for the given key in the [Dictionary]. +// The value may be nil, which removes the key from the dictionary. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_value(_:_:_:)?language=objc +func (o *Dictionary) SetValue(key string, value Object) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + C.xpcDictionarySetValue(objc.Ptr(o), cKey, objc.Ptr(value)) +} + +// Count returns the number of key-value pairs in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_count(_:)?language=objc +func (o *Dictionary) Count() int { + return int(C.xpcDictionaryGetCount(objc.Ptr(o))) +} + +// GetValue retrieves an [Object] value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_value(_:_:)?language=objc +// +// Returns nil if the key does not exist. +func (o *Dictionary) GetValue(key string) Object { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + value := C.xpcDictionaryGetValue(objc.Ptr(o), cKey) + if value == nil { + return nil + } + return NewObject(value) +} + +// MARK: - Iteration + +// DictionaryApplier is a function type for applying to each key-value pair in the XPC dictionary. +type DictionaryApplier func(string, Object) bool + +// callDictionaryApplier is called from C to apply a function to each key-value pair in the XPC dictionary object. +// +//export callDictionaryApplier +func callDictionaryApplier(cgoApplier uintptr, cKey *C.char, cgoValue uintptr) C.bool { + applier := cgohandler.Unwrap[DictionaryApplier](cgoApplier) + return C.bool(applier(C.GoString(cKey), unwrapObject[Object](cgoValue))) +} + +// All iterates over all key-value pairs in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_apply(_:_:)?language=objc +func (o *Dictionary) All() iter.Seq2[string, Object] { + return func(yieald func(string, Object) bool) { + cgoApplier := cgo.NewHandle(DictionaryApplier(yieald)) + defer cgoApplier.Delete() + C.xpcDictionaryApply(objc.Ptr(o), C.uintptr_t(cgoApplier)) + } +} + +// Keys iterates over all keys in the [Dictionary] using [Dictionary.All]. +func (o *Dictionary) Keys() iter.Seq[string] { + return func(yieald func(string) bool) { + for key := range o.All() { + if !yieald(key) { + return + } + } + } +} + +// Values iterates over all [Object] values in the [Dictionary] using [Dictionary.All]. +func (o *Dictionary) Values() iter.Seq[Object] { + return func(yieald func(Object) bool) { + for _, value := range o.All() { + if !yieald(value) { + return + } + } + } +} + +// Entries iterates over all [DictionaryEntry] entries in the [Dictionary] using [Dictionary.All]. +func (o *Dictionary) Entries() iter.Seq[DictionaryEntry] { + return func(yieald func(DictionaryEntry) bool) { + for key, value := range o.All() { + if !yieald(KeyValue(key, value)) { + return + } + } + } +} + +// MARK: - Typed Getters + +// DupFd retrieves a duplicated file descriptor from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_dup_fd(_:_:)?language=objc +func (o *Dictionary) DupFd(key string) uintptr { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + return uintptr(C.xpcDictionaryDupFd(objc.Ptr(o), cKey)) +} + +// GetArray retrieves an [Array] value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_array(_:_:)?language=objc +// +// Returns nil if the key does not exist. +func (o *Dictionary) GetArray(key string) *Array { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + p := C.xpcDictionaryGetArray(objc.Ptr(o), cKey) + if p == nil { + return nil + } + return &Array{newXpcObject(p)} +} + +// GetBool retrieves a boolean value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_bool(_:_:)?language=objc +func (o *Dictionary) GetBool(key string) bool { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + value := C.xpcDictionaryGetBool(objc.Ptr(o), cKey) + return bool(value) +} + +// GetData retrieves a byte slice value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_data(_:_:_:)?language=objc +// +// Returns nil if the key does not exist. +func (o *Dictionary) GetData(key string) []byte { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + var n C.size_t + p := C.xpcDictionaryGetData(objc.Ptr(o), cKey, &n) + if p == nil || n == 0 { + return nil + } + return C.GoBytes(p, C.int(n)) +} + +// GetDate retrieves a date value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_date(_:_:)?language=objc +func (o *Dictionary) GetDate(key string) time.Time { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + unixNano := C.xpcDictionaryGetDate(objc.Ptr(o), cKey) + return time.Unix(0, int64(unixNano)) +} + +// GetDictionary retrieves a [Dictionary] value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_dictionary(_:_:)?language=objc +func (o *Dictionary) GetDictionary(key string) *Dictionary { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + p := C.xpcDictionaryGetDictionary(objc.Ptr(o), cKey) + if p == nil { + return nil + } + return &Dictionary{newXpcObject(p)} +} + +// GetDouble retrieves a double value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_double(_:_:)?language=objc +func (o *Dictionary) GetDouble(key string) float64 { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + value := C.xpcDictionaryGetDouble(objc.Ptr(o), cKey) + return float64(value) +} + +// GetInt64 retrieves an int64 value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_int64(_:_:)?language=objc +func (o *Dictionary) GetInt64(key string) int64 { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + value := C.xpcDictionaryGetInt64(objc.Ptr(o), cKey) + return int64(value) +} + +// GetString retrieves a string value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_string(_:_:)?language=objc +// +// Returns an empty string if the key does not exist. +func (o *Dictionary) GetString(key string) string { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + value := C.xpcDictionaryGetString(objc.Ptr(o), cKey) + return C.GoString(value) +} + +// GetUInt64 retrieves a uint64 value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_uint64(_:_:)?language=objc +func (o *Dictionary) GetUInt64(key string) uint64 { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + value := C.xpcDictionaryGetUInt64(objc.Ptr(o), cKey) + return uint64(value) +} + +// GetUUID retrieves a UUID value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_uuid(_:_:)?language=objc +func (o *Dictionary) GetUUID(key string) [16]byte { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + var uuid [16]byte + ptr := C.xpcDictionaryGetUUID(objc.Ptr(o), cKey) + if ptr == nil { + return uuid + } + copy(uuid[:], unsafe.Slice((*byte)(ptr), 16)) + return uuid +} + +// MARK: - Typed Setters + +// SetBool sets a boolean value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_bool(_:_:_:)?language=objc +func (o *Dictionary) SetBool(key string, value bool) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + C.xpcDictionarySetBool(objc.Ptr(o), cKey, C.bool(value)) +} + +// SetData sets a byte slice value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_data(_:_:_:_:)?language=objc +func (o *Dictionary) SetData(key string, data []byte) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + var ptr unsafe.Pointer + var length C.size_t + if len(data) > 0 { + ptr = unsafe.Pointer(&data[0]) + length = C.size_t(len(data)) + } + C.xpcDictionarySetData(objc.Ptr(o), cKey, ptr, length) +} + +// SetDate sets a date value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_date(_:_:_:)?language=objc +func (o *Dictionary) SetDate(key string, t time.Time) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + timestamp := C.int64_t(t.UnixNano()) + C.xpcDictionarySetDate(objc.Ptr(o), cKey, timestamp) +} + +// SetDouble sets a double value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_double(_:_:_:)?language=objc +func (o *Dictionary) SetDouble(key string, value float64) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + C.xpcDictionarySetDouble(objc.Ptr(o), cKey, C.double(value)) +} + +// SetFd sets a file descriptor value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_fd(_:_:_:)?language=objc +func (o *Dictionary) SetFd(key string, fd uintptr) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + C.xpcDictionarySetFd(objc.Ptr(o), cKey, C.int(fd)) +} + +// SetInt64 sets an int64 value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_int64(_:_:_:)?language=objc +func (o *Dictionary) SetInt64(key string, value int64) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + C.xpcDictionarySetInt64(objc.Ptr(o), cKey, C.int64_t(value)) +} + +// SetString sets a string value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_string(_:_:_:)?language=objc +func (o *Dictionary) SetString(key string, value string) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + cValue := C.CString(value) + defer C.free(unsafe.Pointer(cValue)) + C.xpcDictionarySetString(objc.Ptr(o), cKey, cValue) +} + +// SetUInt64 sets a uint64 value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_uint64(_:_:_:)?language=objc +func (o *Dictionary) SetUInt64(key string, value uint64) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + C.xpcDictionarySetUInt64(objc.Ptr(o), cKey, C.uint64_t(value)) +} + +// SetUUID sets a UUID value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_uuid(_:_:_:)?language=objc +func (o *Dictionary) SetUUID(key string, uuid [16]byte) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + C.xpcDictionarySetUUID(objc.Ptr(o), cKey, (*C.uint8_t)(unsafe.Pointer(&uuid[0]))) +} + +// MARK: - Peer Requirement + +// SenderSatisfies checks if the sender of the message [Dictionary] satisfies the given [PeerRequirement]. +// - https://developer.apple.com/documentation/xpc/xpc_peer_requirement_match_received_message?language=objc +func (d *Dictionary) SenderSatisfies(requirement *PeerRequirement) (bool, error) { + var err_out unsafe.Pointer + res := C.xpcPeerRequirementMatchReceivedMessage(objc.Ptr(requirement), objc.Ptr(d), &err_out) + if err_out != nil { + return false, fmt.Errorf("error matching peer requirement: %w", newRichError(err_out)) + } + return bool(res), nil +} diff --git a/xpc/error.go b/xpc/error.go new file mode 100644 index 00000000..e38d8ec1 --- /dev/null +++ b/xpc/error.go @@ -0,0 +1,48 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +# include "xpc_darwin.h" +*/ +import "C" +import ( + "unsafe" + + "github.com/Code-Hex/vz/v3/internal/objc" +) + +// RichError represents an XPC rich error. ([XPC_TYPE_RICH_ERROR]) [TypeRichError] +// +// [XPC_TYPE_RICH_ERROR]: https://developer.apple.com/documentation/xpc/xpc_rich_error_t?language=objc +type RichError struct { + *xpcObject +} + +var _ Object = &RichError{} + +var _ error = RichError{} + +// newRichError creates a new RichError from an existing xpc_rich_error_t. +// internal use only. +func newRichError(richErr unsafe.Pointer) *RichError { + if richErr == nil { + return nil + } + return &RichError{newXpcObject(richErr)} +} + +// CanRetry indicates whether the operation that caused the [RichError] can be retried. +// +// - https://developer.apple.com/documentation/xpc/xpc_rich_error_can_retry(_:)?language=objc +func (e RichError) CanRetry() bool { + return bool(C.xpcRichErrorCanRetry(objc.Ptr(e))) +} + +// Error implements the [error] interface. +// +// - https://developer.apple.com/documentation/xpc/xpc_rich_error_copy_description(_:)?language=objc +func (e RichError) Error() string { + desc := C.xpcRichErrorCopyDescription(objc.Ptr(e)) + defer C.free(unsafe.Pointer(desc)) + return C.GoString(desc) +} diff --git a/xpc/listener.go b/xpc/listener.go new file mode 100644 index 00000000..e186c16d --- /dev/null +++ b/xpc/listener.go @@ -0,0 +1,100 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +#cgo darwin LDFLAGS: -lobjc -framework Foundation +# include "xpc_darwin.h" +*/ +import "C" +import ( + "unsafe" + + "github.com/Code-Hex/vz/v3/internal/cgohandler" + "github.com/Code-Hex/vz/v3/internal/objc" +) + +// Listener represents an XPC listener. (macOS 14.0+) +// - https://developer.apple.com/documentation/xpc/xpc_listener_t?language=objc +type Listener struct { + *xpcObject + sessionHandler *cgohandler.Handler +} + +// SessionHandler is a function that handles incoming sessions. +type SessionHandler func(session *Session) + +// Option represents an option for creating a [Listener]. +type ListenerOption interface { + inactiveListenerSet(*Listener) +} + +var ( + _ ListenerOption = (*PeerRequirement)(nil) +) + +// NewListener creates a new [Listener] for the given service name. (macOS 14.0+) +// - https://developer.apple.com/documentation/xpc/xpc_listener_create +// +// You need to call [Listener.Activate] to start accepting incoming connections. +func NewListener(service string, handler SessionHandler, options ...ListenerOption) (*Listener, error) { + if err := macOSAvailable(14); err != nil { + return nil, err + } + + cname := C.CString(service) + defer C.free(unsafe.Pointer(cname)) + // Use a serial dispatch queue for the listener, + // because the vmnet framework API does not seem to work well with concurrent queues. + // For example, vmnet_network_create fails when using a concurrent queue. + q := C.dispatchQueueCreateSerial(cname) + defer C.dispatchRelease(q) + cgoHandler, p := cgohandler.New(handler) + var err_out unsafe.Pointer + ptr := C.xpcListenerCreate(cname, q, C.XPC_LISTENER_CREATE_INACTIVE, C.uintptr_t(p), &err_out) + if err_out != nil { + return nil, newRichError(err_out) + } + listener := ReleaseOnCleanup(&Listener{ + xpcObject: newXpcObject(ptr), + sessionHandler: cgoHandler, + }) + for _, opt := range options { + opt.inactiveListenerSet(listener) + } + return listener, nil +} + +// callSessionHandler is called from C to handle incoming sessions. +// +//export callSessionHandler +func callSessionHandler(cgoSessionHandler, cgoSession uintptr) { + handler := cgohandler.Unwrap[SessionHandler](cgoSessionHandler) + session := unwrapObject[*Session](cgoSession) + handler(session) +} + +// String returns a description of the [Listener]. (macOS 14.0+) +// - https://developer.apple.com/documentation/xpc/xpc_listener_copy_description +func (l *Listener) String() string { + desc := C.xpcListenerCopyDescription(objc.Ptr(l)) + defer C.free(unsafe.Pointer(desc)) + return C.GoString(desc) +} + +// Activate starts the [Listener] to accept incoming connections. (macOS 14.0+) +// - https://developer.apple.com/documentation/xpc/xpc_listener_activate +func (l *Listener) Activate() error { + var err_out unsafe.Pointer + C.xpcListenerActivate(objc.Ptr(l), &err_out) + if err_out != nil { + return newRichError(err_out) + } + return nil +} + +// Close stops the [Listener] from accepting incoming connections. (macOS 14.0+) +// - https://developer.apple.com/documentation/xpc/xpc_listener_cancel +func (l *Listener) Close() error { + C.xpcListenerCancel(objc.Ptr(l)) + return nil +} diff --git a/xpc/object.go b/xpc/object.go new file mode 100644 index 00000000..59355cdf --- /dev/null +++ b/xpc/object.go @@ -0,0 +1,350 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +# include "xpc_darwin.h" +*/ +import "C" +import ( + "runtime/cgo" + "time" + "unsafe" + + "github.com/Code-Hex/vz/v3/internal/objc" +) + +// MARK: - Object: Untyped XPC Object + +// Object represents an untyped XPC object ([xpc_object_t]). +// +// [xpc_object_t]: https://developer.apple.com/documentation/xpc/xpc_object_t?language=objc +type Object interface { + objc.NSObject + String() string // String returns the description of the XPC object. + releaseOnCleanup() // releaseOnCleanup releases the XPC object on cleanup. +} + +// GetType returns the [Type] of the given [Object]. +// - https://developer.apple.com/documentation/xpc/xpc_get_type(_:)?language=objc +func GetType(o Object) Type { + return Type{C.xpcGetType(objc.Ptr(o))} +} + +// NewObject creates a new [Object] from an existing [xpc_object_t]. +// The XPC APIs should be wrapped in C to use void* instead of [xpc_object_t]. +// This function accepts an [unsafe.Pointer] that represents void* in C. +// It determines the specific type and returns the appropriate wrapper. +// +// [xpc_object_t]: https://developer.apple.com/documentation/xpc/xpc_object_t?language=objc +func NewObject(o unsafe.Pointer) Object { + if o == nil { + return nil + } + xpcObject := newXpcObject(o) + // Determine the specific type and return the appropriate wrapper. + // It allows users to use type assertions to access type-specific methods. + switch GetType(xpcObject) { + case TypeArray: + return &Array{xpcObject} + case TypeBool: + return &Bool{xpcObject} + case TypeData: + return &Data{xpcObject} + case TypeDate: + return &Date{xpcObject} + case TypeDictionary: + return &Dictionary{xpcObject} + case TypeDouble: + return &Double{xpcObject} + case TypeFD: + return &Fd{xpcObject} + case TypeInt64: + return &Int64{xpcObject} + case TypeNull: + return &Null{xpcObject} + case TypeRichError: + return &RichError{xpcObject} + case TypeSession: + return &Session{xpcObject: xpcObject} + case TypeString: + return &String{xpcObject} + case TypeUInt64: + return &UInt64{xpcObject} + case TypeUUID: + return &UUID{xpcObject} + default: + return xpcObject + } +} + +// wrapRawObject wraps an existing xpc_object_t into an Object and returns a handle. +// intended to be called from C. +// +//export wrapRawObject +func wrapRawObject(ptr unsafe.Pointer) uintptr { + o := NewObject(ptr) + if o == nil { + return 0 + } + return uintptr(cgo.NewHandle(o)) +} + +// unwrapObject unwraps the [cgo.Handle] from the given uintptr and returns the associated Object. +// It also deletes the handle to avoid memory leaks. +func unwrapObject[T any](handle uintptr) T { + if handle == 0 { + var zero T + return zero + } + defer cgo.Handle(handle).Delete() + return cgo.Handle(handle).Value().(T) +} + +// MARK: - Bool: XPC_TYPE_BOOL represents an XPC boolean object. + +// Bool represents an XPC boolean([XPC_TYPE_BOOL]) object. [TypeBool] +// +// [XPC_TYPE_BOOL]: https://developer.apple.com/documentation/xpc/xpc_type_bool-c.macro?language=objc +type Bool struct{ *xpcObject } + +var _ Object = &Bool{} + +// NewBool returns a new [Bool] object from the given Go bool. +// - https://developer.apple.com/documentation/xpc/xpc_bool_create(_:)?language=objc +func NewBool(b bool) *Bool { + cbool := C.bool(b) + return ReleaseOnCleanup(&Bool{newXpcObject(C.xpcBoolCreate(cbool))}) +} + +// Value returns the boolean value of the [Bool] object. +// - https://developer.apple.com/documentation/xpc/xpc_bool_get_value(_:)?language=objc +func (b *Bool) Bool() bool { + return bool(C.xpcBoolGetValue(objc.Ptr(b))) +} + +var ( + BoolTrue = &Bool{newXpcObject(C.xpcBoolTrue())} + BoolFalse = &Bool{newXpcObject(C.xpcBoolFalse())} +) + +// MARK: - Data: XPC_TYPE_DATA represents an XPC data object. + +// Data represents an XPC data([XPC_TYPE_DATA]) object. [TypeData] +// +// [XPC_TYPE_DATA]: https://developer.apple.com/documentation/xpc/xpc_type_data-c.macro?language=objc +type Data struct{ *xpcObject } + +var _ Object = &Data{} + +// NewData returns a new [Data] object from the given byte slice. +// - https://developer.apple.com/documentation/xpc/xpc_data_create(_:_:)?language=objc +func NewData(b []byte) *Data { + if len(b) == 0 { + return ReleaseOnCleanup(&Data{newXpcObject(C.xpcDataCreate(nil, 0))}) + } + return ReleaseOnCleanup(&Data{newXpcObject(C.xpcDataCreate( + unsafe.Pointer(&b[0]), + C.size_t(len(b)), + ))}) +} + +// Bytes returns the byte slice of the [Data] object. +// - https://developer.apple.com/documentation/xpc/xpc_data_get_bytes_ptr(_:)?language=objc +// - https://developer.apple.com/documentation/xpc/xpc_data_get_length(_:)?language=objc +func (d *Data) Bytes() []byte { + size := C.xpcDataGetLength(objc.Ptr(d)) + ptr := C.xpcDataGetBytesPtr(objc.Ptr(d)) + if ptr == nil || size == 0 { + return nil + } + return C.GoBytes(ptr, C.int(size)) +} + +// MARK: - Double: XPC_TYPE_DOUBLE represents an XPC double object. + +// Double represents an XPC double([XPC_TYPE_DOUBLE]) object. [TypeDouble] +// +// [XPC_TYPE_DOUBLE]: https://developer.apple.com/documentation/xpc/xpc_type_double-c.macro?language=objc +type Double struct{ *xpcObject } + +var _ Object = &Double{} + +// NewDouble returns a new [Double] object from the given Go float64. +// - https://developer.apple.com/documentation/xpc/xpc_double_create(_:)?language=objc +func NewDouble(f float64) *Double { + cdouble := C.double(f) + return ReleaseOnCleanup(&Double{newXpcObject(C.xpcDoubleCreate(cdouble))}) +} + +// Value returns the float64 value of the [Double] object. +// - https://developer.apple.com/documentation/xpc/xpc_double_get_value(_:)?language=objc +func (d *Double) Float64() float64 { + return float64(C.xpcDoubleGetValue(objc.Ptr(d))) +} + +// MARK: - Int64: XPC_TYPE_INT64 represents an XPC int64 object. + +// Int64 represents an XPC int64([XPC_TYPE_INT64]) object. [TypeInt64] +// +// [XPC_TYPE_INT64]: https://developer.apple.com/documentation/xpc/xpc_type_int64-c.macro?language=objc +type Int64 struct{ *xpcObject } + +var _ Object = &Int64{} + +// NewInt64 returns a new [Int64] object from the given Go int64. +// - https://developer.apple.com/documentation/xpc/xpc_int64_create(_:)?language=objc +func NewInt64(i int64) *Int64 { + cint64 := C.int64_t(i) + return ReleaseOnCleanup(&Int64{newXpcObject(C.xpcInt64Create(cint64))}) +} + +// Value returns the int64 value of the [Int64] object. +// - https://developer.apple.com/documentation/xpc/xpc_int64_get_value(_:)?language=objc +func (i *Int64) Int64() int64 { + return int64(C.xpcInt64GetValue(objc.Ptr(i))) +} + +// MARK: - UInt64: XPC_TYPE_UINT64 represents an XPC uint64 object. + +// UInt64 represents an XPC uint64([XPC_TYPE_UINT64]) object. [TypeUInt64] +// +// [XPC_TYPE_UINT64]: https://developer.apple.com/documentation/xpc/xpc_type_uint64-c.macro?language=objc +type UInt64 struct{ *xpcObject } + +var _ Object = &UInt64{} + +// NewUInt64 returns a new [UInt64] object from the given Go uint64. +// - https://developer.apple.com/documentation/xpc/xpc_uint64_create(_:)?language=objc +func NewUInt64(u uint64) *UInt64 { + cuint64 := C.uint64_t(u) + return ReleaseOnCleanup(&UInt64{newXpcObject(C.xpcUInt64Create(cuint64))}) +} + +// Value returns the uint64 value of the [UInt64] object. +// - https://developer.apple.com/documentation/xpc/xpc_uint64_get_value(_:)?language=objc +func (u *UInt64) UInt64() uint64 { + return uint64(C.xpcUInt64GetValue(objc.Ptr(u))) +} + +// MARK: - String: XPC_TYPE_STRING represents an XPC string object. + +// String represents an XPC string([XPC_TYPE_STRING]) object. [TypeString] +// +// [XPC_TYPE_STRING]: https://developer.apple.com/documentation/xpc/xpc_type_string-c.macro?language=objc +type String struct{ *xpcObject } + +var _ Object = &String{} + +// NewString returns a new [String] object from the given Go string. +// - https://developer.apple.com/documentation/xpc/xpc_string_create(_:)?language=objc +func NewString(s string) *String { + cstr := C.CString(s) + defer C.free(unsafe.Pointer(cstr)) + return ReleaseOnCleanup(&String{newXpcObject(C.xpcStringCreate(cstr))}) +} + +// String returns the Go string value of the [String] object. +// - https://developer.apple.com/documentation/xpc/xpc_string_get_string_ptr(_:)?language=objc +func (s *String) String() string { + cstr := C.xpcStringGetStringPtr(objc.Ptr(s)) + len := C.xpcStringGetLength(objc.Ptr(s)) + if cstr == nil || len == 0 { + return "" + } + return C.GoStringN(cstr, C.int(len)) +} + +// MARK: - Fd: XPC_TYPE_FD represents an XPC file descriptor object. + +// Fd represents an XPC file descriptor([XPC_TYPE_FD]) object. [TypeFd] +// +// [XPC_TYPE_FD]: https://developer.apple.com/documentation/xpc/xpc_type_fd-c.macro?language=objc +type Fd struct{ *xpcObject } + +var _ Object = &Fd{} + +// NewFd returns a new [Fd] object from the given file descriptor. +// - https://developer.apple.com/documentation/xpc/xpc_fd_create(_:)?language=objc +func NewFd(fd uintptr) *Fd { + cfd := C.int(fd) + return ReleaseOnCleanup(&Fd{newXpcObject(C.xpcFdCreate(cfd))}) +} + +// Dup returns a duplicated file descriptor from the [Fd] object. +// - https://developer.apple.com/documentation/xpc/xpc_fd_dup(_:)?language=objc +func (f *Fd) Dup() uintptr { + return uintptr(C.xpcFdDup(objc.Ptr(f))) +} + +// MARK: - Date: XPC_TYPE_DATE represents an XPC date object. + +// Date represents an XPC date([XPC_TYPE_DATE]) object. [TypeDate] +// +// [XPC_TYPE_DATE]: https://developer.apple.com/documentation/xpc/xpc_type_date-c.macro?language=objc +type Date struct{ *xpcObject } + +var _ Object = &Date{} + +// NewDate returns a new [Date] object from the given Go int64 representing nanoseconds since epoch. +// - https://developer.apple.com/documentation/xpc/xpc_date_create(_:)?language=objc +func NewDate(nanoseconds int64) *Date { + cinterval := C.int64_t(nanoseconds) + return ReleaseOnCleanup(&Date{newXpcObject(C.xpcDateCreate(cinterval))}) +} + +// NewDateFromCurrent returns a new [Date] object representing the current date and time. +// - https://developer.apple.com/documentation/xpc/xpc_date_from_current()?language=objc +func NewDateFromCurrent() *Date { + return ReleaseOnCleanup(&Date{newXpcObject(C.xpcDateCreateFromCurrent())}) +} + +// Value returns the [time.Time] value of the [Date] object. +// - https://developer.apple.com/documentation/xpc/xpc_date_get_value(_:)?language=objc +func (d *Date) Time() time.Time { + unixNano := int64(C.xpcDateGetValue(objc.Ptr(d))) + return time.Unix(0, unixNano) +} + +// MARK: - UUID: XPC_TYPE_UUID represents an XPC UUID object. + +// UUID represents an XPC UUID([XPC_TYPE_UUID]) object. [TypeUUID] +// +// [XPC_TYPE_UUID]: https://developer.apple.com/documentation/xpc/xpc_type_uuid-c.macro?language=objc +type UUID struct{ *xpcObject } + +var _ Object = &UUID{} + +// NewUUID returns a new [UUID] object from the given UUID byte array. +// - https://developer.apple.com/documentation/xpc/xpc_uuid_create(_:)?language=objc +func NewUUID(uuid [16]byte) *UUID { + cuuid := (*C.uint8_t)(unsafe.Pointer(&uuid[0])) + return ReleaseOnCleanup(&UUID{newXpcObject(C.xpcUUIDCreate(cuuid))}) +} + +// Bytes returns the UUID byte array of the [UUID] object. +// - https://developer.apple.com/documentation/xpc/xpc_uuid_get_bytes(_:)?language=objc +func (u *UUID) Bytes() [16]byte { + var uuid [16]byte + ptr := C.xpcUUIDGetBytes(objc.Ptr(u)) + if ptr == nil { + return uuid + } + copy(uuid[:], unsafe.Slice((*byte)(ptr), 16)) + return uuid +} + +// MARK: - Shared Memory: XPC_TYPE_SHMEM represents an XPC shared memory object. +// MARK: - Null: XPC_TYPE_NULL represents an XPC null object. + +// Null represents an XPC null([XPC_TYPE_NULL]) object. [TypeNull] +// +// [XPC_TYPE_NULL]: https://developer.apple.com/documentation/xpc/xpc_type_null-c.macro?language=objc +type Null struct{ *xpcObject } + +var _ Object = &Null{} + +// NewNull returns a new [Null] object. +// - https://developer.apple.com/documentation/xpc/xpc_null_create()?language=objc +func NewNull() *Null { + return ReleaseOnCleanup(&Null{newXpcObject(C.xpcNullCreate())}) +} diff --git a/xpc/osversion_alias.go b/xpc/osversion_alias.go new file mode 100644 index 00000000..cb747790 --- /dev/null +++ b/xpc/osversion_alias.go @@ -0,0 +1,7 @@ +package xpc + +import ( + "github.com/Code-Hex/vz/v3/internal/osversion" +) + +var macOSAvailable = osversion.MacOSAvailable diff --git a/xpc/peer_requirement.go b/xpc/peer_requirement.go new file mode 100644 index 00000000..d3ea8c20 --- /dev/null +++ b/xpc/peer_requirement.go @@ -0,0 +1,62 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +#cgo darwin LDFLAGS: -lobjc -framework Foundation +# include "xpc_darwin.h" +*/ +import "C" +import ( + "unsafe" + + "github.com/Code-Hex/vz/v3/internal/objc" +) + +// PeerRequirement represents an [xpc_peer_requirement_t]. (macOS 26.0+) +// +// [xpc_peer_requirement_t]: https://developer.apple.com/documentation/xpc/xpc_peer_requirement_t?language=objc +type PeerRequirement struct { + *xpcObject +} + +var _ Object = &PeerRequirement{} + +// NewPeerRequirementLwcr creates a [PeerRequirement] from a LWCR object *[Dictionary]. (macOS 26.0+) +// - https://developer.apple.com/documentation/xpc/xpc_peer_requirement_create_lwcr +// - https://developer.apple.com/documentation/security/defining-launch-environment-and-library-constraints?language=objc +func NewPeerRequirementLwcr(lwcr *Dictionary) (*PeerRequirement, error) { + if err := macOSAvailable(26); err != nil { + return nil, err + } + + var err_out unsafe.Pointer + ptr := C.xpcPeerRequirementCreateLwcr(objc.Ptr(lwcr), &err_out) + if err_out != nil { + return nil, newRichError(err_out) + } + return ReleaseOnCleanup(&PeerRequirement{newXpcObject(ptr)}), nil +} + +// NewPeerRequirementLwcrWithEntries creates a [PeerRequirement] from a LWCR object *[Dictionary] constructed +// with the given [DictionaryEntry]s. (macOS 26.0+) +// - https://developer.apple.com/documentation/xpc/xpc_peer_requirement_create_lwcr +// - https://developer.apple.com/documentation/security/defining-launch-environment-and-library-constraints?language=objc +func NewPeerRequirementLwcrWithEntries(entries ...DictionaryEntry) (*PeerRequirement, error) { + return NewPeerRequirementLwcr(NewDictionary(entries...)) +} + +// inactiveListenerSet configures the given [Listener] with the [PeerRequirement]. (macOS 26.0+) +// - https://developer.apple.com/documentation/xpc/xpc_listener_set_peer_requirement +// +// This method implements the [ListenerOption] interface. +func (pr *PeerRequirement) inactiveListenerSet(listener *Listener) { + C.xpcListenerSetPeerRequirement(objc.Ptr(listener), objc.Ptr(pr)) +} + +// inactiveSessionSet configures the given [Session] with the [PeerRequirement]. (macOS 26.0+) +// - https://developer.apple.com/documentation/xpc/xpc_session_set_peer_requirement +// +// This method implements the [SessionOption] interface. +func (pr *PeerRequirement) inactiveSessionSet(session *Session) { + C.xpcSessionSetPeerRequirement(objc.Ptr(session), objc.Ptr(pr)) +} diff --git a/xpc/session.go b/xpc/session.go new file mode 100644 index 00000000..0985e9f1 --- /dev/null +++ b/xpc/session.go @@ -0,0 +1,228 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +#cgo darwin LDFLAGS: -lobjc -framework Foundation +# include "xpc_darwin.h" +*/ +import "C" +import ( + "context" + "runtime/cgo" + "unsafe" + + "github.com/Code-Hex/vz/v3/internal/cgohandler" + "github.com/Code-Hex/vz/v3/internal/objc" +) + +// Session represents an [xpc_session_t]. (macOS 13.0+) +// +// [xpc_session_t]: https://developer.apple.com/documentation/xpc/xpc_session_t?language=objc +type Session struct { + // Exported for use in other packages since unimplemented XPC API may require direct access to xpc_session_t. + *xpcObject + cancellationHandler *cgohandler.Handler + incomingMessageHandler *cgohandler.Handler +} + +var _ Object = &Session{} + +// SessionOption represents an option for configuring a inactive [Session]. +type SessionOption interface { + inactiveSessionSet(*Session) +} + +var ( + _ SessionOption = (MessageHandler)(nil) + _ SessionOption = (CancellationHandler)(nil) + _ SessionOption = (*PeerRequirement)(nil) +) + +// NewSession creates a new [Session] for the given Mach service name. (macOS 13.0+) +// +// [SessionOption](s) can be provided to configure the inactive session before activation. +// Available options include [MessageHandler], [CancellationHandler], and [PeerRequirement]. +// - https://developer.apple.com/documentation/xpc/xpc_session_create_mach_service +func NewSession(macServiceName string, sessionOpts ...SessionOption) (*Session, error) { + if err := macOSAvailable(13); err != nil { + return nil, err + } + + cServiceName := C.CString(macServiceName) + defer C.free(unsafe.Pointer(cServiceName)) + + var err_out unsafe.Pointer + ptr := C.xpcSessionCreateMachService(cServiceName, nil, C.XPC_SESSION_CREATE_INACTIVE, &err_out) + if err_out != nil { + return nil, newRichError(err_out) + } + session := ReleaseOnCleanup(&Session{xpcObject: newXpcObject(ptr)}) + for _, o := range sessionOpts { + o.inactiveSessionSet(session) + } + err := session.activate() + if err != nil { + session.Cancel() + return nil, err + } + return session, nil +} + +// MessageHandler is a function [SessionOption] that handles incoming messages in a [Session]. +// It receives the incoming message *[Dictionary] and returns a reply message *[Dictionary]. +type MessageHandler func(msg *Dictionary) (reply *Dictionary) + +// inactiveSessionSet configures the given [Session] with the [MessageHandler]. (macOS 13.0+) +// - https://developer.apple.com/documentation/xpc/xpc_session_set_incoming_message_handler +func (mh MessageHandler) inactiveSessionSet(s *Session) { + s.setIncomingMessageHandler(mh) +} + +// CancellationHandler is a function [SessionOption] that handles session cancellation in a [Session] +// It receives the [RichError] that caused the cancellation. +type CancellationHandler func(err *RichError) + +// inactiveSessionSet configures the given [Session] with the [CancellationHandler]. (macOS 13.0+) +// - https://developer.apple.com/documentation/xpc/xpc_session_set_cancel_handler +func (ch CancellationHandler) inactiveSessionSet(s *Session) { + s.setCancellationHandler(ch) +} + +// Reject rejects the incoming [Session] with the given reason. (macOS 14.0+) +// - https://developer.apple.com/documentation/xpc/xpc_listener_reject_peer +func (s *Session) Reject(reason string) { + cReason := C.CString(reason) + defer C.free(unsafe.Pointer(cReason)) + C.xpcListenerRejectPeer(objc.Ptr(s), cReason) +} + +// Accept creates a [SessionHandler] that accepts incoming sessions with the given [SessionOption]s. +func Accept(sessionOptions ...SessionOption) SessionHandler { + return func(session *Session) { + for _, opt := range sessionOptions { + opt.inactiveSessionSet(session) + } + } +} + +// String returns a description of the [Session]. (macOS 13.0+) +// - https://developer.apple.com/documentation/xpc/xpc_session_copy_description +func (s *Session) String() string { + desc := C.xpcSessionCopyDescription(objc.Ptr(s)) + defer C.free(unsafe.Pointer(desc)) + return C.GoString(desc) +} + +// activate activates the [Session]. (macOS 13.0+) +// It is called internally after applying all [SessionOption]s in [NewSession]. +// - https://developer.apple.com/documentation/xpc/xpc_session_activate +func (s *Session) activate() error { + var err_out unsafe.Pointer + C.xpcSessionActivate(objc.Ptr(s), &err_out) + if err_out != nil { + return newRichError(err_out) + } + return nil +} + +// callMessageHandler is called from C to handle incoming messages. +// +//export callMessageHandler +func callMessageHandler(cgoMessageHandler, cgoMessage uintptr) (reply unsafe.Pointer) { + handler := cgohandler.Unwrap[MessageHandler](cgoMessageHandler) + message := unwrapObject[*Dictionary](cgoMessage) + return objc.Ptr(handler(message)) +} + +// setIncomingMessageHandler sets the [MessageHandler] for the inactive [Session]. (macOS 13.0+) +// - https://developer.apple.com/documentation/xpc/xpc_session_set_incoming_message_handler +func (s *Session) setIncomingMessageHandler(handler MessageHandler) { + cgoHandler, p := cgohandler.New(handler) + C.xpcSessionSetIncomingMessageHandler(objc.Ptr(s), C.uintptr_t(p)) + // Store the handler after setting it to avoid premature garbage collection of the previous handler. + s.incomingMessageHandler = cgoHandler +} + +// Cancel cancels the [Session]. (macOS 13.0+) +// - https://developer.apple.com/documentation/xpc/xpc_session_cancel +func (s *Session) Cancel() { + C.xpcSessionCancel(objc.Ptr(s)) +} + +// callCancelHandler is called from C to handle session cancellation. +// +//export callCancelHandler +func callCancelHandler(cgoCancelHandler, cgoErr uintptr) { + handler := cgohandler.Unwrap[CancellationHandler](cgoCancelHandler) + err := unwrapObject[*RichError](cgoErr) + handler(err) +} + +// setCancellationHandler sets the [CancellationHandler] for the inactive [Session]. (macOS 13.0+) +// The handler will call [Session.handleCancellation] after executing the provided handler. +// - https://developer.apple.com/documentation/xpc/xpc_session_set_cancel_handler +func (s *Session) setCancellationHandler(handler CancellationHandler) { + cgoHandler, p := cgohandler.New((CancellationHandler)(func(err *RichError) { + if handler != nil { + handler(err) + } + s.handleCancellation(err) + })) + C.xpcSessionSetCancelHandler(objc.Ptr(s), C.uintptr_t(p)) + // Store the handler after setting it to avoid premature garbage collection of the previous handler. + s.cancellationHandler = cgoHandler +} + +// handleCancellation handles [Session] cancellation by deleting the associated handles. +func (s *Session) handleCancellation(_ *RichError) { +} + +type ReplyHandler func(*Dictionary, *RichError) + +// callReplyHandler is called from C to handle reply messages. +// +//export callReplyHandler +func callReplyHandler(cgoReplyHandler uintptr, cgoReply, cgoError uintptr) { + handler := cgohandler.Unwrap[ReplyHandler](cgoReplyHandler) + reply := unwrapObject[*Dictionary](uintptr(cgoReply)) + err := unwrapObject[*RichError](cgoError) + handler(reply, err) + cgo.Handle(cgoReplyHandler).Delete() +} + +// SendMessageWithReply sends a message *[Dictionary] to the [Session] and waits for a reply *[Dictionary]. (macOS 13.0+) +// +// Use [context.Context] to control cancellation and timeouts. +// - https://developer.apple.com/documentation/xpc/xpc_session_send_message_with_reply_async +func (s *Session) SendMessageWithReply(ctx context.Context, message *Dictionary) (*Dictionary, error) { + replyCh := make(chan *Dictionary, 1) + errCh := make(chan *RichError, 1) + replyHandler := (ReplyHandler)(func(reply *Dictionary, err *RichError) { + defer close(replyCh) + defer close(errCh) + if err != nil { + errCh <- ReleaseOnCleanup(Retain(err)) + } else { + replyCh <- ReleaseOnCleanup(Retain(reply)) + } + }) + // handle will be deleted in callReplyHandler + cgoReplyHandler := cgo.NewHandle(replyHandler) + C.xpcSessionSendMessageWithReplyAsync(objc.Ptr(s), objc.Ptr(message), C.uintptr_t(cgoReplyHandler)) + select { + case reply := <-replyCh: + return reply, nil + case err := <-errCh: + return nil, err + case <-ctx.Done(): + return nil, ctx.Err() + } +} + +// SendDictionaryWithReply creates a message *[Dictionary] and calls [Session.SendMessageWithReply] with it. A`(macOS 13.0+) +// +// Use [context.Context] to control cancellation and timeouts. +// The message *[Dictionary] can be customized using [DictionaryEntry]. +func (s *Session) SendDictionaryWithReply(ctx context.Context, entries ...DictionaryEntry) (*Dictionary, error) { + return s.SendMessageWithReply(ctx, NewDictionary(entries...)) +} diff --git a/xpc/type.go b/xpc/type.go new file mode 100644 index 00000000..5a80d67d --- /dev/null +++ b/xpc/type.go @@ -0,0 +1,46 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +# include "xpc_darwin.h" +*/ +import "C" + +// Type represents an XPC type (xpc_type_t). +// - https://developer.apple.com/documentation/xpc/xpc_type_t?language=objc +type Type struct { + xpcType C.xpc_type_t +} + +var ( + TypeActivity = Type{C.XPC_TYPE_ACTIVITY} // https://developer.apple.com/documentation/xpc/xpc_type_activity-c.macro?language=objc + TypeArray = Type{C.XPC_TYPE_ARRAY} // https://developer.apple.com/documentation/xpc/xpc_type_array-c.macro?language=objc + TypeBool = Type{C.XPC_TYPE_BOOL} // https://developer.apple.com/documentation/xpc/xpc_type_bool-c.macro?language=objc + TypeConnection = Type{C.XPC_TYPE_CONNECTION} // https://developer.apple.com/documentation/xpc/xpc_type_connection-c.macro?language=objc + TypeData = Type{C.XPC_TYPE_DATA} // https://developer.apple.com/documentation/xpc/xpc_type_data-c.macro?language=objc + TypeDate = Type{C.XPC_TYPE_DATE} // https://developer.apple.com/documentation/xpc/xpc_type_date-c.macro?language=objc + TypeDictionary = Type{C.XPC_TYPE_DICTIONARY} // https://developer.apple.com/documentation/xpc/xpc_type_dictionary-c.macro?language=objc + TypeDouble = Type{C.XPC_TYPE_DOUBLE} // https://developer.apple.com/documentation/xpc/xpc_type_double-c.macro?language=objc + TypeEndpoint = Type{C.XPC_TYPE_ENDPOINT} // https://developer.apple.com/documentation/xpc/xpc_type_endpoint-c.macro?language=objc + TypeError = Type{C.XPC_TYPE_ERROR} // https://developer.apple.com/documentation/xpc/xpc_type_error-c.macro?language=objc + TypeFD = Type{C.XPC_TYPE_FD} // https://developer.apple.com/documentation/xpc/xpc_type_fd-c.macro?language=objc + TypeInt64 = Type{C.XPC_TYPE_INT64} // https://developer.apple.com/documentation/xpc/xpc_type_int64-c.macro?language=objc + TypeNull = Type{C.XPC_TYPE_NULL} // https://developer.apple.com/documentation/xpc/xpc_type_null-c.macro?language=objc + TypeRichError = Type{C.XPC_TYPE_RICH_ERROR} // does not have official documentation, but defined in + TypeSession = Type{C.XPC_TYPE_SESSION} // does not have official documentation, but defined in + TypeShmem = Type{C.XPC_TYPE_SHMEM} // https://developer.apple.com/documentation/xpc/xpc_type_shmem-c.macro?language=objc + TypeString = Type{C.XPC_TYPE_STRING} // https://developer.apple.com/documentation/xpc/xpc_type_string-c.macro?language=objc + TypeUInt64 = Type{C.XPC_TYPE_UINT64} // https://developer.apple.com/documentation/xpc/xpc_type_uint64-c.macro?language=objc + TypeUUID = Type{C.XPC_TYPE_UUID} // https://developer.apple.com/documentation/xpc/xpc_type_uuid-c.macro?language=objc +) + +// String returns the name of the [XpcType]. +// see: https://developer.apple.com/documentation/xpc/xpc_type_get_name(_:)?language=objc +func (t Type) String() string { + cs := C.xpc_type_get_name(t.xpcType) + if cs == nil { + return "" + } + // do not free cs since it is managed by XPC runtime. + return C.GoString(cs) +} diff --git a/xpc/xpc_darwin.h b/xpc/xpc_darwin.h new file mode 100644 index 00000000..d0682cdf --- /dev/null +++ b/xpc/xpc_darwin.h @@ -0,0 +1,212 @@ +#pragma once + +#import "../internal/osversion/virtualization_helper.h" +#import + +// MARK: - dispatch_queue_t + +void *dispatchQueueCreateSerial(const char *label); +void dispatchRelease(void *queue); + +// MARK: - xpc.h types +// +// The following types are listed in the same order as the XPC documentation index page. +// https://developer.apple.com/documentation/xpc?language=objc + +// MARK: - xpc_listener_t (macOS 14+) + +void *xpcListenerCreate(const char *service_name, void *queue, uint64_t flags, uintptr_t cgo_session_handler, void **error_out); +const char *xpcListenerCopyDescription(void *listener); +bool xpcListenerActivate(void *listener, void **error_out); +void xpcListenerCancel(void *listener); +void xpcListenerRejectPeer(void *session, const char *reason); +// int xpcListenerSetPeerCodeSigningRequirement(void *listener, const char *requirement); + +// MARK: - xpc_session_t (XPC_TYPE_SESSION) (macOS 13+) + +// void *xpcSessionCreateXpcService(const char *service_name, void *queue, uint64_t flags, void **error_out); +void *xpcSessionCreateMachService(const char *service_name, void *queue, uint64_t flags, void **error_out); +// void xpcSessionSetTargetQueue(void *session, void *queue); +const char *xpcSessionCopyDescription(void *session); +bool xpcSessionActivate(void *session, void **error_out); +void xpcSessionSetIncomingMessageHandler(void *session, uintptr_t cgo_message_handler); +void xpcSessionCancel(void *session); +void xpcSessionSetCancelHandler(void *session, uintptr_t cgo_cancel_handler); +// void *xpcSessionSendMessage(void *session, void *message); +void xpcSessionSendMessageWithReplyAsync(void *session, void *message, uintptr_t cgo_reply_handler); +// void *xpcSessionSendMessageWithReplySync(void *session, void *message, void **error_out); + +// MARK: - xpc_rich_error_t (XPC_TYPE_RICH_ERROR) +bool xpcRichErrorCanRetry(void *err); +const char *xpcRichErrorCopyDescription(void *err); + +// MARK: - Identity + +// # xpc_type_t +xpc_type_t xpcGetType(void *object); +const char *xpcTypeGetName(xpc_type_t type); +// # xpc_object_t +// size_t xpxHash(void *object); + +// MARK: - Comparison + +// # xpc_object_t +// bool xpcEqual(void *object1, void *object2); + +// MARK: - Copying + +// # xpc_object_t +// void *xpcCopy(void *object); +const char *xpcCopyDescription(void *object); + +// MARK: - Boolean objects + +// # xpc_object_t (XPC_TYPE_BOOL) +void *xpcBoolCreate(bool value); +bool xpcBoolGetValue(void *object); +void *xpcBoolTrue(); // XPC_BOOL_TRUE +void *xpcBoolFalse(); // XPC_BOOL_FALSE + +// MARK: - Data objects + +// # xpc_object_t (XPC_TYPE_DATA) +void *xpcDataCreate(const void *bytes, size_t length); +// size_t xpcDataGetBytes(void *object, void *buffer, size_t offset, size_t length); +const void *xpcDataGetBytesPtr(void *object); +size_t xpcDataGetLength(void *object); + +// MARK: - Number objects + +// # xpc_object_t (XPC_TYPE_DOUBLE) +void *xpcDoubleCreate(double value); +double xpcDoubleGetValue(void *object); + +// # xpc_object_t (XPC_TYPE_INT64) +void *xpcInt64Create(int64_t value); +int64_t xpcInt64GetValue(void *object); + +// # xpc_object_t (XPC_TYPE_UINT64) +void *xpcUInt64Create(uint64_t value); +uint64_t xpcUInt64GetValue(void *object); + +// MARK: - Array objects + +// # xpc_object_t (XPC_TYPE_ARRAY) +void *xpcArrayCreate(void *const *object, size_t count); +// void *xpcArrayCreateEmpty(); +// void *xpcArrayCreateConnection +void *xpcArrayGetValue(void *object, size_t index); +void xpcArraySetValue(void *object, size_t index, void *value); +void xpcArrayAppendValue(void *object, void *value); +size_t xpcArrayGetCount(void *object); +bool xpcArrayApply(void *object, uintptr_t cgo_applier); +int xpcArrayDupFd(void *object, size_t index); +void *xpcArrayGetArray(void *object, size_t index); +bool xpcArrayGetBool(void *object, size_t index); +const void *xpcArrayGetData(void *object, size_t index, size_t *length); +int64_t xpcArrayGetDate(void *object, size_t index); +void *xpcArrayGetDictionary(void *object, size_t index); +double xpcArrayGetDouble(void *object, size_t index); +int64_t xpcArrayGetInt64(void *object, size_t index); +const char *xpcArrayGetString(void *object, size_t index); +uint64_t xpcArrayGetUInt64(void *object, size_t index); +const uint8_t *xpcArrayGetUUID(void *object, size_t index); +void xpcArraySetBool(void *object, size_t index, bool value); +// void xpcArraySetConnection +void xpcArraySetData(void *object, size_t index, const void *bytes, size_t length); +void xpcArraySetDate(void *object, size_t index, int64_t value); +void xpcArraySetDouble(void *object, size_t index, double value); +void xpcArraySetFd(void *object, size_t index, int fd); +void xpcArraySetInt64(void *object, size_t index, int64_t value); +void xpcArraySetString(void *object, size_t index, const char *string); +void xpcArraySetUInt64(void *object, size_t index, uint64_t value); +void xpcArraySetUUID(void *object, size_t index, const uuid_t uuid); +// XPC_ARRAY_APPEND + +// MARK: - Dictionary objects + +// # xpc_object_t (XPC_TYPE_DICTIONARY) +// void *xpcDictionaryCreate(const char *const *keys, void *const *values, size_t count); +void *xpcDictionaryCreateEmpty(void); +// void *xpcDictionaryCreateConnection +void *xpcDictionaryCreateReply(void *object); +void xpcDictionarySetValue(void *object, const char *key, void *value); +size_t xpcDictionaryGetCount(void *object); +void *xpcDictionaryGetValue(void *object, const char *key); +bool xpcDictionaryApply(void *object, uintptr_t cgo_applier); +int xpcDictionaryDupFd(void *object, const char *key); +void *xpcDictionaryGetArray(void *object, const char *key); +bool xpcDictionaryGetBool(void *object, const char *key); +const void *xpcDictionaryGetData(void *object, const char *key, size_t *length); +int64_t xpcDictionaryGetDate(void *object, const char *key); +void *xpcDictionaryGetDictionary(void *object, const char *key); +double xpcDictionaryGetDouble(void *object, const char *key); +int64_t xpcDictionaryGetInt64(void *object, const char *key); +// void *xpcDictionaryGetRemoteConnection +const char *xpcDictionaryGetString(void *object, const char *key); +uint64_t xpcDictionaryGetUInt64(void *object, const char *key); +const uint8_t *xpcDictionaryGetUUID(void *object, const char *key); +void xpcDictionarySetBool(void *object, const char *key, bool value); +// void xpcDictionarySetConnection +void xpcDictionarySetData(void *object, const char *key, const void *bytes, size_t length); +void xpcDictionarySetDate(void *object, const char *key, int64_t value); +void xpcDictionarySetDouble(void *object, const char *key, double value); +void xpcDictionarySetFd(void *object, const char *key, int fd); +void xpcDictionarySetInt64(void *object, const char *key, int64_t value); +void xpcDictionarySetString(void *object, const char *key, const char *value); +void xpcDictionarySetUInt64(void *object, const char *key, uint64_t value); +void xpcDictionarySetUUID(void *object, const char *key, const uint8_t *uuid); +// void *xpcDictionaryCopyMachSend +// void xpcDictionarySetMachSend + +// MARK: - String objects + +// # xpc_object_t (XPC_TYPE_STRING) +void *xpcStringCreate(const char *string); +// void *xpcStringCreateWithFormat(const char *format, ...); +// void *xpcStringCreateWithFormatAndArguments(const char *format, va_list args); +size_t xpcStringGetLength(void *object); +const char *xpcStringGetStringPtr(void *object); + +// MARK: - File Descriptor objects + +// # xpc_object_t (XPC_TYPE_FD) +void *xpcFdCreate(int fd); +int xpcFdDup(void *object); + +// MARK: - Date objects + +// # xpc_object_t (XPC_TYPE_DATE) +void *xpcDateCreate(int64_t interval); +void *xpcDateCreateFromCurrent(); +int64_t xpcDateGetValue(void *object); + +// MARK: - UUID objects + +// # xpc_object_t (XPC_TYPE_UUID) +void *xpcUUIDCreate(const uuid_t uuid); +const uint8_t *xpcUUIDGetBytes(void *object); + +// MARK: - Shared Memory objects + +// # xpc_object_t (XPC_TYPE_SHMEM) +// void *xpcShmemCreate(void *region, size_t length); +// size_t xpcShmemMap(void *object, void **region); + +// MARK: - Null objects +// # xpc_object_t (XPC_TYPE_NULL) +void *xpcNullCreate(); + +// MARK: - Object life cycle +void *xpcRetain(void *object); +void xpcRelease(void *object); + +// MARK: - xpc_peer_requirement_t (macOS 26+) +void xpcListenerSetPeerRequirement(void *listener, void *peer_requirement); +// void *xpcPeerRequirementCreateEntitlementExists(const char *entitlement, void **error_out); +// void *xpcPeerRequirementCreateEntitlementMatchesValue(const char *entitlement, void *value, void **error_out); +void *xpcPeerRequirementCreateLwcr(void *lwcr, void **error_out); +// void *xpcPeerRequirementCreatePlatformIdentity(const char * signing_identifier, void **error_out); +// void *xpcPeerRequirementCreateTeamIdentity(const char * team_identifier, void **error_out); +bool xpcPeerRequirementMatchReceivedMessage(void *peer_requirement, void *message, void **error_out); +void xpcSessionSetPeerRequirement(void *session, void *peer_requirement); diff --git a/xpc/xpc_darwin.m b/xpc/xpc_darwin.m new file mode 100644 index 00000000..9a3ea3a7 --- /dev/null +++ b/xpc/xpc_darwin.m @@ -0,0 +1,713 @@ +#include "xpc_darwin.h" + +// MARK: - Helper functions defined in Go + +// # xpc_object_t +extern uintptr_t wrapRawObject(void *obj); +// # xpc_listener_t +extern void callSessionHandler(uintptr_t cgoSessionHandler, uintptr_t cgoSession); + +// # xpc_session_t +extern void callReplyHandler(uintptr_t cgoReplyHandler, uintptr_t cgoReply, uintptr_t cgoError); +extern void callCancelHandler(uintptr_t cgoCancelHandler, uintptr_t cgoError); +extern void *callMessageHandler(uintptr_t cgoMessageHandler, uintptr_t cgoMessage); + +// # xpc_object_t (XPC_TYPE_ARRAY) +extern bool callArrayApplier(uintptr_t cgoApplier, size_t index, uintptr_t cgoValue); +// # xpc_object_t (XPC_TYPE_DICTIONARY) +extern bool callDictionaryApplier(uintptr_t cgoApplier, const char *_Nonnull key, uintptr_t cgoValue); + +// MARK: -dispatch_queue_t + +void *dispatchQueueCreateSerial(const char *label) +{ + return dispatch_queue_create(label, DISPATCH_QUEUE_SERIAL); +} + +void dispatchRelease(void *queue) +{ + dispatch_release((dispatch_queue_t)queue); +} + +// MARK: - xpc.h types +// +// The following types are listed in the same order as the XPC documentation index page. +// https://developer.apple.com/documentation/xpc?language=objc + +// MARK: - xpc_listener_t (macOS 14+) + +void *xpcListenerCreate(const char *service_name, void *queue, uint64_t flags, uintptr_t cgo_session_handler, void **error_out) +{ +#ifdef INCLUDE_TARGET_OSX_14 + if (@available(macOS 14, *)) { + return xpc_listener_create( + service_name, + queue, + flags, + ^(xpc_session_t _Nonnull session) { + callSessionHandler(cgo_session_handler, wrapRawObject(session)); + }, + (xpc_rich_error_t *)error_out); + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +const char *xpcListenerCopyDescription(void *listener) +{ +#ifdef INCLUDE_TARGET_OSX_14 + if (@available(macOS 14, *)) { + return xpc_listener_copy_description((xpc_listener_t)listener); + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +bool xpcListenerActivate(void *listener, void **error_out) +{ +#ifdef INCLUDE_TARGET_OSX_14 + if (@available(macOS 14, *)) { + return xpc_listener_activate((xpc_listener_t)listener, (xpc_rich_error_t *)error_out); + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void xpcListenerCancel(void *listener) +{ +#ifdef INCLUDE_TARGET_OSX_14 + if (@available(macOS 14, *)) { + xpc_listener_cancel((xpc_listener_t)listener); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void xpcListenerRejectPeer(void *session, const char *reason) +{ +#ifdef INCLUDE_TARGET_OSX_14 + if (@available(macOS 14, *)) { + xpc_listener_reject_peer((xpc_session_t)session, reason); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +// MARK: - xpc_session_t (XPC_TYPE_SESSION) (macOS 13+) + +void *xpcSessionCreateMachService(const char *service_name, void *queue, uint64_t flags, void **error_out) +{ +#ifdef INCLUDE_TARGET_OSX_13 + if (@available(macOS 13, *)) { + return xpc_session_create_mach_service( + service_name, + (dispatch_queue_t)queue, + flags, + (xpc_rich_error_t *)error_out); + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +const char *xpcSessionCopyDescription(void *session) +{ +#ifdef INCLUDE_TARGET_OSX_13 + if (@available(macOS 13, *)) { + return xpc_session_copy_description((xpc_session_t)session); + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +bool xpcSessionActivate(void *session, void **error_out) +{ +#ifdef INCLUDE_TARGET_OSX_13 + if (@available(macOS 13, *)) { + return xpc_session_activate((xpc_session_t)session, (xpc_rich_error_t *)error_out); + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void xpcSessionSetIncomingMessageHandler(void *session, uintptr_t cgo_message_handler) +{ +#ifdef INCLUDE_TARGET_OSX_13 + if (@available(macOS 13, *)) { + xpc_session_set_incoming_message_handler( + (xpc_session_t)session, + ^(xpc_object_t _Nonnull message) { + // Ensure the message is a dictionary. + if (xpc_get_type(message) != XPC_TYPE_DICTIONARY) { + xpc_session_cancel((xpc_session_t)session); + return; + } + xpc_object_t reply = (xpc_object_t)callMessageHandler(cgo_message_handler, wrapRawObject(message)); + xpc_rich_error_t err; + do { + err = xpc_session_send_message(session, reply); + } while (err != nil && xpc_rich_error_can_retry(err)); + xpc_release(reply); + }); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void xpcSessionCancel(void *session) +{ +#ifdef INCLUDE_TARGET_OSX_13 + if (@available(macOS 13, *)) { + xpc_session_cancel((xpc_session_t)session); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void xpcSessionSetCancelHandler(void *session, uintptr_t cgo_cancel_handler) +{ +#ifdef INCLUDE_TARGET_OSX_13 + if (@available(macOS 13, *)) { + xpc_session_set_cancel_handler( + (xpc_session_t)session, + ^(xpc_rich_error_t _Nonnull err) { + callCancelHandler(cgo_cancel_handler, wrapRawObject(err)); + }); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void xpcSessionSendMessageWithReplyAsync(void *session, void *message, uintptr_t cgo_reply_handler) +{ +#ifdef INCLUDE_TARGET_OSX_13 + if (@available(macOS 13, *)) { + xpc_session_send_message_with_reply_async( + (xpc_session_t)session, + (xpc_object_t)message, + ^(xpc_object_t _Nonnull reply, xpc_rich_error_t _Nullable error) { + callReplyHandler(cgo_reply_handler, wrapRawObject(reply), wrapRawObject(error)); + }); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +// MARK: - xpc_rich_error_t (XPC_TYPE_RICH_ERROR) + +bool xpcRichErrorCanRetry(void *err) +{ + return xpc_rich_error_can_retry((xpc_rich_error_t)err); +} + +const char *xpcRichErrorCopyDescription(void *err) +{ + return xpc_rich_error_copy_description((xpc_rich_error_t)err); +} + +// MARK: - Identity + +// # xpc_type_t + +xpc_type_t xpcGetType(void *object) +{ + return xpc_get_type((xpc_object_t)object); +} + +const char *xpcTypeGetName(xpc_type_t type) +{ + return xpc_type_get_name(type); +} + +// MARK: - Comparison +// MARK: - Copying + +// # xpc_object_t +const char *xpcCopyDescription(void *object) +{ + return xpc_copy_description((xpc_object_t)object); +} + +// MARK: - Boolean objects + +// # xpc_object_t (XPC_TYPE_BOOL) + +void *xpcBoolCreate(bool value) +{ + return xpc_bool_create(value); +} + +bool xpcBoolGetValue(void *object) +{ + return xpc_bool_get_value((xpc_object_t)object); +} + +void *xpcBoolTrue() +{ + return XPC_BOOL_TRUE; +} + +void *xpcBoolFalse() +{ + return XPC_BOOL_FALSE; +} + +// MARK: - Data objects + +// # xpc_object_t (XPC_TYPE_DATA) + +void *xpcDataCreate(const void *bytes, size_t length) +{ + return xpc_data_create(bytes, length); +} + +const void *xpcDataGetBytesPtr(void *object) +{ + return xpc_data_get_bytes_ptr((xpc_object_t)object); +} + +size_t xpcDataGetLength(void *object) +{ + return xpc_data_get_length((xpc_object_t)object); +} + +// MARK: - Number objects + +// # xpc_object_t (XPC_TYPE_DOUBLE) +void *xpcDoubleCreate(double value) +{ + return xpc_double_create(value); +} + +double xpcDoubleGetValue(void *object) +{ + return xpc_double_get_value((xpc_object_t)object); +} + +// MARK: - Int64 objects +// # xpc_object_t (XPC_TYPE_INT64) +void *xpcInt64Create(int64_t value) +{ + return xpc_int64_create(value); +} + +int64_t xpcInt64GetValue(void *object) +{ + return xpc_int64_get_value((xpc_object_t)object); +} + +// MARK: - UInt64 objects +// # xpc_object_t (XPC_TYPE_UINT64) +void *xpcUInt64Create(uint64_t value) +{ + return xpc_uint64_create(value); +} + +uint64_t xpcUInt64GetValue(void *object) +{ + return xpc_uint64_get_value((xpc_object_t)object); +} + +// MARK: - Array objects + +// # xpc_object_t (XPC_TYPE_ARRAY) + +void *xpcArrayCreate(void *const *object, size_t count) +{ + return xpc_array_create((xpc_object_t const *)object, count); +} + +void *xpcArrayGetValue(void *object, size_t index) +{ + return xpc_array_get_value((xpc_object_t)object, index); +} + +void xpcArraySetValue(void *object, size_t index, void *value) +{ + xpc_array_set_value((xpc_object_t)object, index, (xpc_object_t)value); +} + +void xpcArrayAppendValue(void *object, void *value) +{ + xpc_array_append_value((xpc_object_t)object, (xpc_object_t)value); +} + +size_t xpcArrayGetCount(void *object) +{ + return xpc_array_get_count((xpc_object_t)object); +} + +bool xpcArrayApply(void *object, uintptr_t cgo_applier) +{ + return xpc_array_apply((xpc_object_t)object, ^bool(size_t index, xpc_object_t _Nonnull value) { + return callArrayApplier(cgo_applier, index, wrapRawObject(value)); + }); +} + +int xpcArrayDupFd(void *object, size_t index) +{ + return xpc_array_dup_fd((xpc_object_t)object, index); +} + +void *xpcArrayGetArray(void *object, size_t index) +{ + return xpc_array_get_array((xpc_object_t)object, index); +} + +bool xpcArrayGetBool(void *object, size_t index) +{ + return xpc_array_get_bool((xpc_object_t)object, index); +} + +const void *xpcArrayGetData(void *object, size_t index, size_t *length) +{ + return xpc_array_get_data((xpc_object_t)object, index, length); +} + +int64_t xpcArrayGetDate(void *object, size_t index) +{ + return xpc_array_get_date((xpc_object_t)object, index); +} + +void *xpcArrayGetDictionary(void *object, size_t index) +{ + return xpc_array_get_dictionary((xpc_object_t)object, index); +} + +double xpcArrayGetDouble(void *object, size_t index) +{ + return xpc_array_get_double((xpc_object_t)object, index); +} + +int64_t xpcArrayGetInt64(void *object, size_t index) +{ + return xpc_array_get_int64((xpc_object_t)object, index); +} + +const char *xpcArrayGetString(void *object, size_t index) +{ + return xpc_array_get_string((xpc_object_t)object, index); +} + +uint64_t xpcArrayGetUInt64(void *object, size_t index) +{ + return xpc_array_get_uint64((xpc_object_t)object, index); +} + +const uint8_t *xpcArrayGetUUID(void *object, size_t index) +{ + return xpc_array_get_uuid((xpc_object_t)object, index); +} + +void xpcArraySetBool(void *object, size_t index, bool value) +{ + xpc_array_set_bool((xpc_object_t)object, index, value); +} + +void xpcArraySetData(void *object, size_t index, const void *bytes, size_t length) +{ + xpc_array_set_data((xpc_object_t)object, index, bytes, length); +} + +void xpcArraySetDate(void *object, size_t index, int64_t value) +{ + xpc_array_set_date((xpc_object_t)object, index, value); +} + +void xpcArraySetDouble(void *object, size_t index, double value) +{ + xpc_array_set_double((xpc_object_t)object, index, value); +} + +void xpcArraySetFd(void *object, size_t index, int fd) +{ + xpc_array_set_fd((xpc_object_t)object, index, fd); +} + +void xpcArraySetInt64(void *object, size_t index, int64_t value) +{ + xpc_array_set_int64((xpc_object_t)object, index, value); +} + +void xpcArraySetString(void *object, size_t index, const char *value) +{ + xpc_array_set_string((xpc_object_t)object, index, value); +} + +void xpcArraySetUInt64(void *object, size_t index, uint64_t value) +{ + xpc_array_set_uint64((xpc_object_t)object, index, value); +} + +void xpcArraySetUUID(void *object, size_t index, const uint8_t *uuid) +{ + xpc_array_set_uuid((xpc_object_t)object, index, uuid); +} + +// MARK: - Dictionary objects + +// xpc_object_t (XPC_TYPE_DICTIONARY) + +void *xpcDictionaryCreateEmpty(void) +{ + return xpc_dictionary_create_empty(); +} + +void *xpcDictionaryCreateReply(void *object) +{ + return xpc_dictionary_create_reply((xpc_object_t)object); +} + +void xpcDictionarySetValue(void *object, const char *key, void *value) +{ + xpc_dictionary_set_value((xpc_object_t)object, key, (xpc_object_t)value); +} + +size_t xpcDictionaryGetCount(void *object) +{ + return xpc_dictionary_get_count((xpc_object_t)object); +} + +void *xpcDictionaryGetValue(void *object, const char *key) +{ + return xpc_dictionary_get_value((xpc_object_t)object, key); +} + +bool xpcDictionaryApply(void *object, uintptr_t cgo_applier) +{ + return xpc_dictionary_apply((xpc_object_t)object, ^bool(const char *_Nonnull key, xpc_object_t _Nonnull value) { + return callDictionaryApplier(cgo_applier, key, wrapRawObject(value)); + }); +} + +int xpcDictionaryDupFd(void *object, const char *key) +{ + return xpc_dictionary_dup_fd((xpc_object_t)object, key); +} + +void *xpcDictionaryGetArray(void *object, const char *key) +{ + return xpc_dictionary_get_array((xpc_object_t)object, key); +} + +bool xpcDictionaryGetBool(void *object, const char *key) +{ + return xpc_dictionary_get_bool((xpc_object_t)object, key); +} + +const void *xpcDictionaryGetData(void *object, const char *key, size_t *length) +{ + return xpc_dictionary_get_data((xpc_object_t)object, key, length); +} + +int64_t xpcDictionaryGetDate(void *object, const char *key) +{ + return xpc_dictionary_get_date((xpc_object_t)object, key); +} + +void *xpcDictionaryGetDictionary(void *object, const char *key) +{ + return xpc_dictionary_get_dictionary((xpc_object_t)object, key); +} + +double xpcDictionaryGetDouble(void *object, const char *key) +{ + return xpc_dictionary_get_double((xpc_object_t)object, key); +} + +int64_t xpcDictionaryGetInt64(void *object, const char *key) +{ + return xpc_dictionary_get_int64((xpc_object_t)object, key); +} + +const char *xpcDictionaryGetString(void *object, const char *key) +{ + return xpc_dictionary_get_string((xpc_object_t)object, key); +} + +uint64_t xpcDictionaryGetUInt64(void *object, const char *key) +{ + return xpc_dictionary_get_uint64((xpc_object_t)object, key); +} + +const uint8_t *xpcDictionaryGetUUID(void *object, const char *key) +{ + return xpc_dictionary_get_uuid((xpc_object_t)object, key); +} + +void xpcDictionarySetBool(void *object, const char *key, bool value) +{ + xpc_dictionary_set_bool((xpc_object_t)object, key, value); +} + +void xpcDictionarySetData(void *object, const char *key, const void *bytes, size_t length) +{ + xpc_dictionary_set_data((xpc_object_t)object, key, bytes, length); +} + +void xpcDictionarySetDate(void *object, const char *key, int64_t value) +{ + xpc_dictionary_set_date((xpc_object_t)object, key, value); +} + +void xpcDictionarySetDouble(void *object, const char *key, double value) +{ + xpc_dictionary_set_double((xpc_object_t)object, key, value); +} + +void xpcDictionarySetFd(void *object, const char *key, int fd) +{ + xpc_dictionary_set_fd((xpc_object_t)object, key, fd); +} + +void xpcDictionarySetInt64(void *object, const char *key, int64_t value) +{ + xpc_dictionary_set_int64((xpc_object_t)object, key, value); +} + +void xpcDictionarySetString(void *object, const char *key, const char *value) +{ + xpc_dictionary_set_string((xpc_object_t)object, key, value); +} + +void xpcDictionarySetUInt64(void *object, const char *key, uint64_t value) +{ + xpc_dictionary_set_uint64((xpc_object_t)object, key, value); +} + +void xpcDictionarySetUUID(void *object, const char *key, const uint8_t *uuid) +{ + xpc_dictionary_set_uuid((xpc_object_t)object, key, uuid); +} + +// MARK: - String objects + +void *xpcStringCreate(const char *string) +{ + return xpc_string_create(string); +} + +size_t xpcStringGetLength(void *object) +{ + return xpc_string_get_length((xpc_object_t)object); +} + +const char *xpcStringGetStringPtr(void *object) +{ + return xpc_string_get_string_ptr((xpc_object_t)object); +} + +// MARK: - File descriptor objects + +void *xpcFdCreate(int fd) +{ + return xpc_fd_create(fd); +} + +int xpcFdDup(void *object) +{ + return xpc_fd_dup((xpc_object_t)object); +} + +// MARK: - Date objects + +void *xpcDateCreate(int64_t interval) +{ + return xpc_date_create(interval); +} + +void *xpcDateCreateFromCurrent() +{ + return xpc_date_create_from_current(); +} + +int64_t xpcDateGetValue(void *object) +{ + return xpc_date_get_value((xpc_object_t)object); +} + +// MARK: - UUID objects + +void *xpcUUIDCreate(const uuid_t uuid) +{ + return xpc_uuid_create(uuid); +} + +const uint8_t *xpcUUIDGetBytes(void *object) +{ + return xpc_uuid_get_bytes((xpc_object_t)object); +} + +// MARK: - Shared memory objects +// MARK: - Null objects + +void *xpcNullCreate() +{ + return xpc_null_create(); +} + +// MARK: - Object life cycle + +// xpc_object_t + +void *xpcRetain(void *object) +{ + return xpc_retain((xpc_object_t)object); +} + +void xpcRelease(void *object) +{ + xpc_release((xpc_object_t)object); +} + +// MARK: - xpc_peer_requirement_t (macOS 26+) + +void xpcListenerSetPeerRequirement(void *listener, void *peer_requirement) +{ +#ifdef INCLUDE_TARGET_OSX_26 + if (@available(macOS 26, *)) { + xpc_listener_set_peer_requirement((xpc_listener_t)listener, (xpc_peer_requirement_t)peer_requirement); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void *xpcPeerRequirementCreateLwcr(void *lwcr, void **error_out) +{ +#ifdef INCLUDE_TARGET_OSX_26 + if (@available(macOS 26, *)) { + return xpc_peer_requirement_create_lwcr((xpc_object_t)lwcr, (xpc_rich_error_t *)error_out); + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +bool xpcPeerRequirementMatchReceivedMessage(void *peer_requirement, void *message, void **error_out) +{ +#ifdef INCLUDE_TARGET_OSX_26 + if (@available(macOS 26, *)) { + return xpc_peer_requirement_match_received_message( + (xpc_peer_requirement_t)peer_requirement, + (xpc_object_t)message, + (xpc_rich_error_t *)error_out); + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void xpcSessionSetPeerRequirement(void *session, void *peer_requirement) +{ +#ifdef INCLUDE_TARGET_OSX_26 + if (@available(macOS 26, *)) { + xpc_session_set_peer_requirement((xpc_session_t)session, (xpc_peer_requirement_t)peer_requirement); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} \ No newline at end of file diff --git a/xpc/xpc_object.go b/xpc/xpc_object.go new file mode 100644 index 00000000..8dde1070 --- /dev/null +++ b/xpc/xpc_object.go @@ -0,0 +1,64 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +# include "xpc_darwin.h" +*/ +import "C" +import ( + "runtime" + "unsafe" + + "github.com/Code-Hex/vz/v3/internal/objc" +) + +type pointer = objc.Pointer + +// xpcObject wraps an XPC object ([xpc_object_t]). +// It is expected to be embedded in other structs as pointer to provide common functionality. +// +// [xpc_object_t]: https://developer.apple.com/documentation/xpc/xpc_object_t?language=objc +type xpcObject struct { + *pointer +} + +var _ Object = (*xpcObject)(nil) + +// newXpcObject creates a new [xpcObject] from an existing xpc_object_t. +func newXpcObject(ptr unsafe.Pointer) *xpcObject { + return &xpcObject{objc.NewPointer(ptr)} +} + +// String returns the description of the [xpcObject]. +// - https://developer.apple.com/documentation/xpc/xpc_copy_description(_:)?language=objc +func (x *xpcObject) String() string { + cs := C.xpcCopyDescription(objc.Ptr(x)) + defer C.free(unsafe.Pointer(cs)) + return C.GoString(cs) +} + +// retain retains the [xpcObject]. +// - https://developer.apple.com/documentation/xpc/xpc_retain?language=objc +func (x *xpcObject) retain() { + C.xpcRetain(objc.Ptr(x)) +} + +// releaseOnCleanup registers a cleanup function to release the [xpcObject] when cleaned up. +// - https://developer.apple.com/documentation/xpc/xpc_release?language=objc +func (x *xpcObject) releaseOnCleanup() { + runtime.AddCleanup(x, func(p unsafe.Pointer) { + C.xpcRelease(p) + }, objc.Ptr(x)) +} + +// Retain calls retain method on the given object and returns it. +func Retain[T interface{ retain() }](o T) T { + o.retain() + return o +} + +// ReleaseOnCleanup calls releaseOnCleanup method on the given object and returns it. +func ReleaseOnCleanup[T interface{ releaseOnCleanup() }](o T) T { + o.releaseOnCleanup() + return o +} From f2639fe0e8b8d4fc3e2dba599f3591fa963151ea Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Mon, 28 Sep 2026 14:17:35 +0900 Subject: [PATCH 2/2] fix(objc): allow nil in Ptr XPC dictionary deletion passes a nil value through objc.Ptr. Carry the guard from PR #205 commit 43fded6. --- internal/objc/objc.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/objc/objc.go b/internal/objc/objc.go index cb232eaf..12fcc48a 100644 --- a/internal/objc/objc.go +++ b/internal/objc/objc.go @@ -119,6 +119,9 @@ func Retain(o NSObject) { // Ptr returns unsafe.Pointer of the NSObject func Ptr(o NSObject) unsafe.Pointer { + if o == nil { + return nil + } return o.ptr() }