import 'dart:convert'; import 'dart:ffi'; import 'dart:io'; import 'dart:typed_data'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:external_path/external_path.dart'; import 'package:ffi/ffi.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; import '../common.dart'; import '../generated_bridge.dart'; class RgbaFrame extends Struct { @Uint32() external int len; external Pointer data; } typedef F2 = Pointer Function(Pointer, Pointer); typedef F3 = void Function(Pointer, Pointer); typedef HandleEvent = void Function(Map evt); /// FFI wrapper around the native Rust core. /// Hides the platform differences. class PlatformFFI { String _dir = ''; String _homeDir = ''; F2? _translate; final _eventHandlers = Map>(); late RustdeskImpl _ffiBind; late String _appType; void Function(Map)? _eventCallback; PlatformFFI._(); static final PlatformFFI instance = PlatformFFI._(); final _toAndroidChannel = MethodChannel("mChannel"); RustdeskImpl get ffiBind => _ffiBind; static get localeName => Platform.localeName; static Future getVersion() async { PackageInfo packageInfo = await PackageInfo.fromPlatform(); return packageInfo.version; } bool registerEventHandler( String eventName, String handlerName, HandleEvent handler) { debugPrint('registerEventHandler $eventName $handlerName'); var handlers = _eventHandlers[eventName]; if (handlers == null) { _eventHandlers[eventName] = {handlerName: handler}; return true; } else { if (handlers.containsKey(handlerName)) { return false; } else { handlers[handlerName] = handler; return true; } } } void unregisterEventHandler(String eventName, String handlerName) { debugPrint('unregisterEventHandler $eventName $handlerName'); var handlers = _eventHandlers[eventName]; if (handlers != null) { handlers.remove(handlerName); } } String translate(String name, String locale) { if (_translate == null) return name; var a = name.toNativeUtf8(); var b = locale.toNativeUtf8(); var p = _translate!(a, b); assert(p != nullptr); final res = p.toDartString(); calloc.free(p); calloc.free(a); calloc.free(b); return res; } /// Init the FFI class, loads the native Rust core library. Future init(String appType) async { _appType = appType; // if (isDesktop) { // // TODO // return; // } final dylib = Platform.isAndroid ? DynamicLibrary.open('librustdesk.so') : Platform.isLinux ? DynamicLibrary.open("librustdesk.so") : Platform.isWindows ? DynamicLibrary.open("librustdesk.dll") : Platform.isMacOS ? DynamicLibrary.open("librustdesk.dylib") : DynamicLibrary.process(); debugPrint('initializing FFI ${_appType}'); try { _translate = dylib.lookupFunction('translate'); _dir = (await getApplicationDocumentsDirectory()).path; _ffiBind = RustdeskImpl(dylib); _startListenEvent(_ffiBind); // global event try { if (isAndroid) { // only support for android _homeDir = (await ExternalPath.getExternalStorageDirectories())[0]; } else { _homeDir = (await getDownloadsDirectory())?.path ?? ""; } } catch (e) { print("initialize failed: $e"); } String id = 'NA'; String name = 'Flutter'; DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); if (Platform.isAndroid) { AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; name = '${androidInfo.brand}-${androidInfo.model}'; id = androidInfo.id.hashCode.toString(); androidVersion = androidInfo.version.sdkInt ?? 0; } else if (Platform.isIOS) { IosDeviceInfo iosInfo = await deviceInfo.iosInfo; name = iosInfo.utsname.machine ?? ""; id = iosInfo.identifierForVendor.hashCode.toString(); } else if (Platform.isLinux) { LinuxDeviceInfo linuxInfo = await deviceInfo.linuxInfo; name = linuxInfo.name; id = linuxInfo.machineId ?? linuxInfo.id; } else if (Platform.isWindows) { WindowsDeviceInfo winInfo = await deviceInfo.windowsInfo; name = winInfo.computerName; id = winInfo.computerName; } else if (Platform.isMacOS) { MacOsDeviceInfo macOsInfo = await deviceInfo.macOsInfo; name = macOsInfo.computerName; id = macOsInfo.systemGUID ?? ""; } print( "_appType:$_appType,info1-id:$id,info2-name:$name,dir:$_dir,homeDir:$_homeDir"); await _ffiBind.mainDeviceId(id: id); await _ffiBind.mainDeviceName(name: name); await _ffiBind.mainSetHomeDir(home: _homeDir); await _ffiBind.mainInit(appDir: _dir); } catch (e) { print("initialize failed: $e"); } version = await getVersion(); } bool _tryHandle(Map evt) { final name = evt['name']; if (name != null) { final handlers = _eventHandlers[name]; if (handlers != null) { if (handlers.isNotEmpty) { handlers.values.forEach((handler) { handler(evt); }); return true; } } } return false; } /// Start listening to the Rust core's events and frames. void _startListenEvent(RustdeskImpl rustdeskImpl) { () async { await for (final message in rustdeskImpl.startGlobalEventStream(appType: _appType)) { try { Map event = json.decode(message); // _tryHandle here may be more flexible than _eventCallback if (!_tryHandle(event)) { if (_eventCallback != null) { _eventCallback!(event); } } } catch (e) { print('json.decode fail(): $e'); } } }(); } void setEventCallback(void Function(Map) fun) async { _eventCallback = fun; } void setRgbaCallback(void Function(Uint8List) fun) async {} void startDesktopWebListener() {} void stopDesktopWebListener() {} void setMethodCallHandler(FMethod callback) { _toAndroidChannel.setMethodCallHandler((call) async { callback(call.method, call.arguments); return null; }); } invokeMethod(String method, [dynamic arguments]) async { if (!isAndroid) return Future(() => false); return await _toAndroidChannel.invokeMethod(method, arguments); } }