013d307bcd
Signed-off-by: dignow <linlong1265@gmail.com>
266 lines
8.7 KiB
Dart
266 lines
8.7 KiB
Dart
import 'dart:convert';
|
|
import 'dart:ffi';
|
|
import 'dart:io';
|
|
|
|
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:flutter_hbb/consts.dart';
|
|
import 'package:flutter_hbb/main.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<Uint8> data;
|
|
}
|
|
|
|
typedef F3 = Pointer<Uint8> Function(Pointer<Utf8>, int);
|
|
typedef F3Dart = Pointer<Uint8> Function(Pointer<Utf8>, Int32);
|
|
typedef HandleEvent = Future<void> Function(Map<String, dynamic> evt);
|
|
|
|
/// FFI wrapper around the native Rust core.
|
|
/// Hides the platform differences.
|
|
class PlatformFFI {
|
|
String _dir = '';
|
|
// _homeDir is only needed for Android and IOS.
|
|
String _homeDir = '';
|
|
final _eventHandlers = <String, Map<String, HandleEvent>>{};
|
|
late RustdeskImpl _ffiBind;
|
|
late String _appType;
|
|
StreamEventHandler? _eventCallback;
|
|
|
|
PlatformFFI._();
|
|
|
|
static final PlatformFFI instance = PlatformFFI._();
|
|
final _toAndroidChannel = const MethodChannel('mChannel');
|
|
|
|
RustdeskImpl get ffiBind => _ffiBind;
|
|
F3? _session_get_rgba;
|
|
|
|
static get localeName => Platform.localeName;
|
|
|
|
static get isMain => instance._appType == kAppTypeMain;
|
|
|
|
static Future<String> 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) =>
|
|
_ffiBind.translate(name: name, locale: locale);
|
|
|
|
Uint8List? getRgba(SessionID sessionId, int display, int bufSize) {
|
|
if (_session_get_rgba == null) return null;
|
|
final sessionIdStr = sessionId.toString();
|
|
var a = sessionIdStr.toNativeUtf8();
|
|
try {
|
|
final buffer = _session_get_rgba!(a, display);
|
|
if (buffer == nullptr) {
|
|
return null;
|
|
}
|
|
final data = buffer.asTypedList(bufSize);
|
|
return data;
|
|
} finally {
|
|
malloc.free(a);
|
|
}
|
|
}
|
|
|
|
int getRgbaSize(SessionID sessionId, int display) =>
|
|
_ffiBind.sessionGetRgbaSize(sessionId: sessionId, display: display);
|
|
void nextRgba(SessionID sessionId, int display) => _ffiBind.sessionNextRgba(sessionId: sessionId, display: display);
|
|
void registerTexture(SessionID sessionId, int display, int ptr) =>
|
|
_ffiBind.sessionRegisterTexture(sessionId: sessionId, display: display, ptr: ptr);
|
|
|
|
/// Init the FFI class, loads the native Rust core library.
|
|
Future<void> init(String appType) async {
|
|
_appType = appType;
|
|
final dylib = Platform.isAndroid
|
|
? DynamicLibrary.open('librustdesk.so')
|
|
: Platform.isLinux
|
|
? DynamicLibrary.open('librustdesk.so')
|
|
: Platform.isWindows
|
|
? DynamicLibrary.open('librustdesk.dll')
|
|
: Platform.isMacOS
|
|
? DynamicLibrary.open("liblibrustdesk.dylib")
|
|
: DynamicLibrary.process();
|
|
debugPrint('initializing FFI $_appType');
|
|
try {
|
|
_session_get_rgba = dylib.lookupFunction<F3Dart, F3>("session_get_rgba");
|
|
try {
|
|
// SYSTEM user failed
|
|
_dir = (await getApplicationDocumentsDirectory()).path;
|
|
} catch (e) {
|
|
debugPrint('Failed to get documents directory: $e');
|
|
}
|
|
_ffiBind = RustdeskImpl(dylib);
|
|
if (Platform.isLinux) {
|
|
// Start a dbus service, no need to await
|
|
_ffiBind.mainStartDbusServer();
|
|
_ffiBind.mainStartPa();
|
|
} else if (Platform.isMacOS && isMain) {
|
|
// Start ipc service for uri links.
|
|
_ffiBind.mainStartIpcUrlServer();
|
|
}
|
|
_startListenEvent(_ffiBind); // global event
|
|
try {
|
|
if (isAndroid) {
|
|
// only support for android
|
|
_homeDir = (await ExternalPath.getExternalStorageDirectories())[0];
|
|
} else if (isIOS) {
|
|
_homeDir = _ffiBind.mainGetDataDirIos();
|
|
} else {
|
|
// no need to set home dir
|
|
}
|
|
} catch (e) {
|
|
debugPrintStack(label: '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) {
|
|
try {
|
|
// request windows build number to fix overflow on win7
|
|
windowsBuildNumber = getWindowsTargetBuildNumber();
|
|
WindowsDeviceInfo winInfo = await deviceInfo.windowsInfo;
|
|
name = winInfo.computerName;
|
|
id = winInfo.computerName;
|
|
} catch (e) {
|
|
debugPrintStack(label: "get windows device info failed: $e");
|
|
name = "unknown";
|
|
id = "unknown";
|
|
}
|
|
} else if (Platform.isMacOS) {
|
|
MacOsDeviceInfo macOsInfo = await deviceInfo.macOsInfo;
|
|
name = macOsInfo.computerName;
|
|
id = macOsInfo.systemGUID ?? '';
|
|
}
|
|
if (isAndroid || isIOS) {
|
|
debugPrint(
|
|
'_appType:$_appType,info1-id:$id,info2-name:$name,dir:$_dir,homeDir:$_homeDir');
|
|
} else {
|
|
debugPrint(
|
|
'_appType:$_appType,info1-id:$id,info2-name:$name,dir:$_dir');
|
|
}
|
|
if (desktopType == DesktopType.cm) {
|
|
await _ffiBind.cmInit();
|
|
}
|
|
await _ffiBind.mainDeviceId(id: id);
|
|
await _ffiBind.mainDeviceName(name: name);
|
|
await _ffiBind.mainSetHomeDir(home: _homeDir);
|
|
await _ffiBind.mainInit(appDir: _dir);
|
|
} catch (e) {
|
|
debugPrintStack(label: 'initialize failed: $e');
|
|
}
|
|
version = await getVersion();
|
|
}
|
|
|
|
Future<bool> tryHandle(Map<String, dynamic> evt) async {
|
|
final name = evt['name'];
|
|
if (name != null) {
|
|
final handlers = _eventHandlers[name];
|
|
if (handlers != null) {
|
|
if (handlers.isNotEmpty) {
|
|
for (var handler in handlers.values) {
|
|
await handler(evt);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Start listening to the Rust core's events and frames.
|
|
void _startListenEvent(RustdeskImpl rustdeskImpl) {
|
|
final appType =
|
|
_appType == kAppTypeDesktopRemote ? '$_appType,$kWindowId' : _appType;
|
|
var sink = rustdeskImpl.startGlobalEventStream(appType: appType);
|
|
sink.listen((message) {
|
|
() async {
|
|
try {
|
|
Map<String, dynamic> event = json.decode(message);
|
|
// _tryHandle here may be more flexible than _eventCallback
|
|
if (!await tryHandle(event)) {
|
|
if (_eventCallback != null) {
|
|
await _eventCallback!(event);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
debugPrint('json.decode fail(): $e');
|
|
}
|
|
}();
|
|
});
|
|
}
|
|
|
|
void setEventCallback(StreamEventHandler 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<bool>(() => false);
|
|
return await _toAndroidChannel.invokeMethod(method, arguments);
|
|
}
|
|
|
|
void syncAndroidServiceAppDirConfigPath() {
|
|
invokeMethod(AndroidChannel.kSyncAppDirConfigPath, _dir);
|
|
}
|
|
}
|