From 64b143bf71cc6cd71b02ca46ef713295e650666d Mon Sep 17 00:00:00 2001 From: Drakirus Date: Fri, 14 Sep 2018 22:24:39 +0200 Subject: [PATCH 1/8] execute the binary file present in different directory --- example/simpleDemo/main.go | 14 ++++++++++++-- flutter/flutter.go | 11 +++++++---- gutter.go | 6 ++++-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/example/simpleDemo/main.go b/example/simpleDemo/main.go index 2a2b2f32..fd67e903 100644 --- a/example/simpleDemo/main.go +++ b/example/simpleDemo/main.go @@ -5,6 +5,7 @@ import ( _ "image/png" "log" "os" + "path/filepath" gutter "github.com/Drakirus/go-flutter-desktop-embedder" @@ -16,8 +17,13 @@ func main() { err error ) + dir, err := filepath.Abs(filepath.Dir(os.Args[0])) + if err != nil { + log.Fatal(err) + } + options := []gutter.Option{ - gutter.OptionAssetPath("flutter_project/demo/build/flutter_assets"), + gutter.OptionAssetPath(dir + "/flutter_project/demo/build/flutter_assets"), gutter.OptionICUDataPath("/opt/flutter/bin/cache/artifacts/engine/linux-x64/icudtl.dat"), gutter.OptionWindowInitializer(setIcon), } @@ -29,7 +35,11 @@ func main() { } func setIcon(window *glfw.Window) error { - imgFile, err := os.Open("assets/icon.png") + dir, err := filepath.Abs(filepath.Dir(os.Args[0])) + if err != nil { + return err + } + imgFile, err := os.Open(dir + "/assets/icon.png") if err != nil { return err } diff --git a/flutter/flutter.go b/flutter/flutter.go index b8d3c2ff..4c3e45f0 100644 --- a/flutter/flutter.go +++ b/flutter/flutter.go @@ -22,6 +22,9 @@ const ( KInvalidArguments Result = C.kInvalidArguments ) +// CharExportedType wrap the C char type +type CharExportedType C.char + // EngineOpenGL corresponds to the C.FlutterEngine with his associated callback's method. type EngineOpenGL struct { // Flutter Engine. @@ -38,8 +41,8 @@ type EngineOpenGL struct { FPlatfromMessage func(message PlatformMessage, window unsafe.Pointer) bool // Engine arguments - AssetsPath string - IcuDataPath string + AssetsPath *CharExportedType + IcuDataPath *CharExportedType } // Run launches the Flutter Engine in a background thread. @@ -48,10 +51,10 @@ func (flu *EngineOpenGL) Run(window uintptr) Result { globalFlutterOpenGL = *flu args := C.FlutterProjectArgs{ - assets_path: C.CString(flu.AssetsPath), + assets_path: (*C.char)(flu.AssetsPath), main_path: C.CString(""), packages_path: C.CString(""), - icu_data_path: C.CString(flu.IcuDataPath), + icu_data_path: (*C.char)(flu.IcuDataPath), } args.struct_size = C.size_t(unsafe.Sizeof(args)) diff --git a/gutter.go b/gutter.go index acc876fe..c164d094 100644 --- a/gutter.go +++ b/gutter.go @@ -6,6 +6,8 @@ import ( "time" "unsafe" + "C" + "github.com/Drakirus/go-flutter-desktop-embedder/flutter" "github.com/go-gl/glfw/v3.2/glfw" ) @@ -204,8 +206,8 @@ func runFlutter(window *glfw.Window, assetsPath string, icuDataPath string) *flu flutterOGL := flutter.EngineOpenGL{ // Engine arguments - AssetsPath: assetsPath, - IcuDataPath: icuDataPath, + AssetsPath: (*flutter.CharExportedType)(C.CString(assetsPath)), + IcuDataPath: (*flutter.CharExportedType)(C.CString(icuDataPath)), // Render callbacks FMakeCurrent: func(v unsafe.Pointer) bool { w := glfw.GoWindow(v) From f068c28743553ed15a810460106c0adb1d554df2 Mon Sep 17 00:00:00 2001 From: Drakirus Date: Sat, 15 Sep 2018 12:09:45 +0200 Subject: [PATCH 2/8] Print PlatformMessage --- .../simpleDemo/flutter_project/demo/lib/main.dart | 15 ++++++++++++++- flutter/flutter_proxy.go | 9 +++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/example/simpleDemo/flutter_project/demo/lib/main.dart b/example/simpleDemo/flutter_project/demo/lib/main.dart index 562e6210..16686a9b 100644 --- a/example/simpleDemo/flutter_project/demo/lib/main.dart +++ b/example/simpleDemo/flutter_project/demo/lib/main.dart @@ -3,6 +3,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' show debugDefaultTargetPlatformOverride; +import 'package:flutter/services.dart'; +import 'dart:async'; + void main() { // Desktop platforms aren't a valid platform. debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; @@ -30,8 +33,18 @@ class MyHomePage extends StatefulWidget { final String title; + static const MethodChannel _channel = const MethodChannel('plugin_demo'); + static Future GetVersion() async { + var res = await _channel.invokeMethod('getPlatformVersion'); + print(res); + + } + @override - _MyHomePageState createState() => new _MyHomePageState(); + _MyHomePageState createState() { + GetVersion(); + return new _MyHomePageState(); + } } class _MyHomePageState extends State { diff --git a/flutter/flutter_proxy.go b/flutter/flutter_proxy.go index 3179a600..7634bbf4 100644 --- a/flutter/flutter_proxy.go +++ b/flutter/flutter_proxy.go @@ -22,6 +22,10 @@ func proxy_on_platform_message(message *C.FlutterPlatformMessage, userPointer un if message.message != nil { str := C.GoString(C.converter(message.message, message.message_size)) + fmt.Println("Channel:" + C.GoString(message.channel)) + fmt.Println(str) + fmt.Println() + FlutterPlatformMessage := PlatformMessage{} messageContent := Message{} @@ -33,6 +37,11 @@ func proxy_on_platform_message(message *C.FlutterPlatformMessage, userPointer un fmt.Println("==================== NIL") } + C.FlutterEngineSendPlatformMessageResponse( + globalFlutterOpenGL.Engine, + message.response_handle, + (*C.uint8_t)(unsafe.Pointer(C.CString("[{ \"args\" : \"test\", \"method\" : \"plugin_demo.getPlatformVersion\"}]"))), 65) + return C.bool(globalFlutterOpenGL.FPlatfromMessage(FlutterPlatformMessage, userPointer)) } return C.bool(false) From 683f8435b202eaa1553f5ee53d1b9283d3d37999 Mon Sep 17 00:00:00 2001 From: Drakirus Date: Sun, 28 Oct 2018 21:52:23 +0100 Subject: [PATCH 3/8] WIP: pluggin support --- README.md | 52 ++++++++++-- example/simpleDemo/.build/.gitignore | 3 +- .../flutter_project/demo/lib/main.dart | 2 +- .../flutter_project/demo/pubspec.lock | 6 +- example/simpleDemo/main.go | 19 +++-- example/stocks/.build/.gitignore | 3 +- example/stocks/main.go | 6 +- flutter/build.go | 7 +- flutter/flutter.go | 26 +++--- flutter/flutter_helper.c | 52 ++++++++---- flutter/flutter_proxy.go | 47 ++++++++--- gutter.go | 84 ++++++++++++++----- 12 files changed, 227 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 96644d21..c0f0c813 100755 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ for desktop This project doesn't compete with [this](https://github.com/google/flutter-desktop-embedding) awesome one. The purpose of this project is to support the -[Flutter](https://github.com/flutter/flutter) framework on Windows, macOS, and +[Flutter](https://github.com/flutter/flutter) framework on Windows, MacOS, and Linux using a **SINGLE** code base. [**GLFW**](https://github.com/go-gl/glfw) fits the job because it @@ -58,11 +58,11 @@ cd ../.. # Download the share library (CORRESPONDING to the Flutter's version shown above) wget https://storage.googleapis.com/flutter_infra/flutter/af42b6dc95bd9f719e43c4e9f29a00640f0f0bba/linux-x64/linux-x64-embedder -O .build/temp.zip -# Move the share library -unzip .build/temp.zip -x flutter_embedder.h && mv libflutter_engine.so flutter/library/linux/ +# Extract the share library +unzip .build/temp.zip -x flutter_embedder.h # REQUIRED: When using `go build` or `go run main.go`, the go library need to know where to look for the share library -export CGO_LDFLAGS="-L${PWD}/flutter/library/linux" +export CGO_LDFLAGS="-L${PWD}" # If you `go build`, the share library must stay in the same path, relative to the go binary @@ -118,6 +118,48 @@ go run main.go +
+ :package: :apple: MacOS +

From binaries

+Check out the Release page for prebuilt versions. + +

From source

+ +Go read first: [go-gl/glfw](https://github.com/go-gl/glfw/) + + +```bash +# Clone +git clone https://github.com/Drakirus/go-flutter-desktop-embedder.git +cd go-flutter-desktop-embedder + +# Build the flutter simpleDemo project +cd example/simpleDemo/ +cd flutter_project/demo/ +flutter build bundle +cd ../.. + +# Download the share library (CORRESPONDING to the Flutter's version shown above) +wget https://storage.googleapis.com/flutter_infra/flutter/af42b6dc95bd9f719e43c4e9f29a00640f0f0bba/darwin-x64/FlutterEmbedder.framework.zip -O .build/temp.zip + +# Move the share library +unzip .build/temp.zip -d .build && unzip .build/FlutterEmbedder.framework.zip -d .build/FlutterEmbedder.framework +mv .build/FlutterEmbedder.framework . + +# REQUIRED: When using `go build` or `go run main.go`, the go library need to know where to look for the share library +export CGO_LDFLAGS="-F${PWD} -Wl,-rpath,@executable_path" + +# If you `go build`, the share library must stay in the same path, relative to the go binary + +# Get the libraries +go get -u -v github.com/Drakirus/go-flutter-desktop-embedder + +# Make sure the path in "main.go" to the `icudtl.dat` is correct. +# Build the example project +go build +``` + +
## Flutter Demos Projects @@ -128,7 +170,7 @@ The examples are available [here](./example/) - [x] Linux :penguin: - [x] Windows :checkered_flag: -- [ ] MacOS :apple: +- [x] MacOS :apple: - [x] Text input - [ ] Plugins - [x] Importable go library diff --git a/example/simpleDemo/.build/.gitignore b/example/simpleDemo/.build/.gitignore index 5d751cbb..d6b7ef32 100644 --- a/example/simpleDemo/.build/.gitignore +++ b/example/simpleDemo/.build/.gitignore @@ -1 +1,2 @@ -temp.zip +* +!.gitignore diff --git a/example/simpleDemo/flutter_project/demo/lib/main.dart b/example/simpleDemo/flutter_project/demo/lib/main.dart index 16686a9b..528f45a1 100644 --- a/example/simpleDemo/flutter_project/demo/lib/main.dart +++ b/example/simpleDemo/flutter_project/demo/lib/main.dart @@ -33,7 +33,7 @@ class MyHomePage extends StatefulWidget { final String title; - static const MethodChannel _channel = const MethodChannel('plugin_demo'); + static MethodChannel _channel = new MethodChannel('plugin_demo', new JSONMethodCodec()); static Future GetVersion() async { var res = await _channel.invokeMethod('getPlatformVersion'); print(res); diff --git a/example/simpleDemo/flutter_project/demo/pubspec.lock b/example/simpleDemo/flutter_project/demo/pubspec.lock index f0b44480..498026c7 100644 --- a/example/simpleDemo/flutter_project/demo/pubspec.lock +++ b/example/simpleDemo/flutter_project/demo/pubspec.lock @@ -7,7 +7,7 @@ packages: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.32.5" + version: "0.32.4" args: dependency: transitive description: @@ -87,7 +87,7 @@ packages: name: front_end url: "https://pub.dartlang.org" source: hosted - version: "0.1.4+1" + version: "0.1.4" glob: dependency: transitive description: @@ -150,7 +150,7 @@ packages: name: kernel url: "https://pub.dartlang.org" source: hosted - version: "0.3.4+1" + version: "0.3.4" logging: dependency: transitive description: diff --git a/example/simpleDemo/main.go b/example/simpleDemo/main.go index fd67e903..a759e1b5 100644 --- a/example/simpleDemo/main.go +++ b/example/simpleDemo/main.go @@ -5,7 +5,8 @@ import ( _ "image/png" "log" "os" - "path/filepath" + "path" + "runtime" gutter "github.com/Drakirus/go-flutter-desktop-embedder" @@ -17,15 +18,17 @@ func main() { err error ) - dir, err := filepath.Abs(filepath.Dir(os.Args[0])) - if err != nil { - log.Fatal(err) - } + _, currentFilePath, _, _ := runtime.Caller(0) + dir := path.Dir(currentFilePath) options := []gutter.Option{ gutter.OptionAssetPath(dir + "/flutter_project/demo/build/flutter_assets"), gutter.OptionICUDataPath("/opt/flutter/bin/cache/artifacts/engine/linux-x64/icudtl.dat"), gutter.OptionWindowInitializer(setIcon), + gutter.OptionWindowDimension(800, 600), + gutter.OptionWindowInitializer(setIcon), + gutter.OptionPixelRatio(1.2), + gutter.OptionVMArguments([]string{"--dart-non-checked-mode", "--observatory-port=50300"}), } if err = gutter.Run(options...); err != nil { @@ -35,10 +38,8 @@ func main() { } func setIcon(window *glfw.Window) error { - dir, err := filepath.Abs(filepath.Dir(os.Args[0])) - if err != nil { - return err - } + _, currentFilePath, _, _ := runtime.Caller(0) + dir := path.Dir(currentFilePath) imgFile, err := os.Open(dir + "/assets/icon.png") if err != nil { return err diff --git a/example/stocks/.build/.gitignore b/example/stocks/.build/.gitignore index 5d751cbb..d6b7ef32 100644 --- a/example/stocks/.build/.gitignore +++ b/example/stocks/.build/.gitignore @@ -1 +1,2 @@ -temp.zip +* +!.gitignore diff --git a/example/stocks/main.go b/example/stocks/main.go index 4d83eaf6..3cbd5590 100644 --- a/example/stocks/main.go +++ b/example/stocks/main.go @@ -18,8 +18,12 @@ func main() { options := []gutter.Option{ gutter.OptionAssetPath("flutter_project/stocks/build/flutter_assets"), - gutter.OptionICUDataPath("/opt/flutter/bin/cache/artifacts/engine/linux-x64/icudtl.dat"), + gutter.OptionICUDataPath("/opt/flutter/bin/cache/artifacts/engine/linux-x64/icudtl.dat"), // Linux (arch) + // gutter.OptionICUDataPath("./FlutterEmbedder.framework/Resources/icudtl.dat"), // OSX + gutter.OptionWindowDimension(800, 600), gutter.OptionWindowInitializer(setIcon), + gutter.OptionPixelRatio(1.2), + gutter.OptionVMArguments([]string{"--dart-non-checked-mode", "--observatory-port=50300"}), } if err = gutter.Run(options...); err != nil { diff --git a/flutter/build.go b/flutter/build.go index 780756f3..4180647f 100644 --- a/flutter/build.go +++ b/flutter/build.go @@ -4,13 +4,18 @@ package flutter // Linux Build Tags // ---------------- #cgo linux CFLAGS: -I${SRCDIR}/library -#cgo linux LDFLAGS: -lflutter_engine -Wl,-rpath,$ORIGIN/flutter/library/linux +#cgo linux LDFLAGS: -lflutter_engine -Wl,-rpath,$ORIGIN // Windows Build Tags // ---------------- #cgo windows CFLAGS: -I${SRCDIR}/library #cgo windows LDFLAGS: -lflutter_engine +// Darwin Build Tags +// ---------------- +#cgo darwin CFLAGS: -I${SRCDIR}/library +#cgo darwin LDFLAGS: -framework FlutterEmbedder + */ import "C" diff --git a/flutter/flutter.go b/flutter/flutter.go index 4c3e45f0..785e8915 100644 --- a/flutter/flutter.go +++ b/flutter/flutter.go @@ -1,7 +1,10 @@ package flutter // #include "flutter_embedder.h" -// FlutterResult runFlutter(uintptr_t window, FlutterEngine *engine, FlutterProjectArgs * Args); +// FlutterResult runFlutter(uintptr_t window, FlutterEngine *engine, FlutterProjectArgs * Args, +// const char *const * vmArgs, int nVmAgrs, int engineID); +// char** makeCharArray(int size); +// void setArrayString(char **a, char *s, int n); import "C" import ( "encoding/json" @@ -9,7 +12,7 @@ import ( ) // the current FlutterEngine running (associated with his callback) -var globalFlutterOpenGL EngineOpenGL +var flutterEngines []EngineOpenGL // Result corresponds to the C.enum retuned by the shared flutter library // whenever we call it. @@ -22,9 +25,6 @@ const ( KInvalidArguments Result = C.kInvalidArguments ) -// CharExportedType wrap the C char type -type CharExportedType C.char - // EngineOpenGL corresponds to the C.FlutterEngine with his associated callback's method. type EngineOpenGL struct { // Flutter Engine. @@ -41,14 +41,15 @@ type EngineOpenGL struct { FPlatfromMessage func(message PlatformMessage, window unsafe.Pointer) bool // Engine arguments - AssetsPath *CharExportedType - IcuDataPath *CharExportedType + PixelRatio float64 + AssetsPath unsafe.Pointer + IcuDataPath unsafe.Pointer } // Run launches the Flutter Engine in a background thread. -func (flu *EngineOpenGL) Run(window uintptr) Result { +func (flu *EngineOpenGL) Run(window uintptr, vmArgs []string) Result { - globalFlutterOpenGL = *flu + flutterEngines = append(flutterEngines, *flu) args := C.FlutterProjectArgs{ assets_path: (*C.char)(flu.AssetsPath), @@ -59,7 +60,12 @@ func (flu *EngineOpenGL) Run(window uintptr) Result { args.struct_size = C.size_t(unsafe.Sizeof(args)) - res := C.runFlutter(C.uintptr_t(window), &flu.Engine, &args) + cVMArgs := C.makeCharArray(C.int(len(vmArgs))) + for i, s := range vmArgs { + C.setArrayString(cVMArgs, C.CString(s), C.int(i)) + } + + res := C.runFlutter(C.uintptr_t(window), &flu.Engine, &args, cVMArgs, C.int(len(vmArgs)), C.int(len(flutterEngines))-1) if flu.Engine == nil { return KInvalidArguments } diff --git a/flutter/flutter_helper.c b/flutter/flutter_helper.c index bfbcc412..db480654 100644 --- a/flutter/flutter_helper.c +++ b/flutter/flutter_helper.c @@ -1,7 +1,17 @@ - #include "library/flutter_embedder.h" +#include "library/flutter_embedder.h" +#include + +// Structure used to keep a pointer to a function and the first parameter of that function. +// Used to call the correct flutter. +// typedef struct _closure { + // int engineID; + // void* (** func_proxy)(void*); +// } closure; // C proxies def +bool proxy_make_current_test(int engineID, void *v); +BoolCallback proxy_make_current_func(int engineID); bool proxy_make_current(void *v); bool proxy_clear_current(void *v); bool proxy_present(void *v); @@ -12,29 +22,39 @@ bool proxy_on_platform_message(FlutterPlatformMessage *message, void *window); // C helper -FlutterResult runFlutter(uintptr_t window, FlutterEngine *engine, FlutterProjectArgs * Args){ +FlutterResult runFlutter(uintptr_t window, FlutterEngine *engine, FlutterProjectArgs * Args, + const char *const * vmArgs, int nVmAgrs, int engineID) { FlutterRendererConfig config = {}; config.type = kOpenGL; config.open_gl.struct_size = sizeof(FlutterOpenGLRendererConfig); - config.open_gl.make_current = proxy_make_current; - config.open_gl.clear_current = proxy_clear_current; - config.open_gl.present = proxy_present; - config.open_gl.fbo_callback = proxy_fbo_callback; - config.open_gl.make_resource_current = proxy_make_resource_current; - - const char *args_arr[] = { - "", - "--dart-non-checked-mode", - NULL, - }; - - Args->command_line_argc = 2; - Args->command_line_argv = args_arr; + // bool closure_make_current(void *v) { + // printf("%i ---\n", engineID); + // return proxy_make_current_test(engineID, v); + // } + + config.open_gl.make_current = proxy_make_current_func(engineID); + // config.open_gl.make_current = closure_make_current; + // config.open_gl.make_current = proxy_make_current; + config.open_gl.clear_current = proxy_clear_current; + config.open_gl.present = proxy_present; + config.open_gl.fbo_callback = proxy_fbo_callback; + config.open_gl.make_resource_current = proxy_make_resource_current; + + Args->command_line_argc = nVmAgrs; + Args->command_line_argv = vmArgs; Args->platform_message_callback = (FlutterPlatformMessageCallback)proxy_on_platform_message; return FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, Args, (void*)window, engine); } +char** makeCharArray(int size) { + return calloc(sizeof(char*), size); +} + +void setArrayString(char **a, char *s, int n) { + a[n] = s; +} + diff --git a/flutter/flutter_proxy.go b/flutter/flutter_proxy.go index 7634bbf4..f185e74e 100644 --- a/flutter/flutter_proxy.go +++ b/flutter/flutter_proxy.go @@ -7,6 +7,17 @@ static char* converter(uint8_t *str, size_t size){ str[size] = '\0'; // Prevent overFlow return (char *)str; } + + +#include +static int am_i_null(void* pointer) { + if (NULL == pointer) { + printf("NULL"); + } +} + +bool proxy_make_current(void *v); + */ import "C" import ( @@ -22,10 +33,6 @@ func proxy_on_platform_message(message *C.FlutterPlatformMessage, userPointer un if message.message != nil { str := C.GoString(C.converter(message.message, message.message_size)) - fmt.Println("Channel:" + C.GoString(message.channel)) - fmt.Println(str) - fmt.Println() - FlutterPlatformMessage := PlatformMessage{} messageContent := Message{} @@ -33,16 +40,20 @@ func proxy_on_platform_message(message *C.FlutterPlatformMessage, userPointer un FlutterPlatformMessage.Message = messageContent FlutterPlatformMessage.Channel = C.GoString(message.channel) + // fmt.Println(unsafe.Pointer(message.response_handle)) + // fmt.Println(str) if message.response_handle == nil { fmt.Println("==================== NIL") } + response := "[ 1 ]" + C.FlutterEngineSendPlatformMessageResponse( - globalFlutterOpenGL.Engine, + flutterEngines[0].Engine, message.response_handle, - (*C.uint8_t)(unsafe.Pointer(C.CString("[{ \"args\" : \"test\", \"method\" : \"plugin_demo.getPlatformVersion\"}]"))), 65) + (*C.uint8_t)(unsafe.Pointer(C.CString(response))), (C.ulong)(len(response))) - return C.bool(globalFlutterOpenGL.FPlatfromMessage(FlutterPlatformMessage, userPointer)) + return C.bool(flutterEngines[0].FPlatfromMessage(FlutterPlatformMessage, userPointer)) } return C.bool(false) @@ -50,25 +61,37 @@ func proxy_on_platform_message(message *C.FlutterPlatformMessage, userPointer un //export proxy_make_current func proxy_make_current(v unsafe.Pointer) C.bool { - return C.bool(globalFlutterOpenGL.FMakeCurrent(v)) + return C.bool(flutterEngines[0].FMakeCurrent(v)) +} + +//export proxy_make_current_test +func proxy_make_current_test(engineID int, v unsafe.Pointer) C.bool { + return C.bool(flutterEngines[engineID].FMakeCurrent(v)) +} + +//export proxy_make_current_func +func proxy_make_current_func(engineID int) unsafe.Pointer { + + print(engineID) + return C.proxy_make_current } //export proxy_clear_current func proxy_clear_current(v unsafe.Pointer) C.bool { - return C.bool(globalFlutterOpenGL.FClearCurrent(v)) + return C.bool(flutterEngines[0].FClearCurrent(v)) } //export proxy_present func proxy_present(v unsafe.Pointer) C.bool { - return C.bool(globalFlutterOpenGL.FPresent(v)) + return C.bool(flutterEngines[0].FPresent(v)) } //export proxy_fbo_callback func proxy_fbo_callback(v unsafe.Pointer) C.uint32_t { - return C.uint32_t(globalFlutterOpenGL.FFboCallback(v)) + return C.uint32_t(flutterEngines[0].FFboCallback(v)) } //export proxy_make_resource_current func proxy_make_resource_current(v unsafe.Pointer) C.bool { - return C.bool(globalFlutterOpenGL.FMakeResourceCurrent(v)) + return C.bool(flutterEngines[0].FMakeResourceCurrent(v)) } diff --git a/gutter.go b/gutter.go index c164d094..02f5a8b2 100644 --- a/gutter.go +++ b/gutter.go @@ -29,6 +29,22 @@ func OptionICUDataPath(p string) Option { } } +// OptionVMArguments specify the arguments to the Dart VM. +func OptionVMArguments(a []string) Option { + return func(c *config) { + // First should be argument is argv[0] + c.VMArguments = append([]string{""}, a...) + } +} + +// OptionWindowDimension specify the startup's dimention of the window. +func OptionWindowDimension(x int, y int) Option { + return func(c *config) { + c.WindowDimension.x = x + c.WindowDimension.y = y + } +} + // OptionWindowInitializer allow initializing the window. func OptionWindowInitializer(ini func(*glfw.Window) error) Option { return func(c *config) { @@ -36,10 +52,24 @@ func OptionWindowInitializer(ini func(*glfw.Window) error) Option { } } +// OptionPixelRatio specify the scale factor for the physical screen. +func OptionPixelRatio(ratio float64) Option { + return func(c *config) { + c.PixelRatio = ratio + } +} + type config struct { - AssetPath string - ICUDataPath string - WindowInitializer func(*glfw.Window) error + WindowDimension struct { + x int + y int + } + AssetPath string + ICUDataPath string + WindowInitializer func(*glfw.Window) error + PixelRatio float64 + VMArguments []string + PlatformMessageReceivers []func(message flutter.PlatformMessage) bool } func (t config) merge(options ...Option) config { @@ -59,12 +89,18 @@ func Run(options ...Option) (err error) { ) c = c.merge(options...) + c.PlatformMessageReceivers = append(c.PlatformMessageReceivers, func(message flutter.PlatformMessage) bool { + print("==============", message.Channel) + return true + }) + if err = glfw.Init(); err != nil { return err } defer glfw.Terminate() - if window, err = glfw.CreateWindow(800, 600, "Loading..", nil, nil); err != nil { + window, err = glfw.CreateWindow(c.WindowDimension.x, c.WindowDimension.y, "Loading..", nil, nil) + if err != nil { return err } defer window.Destroy() @@ -73,7 +109,7 @@ func Run(options ...Option) (err error) { return err } - engine := runFlutter(window, c.AssetPath, c.ICUDataPath) + engine := runFlutter(window, c) defer engine.Shutdown() @@ -91,11 +127,13 @@ func glfwCursorPositionCallbackAtPhase( window *glfw.Window, phase flutter.PointerPhase, x float64, y float64, ) { - + winWidth, _ := window.GetSize() + frameBuffWidth, _ := window.GetFramebufferSize() + contentScale := float64(frameBuffWidth / winWidth) event := flutter.PointerEvent{ Phase: phase, - X: x, - Y: y, + X: x * contentScale, + Y: y * contentScale, Timestamp: time.Now().UnixNano() / int64(time.Millisecond), } @@ -185,13 +223,12 @@ func glfwKeyCallback(w *glfw.Window, key glfw.Key, scancode int, action glfw.Act } func glfwWindowSizeCallback(window *glfw.Window, width int, height int) { + flutterOGL := *(*flutter.EngineOpenGL)(window.GetUserPointer()) event := flutter.WindowMetricsEvent{ Width: width, Height: height, - PixelRatio: 1.2, + PixelRatio: flutterOGL.PixelRatio, } - - flutterOGL := *(*flutter.EngineOpenGL)(window.GetUserPointer()) flutterOGL.EngineSendWindowMetricsEvent(event) } @@ -202,12 +239,12 @@ func glfwCharCallback(w *glfw.Window, char rune) { } // Flutter Engine -func runFlutter(window *glfw.Window, assetsPath string, icuDataPath string) *flutter.EngineOpenGL { +func runFlutter(window *glfw.Window, c config) *flutter.EngineOpenGL { flutterOGL := flutter.EngineOpenGL{ // Engine arguments - AssetsPath: (*flutter.CharExportedType)(C.CString(assetsPath)), - IcuDataPath: (*flutter.CharExportedType)(C.CString(icuDataPath)), + AssetsPath: unsafe.Pointer(C.CString(c.AssetPath)), + IcuDataPath: unsafe.Pointer(C.CString(c.ICUDataPath)), // Render callbacks FMakeCurrent: func(v unsafe.Pointer) bool { w := glfw.GoWindow(v) @@ -230,7 +267,14 @@ func runFlutter(window *glfw.Window, assetsPath string, icuDataPath string) *flu return false }, // Messaging (TextInput) - FPlatfromMessage: onPlatformMessage, + FPlatfromMessage: func(platMessage flutter.PlatformMessage, window unsafe.Pointer) bool { + // for _, receivers := range c.PlatformMessageReceivers { + // receivers(platMessage) + // } + + return onPlatformMessage(platMessage, window) + }, + PixelRatio: c.PixelRatio, } state.notifyState = func() { @@ -238,7 +282,7 @@ func runFlutter(window *glfw.Window, assetsPath string, icuDataPath string) *flu updateEditingState(window) } - result := flutterOGL.Run(window.GLFWWindow()) + result := flutterOGL.Run(window.GLFWWindow(), c.VMArguments) if result != flutter.KSuccess { window.Destroy() @@ -247,18 +291,18 @@ func runFlutter(window *glfw.Window, assetsPath string, icuDataPath string) *flu window.SetUserPointer(unsafe.Pointer(&flutterOGL)) - width, height := window.GetSize() + width, height := window.GetFramebufferSize() glfwWindowSizeCallback(window, width, height) window.SetKeyCallback(glfwKeyCallback) - window.SetSizeCallback(glfwWindowSizeCallback) + window.SetFramebufferSizeCallback(glfwWindowSizeCallback) window.SetMouseButtonCallback(glfwMouseButtonCallback) window.SetCharCallback(glfwCharCallback) return &flutterOGL } -// Message from the Flutter Engine - +// Dispatch the message from the Flutter Engine, +// to all of his Receivers func onPlatformMessage(platMessage flutter.PlatformMessage, window unsafe.Pointer) bool { windows := glfw.GoWindow(window) From e85b1925e8af192b448fb2c752157c358acbf108 Mon Sep 17 00:00:00 2001 From: Drakirus Date: Sun, 28 Oct 2018 23:23:07 +0100 Subject: [PATCH 4/8] wip --- flutter/flutter.go | 16 +++++++++++----- gutter.go | 17 ++++++++++++----- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/flutter/flutter.go b/flutter/flutter.go index 785e8915..4e88a862 100644 --- a/flutter/flutter.go +++ b/flutter/flutter.go @@ -12,7 +12,12 @@ import ( ) // the current FlutterEngine running (associated with his callback) -var flutterEngines []EngineOpenGL +var flutterEngines []*EngineOpenGL + +// SelectEngine return a EngineOpenGL from an index +func SelectEngine(index int) *EngineOpenGL { + return flutterEngines[index] +} // Result corresponds to the C.enum retuned by the shared flutter library // whenever we call it. @@ -47,9 +52,10 @@ type EngineOpenGL struct { } // Run launches the Flutter Engine in a background thread. -func (flu *EngineOpenGL) Run(window uintptr, vmArgs []string) Result { +// return a flutter Result and the index of the EngineOpenGL in the global List +func (flu *EngineOpenGL) Run(window uintptr, vmArgs []string) (Result, C.int) { - flutterEngines = append(flutterEngines, *flu) + flutterEngines = append(flutterEngines, flu) args := C.FlutterProjectArgs{ assets_path: (*C.char)(flu.AssetsPath), @@ -67,10 +73,10 @@ func (flu *EngineOpenGL) Run(window uintptr, vmArgs []string) Result { res := C.runFlutter(C.uintptr_t(window), &flu.Engine, &args, cVMArgs, C.int(len(vmArgs)), C.int(len(flutterEngines))-1) if flu.Engine == nil { - return KInvalidArguments + return KInvalidArguments, 0 } - return (Result)(res) + return (Result)(res), C.int(len(flutterEngines) - 1) } // Shutdown stops the Flutter engine. diff --git a/gutter.go b/gutter.go index 02f5a8b2..74bec2ee 100644 --- a/gutter.go +++ b/gutter.go @@ -137,7 +137,9 @@ func glfwCursorPositionCallbackAtPhase( Timestamp: time.Now().UnixNano() / int64(time.Millisecond), } - flutterOGL := *(*flutter.EngineOpenGL)(window.GetUserPointer()) + // flutterOGL := *(*flutter.EngineOpenGL)(window.GetUserPointer()) + flutterOGL := *flutter.SelectEngine(0) + flutterOGL.EngineSendPointerEvent(event) } @@ -223,7 +225,10 @@ func glfwKeyCallback(w *glfw.Window, key glfw.Key, scancode int, action glfw.Act } func glfwWindowSizeCallback(window *glfw.Window, width int, height int) { - flutterOGL := *(*flutter.EngineOpenGL)(window.GetUserPointer()) + + // flutterOGL := *(*flutter.EngineOpenGL)(window.GetUserPointer()) + flutterOGL := *flutter.SelectEngine(0) + event := flutter.WindowMetricsEvent{ Width: width, Height: height, @@ -282,14 +287,15 @@ func runFlutter(window *glfw.Window, c config) *flutter.EngineOpenGL { updateEditingState(window) } - result := flutterOGL.Run(window.GLFWWindow(), c.VMArguments) + // result, engineIndex := flutterOGL.Run(window.GLFWWindow(), c.VMArguments) + result, _ := flutterOGL.Run(window.GLFWWindow(), c.VMArguments) if result != flutter.KSuccess { window.Destroy() panic("Couldn't launch the FlutterEngine") } - window.SetUserPointer(unsafe.Pointer(&flutterOGL)) + // window.SetUserPointer(unsafe.Pointer(&engineIndex)) width, height := window.GetFramebufferSize() glfwWindowSizeCallback(window, width, height) @@ -368,6 +374,7 @@ func updateEditingState(window *glfw.Window) { Message: message, } - flutterOGL := *(*flutter.EngineOpenGL)(window.GetUserPointer()) + // flutterOGL := *(*flutter.EngineOpenGL)(window.GetUserPointer()) + flutterOGL := *flutter.SelectEngine(0) flutterOGL.EngineSendPlatformMessage(mess) } From a98a6ca92445e0f4d4285eb832dbe15a262eb7a6 Mon Sep 17 00:00:00 2001 From: Drakirus Date: Mon, 29 Oct 2018 00:51:38 +0100 Subject: [PATCH 5/8] WIP plugin --- flutter/flutter_proxy_glfw.go | 74 +++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 flutter/flutter_proxy_glfw.go diff --git a/flutter/flutter_proxy_glfw.go b/flutter/flutter_proxy_glfw.go new file mode 100644 index 00000000..8ebc9102 --- /dev/null +++ b/flutter/flutter_proxy_glfw.go @@ -0,0 +1,74 @@ +package flutter + +/* +#include "flutter_embedder.h" + +static char* converter(uint8_t *str, size_t size){ + str[size] = '\0'; // Prevent overFlow + return (char *)str; +} +*/ +import "C" +import ( + "encoding/json" + "unsafe" + + "github.com/go-gl/glfw/v3.2/glfw" +) + +// C proxies + +//export proxy_on_platform_message +func proxy_on_platform_message(message *C.FlutterPlatformMessage, userPointer unsafe.Pointer) C.bool { + if message.message != nil { + str := C.GoString(C.converter(message.message, message.message_size)) + + FlutterPlatformMessage := PlatformMessage{} + + messageContent := Message{} + json.Unmarshal([]byte(str), &messageContent) + + FlutterPlatformMessage.Message = messageContent + FlutterPlatformMessage.Channel = C.GoString(message.channel) + FlutterPlatformMessage.ResponseHandle = message.response_handle + + return C.bool(flutterEngines[0].FPlatfromMessage(FlutterPlatformMessage, userPointer)) + } + return C.bool(false) + +} + +//export proxy_make_current +func proxy_make_current(v unsafe.Pointer) C.bool { + w := glfw.GoWindow(v) + index := *(*C.int)(w.GetUserPointer()) + return C.bool(flutterEngines[index].FMakeCurrent(v)) +} + +//export proxy_clear_current +func proxy_clear_current(v unsafe.Pointer) C.bool { + w := glfw.GoWindow(v) + index := *(*C.int)(w.GetUserPointer()) + return C.bool(flutterEngines[index].FClearCurrent(v)) +} + +//export proxy_present +func proxy_present(v unsafe.Pointer) C.bool { + w := glfw.GoWindow(v) + index := *(*C.int)(w.GetUserPointer()) + return C.bool(flutterEngines[index].FPresent(v)) +} + +//export proxy_fbo_callback +func proxy_fbo_callback(v unsafe.Pointer) C.uint32_t { + w := glfw.GoWindow(v) + index := *(*C.int)(w.GetUserPointer()) + return C.uint32_t(flutterEngines[index].FFboCallback(v)) +} + +//export proxy_make_resource_current +func proxy_make_resource_current(v unsafe.Pointer) C.bool { + w := glfw.GoWindow(v) + index := *(*C.int)(w.GetUserPointer()) + return C.bool(flutterEngines[index].FMakeResourceCurrent(v)) +} From d5deb5c1c1fe7da67843470cb80bc092544c028f Mon Sep 17 00:00:00 2001 From: Drakirus Date: Mon, 29 Oct 2018 23:08:35 +0100 Subject: [PATCH 6/8] Draft Plugin management. Changes have been made to the Engine's callback system. Rather than storing in the "C" glfw.Window a user pointer to the "Golang" `flutter.EngineOpenGL`, the "C" user pointer is now storing an `int`. This improves the code segmentation, and remove the famous > panic: runtime error: cgo argument has Go pointer to Go pointer The user pointer `int` is then later used to associate the C callback to the correct flutter.EngineOpenGL. The Embedder can open one Flutter app at the time, work needs to be done to allow 'multiplexing' The user can register a Plugin using the `gutter.OptionAddPluginReceiver` function, I have not look too much at `MessageChannel`, `MethodChannel`, Codec stuff. **The whole method channel/plugin API doesn't match the existing Flutter API** For now, only `string` messages can be sent/received by the plugin system, please use the `JSONMethodCodec` on the Dart side. --- .../flutter/library/linux/.gitignore | 2 - .../flutter_project/demo/lib/main.dart | 25 ++- .../flutter_project/demo/pubspec.lock | 18 +- example/simpleDemo/main.go | 22 +++ flutter/flutter.go | 46 +++-- flutter/flutter_helper.c | 13 +- flutter/flutter_proxy.go | 97 ---------- flutter/message.go | 35 ---- gutter.go | 170 ++++-------------- option.go | 90 ++++++++++ system_channels.go | 114 ++++++++++++ 11 files changed, 318 insertions(+), 314 deletions(-) delete mode 100644 example/simpleDemo/flutter/library/linux/.gitignore delete mode 100644 flutter/flutter_proxy.go create mode 100644 option.go create mode 100644 system_channels.go diff --git a/example/simpleDemo/flutter/library/linux/.gitignore b/example/simpleDemo/flutter/library/linux/.gitignore deleted file mode 100644 index d6b7ef32..00000000 --- a/example/simpleDemo/flutter/library/linux/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/example/simpleDemo/flutter_project/demo/lib/main.dart b/example/simpleDemo/flutter_project/demo/lib/main.dart index 528f45a1..1ea0226b 100644 --- a/example/simpleDemo/flutter_project/demo/lib/main.dart +++ b/example/simpleDemo/flutter_project/demo/lib/main.dart @@ -33,22 +33,27 @@ class MyHomePage extends StatefulWidget { final String title; - static MethodChannel _channel = new MethodChannel('plugin_demo', new JSONMethodCodec()); - static Future GetVersion() async { - var res = await _channel.invokeMethod('getPlatformVersion'); - print(res); - - } - @override _MyHomePageState createState() { - GetVersion(); return new _MyHomePageState(); } } class _MyHomePageState extends State { + + + static MethodChannel _channel = new MethodChannel('plugin_demo', new JSONMethodCodec()); + Future GetVersion() async { + var res = await _channel.invokeMethod('getPlatformVersion'); + print(res); + setState(() { + _counter = res; + }); + } + + int _counter = 0; + bool _ok = false; void _incrementCounter() { setState(() { @@ -58,6 +63,10 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { + if (!_ok) { + GetVersion(); + _ok = true; + } return new Scaffold( appBar: new AppBar( title: new Text(widget.title), diff --git a/example/simpleDemo/flutter_project/demo/pubspec.lock b/example/simpleDemo/flutter_project/demo/pubspec.lock index 498026c7..625ba5f4 100644 --- a/example/simpleDemo/flutter_project/demo/pubspec.lock +++ b/example/simpleDemo/flutter_project/demo/pubspec.lock @@ -7,7 +7,7 @@ packages: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.32.4" + version: "0.33.0" args: dependency: transitive description: @@ -63,7 +63,7 @@ packages: name: csslib url: "https://pub.dartlang.org" source: hosted - version: "0.14.5" + version: "0.14.6" cupertino_icons: dependency: "direct main" description: @@ -87,7 +87,7 @@ packages: name: front_end url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.6" glob: dependency: transitive description: @@ -108,7 +108,7 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.11.3+17" + version: "0.12.0" http_multi_server: dependency: transitive description: @@ -150,7 +150,7 @@ packages: name: kernel url: "https://pub.dartlang.org" source: hosted - version: "0.3.4" + version: "0.3.6" logging: dependency: transitive description: @@ -206,7 +206,7 @@ packages: name: package_resolver url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.6" path: dependency: transitive description: @@ -288,7 +288,7 @@ packages: name: source_maps url: "https://pub.dartlang.org" source: hosted - version: "0.10.7" + version: "0.10.8" source_span: dependency: transitive description: @@ -330,7 +330,7 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.4" typed_data: dependency: transitive description: @@ -381,4 +381,4 @@ packages: source: hosted version: "2.1.15" sdks: - dart: ">=2.0.0-dev.68.0 <3.0.0" + dart: ">=2.0.0 <3.0.0" diff --git a/example/simpleDemo/main.go b/example/simpleDemo/main.go index a759e1b5..e0ddc967 100644 --- a/example/simpleDemo/main.go +++ b/example/simpleDemo/main.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "image" _ "image/png" "log" @@ -9,6 +10,7 @@ import ( "runtime" gutter "github.com/Drakirus/go-flutter-desktop-embedder" + "github.com/Drakirus/go-flutter-desktop-embedder/flutter" "github.com/go-gl/glfw/v3.2/glfw" ) @@ -29,6 +31,7 @@ func main() { gutter.OptionWindowInitializer(setIcon), gutter.OptionPixelRatio(1.2), gutter.OptionVMArguments([]string{"--dart-non-checked-mode", "--observatory-port=50300"}), + gutter.OptionAddPluginReceiver(ownPlugin), } if err = gutter.Run(options...); err != nil { @@ -51,3 +54,22 @@ func setIcon(window *glfw.Window) error { window.SetIcon([]image.Image{img}) return nil } + +// Plugin that read the stdin and send the number to the dart side +func ownPlugin( + platMessage flutter.PlatformMessage, + flutterEngine *flutter.EngineOpenGL, + window *glfw.Window, +) bool { + if platMessage.Channel == "plugin_demo" { + + go func() { + reader := bufio.NewReader(os.Stdin) + print("Reading (A number): ") + s, _ := reader.ReadString('\n') + flutterEngine.SendPlatformMessageResponse("[ "+s+" ]", platMessage) + }() + + } + return true +} diff --git a/flutter/flutter.go b/flutter/flutter.go index 4e88a862..821f9635 100644 --- a/flutter/flutter.go +++ b/flutter/flutter.go @@ -2,7 +2,7 @@ package flutter // #include "flutter_embedder.h" // FlutterResult runFlutter(uintptr_t window, FlutterEngine *engine, FlutterProjectArgs * Args, -// const char *const * vmArgs, int nVmAgrs, int engineID); +// const char *const * vmArgs, int nVmAgrs); // char** makeCharArray(int size); // void setArrayString(char **a, char *s, int n); import "C" @@ -19,6 +19,11 @@ func SelectEngine(index int) *EngineOpenGL { return flutterEngines[index] } +// NumberOfEngines return the number of engine registered into this embedder +func NumberOfEngines() C.int { + return C.int(len(flutterEngines)) +} + // Result corresponds to the C.enum retuned by the shared flutter library // whenever we call it. type Result int32 @@ -47,21 +52,20 @@ type EngineOpenGL struct { // Engine arguments PixelRatio float64 - AssetsPath unsafe.Pointer - IcuDataPath unsafe.Pointer + AssetsPath string + IcuDataPath string } // Run launches the Flutter Engine in a background thread. -// return a flutter Result and the index of the EngineOpenGL in the global List -func (flu *EngineOpenGL) Run(window uintptr, vmArgs []string) (Result, C.int) { +func (flu *EngineOpenGL) Run(window uintptr, vmArgs []string) Result { flutterEngines = append(flutterEngines, flu) args := C.FlutterProjectArgs{ - assets_path: (*C.char)(flu.AssetsPath), + assets_path: C.CString(flu.AssetsPath), main_path: C.CString(""), packages_path: C.CString(""), - icu_data_path: (*C.char)(flu.IcuDataPath), + icu_data_path: C.CString(flu.IcuDataPath), } args.struct_size = C.size_t(unsafe.Sizeof(args)) @@ -71,12 +75,12 @@ func (flu *EngineOpenGL) Run(window uintptr, vmArgs []string) (Result, C.int) { C.setArrayString(cVMArgs, C.CString(s), C.int(i)) } - res := C.runFlutter(C.uintptr_t(window), &flu.Engine, &args, cVMArgs, C.int(len(vmArgs)), C.int(len(flutterEngines))-1) + res := C.runFlutter(C.uintptr_t(window), &flu.Engine, &args, cVMArgs, C.int(len(vmArgs))) if flu.Engine == nil { - return KInvalidArguments, 0 + return KInvalidArguments } - return (Result)(res), C.int(len(flutterEngines) - 1) + return (Result)(res) } // Shutdown stops the Flutter engine. @@ -142,17 +146,15 @@ func (flu *EngineOpenGL) EngineSendWindowMetricsEvent(Metric WindowMetricsEvent) return (Result)(res) } -type platformMessageResponseHandle C.FlutterPlatformMessageResponseHandle - // PlatformMessage represents a message from or to the Flutter Engine (and thus the dart code) type PlatformMessage struct { Channel string Message Message - ResponseHandle *platformMessageResponseHandle + ResponseHandle *C.FlutterPlatformMessageResponseHandle } -// EngineSendPlatformMessage is used to send a PlatformMessage to the Flutter engine. -func (flu *EngineOpenGL) EngineSendPlatformMessage(Message PlatformMessage) Result { +// SendPlatformMessage is used to send a PlatformMessage to the Flutter engine. +func (flu *EngineOpenGL) SendPlatformMessage(Message PlatformMessage) Result { marshalled, err := json.Marshal(Message.Message) if err != nil { @@ -176,6 +178,20 @@ func (flu *EngineOpenGL) EngineSendPlatformMessage(Message PlatformMessage) Resu return (Result)(res) } +// SendPlatformMessageResponse is used to send a message to the Flutter side using the correct ResponseHandle! +func (flu *EngineOpenGL) SendPlatformMessageResponse( + data string, + message PlatformMessage) Result { + + res := C.FlutterEngineSendPlatformMessageResponse( + flu.Engine, + (*C.FlutterPlatformMessageResponseHandle)(message.ResponseHandle), + (*C.uint8_t)(unsafe.Pointer(C.CString(data))), (C.ulong)(len(data))) + + return (Result)(res) + +} + // EngineFlushPendingTasksNow flush tasks on a message loop not controlled by the Flutter engine. // deprecated soon. func EngineFlushPendingTasksNow() { diff --git a/flutter/flutter_helper.c b/flutter/flutter_helper.c index db480654..aaa0ccb1 100644 --- a/flutter/flutter_helper.c +++ b/flutter/flutter_helper.c @@ -10,8 +10,6 @@ // } closure; // C proxies def -bool proxy_make_current_test(int engineID, void *v); -BoolCallback proxy_make_current_func(int engineID); bool proxy_make_current(void *v); bool proxy_clear_current(void *v); bool proxy_present(void *v); @@ -23,21 +21,14 @@ bool proxy_on_platform_message(FlutterPlatformMessage *message, // C helper FlutterResult runFlutter(uintptr_t window, FlutterEngine *engine, FlutterProjectArgs * Args, - const char *const * vmArgs, int nVmAgrs, int engineID) { + const char *const * vmArgs, int nVmAgrs) { FlutterRendererConfig config = {}; config.type = kOpenGL; config.open_gl.struct_size = sizeof(FlutterOpenGLRendererConfig); - // bool closure_make_current(void *v) { - // printf("%i ---\n", engineID); - // return proxy_make_current_test(engineID, v); - // } - - config.open_gl.make_current = proxy_make_current_func(engineID); - // config.open_gl.make_current = closure_make_current; - // config.open_gl.make_current = proxy_make_current; + config.open_gl.make_current = proxy_make_current; config.open_gl.clear_current = proxy_clear_current; config.open_gl.present = proxy_present; config.open_gl.fbo_callback = proxy_fbo_callback; diff --git a/flutter/flutter_proxy.go b/flutter/flutter_proxy.go deleted file mode 100644 index f185e74e..00000000 --- a/flutter/flutter_proxy.go +++ /dev/null @@ -1,97 +0,0 @@ -package flutter - -/* -#include "flutter_embedder.h" - -static char* converter(uint8_t *str, size_t size){ - str[size] = '\0'; // Prevent overFlow - return (char *)str; -} - - -#include -static int am_i_null(void* pointer) { - if (NULL == pointer) { - printf("NULL"); - } -} - -bool proxy_make_current(void *v); - -*/ -import "C" -import ( - "encoding/json" - "fmt" - "unsafe" -) - -// C proxies - -//export proxy_on_platform_message -func proxy_on_platform_message(message *C.FlutterPlatformMessage, userPointer unsafe.Pointer) C.bool { - if message.message != nil { - str := C.GoString(C.converter(message.message, message.message_size)) - - FlutterPlatformMessage := PlatformMessage{} - - messageContent := Message{} - json.Unmarshal([]byte(str), &messageContent) - - FlutterPlatformMessage.Message = messageContent - FlutterPlatformMessage.Channel = C.GoString(message.channel) - // fmt.Println(unsafe.Pointer(message.response_handle)) - // fmt.Println(str) - if message.response_handle == nil { - fmt.Println("==================== NIL") - } - - response := "[ 1 ]" - - C.FlutterEngineSendPlatformMessageResponse( - flutterEngines[0].Engine, - message.response_handle, - (*C.uint8_t)(unsafe.Pointer(C.CString(response))), (C.ulong)(len(response))) - - return C.bool(flutterEngines[0].FPlatfromMessage(FlutterPlatformMessage, userPointer)) - } - return C.bool(false) - -} - -//export proxy_make_current -func proxy_make_current(v unsafe.Pointer) C.bool { - return C.bool(flutterEngines[0].FMakeCurrent(v)) -} - -//export proxy_make_current_test -func proxy_make_current_test(engineID int, v unsafe.Pointer) C.bool { - return C.bool(flutterEngines[engineID].FMakeCurrent(v)) -} - -//export proxy_make_current_func -func proxy_make_current_func(engineID int) unsafe.Pointer { - - print(engineID) - return C.proxy_make_current -} - -//export proxy_clear_current -func proxy_clear_current(v unsafe.Pointer) C.bool { - return C.bool(flutterEngines[0].FClearCurrent(v)) -} - -//export proxy_present -func proxy_present(v unsafe.Pointer) C.bool { - return C.bool(flutterEngines[0].FPresent(v)) -} - -//export proxy_fbo_callback -func proxy_fbo_callback(v unsafe.Pointer) C.uint32_t { - return C.uint32_t(flutterEngines[0].FFboCallback(v)) -} - -//export proxy_make_resource_current -func proxy_make_resource_current(v unsafe.Pointer) C.bool { - return C.bool(flutterEngines[0].FMakeResourceCurrent(v)) -} diff --git a/flutter/message.go b/flutter/message.go index 865a4141..1d1684f8 100644 --- a/flutter/message.go +++ b/flutter/message.go @@ -2,24 +2,6 @@ package flutter import "encoding/json" -// Constant values used to read/send messages to the Flutter Engine. -const ( - // channel - PlatformChannel = "flutter/platform" - TextInputChannel = "flutter/textinput" - - // Args -> struct AppSwitcherDescription - SetDescriptionMethod = "SystemChrome.setApplicationSwitcherDescription" - - // Args -> struct ArgsEditingState - TextUpdateStateMethod = "TextInputClient.updateEditingState" - - // text - TextInputClientSet = "TextInput.setClient" - TextInputClientClear = "TextInput.clearClient" - TextInputSetEditState = "TextInput.setEditingState" -) - // Message is the json content of a PlatformMessage type Message struct { // Describe the method @@ -27,20 +9,3 @@ type Message struct { // Actual datas Args json.RawMessage `json:"args"` } - -// ArgsAppSwitcherDescription Args content -type ArgsAppSwitcherDescription struct { - Label string `json:"label"` - PrimaryColor int64 `json:"primaryColor"` -} - -// ArgsEditingState Args content -type ArgsEditingState struct { - Text string `json:"text"` - SelectionBase int `json:"selectionBase"` - SelectionExtent int `json:"selectionExtent"` - SelectionAffinity string `json:"selectionAffinity"` - SelectionIsDirectional bool `json:"selectionIsDirectional"` - ComposingBase int `json:"composingBase"` - ComposingExtent int `json:"composingExtent"` -} diff --git a/gutter.go b/gutter.go index 74bec2ee..eafef448 100644 --- a/gutter.go +++ b/gutter.go @@ -12,74 +12,6 @@ import ( "github.com/go-gl/glfw/v3.2/glfw" ) -// Option for gutter -type Option func(*config) - -// OptionAssetPath specify the flutter asset directory. -func OptionAssetPath(p string) Option { - return func(c *config) { - c.AssetPath = p - } -} - -// OptionICUDataPath specify the path to the ICUData. -func OptionICUDataPath(p string) Option { - return func(c *config) { - c.ICUDataPath = p - } -} - -// OptionVMArguments specify the arguments to the Dart VM. -func OptionVMArguments(a []string) Option { - return func(c *config) { - // First should be argument is argv[0] - c.VMArguments = append([]string{""}, a...) - } -} - -// OptionWindowDimension specify the startup's dimention of the window. -func OptionWindowDimension(x int, y int) Option { - return func(c *config) { - c.WindowDimension.x = x - c.WindowDimension.y = y - } -} - -// OptionWindowInitializer allow initializing the window. -func OptionWindowInitializer(ini func(*glfw.Window) error) Option { - return func(c *config) { - c.WindowInitializer = ini - } -} - -// OptionPixelRatio specify the scale factor for the physical screen. -func OptionPixelRatio(ratio float64) Option { - return func(c *config) { - c.PixelRatio = ratio - } -} - -type config struct { - WindowDimension struct { - x int - y int - } - AssetPath string - ICUDataPath string - WindowInitializer func(*glfw.Window) error - PixelRatio float64 - VMArguments []string - PlatformMessageReceivers []func(message flutter.PlatformMessage) bool -} - -func (t config) merge(options ...Option) config { - for _, opt := range options { - opt(&t) - } - - return t -} - // Run executes a flutter application with the provided options. // given limitations this method must be called by the main function directly. func Run(options ...Option) (err error) { @@ -87,12 +19,14 @@ func Run(options ...Option) (err error) { window *glfw.Window c config ) + + // The Windows Title Handler and the TextInput handler come by default + options = append(options, addHandlerWindowTitle()) + options = append(options, addHandlerTextInput()) + c = c.merge(options...) - c.PlatformMessageReceivers = append(c.PlatformMessageReceivers, func(message flutter.PlatformMessage) bool { - print("==============", message.Channel) - return true - }) + // c.addHandlerWindowTitle() if err = glfw.Init(); err != nil { return err @@ -137,8 +71,7 @@ func glfwCursorPositionCallbackAtPhase( Timestamp: time.Now().UnixNano() / int64(time.Millisecond), } - // flutterOGL := *(*flutter.EngineOpenGL)(window.GetUserPointer()) - flutterOGL := *flutter.SelectEngine(0) + flutterOGL := flutter.SelectEngine(0) flutterOGL.EngineSendPointerEvent(event) } @@ -226,8 +159,7 @@ func glfwKeyCallback(w *glfw.Window, key glfw.Key, scancode int, action glfw.Act func glfwWindowSizeCallback(window *glfw.Window, width int, height int) { - // flutterOGL := *(*flutter.EngineOpenGL)(window.GetUserPointer()) - flutterOGL := *flutter.SelectEngine(0) + flutterOGL := flutter.SelectEngine(0) event := flutter.WindowMetricsEvent{ Width: width, @@ -248,8 +180,8 @@ func runFlutter(window *glfw.Window, c config) *flutter.EngineOpenGL { flutterOGL := flutter.EngineOpenGL{ // Engine arguments - AssetsPath: unsafe.Pointer(C.CString(c.AssetPath)), - IcuDataPath: unsafe.Pointer(C.CString(c.ICUDataPath)), + AssetsPath: c.AssetPath, + IcuDataPath: c.ICUDataPath, // Render callbacks FMakeCurrent: func(v unsafe.Pointer) bool { w := glfw.GoWindow(v) @@ -271,32 +203,38 @@ func runFlutter(window *glfw.Window, c config) *flutter.EngineOpenGL { FMakeResourceCurrent: func(v unsafe.Pointer) bool { return false }, - // Messaging (TextInput) - FPlatfromMessage: func(platMessage flutter.PlatformMessage, window unsafe.Pointer) bool { - // for _, receivers := range c.PlatformMessageReceivers { - // receivers(platMessage) - // } - - return onPlatformMessage(platMessage, window) - }, PixelRatio: c.PixelRatio, } + // PlatformMessage + flutterOGL.FPlatfromMessage = func(platMessage flutter.PlatformMessage, window unsafe.Pointer) bool { + windows := glfw.GoWindow(window) + + hasDispatched := false + + // Dispatch the message from the Flutter Engine, + // to all of his Receivers + for _, receivers := range c.PlatformMessageReceivers { + hasDispatched = receivers(platMessage, &flutterOGL, windows) || hasDispatched + } + + return hasDispatched + } + state.notifyState = func() { // log.Printf("Text: Sending to the flutter engine %v", state) updateEditingState(window) } - // result, engineIndex := flutterOGL.Run(window.GLFWWindow(), c.VMArguments) - result, _ := flutterOGL.Run(window.GLFWWindow(), c.VMArguments) + NbEngine := flutter.NumberOfEngines() + window.SetUserPointer(unsafe.Pointer(&NbEngine)) + result := flutterOGL.Run(window.GLFWWindow(), c.VMArguments) if result != flutter.KSuccess { window.Destroy() panic("Couldn't launch the FlutterEngine") } - // window.SetUserPointer(unsafe.Pointer(&engineIndex)) - width, height := window.GetFramebufferSize() glfwWindowSizeCallback(window, width, height) @@ -307,51 +245,10 @@ func runFlutter(window *glfw.Window, c config) *flutter.EngineOpenGL { return &flutterOGL } -// Dispatch the message from the Flutter Engine, -// to all of his Receivers -func onPlatformMessage(platMessage flutter.PlatformMessage, window unsafe.Pointer) bool { - - windows := glfw.GoWindow(window) - message := platMessage.Message - - // fmt.Println(string(platMessage.Message.Args)) - - if message.Method == flutter.SetDescriptionMethod { - msgBody := flutter.ArgsAppSwitcherDescription{} - json.Unmarshal(message.Args, &msgBody) - windows.SetTitle(msgBody.Label) - } - - if platMessage.Channel == flutter.TextInputChannel { - switch message.Method { - case flutter.TextInputClientClear: - state.clientID = 0 - case flutter.TextInputClientSet: - var body []interface{} - json.Unmarshal(message.Args, &body) - state.clientID = body[0].(float64) - case flutter.TextInputSetEditState: - if state.clientID != 0 { - editingState := flutter.ArgsEditingState{} - json.Unmarshal(message.Args, &editingState) - state.word = editingState.Text - state.selectionBase = editingState.SelectionBase - state.selectionExtent = editingState.SelectionExtent - } - default: - // log.Printf("unhandled text input method: %#v\n", platMessage.Message) - } - } - - return true -} - // Update the TextInput with the current state func updateEditingState(window *glfw.Window) { - // state.word = "Лайкаа" - - editingState := flutter.ArgsEditingState{ + editingState := ArgsEditingState{ Text: state.word, SelectionAffinity: "TextAffinity.downstream", SelectionBase: state.selectionBase, @@ -366,15 +263,14 @@ func updateEditingState(window *glfw.Window) { message := flutter.Message{ Args: editingStateMarchalled, - Method: flutter.TextUpdateStateMethod, + Method: TextUpdateStateMethod, } var mess = flutter.PlatformMessage{ - Channel: flutter.TextInputChannel, + Channel: TextInputChannel, Message: message, } - // flutterOGL := *(*flutter.EngineOpenGL)(window.GetUserPointer()) - flutterOGL := *flutter.SelectEngine(0) - flutterOGL.EngineSendPlatformMessage(mess) + flutterOGL := flutter.SelectEngine(0) + flutterOGL.SendPlatformMessage(mess) } diff --git a/option.go b/option.go new file mode 100644 index 00000000..84b4fa2e --- /dev/null +++ b/option.go @@ -0,0 +1,90 @@ +package gutter + +import ( + "github.com/Drakirus/go-flutter-desktop-embedder/flutter" + "github.com/go-gl/glfw/v3.2/glfw" +) + +// Option for gutter +type Option func(*config) + +// OptionAssetPath specify the flutter asset directory. +func OptionAssetPath(p string) Option { + return func(c *config) { + c.AssetPath = p + } +} + +// OptionICUDataPath specify the path to the ICUData. +func OptionICUDataPath(p string) Option { + return func(c *config) { + c.ICUDataPath = p + } +} + +// OptionVMArguments specify the arguments to the Dart VM. +func OptionVMArguments(a []string) Option { + return func(c *config) { + // First should be argument is argv[0] + c.VMArguments = append([]string{""}, a...) + } +} + +// OptionWindowDimension specify the startup's dimention of the window. +func OptionWindowDimension(x int, y int) Option { + return func(c *config) { + c.WindowDimension.x = x + c.WindowDimension.y = y + } +} + +// OptionWindowInitializer allow initializing the window. +func OptionWindowInitializer(ini func(*glfw.Window) error) Option { + return func(c *config) { + c.WindowInitializer = ini + } +} + +// OptionPixelRatio specify the scale factor for the physical screen. +func OptionPixelRatio(ratio float64) Option { + return func(c *config) { + c.PixelRatio = ratio + } +} + +// OptionAddPluginReceiver add a new function that will be trigger +// when the FlutterEngine send a PlatformMessage to the Embedder +func OptionAddPluginReceiver(handler PluginReceivers) Option { + return func(c *config) { + c.PlatformMessageReceivers = append(c.PlatformMessageReceivers, handler) + } +} + +type config struct { + WindowDimension struct { + x int + y int + } + AssetPath string + ICUDataPath string + WindowInitializer func(*glfw.Window) error + PixelRatio float64 + VMArguments []string + PlatformMessageReceivers []PluginReceivers +} + +// PluginReceivers do stuff when receiving Message from the Engine, +// send result with `flutterEngine.SendPlatformMessageResponse` +type PluginReceivers func( + message flutter.PlatformMessage, + flutterEngine *flutter.EngineOpenGL, + window *glfw.Window, +) bool + +func (t config) merge(options ...Option) config { + for _, opt := range options { + opt(&t) + } + + return t +} diff --git a/system_channels.go b/system_channels.go new file mode 100644 index 00000000..c1f07466 --- /dev/null +++ b/system_channels.go @@ -0,0 +1,114 @@ +package gutter + +import ( + "encoding/json" + + "github.com/Drakirus/go-flutter-desktop-embedder/flutter" + "github.com/go-gl/glfw/v3.2/glfw" +) + +//////////////////// +// Window Title // +//////////////////// + +// const for `addHandlerWindowTitle` +const ( + // Args -> struct ArgsAppSwitcherDescription + setDescriptionMethod = "SystemChrome.setApplicationSwitcherDescription" +) + +// ArgsAppSwitcherDescription Args content +type ArgsAppSwitcherDescription struct { + Label string `json:"label"` + PrimaryColor int64 `json:"primaryColor"` +} + +func addHandlerWindowTitle() Option { + + var handler PluginReceivers = func( + platMessage flutter.PlatformMessage, + flutterEngine *flutter.EngineOpenGL, + window *glfw.Window, + ) bool { + message := platMessage.Message + + if message.Method == setDescriptionMethod { + msgBody := ArgsAppSwitcherDescription{} + json.Unmarshal(message.Args, &msgBody) + window.SetTitle(msgBody.Label) + return true + } + return false + } + + return OptionAddPluginReceiver(handler) + +} + +///////////////// +// TextInput // +///////////////// + +// const for `addHandlerTextInput` +const ( + // channel + PlatformChannel = "flutter/platform" + TextInputChannel = "flutter/textinput" + + // Args -> struct ArgsEditingState + TextUpdateStateMethod = "TextInputClient.updateEditingState" + + // text + TextInputClientSet = "TextInput.setClient" + TextInputClientClear = "TextInput.clearClient" + TextInputSetEditState = "TextInput.setEditingState" +) + +// ArgsEditingState Args content +type ArgsEditingState struct { + Text string `json:"text"` + SelectionBase int `json:"selectionBase"` + SelectionExtent int `json:"selectionExtent"` + SelectionAffinity string `json:"selectionAffinity"` + SelectionIsDirectional bool `json:"selectionIsDirectional"` + ComposingBase int `json:"composingBase"` + ComposingExtent int `json:"composingExtent"` +} + +func addHandlerTextInput() Option { + + var handler PluginReceivers = func( + platMessage flutter.PlatformMessage, + flutterEngine *flutter.EngineOpenGL, + window *glfw.Window, + ) bool { + + message := platMessage.Message + + if platMessage.Channel == TextInputChannel { + switch message.Method { + case TextInputClientClear: + state.clientID = 0 + case TextInputClientSet: + var body []interface{} + json.Unmarshal(message.Args, &body) + state.clientID = body[0].(float64) + case TextInputSetEditState: + if state.clientID != 0 { + editingState := ArgsEditingState{} + json.Unmarshal(message.Args, &editingState) + state.word = editingState.Text + state.selectionBase = editingState.SelectionBase + state.selectionExtent = editingState.SelectionExtent + } + default: + } + } + + return true + + } + + return OptionAddPluginReceiver(handler) + +} From 13b4886da3845a37d3827af0d27dd431dddc2a56 Mon Sep 17 00:00:00 2001 From: Drakirus Date: Tue, 30 Oct 2018 11:29:38 +0100 Subject: [PATCH 7/8] Remove unnecessary import --- gutter.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/gutter.go b/gutter.go index eafef448..9cf4ffb1 100644 --- a/gutter.go +++ b/gutter.go @@ -6,8 +6,6 @@ import ( "time" "unsafe" - "C" - "github.com/Drakirus/go-flutter-desktop-embedder/flutter" "github.com/go-gl/glfw/v3.2/glfw" ) From 9b0673187c317e752981ab131bfc6030d1e08313 Mon Sep 17 00:00:00 2001 From: Drakirus Date: Sun, 4 Nov 2018 20:00:03 +0100 Subject: [PATCH 8/8] :sparkles: Add support for the JSON MethodChannel Plugins --- README.md | 28 +++++---- .../flutter_project/demo/lib/main.dart | 2 +- example/simpleDemo/main.go | 22 ++++--- flutter/flutter.go | 21 +++++-- flutter/message.go | 11 ---- gutter.go | 13 ++-- option.go | 20 +++---- plugin_receiver.go | 14 +++++ system_channels.go => system_plugins.go | 59 ++++++++++--------- textModel.go => text_model.go | 0 10 files changed, 106 insertions(+), 84 deletions(-) delete mode 100644 flutter/message.go create mode 100644 plugin_receiver.go rename system_channels.go => system_plugins.go (58%) rename textModel.go => text_model.go (100%) diff --git a/README.md b/README.md index c0f0c813..d8b670fc 100755 --- a/README.md +++ b/README.md @@ -171,16 +171,20 @@ The examples are available [here](./example/) - [x] Linux :penguin: - [x] Windows :checkered_flag: - [x] MacOS :apple: -- [x] Text input -- [ ] Plugins - [x] Importable go library -- [x] Clipboard (through shortcuts) -- [ ] Clipboard (through the click) -- [x] Keyboard shortcuts - - [x] ctrl-c ctrl-v ctrl-x ctrl-a - - [x] Home End shift-Home shift-End - - [x] Left ctrl-Left ctrl-shift-Left - - [x] Right ctrl-Right ctrl-shift-Right - - [x] Backspace ctrl-Backspace Delete - - [ ] ctrl-Delete -- [ ] Key events +- [ ] Plugins [Medium article on how the the Flutter's messaging works](https://medium.com/flutter-io/flutter-platform-channels-ce7f540a104e) + - [x] JSON MethodChannel + - [ ] StandardMethodCodec, ... +- [ ] System plugins [Platform channels used by the Flutter system](https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/system_channels.dart) + - [x] Window Title + - [x] Text input + - [x] Clipboard (through shortcuts) + - [ ] Clipboard (through the click) + - [x] Keyboard shortcuts + - [x] ctrl-c ctrl-v ctrl-x ctrl-a + - [x] Home End shift-Home shift-End + - [x] Left ctrl-Left ctrl-shift-Left + - [x] Right ctrl-Right ctrl-shift-Right + - [x] Backspace ctrl-Backspace Delete + - [ ] ctrl-Delete + - [ ] Key events diff --git a/example/simpleDemo/flutter_project/demo/lib/main.dart b/example/simpleDemo/flutter_project/demo/lib/main.dart index 1ea0226b..083c642a 100644 --- a/example/simpleDemo/flutter_project/demo/lib/main.dart +++ b/example/simpleDemo/flutter_project/demo/lib/main.dart @@ -44,7 +44,7 @@ class _MyHomePageState extends State { static MethodChannel _channel = new MethodChannel('plugin_demo', new JSONMethodCodec()); Future GetVersion() async { - var res = await _channel.invokeMethod('getPlatformVersion'); + var res = await _channel.invokeMethod('getNumber'); print(res); setState(() { _counter = res; diff --git a/example/simpleDemo/main.go b/example/simpleDemo/main.go index e0ddc967..87dc6b45 100644 --- a/example/simpleDemo/main.go +++ b/example/simpleDemo/main.go @@ -31,7 +31,7 @@ func main() { gutter.OptionWindowInitializer(setIcon), gutter.OptionPixelRatio(1.2), gutter.OptionVMArguments([]string{"--dart-non-checked-mode", "--observatory-port=50300"}), - gutter.OptionAddPluginReceiver(ownPlugin), + gutter.OptionAddPluginReceiver(ownPlugin, "plugin_demo"), } if err = gutter.Run(options...); err != nil { @@ -61,15 +61,19 @@ func ownPlugin( flutterEngine *flutter.EngineOpenGL, window *glfw.Window, ) bool { - if platMessage.Channel == "plugin_demo" { + if platMessage.Message.Method != "getNumber" { + log.Printf("Unhandled platform method: %#v from channel %#v\n", + platMessage.Message.Method, platMessage.Channel) + return false + } - go func() { - reader := bufio.NewReader(os.Stdin) - print("Reading (A number): ") - s, _ := reader.ReadString('\n') - flutterEngine.SendPlatformMessageResponse("[ "+s+" ]", platMessage) - }() + go func() { + reader := bufio.NewReader(os.Stdin) + print("Reading (A number): ") + s, _ := reader.ReadString('\n') + flutterEngine.SendPlatformMessageResponse(platMessage, []byte("[ "+s+" ]")) + }() - } return true + } diff --git a/flutter/flutter.go b/flutter/flutter.go index 821f9635..b340d2e3 100644 --- a/flutter/flutter.go +++ b/flutter/flutter.go @@ -146,13 +146,22 @@ func (flu *EngineOpenGL) EngineSendWindowMetricsEvent(Metric WindowMetricsEvent) return (Result)(res) } -// PlatformMessage represents a message from or to the Flutter Engine (and thus the dart code) +// PlatformMessage represents a `MethodChannel` serialized with the `JSONMethodCodec` +// TODO Support for `StandardMethodCodec` type PlatformMessage struct { Channel string Message Message ResponseHandle *C.FlutterPlatformMessageResponseHandle } +// Message is the json content of a PlatformMessage +type Message struct { + // Describe the method + Method string `json:"method"` + // Actual datas + Args json.RawMessage `json:"args"` +} + // SendPlatformMessage is used to send a PlatformMessage to the Flutter engine. func (flu *EngineOpenGL) SendPlatformMessage(Message PlatformMessage) Result { @@ -180,13 +189,15 @@ func (flu *EngineOpenGL) SendPlatformMessage(Message PlatformMessage) Result { // SendPlatformMessageResponse is used to send a message to the Flutter side using the correct ResponseHandle! func (flu *EngineOpenGL) SendPlatformMessageResponse( - data string, - message PlatformMessage) Result { + responseTo PlatformMessage, + data []byte, +) Result { res := C.FlutterEngineSendPlatformMessageResponse( flu.Engine, - (*C.FlutterPlatformMessageResponseHandle)(message.ResponseHandle), - (*C.uint8_t)(unsafe.Pointer(C.CString(data))), (C.ulong)(len(data))) + (*C.FlutterPlatformMessageResponseHandle)(responseTo.ResponseHandle), + (*C.uint8_t)(unsafe.Pointer(C.CBytes(data))), + (C.ulong)(len(data))) return (Result)(res) diff --git a/flutter/message.go b/flutter/message.go deleted file mode 100644 index 1d1684f8..00000000 --- a/flutter/message.go +++ /dev/null @@ -1,11 +0,0 @@ -package flutter - -import "encoding/json" - -// Message is the json content of a PlatformMessage -type Message struct { - // Describe the method - Method string `json:"method"` - // Actual datas - Args json.RawMessage `json:"args"` -} diff --git a/gutter.go b/gutter.go index 9cf4ffb1..50ebd106 100644 --- a/gutter.go +++ b/gutter.go @@ -92,6 +92,7 @@ func glfwMouseButtonCallback(window *glfw.Window, key glfw.MouseButton, action g } +// TODO Link the textModel to one FlutterEngine var state = textModel{} func glfwKeyCallback(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { @@ -210,9 +211,9 @@ func runFlutter(window *glfw.Window, c config) *flutter.EngineOpenGL { hasDispatched := false - // Dispatch the message from the Flutter Engine, - // to all of his Receivers - for _, receivers := range c.PlatformMessageReceivers { + // Dispatch the message from the Flutter Engine, to all of the PluginReceivers + // having the same flutter.PlatformMessage.Channel name + for _, receivers := range c.PlatformMessageReceivers[platMessage.Channel] { hasDispatched = receivers(platMessage, &flutterOGL, windows) || hasDispatched } @@ -246,7 +247,7 @@ func runFlutter(window *glfw.Window, c config) *flutter.EngineOpenGL { // Update the TextInput with the current state func updateEditingState(window *glfw.Window) { - editingState := ArgsEditingState{ + editingState := argsEditingState{ Text: state.word, SelectionAffinity: "TextAffinity.downstream", SelectionBase: state.selectionBase, @@ -261,11 +262,11 @@ func updateEditingState(window *glfw.Window) { message := flutter.Message{ Args: editingStateMarchalled, - Method: TextUpdateStateMethod, + Method: textUpdateStateMethod, } var mess = flutter.PlatformMessage{ - Channel: TextInputChannel, + Channel: textInputChannel, Message: message, } diff --git a/option.go b/option.go index 84b4fa2e..51c79a9b 100644 --- a/option.go +++ b/option.go @@ -1,7 +1,6 @@ package gutter import ( - "github.com/Drakirus/go-flutter-desktop-embedder/flutter" "github.com/go-gl/glfw/v3.2/glfw" ) @@ -54,9 +53,14 @@ func OptionPixelRatio(ratio float64) Option { // OptionAddPluginReceiver add a new function that will be trigger // when the FlutterEngine send a PlatformMessage to the Embedder -func OptionAddPluginReceiver(handler PluginReceivers) Option { +func OptionAddPluginReceiver(handler PluginReceivers, channelName string) Option { return func(c *config) { - c.PlatformMessageReceivers = append(c.PlatformMessageReceivers, handler) + // Check for nil, else initialise the map + if c.PlatformMessageReceivers == nil { + c.PlatformMessageReceivers = make(map[string][]PluginReceivers) + } + c.PlatformMessageReceivers[channelName] = + append(c.PlatformMessageReceivers[channelName], handler) } } @@ -70,17 +74,9 @@ type config struct { WindowInitializer func(*glfw.Window) error PixelRatio float64 VMArguments []string - PlatformMessageReceivers []PluginReceivers + PlatformMessageReceivers map[string][]PluginReceivers // The Key is the Channel name. } -// PluginReceivers do stuff when receiving Message from the Engine, -// send result with `flutterEngine.SendPlatformMessageResponse` -type PluginReceivers func( - message flutter.PlatformMessage, - flutterEngine *flutter.EngineOpenGL, - window *glfw.Window, -) bool - func (t config) merge(options ...Option) config { for _, opt := range options { opt(&t) diff --git a/plugin_receiver.go b/plugin_receiver.go new file mode 100644 index 00000000..a3d52a10 --- /dev/null +++ b/plugin_receiver.go @@ -0,0 +1,14 @@ +package gutter + +import ( + "github.com/Drakirus/go-flutter-desktop-embedder/flutter" + "github.com/go-gl/glfw/v3.2/glfw" +) + +// PluginReceivers do stuff when receiving Message from the Engine, +// send result with `flutterEngine.SendPlatformMessageResponse` +type PluginReceivers func( + message flutter.PlatformMessage, + flutterEngine *flutter.EngineOpenGL, + window *glfw.Window, +) bool diff --git a/system_channels.go b/system_plugins.go similarity index 58% rename from system_channels.go rename to system_plugins.go index c1f07466..e2f0f478 100644 --- a/system_channels.go +++ b/system_plugins.go @@ -7,12 +7,17 @@ import ( "github.com/go-gl/glfw/v3.2/glfw" ) +// Talks to the dart side +// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/system_channels.dart + //////////////////// // Window Title // //////////////////// // const for `addHandlerWindowTitle` const ( + // Channel + platformChannel = "flutter/platform" // Args -> struct ArgsAppSwitcherDescription setDescriptionMethod = "SystemChrome.setApplicationSwitcherDescription" ) @@ -41,7 +46,7 @@ func addHandlerWindowTitle() Option { return false } - return OptionAddPluginReceiver(handler) + return OptionAddPluginReceiver(handler, platformChannel) } @@ -52,20 +57,20 @@ func addHandlerWindowTitle() Option { // const for `addHandlerTextInput` const ( // channel - PlatformChannel = "flutter/platform" - TextInputChannel = "flutter/textinput" + textInputChannel = "flutter/textinput" - // Args -> struct ArgsEditingState - TextUpdateStateMethod = "TextInputClient.updateEditingState" + // Args -> struct argsEditingState + textUpdateStateMethod = "TextInputClient.updateEditingState" // text - TextInputClientSet = "TextInput.setClient" - TextInputClientClear = "TextInput.clearClient" - TextInputSetEditState = "TextInput.setEditingState" + textInputClientSet = "TextInput.setClient" + textInputClientClear = "TextInput.clearClient" + textInputSetEditState = "TextInput.setEditingState" ) -// ArgsEditingState Args content -type ArgsEditingState struct { +// argsEditingState Args content +// To update the embedder text use `flutter.SendPlatformMessage` whenever a keys is pressed +type argsEditingState struct { Text string `json:"text"` SelectionBase int `json:"selectionBase"` SelectionExtent int `json:"selectionExtent"` @@ -85,30 +90,28 @@ func addHandlerTextInput() Option { message := platMessage.Message - if platMessage.Channel == TextInputChannel { - switch message.Method { - case TextInputClientClear: - state.clientID = 0 - case TextInputClientSet: - var body []interface{} - json.Unmarshal(message.Args, &body) - state.clientID = body[0].(float64) - case TextInputSetEditState: - if state.clientID != 0 { - editingState := ArgsEditingState{} - json.Unmarshal(message.Args, &editingState) - state.word = editingState.Text - state.selectionBase = editingState.SelectionBase - state.selectionExtent = editingState.SelectionExtent - } - default: + switch message.Method { + case textInputClientClear: + state.clientID = 0 + case textInputClientSet: + var body []interface{} + json.Unmarshal(message.Args, &body) + state.clientID = body[0].(float64) + case textInputSetEditState: + if state.clientID != 0 { + editingState := argsEditingState{} + json.Unmarshal(message.Args, &editingState) + state.word = editingState.Text + state.selectionBase = editingState.SelectionBase + state.selectionExtent = editingState.SelectionExtent } + default: } return true } - return OptionAddPluginReceiver(handler) + return OptionAddPluginReceiver(handler, textInputChannel) } diff --git a/textModel.go b/text_model.go similarity index 100% rename from textModel.go rename to text_model.go