From dcb7105fcabd17d6a7addef310ca766686919fa5 Mon Sep 17 00:00:00 2001 From: Greg Date: Fri, 10 May 2019 02:00:56 -0400 Subject: [PATCH] Complete the Delegates wrapper. Disambiguate polymorphic methods. Bluetooth example is now a functioning heart rate monitor. Additional bug fixes in Go types (instances where *Id needs to be used instead of NSObject). --- examples/app/main.go | 7 ++- examples/bluetooth/main.go | 94 ++++++++++++++++++++++++++++++++-- examples/bluetooth/nswrap.yaml | 8 ++- wrap/main.go | 56 ++++++++++++-------- 4 files changed, 136 insertions(+), 29 deletions(-) diff --git a/examples/app/main.go b/examples/app/main.go index bc84a66..55bda4f 100644 --- a/examples/app/main.go +++ b/examples/app/main.go @@ -16,6 +16,10 @@ func shouldTerminate(s *ns.NSApplication) ns.NSApplicationTerminateReply { return ns.NSTerminateNow } +func shouldTerminateAfterLastWindowClosed(s *ns.NSApplication) ns.BOOL { + return 1 +} + func willTerminate(n *ns.NSNotification) { fmt.Println("Go: will terminate") } @@ -34,6 +38,7 @@ func app() { del := ns.AppDelegateAlloc() del.ApplicationDidFinishLaunchingCallback(didFinishLaunching) del.ApplicationShouldTerminateCallback(shouldTerminate) + del.ApplicationShouldTerminateAfterLastWindowClosedCallback(shouldTerminateAfterLastWindowClosed) del.ApplicationWillTerminateCallback(willTerminate) del.ApplicationDidBecomeActiveCallback(didBecomeActive) a.SetDelegate(del) @@ -41,7 +46,7 @@ func app() { //Set up an NSWindow w := ns.NSWindowAlloc().InitWithContentRect( ns.NSMakeRect(200,200,600,600), - ns.NSWindowStyleMaskTitled, + ns.NSWindowStyleMaskTitled | ns.NSWindowStyleMaskClosable, ns.NSBackingStoreBuffered, 0, nil, diff --git a/examples/bluetooth/main.go b/examples/bluetooth/main.go index 2d2fa03..e02adb4 100644 --- a/examples/bluetooth/main.go +++ b/examples/bluetooth/main.go @@ -3,7 +3,9 @@ package main import "C" import ( + "encoding/binary" "fmt" + "unsafe" "gitlab.wow.st/gmp/nswrap/examples/bluetooth/ns" ) @@ -22,23 +24,109 @@ func updateState(c *ns.CBCentralManager) { fmt.Printf(" powered off\n") case ns.CBManagerStatePoweredOn: fmt.Printf(" powered on\n") + cm.ScanForPeripheralsWithServices(ns.NSArrayWithObjects(hrm_uuid),nil) + } +} + +func discoverPeripheral(c *ns.CBCentralManager, p *ns.CBPeripheral, d *ns.NSDictionary, rssi *ns.NSNumber) { + fmt.Printf("Did discover peripheral\n") + c.StopScan() + peripheral = p + peripheral.Retain() + c.ConnectPeripheral(p,nil) +} + +func connectPeripheral(c *ns.CBCentralManager, p *ns.CBPeripheral) { + fmt.Printf("Did connect peripheral\n") + p.SetDelegate(cd) + p.DiscoverServices(nil) +} + +func discoverServices(p *ns.CBPeripheral, e *ns.NSError) { + fmt.Printf("Did discover services\n") + p.Services().ObjectEnumerator().ForIn(func(o *ns.Id) bool { + serv := o.CBService() + switch { + case serv.UUID().IsEqualTo(hrm_uuid): + fmt.Printf("--heart rate monitor service\n") + p.DiscoverCharacteristics(nil,serv) + case serv.UUID().IsEqualTo(ns.CBUUIDWithGoString("180A")): + fmt.Printf("--device information service\n") + p.DiscoverCharacteristics(nil,serv) + } + return true + }) +} + +func hr(d *ns.NSData) int { + var x []byte + if l := int(d.Length()); l < 4 { + return 0 + } else { + bs := d.Bytes() + x = make([]byte,l) + for i := 0; i < l; i++ { + x[i] = *(*byte)(unsafe.Pointer(uintptr(bs) + uintptr(i))) + } + } + var hr int + flags := x[0] + if flags & 0x80 != 0 { // uint16 format + hr = int(binary.BigEndian.Uint16(x[1:2])) + } else { + hr = int(x[1]) + } + return hr +} + +func discoverCharacteristics(p *ns.CBPeripheral, s *ns.CBService, e *ns.NSError) { + fmt.Printf("Did discover characteristics\n") + fmt.Printf("----%s\n",s.UUID().UUIDString().UTF8String()) + if s.UUID().IsEqualTo(hrm_uuid) { + s.Characteristics().ObjectEnumerator().ForIn(func(o *ns.Id) bool { + chr := o.CBCharacteristic() + fmt.Printf("------%s\n",chr.UUID().UUIDString().UTF8String()) + if chr.UUID().IsEqualTo(hrv_uuid) { + p.SetNotifyValue(1,chr) + v := chr.Value() + fmt.Println(hr(v)) + } + return true + }) + } +} + +func updateValue(p *ns.CBPeripheral, chr *ns.CBCharacteristic, e *ns.NSError) { + if chr.UUID().IsEqualTo(hrv_uuid) { + v := chr.Value() + fmt.Printf("Heart rate: %d\n",hr(v)) } } var ( + hrm_uuid *ns.CBUUID + hrv_uuid *ns.CBUUID + cd *ns.BleDelegate cm *ns.CBCentralManager + peripheral *ns.CBPeripheral ) func main() { queue := ns.DispatchQueueCreate(ns.CharWithGoString("st.wow.gitlab.ble"),nil) - cd := ns.BleDelegateAlloc() + cd = ns.BleDelegateAlloc() cd.CentralManagerDidUpdateStateCallback(updateState) + cd.CentralManagerDidDiscoverPeripheralCallback(discoverPeripheral) + cd.CentralManagerDidConnectPeripheralCallback(connectPeripheral) + cd.PeripheralDidDiscoverServicesCallback(discoverServices) + cd.PeripheralDidDiscoverCharacteristicsForServiceCallback(discoverCharacteristics) + cd.PeripheralDidUpdateValueForCharacteristicCallback(updateValue) + + hrm_uuid = ns.CBUUIDWithGoString("180D") + hrv_uuid = ns.CBUUIDWithGoString("2A37") cm = ns.CBCentralManagerAlloc() cm.InitWithDelegate(cd,queue,nil) - uuid := ns.CBUUIDWithGoString("180d") - _ = uuid select { } } diff --git a/examples/bluetooth/nswrap.yaml b/examples/bluetooth/nswrap.yaml index 0741725..4031d70 100644 --- a/examples/bluetooth/nswrap.yaml +++ b/examples/bluetooth/nswrap.yaml @@ -3,26 +3,24 @@ inputfiles: - /System/Library/Frameworks/CoreBluetooth.framework/Headers/CoreBluetooth.h classes: - - ble_delegate + - NSObject + - NSData - CBManager - CBCentralManager - CBPeripheralManager - CBPeripheral - CBCentral - CBService + - CBAttribute - CBCharacteristic - CBDescriptor - CBError - CBUUID - CBAdvertisementData - - CBATTRequest - NSArray - NSMutableArray - NSDictionary - NSEnumerator - - NSSet - - NSDate - - NSTimeZone - NSString functions: [ NSMakeRange, dispatch_queue_create ] diff --git a/wrap/main.go b/wrap/main.go index 894cb3c..c98056e 100644 --- a/wrap/main.go +++ b/wrap/main.go @@ -26,6 +26,7 @@ type Wrapper struct { Protocols map[string]*Protocol cgoFlags strings.Builder // put cGo directives here + cImports strings.Builder // put c imports and sysimports here cCode strings.Builder // put cGo code here goTypes strings.Builder // put Go type declarations here goConst strings.Builder // put Go constants (from C enums) here @@ -63,20 +64,20 @@ func (w *Wrapper) Frameworks(ss []string) { return } for _,s := range ss { - w.cgoFlags.WriteString(fmt.Sprintf("#import <%s/%s.h>\n",s,s)) + w.cImports.WriteString(fmt.Sprintf("#import <%s/%s.h>\n",s,s)) } w.cgoFlags.WriteString("#cgo LDFLAGS: -framework " + strings.Join(ss," -framework ")) } func (w *Wrapper) Import(ss []string) { for _,s := range ss { - w.cgoFlags.WriteString("\n#import \"" + s + "\"\n") + w.cImports.WriteString("\n#import \"" + s + "\"\n") } } func (w *Wrapper) SysImport(ss []string) { for _,s := range ss { - w.cgoFlags.WriteString("\n#import <" + s + ">\n") + w.cImports.WriteString("\n#import <" + s + ">\n") } } @@ -116,7 +117,7 @@ type Enum struct { type Protocol struct { Name, GoName string Methods map[string]*Method - Polymorphic map[string]bool + Polymorphic map[string]int } //isVoid() returns true if the method has no return value. @@ -291,7 +292,7 @@ func (w *Wrapper) AddProtocol(n *ast.ObjCProtocolDecl) { Name: n.Name, GoName: types.NewTypeFromString(n.Name,n.Name).GoType(), Methods: map[string]*Method{}, - Polymorphic: map[string]bool{}, + Polymorphic: map[string]int{}, } } for _,c := range n.Children() { @@ -307,10 +308,19 @@ func (w *Wrapper) AddProtocol(n *ast.ObjCProtocolDecl) { var avail bool m.Parameters, avail = w.GetParms(x,p.Name) if avail { - if p.Methods[m.Name] != nil { - p.Polymorphic[m.Name] = true + pname := strings.Title(m.Name) + if x := p.Polymorphic[m.Name]; x != 0 { + p.Polymorphic[m.Name] = x + 1 + pname = pname + strings.Title(m.Parameters[1].Pname) + if m2 := p.Methods[m.Name]; m2 != nil { + pname2 := strings.Title(m.Name) + strings.Title(m2.Parameters[1].Pname) + p.Methods[pname2] = m2 + delete(p.Methods,m.Name) + } + } else { + p.Polymorphic[m.Name] = 1 } - p.Methods[m.Name] = m + p.Methods[pname] = m } } } @@ -862,9 +872,6 @@ const %s %s= C.%s } } -//FIXME: need to disambiguate polymorphic method names. Something like: -//func Disambiguate([]*Method) []*Method {} ... -//Can allow user to decide on a disambiguation strategy //NOTE: The delegate wrapper does not support variadic callback functions. func (w *Wrapper) ProcessDelegate(dname string, ps []string) { //To create (per delegate): @@ -884,6 +891,7 @@ func (w *Wrapper) ProcessDelegate(dname string, ps []string) { //set up array of methods for this delegate methods := []*Method{} + gnames := []string{} // go names for methods for _,pname := range ps { proto := w.Protocols[pname] if proto == nil { @@ -891,18 +899,16 @@ func (w *Wrapper) ProcessDelegate(dname string, ps []string) { os.Exit(-1) } fmt.Printf(" proto %s\n",pname) - for _,m := range proto.Methods { - if proto.Polymorphic[m.Name] { - //FIXME: skip polymorphic methods for now - fmt.Printf(" Skipping polymorphic method '%s'\n",m.Name) + for gname,m := range proto.Methods { + if m.Type.IsFunction() || m.Type.IsFunctionPtr() || m.hasFunctionParam() { continue } methods = append(methods,m) + gnames = append(gnames,gname) } } methprotos := make([]string,len(methods)) // objc method prototypes - gnames := make([]string,len(methods)) // go names for methods gname := strings.Title(dname) // go name for this Delegate vnames := make([][]string,len(methods)) // objc variable names gtypes := make([][]string,len(methods)) // go parameter types for each method @@ -952,7 +958,6 @@ func (w *Wrapper) ProcessDelegate(dname string, ps []string) { } methprotos[i] = fmt.Sprintf( `- (%s)%s%s;`,m.Type.Node.Ctype(),m.Name,parms) - gnames[i] = strings.Title(m.Name) if x := m.Type.GoType(); x == "Void" { grtypes[i] = "" } else { @@ -1061,8 +1066,12 @@ func (d *%s) %sCallback(f func(%s)%s) { garglist := []string{} for j := 1; j < len(vnames[i]); j++ { earglist = append(earglist,vnames[i][j] + " " + getypes[i][j]) + gt2 := gtypes[i][j-1] + if types.IsGoInterface(gt2) { + gt2 = "*Id" + } garglist = append(garglist,fmt.Sprintf( -`(%s)(%s)`,gtypes[i][j-1],vnames[i][j])) +`(%s)(%s)`,gt2,vnames[i][j])) } retdecl := "" retname := "" @@ -1090,7 +1099,6 @@ func %s%s(%s)%s { } `,gname,gnames[i],gname,gnames[i],strings.Join(earglist,", "),crtype,retdecl,gname,gnames[i],retname,retn,strings.Join(garglist,", "),retnparen)) } - //DEBUG w.cCode.WriteString(cprotos.String()) w.cCode.WriteString(ccode.String()) w.goTypes.WriteString(gotypes.String()) @@ -1098,6 +1106,7 @@ func %s%s(%s)%s { w.goExports.WriteString(goexports.String()) } + func (w *Wrapper) Wrap(toproc []string) { if w.Package == "" { w.Package = "ns" } err := os.MkdirAll(w.Package,0755) @@ -1125,6 +1134,11 @@ func (w *Wrapper) Wrap(toproc []string) { if i == nil { continue } + w.processType(types.NewTypeFromString(i.GoName,"")) + gname := i.GoName + if types.IsGoInterface(i.GoName) { + gname = "Id" + } fmt.Printf("Interface %s: %d properties, %d methods\n", i.Name, len(i.Properties), len(i.Methods)) @@ -1132,7 +1146,7 @@ func (w *Wrapper) Wrap(toproc []string) { func %sAlloc() *%s { return (*%s)(unsafe.Pointer(C.%sAlloc())) } -`,i.GoName,i.GoName,i.GoName,i.Name)) +`,i.GoName,gname,gname,i.Name)) if i.Name != "NSAutoreleasePool" { w.cCode.WriteString(fmt.Sprintf(` @@ -1183,6 +1197,8 @@ void* of.WriteString(w.cgoFlags.String() + "\n") ef.WriteString(w.cgoFlags.String() + "\n") + of.WriteString(w.cImports.String() + "\n") + ef.WriteString(w.cImports.String() + "\n") of.WriteString(w.cCode.String()) of.WriteString(`