aboutsummaryrefslogtreecommitdiffstats
path: root/corefoundation.go
diff options
context:
space:
mode:
Diffstat (limited to 'corefoundation.go')
-rw-r--r--corefoundation.go343
1 files changed, 343 insertions, 0 deletions
diff --git a/corefoundation.go b/corefoundation.go
new file mode 100644
index 0000000..e571ecb
--- /dev/null
+++ b/corefoundation.go
@@ -0,0 +1,343 @@
+// Copyright 2016 Keybase, Inc. All rights reserved. Use of
+// this source code is governed by the included BSD license.
+
+// +build darwin
+
+package notifier
+
+/*
+#cgo LDFLAGS: -framework CoreFoundation
+
+#include <CoreFoundation/CoreFoundation.h>
+*/
+import "C"
+import (
+ "errors"
+ "fmt"
+ "math"
+ "reflect"
+ "unicode/utf8"
+ "unsafe"
+)
+
+// Release releases a TypeRef
+func Release(ref C.CFTypeRef) {
+ if ref != nil {
+ C.CFRelease(ref)
+ }
+}
+
+// BytesToCFData will return a CFDataRef and if non-nil, must be released with
+// Release(ref).
+func BytesToCFData(b []byte) (C.CFDataRef, error) {
+ if uint64(len(b)) > math.MaxUint32 {
+ return nil, fmt.Errorf("Data is too large")
+ }
+ var p *C.UInt8
+ if len(b) > 0 {
+ p = (*C.UInt8)(&b[0])
+ }
+ cfData := C.CFDataCreate(nil, p, C.CFIndex(len(b)))
+ if cfData == nil {
+ return nil, fmt.Errorf("CFDataCreate failed")
+ }
+ return cfData, nil
+}
+
+// CFDataToBytes converts CFData to bytes.
+func CFDataToBytes(cfData C.CFDataRef) ([]byte, error) {
+ return C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(cfData)), C.int(C.CFDataGetLength(cfData))), nil
+}
+
+// MapToCFDictionary will return a CFDictionaryRef and if non-nil, must be
+// released with Release(ref).
+func MapToCFDictionary(m map[C.CFTypeRef]C.CFTypeRef) (C.CFDictionaryRef, error) {
+ var keys, values []unsafe.Pointer
+ for key, value := range m {
+ keys = append(keys, unsafe.Pointer(key))
+ values = append(values, unsafe.Pointer(value))
+ }
+ numValues := len(values)
+ var keysPointer, valuesPointer *unsafe.Pointer
+ if numValues > 0 {
+ keysPointer = &keys[0]
+ valuesPointer = &values[0]
+ }
+ cfDict := C.CFDictionaryCreate(nil, keysPointer, valuesPointer, C.CFIndex(numValues), &C.kCFTypeDictionaryKeyCallBacks, &C.kCFTypeDictionaryValueCallBacks)
+ if cfDict == nil {
+ return nil, fmt.Errorf("CFDictionaryCreate failed")
+ }
+ return cfDict, nil
+}
+
+// CFDictionaryToMap converts CFDictionaryRef to a map.
+func CFDictionaryToMap(cfDict C.CFDictionaryRef) (m map[C.CFTypeRef]C.CFTypeRef) {
+ count := C.CFDictionaryGetCount(cfDict)
+ if count > 0 {
+ keys := make([]C.CFTypeRef, count)
+ values := make([]C.CFTypeRef, count)
+ C.CFDictionaryGetKeysAndValues(cfDict, (*unsafe.Pointer)(&keys[0]), (*unsafe.Pointer)(&values[0]))
+ m = make(map[C.CFTypeRef]C.CFTypeRef, count)
+ for i := C.CFIndex(0); i < count; i++ {
+ m[keys[i]] = values[i]
+ }
+ }
+ return
+}
+
+// StringToCFString will return a CFStringRef and if non-nil, must be released with
+// Release(ref).
+func StringToCFString(s string) (C.CFStringRef, error) {
+ if !utf8.ValidString(s) {
+ return nil, errors.New("Invalid UTF-8 string")
+ }
+ if uint64(len(s)) > math.MaxUint32 {
+ return nil, errors.New("String is too large")
+ }
+
+ bytes := []byte(s)
+ var p *C.UInt8
+ if len(bytes) > 0 {
+ p = (*C.UInt8)(&bytes[0])
+ }
+ return C.CFStringCreateWithBytes(nil, p, C.CFIndex(len(s)), C.kCFStringEncodingUTF8, C.false), nil
+}
+
+// CFStringToString converts a CFStringRef to a string.
+func CFStringToString(s C.CFStringRef) string {
+ p := C.CFStringGetCStringPtr(s, C.kCFStringEncodingUTF8)
+ if p != nil {
+ return C.GoString(p)
+ }
+ length := C.CFStringGetLength(s)
+ if length == 0 {
+ return ""
+ }
+ maxBufLen := C.CFStringGetMaximumSizeForEncoding(length, C.kCFStringEncodingUTF8)
+ if maxBufLen == 0 {
+ return ""
+ }
+ buf := make([]byte, maxBufLen)
+ var usedBufLen C.CFIndex
+ _ = C.CFStringGetBytes(s, C.CFRange{0, length}, C.kCFStringEncodingUTF8, C.UInt8(0), C.false, (*C.UInt8)(&buf[0]), maxBufLen, &usedBufLen)
+ return string(buf[:usedBufLen])
+}
+
+// ArrayToCFArray will return a CFArrayRef and if non-nil, must be released with
+// Release(ref).
+func ArrayToCFArray(a []C.CFTypeRef) C.CFArrayRef {
+ var values []unsafe.Pointer
+ for _, value := range a {
+ values = append(values, unsafe.Pointer(value))
+ }
+ numValues := len(values)
+ var valuesPointer *unsafe.Pointer
+ if numValues > 0 {
+ valuesPointer = &values[0]
+ }
+ return C.CFArrayCreate(nil, valuesPointer, C.CFIndex(numValues), &C.kCFTypeArrayCallBacks)
+}
+
+// CFArrayToArray converts a CFArrayRef to an array of CFTypes.
+func CFArrayToArray(cfArray C.CFArrayRef) (a []C.CFTypeRef) {
+ count := C.CFArrayGetCount(cfArray)
+ if count > 0 {
+ a = make([]C.CFTypeRef, count)
+ C.CFArrayGetValues(cfArray, C.CFRange{0, count}, (*unsafe.Pointer)(&a[0]))
+ }
+ return
+}
+
+// Convertable knows how to convert an instance to a CFTypeRef.
+type Convertable interface {
+ Convert() (C.CFTypeRef, error)
+}
+
+// ConvertMapToCFDictionary converts a map to a CFDictionary and if non-nil,
+// must be released with Release(ref).
+func ConvertMapToCFDictionary(attr map[string]interface{}) (C.CFDictionaryRef, error) {
+ m := make(map[C.CFTypeRef]C.CFTypeRef)
+ for key, i := range attr {
+ var valueRef C.CFTypeRef
+ switch i.(type) {
+ default:
+ return nil, fmt.Errorf("Unsupported value type: %v", reflect.TypeOf(i))
+ case C.CFTypeRef:
+ valueRef = i.(C.CFTypeRef)
+ case bool:
+ if i == true {
+ valueRef = C.CFTypeRef(C.kCFBooleanTrue)
+ } else {
+ valueRef = C.CFTypeRef(C.kCFBooleanFalse)
+ }
+ case []byte:
+ bytesRef, err := BytesToCFData(i.([]byte))
+ if err != nil {
+ return nil, err
+ }
+ valueRef = C.CFTypeRef(bytesRef)
+ defer Release(valueRef)
+ case string:
+ stringRef, err := StringToCFString(i.(string))
+ if err != nil {
+ return nil, err
+ }
+ valueRef = C.CFTypeRef(stringRef)
+ defer Release(valueRef)
+ case Convertable:
+ convertedRef, err := (i.(Convertable)).Convert()
+ if err != nil {
+ return nil, err
+ }
+ valueRef = C.CFTypeRef(convertedRef)
+ defer Release(valueRef)
+ }
+ keyRef, err := StringToCFString(key)
+ if err != nil {
+ return nil, err
+ }
+ m[C.CFTypeRef(keyRef)] = valueRef
+ }
+
+ cfDict, err := MapToCFDictionary(m)
+ if err != nil {
+ return nil, err
+ }
+ return cfDict, nil
+}
+
+// CFTypeDescription returns type string for CFTypeRef.
+func CFTypeDescription(ref C.CFTypeRef) string {
+ typeID := C.CFGetTypeID(ref)
+ typeDesc := C.CFCopyTypeIDDescription(typeID)
+ defer Release(C.CFTypeRef(typeDesc))
+ return CFStringToString(typeDesc)
+}
+
+// Convert converts a CFTypeRef to a go instance.
+func Convert(ref C.CFTypeRef) (interface{}, error) {
+ typeID := C.CFGetTypeID(ref)
+ if typeID == C.CFStringGetTypeID() {
+ return CFStringToString(C.CFStringRef(ref)), nil
+ } else if typeID == C.CFDictionaryGetTypeID() {
+ return ConvertCFDictionary(C.CFDictionaryRef(ref))
+ } else if typeID == C.CFArrayGetTypeID() {
+ arr := CFArrayToArray(C.CFArrayRef(ref))
+ results := make([]interface{}, 0, len(arr))
+ for _, ref := range arr {
+ v, err := Convert(ref)
+ if err != nil {
+ return nil, err
+ }
+ results = append(results, v)
+ return results, nil
+ }
+ } else if typeID == C.CFDataGetTypeID() {
+ b, err := CFDataToBytes(C.CFDataRef(ref))
+ if err != nil {
+ return nil, err
+ }
+ return b, nil
+ } else if typeID == C.CFNumberGetTypeID() {
+ return CFNumberToInterface(C.CFNumberRef(ref)), nil
+ } else if typeID == C.CFBooleanGetTypeID() {
+ if C.CFBooleanGetValue(C.CFBooleanRef(ref)) != 0 {
+ return true, nil
+ }
+ return false, nil
+ }
+
+ return nil, fmt.Errorf("Invalid type: %s", CFTypeDescription(ref))
+}
+
+// ConvertCFDictionary converts a CFDictionary to map (deep).
+func ConvertCFDictionary(d C.CFDictionaryRef) (map[interface{}]interface{}, error) {
+ m := CFDictionaryToMap(C.CFDictionaryRef(d))
+ result := make(map[interface{}]interface{})
+
+ for k, v := range m {
+ gk, err := Convert(k)
+ if err != nil {
+ return nil, err
+ }
+ gv, err := Convert(v)
+ if err != nil {
+ return nil, err
+ }
+ result[gk] = gv
+ }
+ return result, nil
+}
+
+// CFNumberToInterface converts the CFNumberRef to the most appropriate numeric
+// type.
+// This code is from github.com/kballard/go-osx-plist.
+func CFNumberToInterface(cfNumber C.CFNumberRef) interface{} {
+ typ := C.CFNumberGetType(cfNumber)
+ switch typ {
+ case C.kCFNumberSInt8Type:
+ var sint C.SInt8
+ C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint))
+ return int8(sint)
+ case C.kCFNumberSInt16Type:
+ var sint C.SInt16
+ C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint))
+ return int16(sint)
+ case C.kCFNumberSInt32Type:
+ var sint C.SInt32
+ C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint))
+ return int32(sint)
+ case C.kCFNumberSInt64Type:
+ var sint C.SInt64
+ C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint))
+ return int64(sint)
+ case C.kCFNumberFloat32Type:
+ var float C.Float32
+ C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float))
+ return float32(float)
+ case C.kCFNumberFloat64Type:
+ var float C.Float64
+ C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float))
+ return float64(float)
+ case C.kCFNumberCharType:
+ var char C.char
+ C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&char))
+ return byte(char)
+ case C.kCFNumberShortType:
+ var short C.short
+ C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&short))
+ return int16(short)
+ case C.kCFNumberIntType:
+ var i C.int
+ C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&i))
+ return int32(i)
+ case C.kCFNumberLongType:
+ var long C.long
+ C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&long))
+ return int(long)
+ case C.kCFNumberLongLongType:
+ // This is the only type that may actually overflow us
+ var longlong C.longlong
+ C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&longlong))
+ return int64(longlong)
+ case C.kCFNumberFloatType:
+ var float C.float
+ C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float))
+ return float32(float)
+ case C.kCFNumberDoubleType:
+ var double C.double
+ C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&double))
+ return float64(double)
+ case C.kCFNumberCFIndexType:
+ // CFIndex is a long
+ var index C.CFIndex
+ C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&index))
+ return int(index)
+ case C.kCFNumberNSIntegerType:
+ // We don't have a definition of NSInteger, but we know it's either an int or a long
+ var nsInt C.long
+ C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&nsInt))
+ return int(nsInt)
+ }
+ panic("Unknown CFNumber type")
+}