From f9ffe915f2e5be93cf1a89ec8d45e770a545a29a Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 26 Feb 2023 15:06:05 +1100 Subject: [PATCH] Add bindings hook --- v3/examples/binding/GreetService.go | 8 + v3/examples/binding/main.go | 6 +- v3/examples/binding/models/index.ts | 8 - v3/examples/binding/models/person.go | 5 - v3/examples/binding/services/GreetService.go | 23 -- v3/examples/binding/services/index.ts | 9 - v3/pkg/application/application.go | 9 +- v3/pkg/application/bindings.go | 242 +++++++++++++++++++ 8 files changed, 259 insertions(+), 51 deletions(-) create mode 100644 v3/examples/binding/GreetService.go delete mode 100755 v3/examples/binding/models/index.ts delete mode 100644 v3/examples/binding/models/person.go delete mode 100644 v3/examples/binding/services/GreetService.go delete mode 100755 v3/examples/binding/services/index.ts create mode 100644 v3/pkg/application/bindings.go diff --git a/v3/examples/binding/GreetService.go b/v3/examples/binding/GreetService.go new file mode 100644 index 000000000..1295acb65 --- /dev/null +++ b/v3/examples/binding/GreetService.go @@ -0,0 +1,8 @@ +package main + +type GreetService struct { +} + +func (*GreetService) Greet(name string) string { + return "Hello " + name +} diff --git a/v3/examples/binding/main.go b/v3/examples/binding/main.go index a91608d99..419e7b2d5 100644 --- a/v3/examples/binding/main.go +++ b/v3/examples/binding/main.go @@ -4,17 +4,13 @@ import ( _ "embed" "log" - "github.com/wailsapp/wails/v3/examples/binding/services" "github.com/wailsapp/wails/v3/pkg/application" ) -type localStruct struct{} - func main() { app := application.New(application.Options{ Bind: []interface{}{ - &localStruct{}, - &services.GreetService{}, + &GreetService{}, }, }) diff --git a/v3/examples/binding/models/index.ts b/v3/examples/binding/models/index.ts deleted file mode 100755 index 7e895a867..000000000 --- a/v3/examples/binding/models/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Code generated by tygo. DO NOT EDIT. - -////////// -// source: person.go - -export interface Person { - Name: string; -} diff --git a/v3/examples/binding/models/person.go b/v3/examples/binding/models/person.go deleted file mode 100644 index 0d0ab94db..000000000 --- a/v3/examples/binding/models/person.go +++ /dev/null @@ -1,5 +0,0 @@ -package models - -type Person struct { - Name string -} diff --git a/v3/examples/binding/services/GreetService.go b/v3/examples/binding/services/GreetService.go deleted file mode 100644 index 13a6f4571..000000000 --- a/v3/examples/binding/services/GreetService.go +++ /dev/null @@ -1,23 +0,0 @@ -package services - -import ( - "github.com/wailsapp/wails/v3/examples/binding/models" -) - -type GreetService struct { - SomeVariable int - lowercase string - Parent *models.Person -} - -func (*GreetService) Greet(name string) string { - return "Hello " + name -} - -func (g *GreetService) GetPerson() *models.Person { - return g.Parent -} - -func (g *GreetService) SetPerson(person *models.Person) { - g.Parent = person -} diff --git a/v3/examples/binding/services/index.ts b/v3/examples/binding/services/index.ts deleted file mode 100755 index 08ac2c0b4..000000000 --- a/v3/examples/binding/services/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Code generated by tygo. DO NOT EDIT. - -////////// -// source: GreetService.go - -export interface GreetService { - SomeVariable: number /* int */; - Parent?: any /* models.Person */; -} diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go index 831a1c363..bce262d81 100644 --- a/v3/pkg/application/application.go +++ b/v3/pkg/application/application.go @@ -112,7 +112,8 @@ type App struct { menuItemsLock sync.Mutex // Running - running bool + running bool + bindings *Bindings // platform app impl platformApp @@ -270,6 +271,12 @@ func (a *App) Run() error { } }() + var err error + a.bindings, err = NewBindings(a.options.Bind) + if err != nil { + return err + } + // run windows for _, window := range a.windows { go window.run() diff --git a/v3/pkg/application/bindings.go b/v3/pkg/application/bindings.go new file mode 100644 index 000000000..8e776cbfa --- /dev/null +++ b/v3/pkg/application/bindings.go @@ -0,0 +1,242 @@ +package application + +import ( + "fmt" + "reflect" + "runtime" + "strings" +) + +// Parameter defines a Go method parameter +type Parameter struct { + Name string `json:"name,omitempty"` + TypeName string `json:"type"` + reflectType reflect.Type +} + +func newParameter(Name string, Type reflect.Type) *Parameter { + return &Parameter{ + Name: Name, + TypeName: Type.String(), + reflectType: Type, + } +} + +// IsType returns true if the given +func (p *Parameter) IsType(typename string) bool { + return p.TypeName == typename +} + +// IsError returns true if the parameter type is an error +func (p *Parameter) IsError() bool { + return p.IsType("error") +} + +// BoundMethod defines all the data related to a Go method that is +// bound to the Wails application +type BoundMethod struct { + Name string `json:"name"` + Inputs []*Parameter `json:"inputs,omitempty"` + Outputs []*Parameter `json:"outputs,omitempty"` + Comments string `json:"comments,omitempty"` + Method reflect.Value `json:"-"` + PackageName string + StructName string + PackagePath string +} + +type Bindings struct { + boundMethods map[string]map[string]map[string]*BoundMethod +} + +func NewBindings(bindings []any) (*Bindings, error) { + b := &Bindings{ + boundMethods: make(map[string]map[string]map[string]*BoundMethod), + } + for _, binding := range bindings { + err := b.Add(binding) + if err != nil { + return nil, err + } + } + return b, nil +} + +// Add the given struct methods to the Bindings +func (b *Bindings) Add(structPtr interface{}) error { + + methods, err := b.getMethods(structPtr) + if err != nil { + return fmt.Errorf("cannot bind value to app: %s", err.Error()) + } + + for _, method := range methods { + packageName := method.PackageName + structName := method.StructName + methodName := method.Name + + // Add it as a regular method + if _, ok := b.boundMethods[packageName]; !ok { + b.boundMethods[packageName] = make(map[string]map[string]*BoundMethod) + } + if _, ok := b.boundMethods[packageName][structName]; !ok { + b.boundMethods[packageName][structName] = make(map[string]*BoundMethod) + } + b.boundMethods[packageName][structName][methodName] = method + //b.db.AddMethod(packageName, structName, methodName, method) + } + return nil +} + +func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) { + + // Create result placeholder + var result []*BoundMethod + + // Check type + if !isStructPtr(value) { + + if isStruct(value) { + name := reflect.ValueOf(value).Type().Name() + return nil, fmt.Errorf("%s is a struct, not a pointer to a struct", name) + } + + if isFunction(value) { + name := runtime.FuncForPC(reflect.ValueOf(value).Pointer()).Name() + return nil, fmt.Errorf("%s is a function, not a pointer to a struct. Wails v2 has deprecated the binding of functions. Please wrap your functions up in a struct and bind a pointer to that struct.", name) + } + + return nil, fmt.Errorf("not a pointer to a struct.") + } + + // Process Struct + structType := reflect.TypeOf(value) + structValue := reflect.ValueOf(value) + structTypeString := structType.String() + baseName := structTypeString[1:] + + // Process Methods + for i := 0; i < structType.NumMethod(); i++ { + methodDef := structType.Method(i) + methodName := methodDef.Name + packageName, structName, _ := strings.Cut(baseName, ".") + fullMethodName := baseName + "." + methodName + method := structValue.MethodByName(methodName) + + // Create new method + boundMethod := &BoundMethod{ + Name: fullMethodName, + PackageName: packageName, + StructName: structName, + Inputs: nil, + Outputs: nil, + Comments: "", + Method: method, + } + + // Iterate inputs + methodType := method.Type() + inputParamCount := methodType.NumIn() + var inputs []*Parameter + for inputIndex := 0; inputIndex < inputParamCount; inputIndex++ { + input := methodType.In(inputIndex) + thisParam := newParameter("", input) + // + //thisInput := input + // + //if thisInput.Kind() == reflect.Slice { + // thisInput = thisInput.Elem() + //} + // + //// Process struct pointer params + //if thisInput.Kind() == reflect.Ptr { + // if thisInput.Elem().Kind() == reflect.Struct { + // typ := thisInput.Elem() + // a := reflect.New(typ) + // s := reflect.Indirect(a).Interface() + // name := typ.Name() + // packageName := getPackageName(thisInput.String()) + // b.AddStructToGenerateTS(packageName, name, s) + // } + //} + // + //// Process struct params + //if thisInput.Kind() == reflect.Struct { + // a := reflect.New(thisInput) + // s := reflect.Indirect(a).Interface() + // name := thisInput.Name() + // packageName := getPackageName(thisInput.String()) + // b.AddStructToGenerateTS(packageName, name, s) + //} + + inputs = append(inputs, thisParam) + } + + boundMethod.Inputs = inputs + + outputParamCount := methodType.NumOut() + var outputs []*Parameter + for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ { + output := methodType.Out(outputIndex) + thisParam := newParameter("", output) + // + //thisOutput := output + // + //if thisOutput.Kind() == reflect.Slice { + // thisOutput = thisOutput.Elem() + //} + // + //// Process struct pointer params + //if thisOutput.Kind() == reflect.Ptr { + // if thisOutput.Elem().Kind() == reflect.Struct { + // typ := thisOutput.Elem() + // a := reflect.New(typ) + // s := reflect.Indirect(a).Interface() + // name := typ.Name() + // packageName := getPackageName(thisOutput.String()) + // } + //} + // + //// Process struct params + //if thisOutput.Kind() == reflect.Struct { + // a := reflect.New(thisOutput) + // s := reflect.Indirect(a).Interface() + // name := thisOutput.Name() + // packageName := getPackageName(thisOutput.String()) + // b.AddStructToGenerateTS(packageName, name, s) + //} + + outputs = append(outputs, thisParam) + } + boundMethod.Outputs = outputs + + // Save method in result + result = append(result, boundMethod) + + } + return result, nil +} + +// isStructPtr returns true if the value given is a +// pointer to a struct +func isStructPtr(value interface{}) bool { + return reflect.ValueOf(value).Kind() == reflect.Ptr && + reflect.ValueOf(value).Elem().Kind() == reflect.Struct +} + +// isFunction returns true if the given value is a function +func isFunction(value interface{}) bool { + return reflect.ValueOf(value).Kind() == reflect.Func +} + +// isStructPtr returns true if the value given is a struct +func isStruct(value interface{}) bool { + return reflect.ValueOf(value).Kind() == reflect.Struct +} + +func getPackageName(in string) string { + result := strings.Split(in, ".")[0] + result = strings.ReplaceAll(result, "[]", "") + result = strings.ReplaceAll(result, "*", "") + return result +}