From 7a2de5d280005410c71fcf9445bd0bd21dfcf9ff Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 3 Aug 2022 22:03:31 +0800 Subject: [PATCH] flutter_desktop: fix global envet stream shading && refactor platform ffi Signed-off-by: fufesou --- flutter/lib/common.dart | 30 +-- flutter/lib/consts.dart | 3 + .../lib/desktop/pages/connection_page.dart | 13 +- .../lib/desktop/pages/desktop_home_page.dart | 35 ++- .../lib/desktop/pages/file_manager_page.dart | 43 ++-- flutter/lib/desktop/pages/remote_page.dart | 68 +++--- flutter/lib/desktop/widgets/peer_widget.dart | 12 +- .../lib/desktop/widgets/peercard_widget.dart | 39 ++-- flutter/lib/main.dart | 78 ++++--- flutter/lib/models/ab_model.dart | 3 +- flutter/lib/models/file_model.dart | 178 ++++++++++----- flutter/lib/models/model.dart | 51 ++--- flutter/lib/models/native_model.dart | 44 ++-- flutter/lib/models/peer_model.dart | 11 +- flutter/lib/models/platform_model.dart | 7 + flutter/lib/models/user_model.dart | 12 +- flutter/lib/models/web_model.dart | 9 +- flutter/linux/CMakeLists.txt | 4 +- src/flutter.rs | 216 +++++++++--------- src/flutter_ffi.rs | 18 +- 20 files changed, 476 insertions(+), 398 deletions(-) create mode 100644 flutter/lib/models/platform_model.dart diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index eda1ed4e7..3ced905fc 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -7,15 +8,16 @@ import 'package:get/instance_manager.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'models/model.dart'; +import 'models/platform_model.dart'; final globalKey = GlobalKey(); final navigationBarKey = GlobalKey(); -var isAndroid = false; -var isIOS = false; +var isAndroid = Platform.isAndroid; +var isIOS = Platform.isIOS; var isWeb = false; var isWebDesktop = false; -var isDesktop = false; +var isDesktop = Platform.isWindows || Platform.isMacOS || Platform.isLinux; var version = ""; int androidVersion = 0; @@ -119,9 +121,9 @@ class DialogManager { static Future show(DialogBuilder builder, {bool clickMaskDismiss = false, - bool backDismiss = false, - String? tag, - bool useAnimation = true}) async { + bool backDismiss = false, + String? tag, + bool useAnimation = true}) async { final t; if (tag != null) { t = tag; @@ -146,10 +148,11 @@ class DialogManager { } class CustomAlertDialog extends StatelessWidget { - CustomAlertDialog({required this.title, - required this.content, - required this.actions, - this.contentPadding}); + CustomAlertDialog( + {required this.title, + required this.content, + required this.actions, + this.contentPadding}); final Widget title; final Widget content; @@ -162,7 +165,7 @@ class CustomAlertDialog extends StatelessWidget { scrollable: true, title: title, contentPadding: - EdgeInsets.symmetric(horizontal: contentPadding ?? 25, vertical: 10), + EdgeInsets.symmetric(horizontal: contentPadding ?? 25, vertical: 10), content: content, actions: actions, ); @@ -364,9 +367,8 @@ Future initGlobalFFI() async { _globalFFI = FFI(); // after `put`, can also be globally found by Get.find(); Get.put(_globalFFI, permanent: true); - await _globalFFI.ffiModel.init(); // trigger connection status updater - await _globalFFI.bind.mainCheckConnectStatus(); + await bind.mainCheckConnectStatus(); // global shared preference await Get.putAsync(() => SharedPreferences.getInstance()); -} \ No newline at end of file +} diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 8f647837f..eea49cf86 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -1 +1,4 @@ double kDesktopRemoteTabBarHeight = 48.0; +String kAppTypeMain = "main"; +String kAppTypeDesktopRemote = "remote"; +String kAppTypeDesktopFileTransfer = "file transfer"; diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index b6a89a48c..e32275373 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -16,6 +16,7 @@ import '../../mobile/pages/home_page.dart'; import '../../mobile/pages/scan_page.dart'; import '../../mobile/pages/settings_page.dart'; import '../../models/model.dart'; +import '../../models/platform_model.dart'; // enum RemoteType { recently, favorite, discovered, addressBook } @@ -428,10 +429,10 @@ class _ConnectionPageState extends State { updateStatus() async { svcStopped.value = gFFI.getOption("stop-service") == "Y"; - final status = jsonDecode(await gFFI.bind.mainGetConnectStatus()) - as Map; + final status = + jsonDecode(await bind.mainGetConnectStatus()) as Map; svcStatusCode.value = status["status_num"]; - svcIsUsingPublicServer.value = await gFFI.bind.mainIsUsingPublicServer(); + svcIsUsingPublicServer.value = await bind.mainIsUsingPublicServer(); } handleLogin() { @@ -906,13 +907,13 @@ class _PeerTabbedPageState extends State<_PeerTabbedPage> if (_tabController.indexIsChanging) { switch (_tabController.index) { case 0: - gFFI.bind.mainLoadRecentPeers(); + bind.mainLoadRecentPeers(); break; case 1: - gFFI.bind.mainLoadFavPeers(); + bind.mainLoadFavPeers(); break; case 2: - gFFI.bind.mainDiscover(); + bind.mainDiscover(); break; case 3: break; diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 54a52b774..0a86350d8 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -8,6 +8,7 @@ import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/desktop/pages/connection_page.dart'; import 'package:flutter_hbb/desktop/widgets/titlebar_widget.dart'; import 'package:flutter_hbb/models/model.dart'; +import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; @@ -630,13 +631,13 @@ class _DesktopHomePageState extends State with TrayListener { setState(() { msg = ""; isInProgress = true; - gFFI.bind.mainChangeId(newId: newId); + bind.mainChangeId(newId: newId); }); - var status = await gFFI.bind.mainGetAsyncStatus(); + var status = await bind.mainGetAsyncStatus(); while (status == " ") { await Future.delayed(Duration(milliseconds: 100)); - status = await gFFI.bind.mainGetAsyncStatus(); + status = await bind.mainGetAsyncStatus(); } if (status.isEmpty) { // ok @@ -655,8 +656,7 @@ class _DesktopHomePageState extends State with TrayListener { } void changeServer() async { - Map oldOptions = - jsonDecode(await gFFI.bind.mainGetOptions()); + Map oldOptions = jsonDecode(await bind.mainGetOptions()); print("${oldOptions}"); String idServer = oldOptions['custom-rendezvous-server'] ?? ""; var idServerMsg = ""; @@ -814,7 +814,7 @@ class _DesktopHomePageState extends State with TrayListener { if (idServer.isNotEmpty) { idServerMsg = translate( - await gFFI.bind.mainTestIfValidServer(server: idServer)); + await bind.mainTestIfValidServer(server: idServer)); if (idServerMsg.isEmpty) { oldOptions['custom-rendezvous-server'] = idServer; } else { @@ -826,8 +826,8 @@ class _DesktopHomePageState extends State with TrayListener { } if (relayServer.isNotEmpty) { - relayServerMsg = translate(await gFFI.bind - .mainTestIfValidServer(server: relayServer)); + relayServerMsg = translate( + await bind.mainTestIfValidServer(server: relayServer)); if (relayServerMsg.isEmpty) { oldOptions['relay-server'] = relayServer; } else { @@ -853,7 +853,7 @@ class _DesktopHomePageState extends State with TrayListener { } // ok oldOptions['key'] = key; - await gFFI.bind.mainSetOptions(json: jsonEncode(oldOptions)); + await bind.mainSetOptions(json: jsonEncode(oldOptions)); close(); }, child: Text(translate("OK"))), @@ -863,8 +863,7 @@ class _DesktopHomePageState extends State with TrayListener { } void changeWhiteList() async { - Map oldOptions = - jsonDecode(await gFFI.bind.mainGetOptions()); + Map oldOptions = jsonDecode(await bind.mainGetOptions()); var newWhiteList = ((oldOptions['whitelist'] ?? "") as String).split(','); var newWhiteListField = newWhiteList.join('\n'); var msg = ""; @@ -935,7 +934,7 @@ class _DesktopHomePageState extends State with TrayListener { newWhiteList = ips.join(','); } oldOptions['whitelist'] = newWhiteList; - await gFFI.bind.mainSetOptions(json: jsonEncode(oldOptions)); + await bind.mainSetOptions(json: jsonEncode(oldOptions)); close(); }, child: Text(translate("OK"))), @@ -945,7 +944,7 @@ class _DesktopHomePageState extends State with TrayListener { } void changeSocks5Proxy() async { - var socks = await gFFI.bind.mainGetSocks(); + var socks = await bind.mainGetSocks(); String proxy = ""; String proxyMsg = ""; @@ -1072,7 +1071,7 @@ class _DesktopHomePageState extends State with TrayListener { if (proxy.isNotEmpty) { proxyMsg = translate( - await gFFI.bind.mainTestIfValidServer(server: proxy)); + await bind.mainTestIfValidServer(server: proxy)); if (proxyMsg.isEmpty) { // ignore } else { @@ -1080,7 +1079,7 @@ class _DesktopHomePageState extends State with TrayListener { return; } } - await gFFI.bind.mainSetSocks( + await bind.mainSetSocks( proxy: proxy, username: username, password: password); close(); }, @@ -1091,9 +1090,9 @@ class _DesktopHomePageState extends State with TrayListener { } void about() async { - final appName = await gFFI.bind.mainGetAppName(); - final license = await gFFI.bind.mainGetLicense(); - final version = await gFFI.bind.mainGetVersion(); + final appName = await bind.mainGetAppName(); + final license = await bind.mainGetLicense(); + final version = await bind.mainGetVersion(); final linkStyle = TextStyle(decoration: TextDecoration.underline); DialogManager.show((setState, close) { return CustomAlertDialog( diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index e37f56404..581a38a3a 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -11,6 +11,7 @@ import 'package:wakelock/wakelock.dart'; import '../../common.dart'; import '../../models/model.dart'; +import '../../models/platform_model.dart'; class FileManagerPage extends StatefulWidget { FileManagerPage({Key? key, required this.id}) : super(key: key); @@ -37,7 +38,7 @@ class _FileManagerPageState extends State @override void initState() { super.initState(); - Get.put(FFI.newFFI()..connect(widget.id, isFileTransfer: true), + Get.put(FFI()..connect(widget.id, isFileTransfer: true), tag: 'ft_${widget.id}'); // _ffi.ffiModel.updateEventListener(widget.id); if (!Platform.isLinux) { @@ -464,13 +465,15 @@ class _FileManagerPageState extends State decoration: BoxDecoration(color: Colors.blue), padding: EdgeInsets.all(8.0), child: FutureBuilder( - future: _ffi.bind.sessionGetPlatform( + future: bind.sessionGetPlatform( id: _ffi.id, isRemote: !isLocal), builder: (context, snapshot) { if (snapshot.hasData && snapshot.data!.isNotEmpty) { return getPlatformImage('${snapshot.data}'); } else { - return CircularProgressIndicator(color: Colors.white,); + return CircularProgressIndicator( + color: Colors.white, + ); } })), Text(isLocal @@ -505,21 +508,25 @@ class _FileManagerPageState extends State border: Border.all(color: Colors.black12)), child: TextField( decoration: InputDecoration( - border: InputBorder.none, - isDense: true, - prefix: Padding(padding: EdgeInsets.only(left: 4.0)), - suffix: DropdownButton( - isDense: true, - underline: Offstage(), - items: [ - // TODO: favourite - DropdownMenuItem(child: Text('/'), value: '/',) - ], onChanged: (path) { - if (path is String && path.isNotEmpty){ - model.openDirectory(path, isLocal: isLocal); - } - }) - ), + border: InputBorder.none, + isDense: true, + prefix: + Padding(padding: EdgeInsets.only(left: 4.0)), + suffix: DropdownButton( + isDense: true, + underline: Offstage(), + items: [ + // TODO: favourite + DropdownMenuItem( + child: Text('/'), + value: '/', + ) + ], + onChanged: (path) { + if (path is String && path.isNotEmpty) { + model.openDirectory(path, isLocal: isLocal); + } + })), controller: TextEditingController( text: isLocal ? model.currentLocalDir.path diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index ef5fb1c0b..0a1979540 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -16,10 +16,10 @@ import 'package:wakelock/wakelock.dart'; // import 'package:window_manager/window_manager.dart'; import '../../common.dart'; -import '../../consts.dart'; import '../../mobile/widgets/dialog.dart'; import '../../mobile/widgets/overlay.dart'; import '../../models/model.dart'; +import '../../models/platform_model.dart'; final initText = '\1' * 1024; @@ -59,8 +59,6 @@ class _RemotePageState extends State var ffitmp = FFI(); ffitmp.canvasModel.tabBarHeight = super.widget.tabBarHeight; final ffi = Get.put(ffitmp, tag: widget.id); - // note: a little trick - ffi.ffiModel.platformFFI = gFFI.ffiModel.platformFFI; ffi.connect(widget.id, tabBarHeight: super.widget.tabBarHeight); WidgetsBinding.instance.addPostFrameCallback((_) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); @@ -157,7 +155,7 @@ class _RemotePageState extends State if (newValue.length > common) { var s = newValue.substring(common); if (s.length > 1) { - _ffi.bind.sessionInputString(id: widget.id, value: s); + bind.sessionInputString(id: widget.id, value: s); } else { inputChar(s); } @@ -191,11 +189,11 @@ class _RemotePageState extends State content == '()' || content == '【】')) { // can not only input content[0], because when input ], [ are also auo insert, which cause ] never be input - _ffi.bind.sessionInputString(id: widget.id, value: content); + bind.sessionInputString(id: widget.id, value: content); openKeyboard(); return; } - _ffi.bind.sessionInputString(id: widget.id, value: content); + bind.sessionInputString(id: widget.id, value: content); } else { inputChar(content); } @@ -509,8 +507,8 @@ class _RemotePageState extends State id: widget.id, ) ]; - final cursor = _ffi.bind - .getSessionToggleOptionSync(id: widget.id, arg: 'show-remote-cursor'); + final cursor = bind.getSessionToggleOptionSync( + id: widget.id, arg: 'show-remote-cursor'); if (keyboard || cursor) { paints.add(CursorPaint( id: widget.id, @@ -519,10 +517,10 @@ class _RemotePageState extends State paints.add(getHelpTools()); return MouseRegion( onEnter: (evt) { - _ffi.bind.hostStopSystemKeyPropagate(stopped: false); + bind.hostStopSystemKeyPropagate(stopped: false); }, onExit: (evt) { - _ffi.bind.hostStopSystemKeyPropagate(stopped: true); + bind.hostStopSystemKeyPropagate(stopped: true); }, child: Container( color: MyTheme.canvasColor, @@ -601,7 +599,7 @@ class _RemotePageState extends State more.add(PopupMenuItem( child: Text(translate('Insert Lock')), value: 'lock')); if (pi.platform == 'Windows' && - await _ffi.bind.getSessionToggleOption(id: id, arg: 'privacy-mode') != + await bind.getSessionToggleOption(id: id, arg: 'privacy-mode') != true) { more.add(PopupMenuItem( child: Text(translate((_ffi.ffiModel.inputBlocked ? 'Unb' : 'B') + @@ -617,28 +615,27 @@ class _RemotePageState extends State elevation: 8, ); if (value == 'cad') { - _ffi.bind.sessionCtrlAltDel(id: widget.id); + bind.sessionCtrlAltDel(id: widget.id); } else if (value == 'lock') { - _ffi.bind.sessionLockScreen(id: widget.id); + bind.sessionLockScreen(id: widget.id); } else if (value == 'block-input') { - _ffi.bind.sessionToggleOption( + bind.sessionToggleOption( id: widget.id, value: (_ffi.ffiModel.inputBlocked ? 'un' : '') + 'block-input'); _ffi.ffiModel.inputBlocked = !_ffi.ffiModel.inputBlocked; } else if (value == 'refresh') { - _ffi.bind.sessionRefresh(id: widget.id); + bind.sessionRefresh(id: widget.id); } else if (value == 'paste') { () async { ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); if (data != null && data.text != null) { - _ffi.bind.sessionInputString(id: widget.id, value: data.text ?? ""); + bind.sessionInputString(id: widget.id, value: data.text ?? ""); } }(); } else if (value == 'enter_os_password') { - var password = - await _ffi.bind.getSessionOption(id: id, arg: "os-password"); + var password = await bind.getSessionOption(id: id, arg: "os-password"); if (password != null) { - _ffi.bind.sessionInputOsPassword(id: widget.id, value: password); + bind.sessionInputOsPassword(id: widget.id, value: password); } else { showSetOSPassword(widget.id, true); } @@ -666,7 +663,7 @@ class _RemotePageState extends State onTouchModeChange: (t) { _ffi.ffiModel.toggleTouchMode(); final v = _ffi.ffiModel.touchMode ? 'Y' : ''; - _ffi.bind.sessionPeerOption( + bind.sessionPeerOption( id: widget.id, name: "touch-mode", value: v); })); })); @@ -892,12 +889,12 @@ class ImagePainter extends CustomPainter { CheckboxListTile getToggle( String id, void Function(void Function()) setState, option, name) { - final opt = ffi(id).bind.getSessionToggleOptionSync(id: id, arg: option); + final opt = bind.getSessionToggleOptionSync(id: id, arg: option); return CheckboxListTile( value: opt, onChanged: (v) { setState(() { - ffi(id).bind.sessionToggleOption(id: id, value: option); + bind.sessionToggleOption(id: id, value: option); }); }, dense: true, @@ -917,11 +914,10 @@ RadioListTile getRadio(String name, String toValue, String curValue, } void showOptions(String id) async { - String quality = - await ffi(id).bind.getSessionImageQuality(id: id) ?? 'balanced'; + String quality = await bind.getSessionImageQuality(id: id) ?? 'balanced'; if (quality == '') quality = 'balanced'; String viewStyle = - await ffi(id).bind.getSessionOption(id: id, arg: 'view-style') ?? ''; + await bind.getSessionOption(id: id, arg: 'view-style') ?? ''; var displays = []; final pi = ffi(id).ffiModel.pi; final image = ffi(id).ffiModel.getConnectionImage(); @@ -934,7 +930,7 @@ void showOptions(String id) async { children.add(InkWell( onTap: () { if (i == cur) return; - ffi(id).bind.sessionSwitchDisplay(id: id, value: i); + bind.sessionSwitchDisplay(id: id, value: i); SmartDialog.dismiss(); }, child: Ink( @@ -979,16 +975,14 @@ void showOptions(String id) async { if (value == null) return; setState(() { quality = value; - ffi(id).bind.sessionSetImageQuality(id: id, value: value); + bind.sessionSetImageQuality(id: id, value: value); }); }; var setViewStyle = (String? value) { if (value == null) return; setState(() { viewStyle = value; - ffi(id) - .bind - .sessionPeerOption(id: id, name: "view-style", value: value); + bind.sessionPeerOption(id: id, name: "view-style", value: value); ffi(id).canvasModel.updateViewStyle(); }); }; @@ -1018,10 +1012,8 @@ void showOptions(String id) async { void showSetOSPassword(String id, bool login) async { final controller = TextEditingController(); - var password = - await ffi(id).bind.getSessionOption(id: id, arg: "os-password") ?? ""; - var autoLogin = - await ffi(id).bind.getSessionOption(id: id, arg: "auto-login") != ""; + var password = await bind.getSessionOption(id: id, arg: "os-password") ?? ""; + var autoLogin = await bind.getSessionOption(id: id, arg: "auto-login") != ""; controller.text = password; DialogManager.show((setState, close) { return CustomAlertDialog( @@ -1054,13 +1046,11 @@ void showSetOSPassword(String id, bool login) async { style: flatButtonStyle, onPressed: () { var text = controller.text.trim(); - ffi(id) - .bind - .sessionPeerOption(id: id, name: "os-password", value: text); - ffi(id).bind.sessionPeerOption( + bind.sessionPeerOption(id: id, name: "os-password", value: text); + bind.sessionPeerOption( id: id, name: "auto-login", value: autoLogin ? 'Y' : ''); if (text != "" && login) { - ffi(id).bind.sessionInputOsPassword(id: id, value: text); + bind.sessionInputOsPassword(id: id, value: text); } close(); }, diff --git a/flutter/lib/desktop/widgets/peer_widget.dart b/flutter/lib/desktop/widgets/peer_widget.dart index 4705516f5..1a66f3a06 100644 --- a/flutter/lib/desktop/widgets/peer_widget.dart +++ b/flutter/lib/desktop/widgets/peer_widget.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; @@ -8,6 +7,7 @@ import 'package:visibility_detector/visibility_detector.dart'; import 'package:window_manager/window_manager.dart'; import '../../models/peer_model.dart'; +import '../../models/platform_model.dart'; import '../../common.dart'; import 'peercard_widget.dart'; @@ -116,7 +116,7 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener { if (!setEquals(_curPeers, _lastQueryPeers)) { if (now.difference(_lastChangeTime) > Duration(seconds: 1)) { if (_curPeers.length > 0) { - gFFI.ffiModel.platformFFI.ffiBind + platformFFI.ffiBind .queryOnlines(ids: _curPeers.toList(growable: false)); _lastQueryPeers = {..._curPeers}; _lastQueryTime = DateTime.now(); @@ -127,7 +127,7 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener { if (_queryCoun < _maxQueryCount) { if (now.difference(_lastQueryTime) > Duration(seconds: 20)) { if (_curPeers.length > 0) { - gFFI.ffiModel.platformFFI.ffiBind + platformFFI.ffiBind .queryOnlines(ids: _curPeers.toList(growable: false)); _lastQueryTime = DateTime.now(); _queryCoun += 1; @@ -169,7 +169,7 @@ class RecentPeerWidget extends BasePeerWidget { @override Widget build(BuildContext context) { final widget = super.build(context); - gFFI.bind.mainLoadRecentPeers(); + bind.mainLoadRecentPeers(); return widget; } } @@ -186,7 +186,7 @@ class FavoritePeerWidget extends BasePeerWidget { @override Widget build(BuildContext context) { final widget = super.build(context); - gFFI.bind.mainLoadFavPeers(); + bind.mainLoadFavPeers(); return widget; } } @@ -203,7 +203,7 @@ class DiscoveredPeerWidget extends BasePeerWidget { @override Widget build(BuildContext context) { final widget = super.build(context); - gFFI.bind.mainLoadLanPeers(); + bind.mainLoadLanPeers(); return widget; } } diff --git a/flutter/lib/desktop/widgets/peercard_widget.dart b/flutter/lib/desktop/widgets/peercard_widget.dart index 3a4dbfada..0782e8426 100644 --- a/flutter/lib/desktop/widgets/peercard_widget.dart +++ b/flutter/lib/desktop/widgets/peercard_widget.dart @@ -5,9 +5,11 @@ import 'package:get/get.dart'; import '../../common.dart'; import '../../models/model.dart'; +import '../../models/platform_model.dart'; import '../../models/peer_model.dart'; typedef PopupMenuItemsFunc = Future>> Function(); + enum PeerType { recent, fav, discovered, ab } class _PeerCard extends StatefulWidget { @@ -15,10 +17,11 @@ class _PeerCard extends StatefulWidget { final PopupMenuItemsFunc popupMenuItemsFunc; final PeerType type; - _PeerCard({required this.peer, - required this.popupMenuItemsFunc, - Key? key, - required this.type}) + _PeerCard( + {required this.peer, + required this.popupMenuItemsFunc, + Key? key, + required this.type}) : super(key: key); @override @@ -54,9 +57,10 @@ class _PeerCardState extends State<_PeerCard> )); } - Widget _buildPeerTile(BuildContext context, Peer peer, Rx deco) { + Widget _buildPeerTile( + BuildContext context, Peer peer, Rx deco) { return Obx( - () => Container( + () => Container( decoration: deco.value, child: Column( mainAxisSize: MainAxisSize.min, @@ -135,7 +139,7 @@ class _PeerCardState extends State<_PeerCard> child: CircleAvatar( radius: 5, backgroundColor: - peer.online ? Colors.green : Colors.yellow)), + peer.online ? Colors.green : Colors.yellow)), Text('${peer.id}') ]), InkWell( @@ -183,12 +187,13 @@ class _PeerCardState extends State<_PeerCard> ); if (value == 'remove') { setState(() => gFFI.setByName('remove', '$id')); - () async { + () async { removePreference(id); }(); } else if (value == 'file') { _connect(id, isFileTransfer: true); - } else if (value == 'add-fav') {} else if (value == 'connect') { + } else if (value == 'add-fav') { + } else if (value == 'connect') { _connect(id, isFileTransfer: false); } else if (value == 'ab-delete') { gFFI.abModel.deletePeer(id); @@ -199,7 +204,7 @@ class _PeerCardState extends State<_PeerCard> } else if (value == 'rename') { _rename(id); } else if (value == 'unremember-password') { - await gFFI.bind.mainForgetPassword(id: id); + await bind.mainForgetPassword(id: id); } } @@ -220,7 +225,7 @@ class _PeerCardState extends State<_PeerCard> child: GestureDetector( onTap: onTap, child: Obx( - () => Container( + () => Container( decoration: BoxDecoration( color: rxTags.contains(tagName) ? Colors.blue : null, border: Border.all(color: MyTheme.darkGray), @@ -264,12 +269,12 @@ class _PeerCardState extends State<_PeerCard> child: Wrap( children: tags .map((e) => _buildTag(e, selectedTag, onTap: () { - if (selectedTag.contains(e)) { - selectedTag.remove(e); - } else { - selectedTag.add(e); - } - })) + if (selectedTag.contains(e)) { + selectedTag.remove(e); + } else { + selectedTag.add(e); + } + })) .toList(growable: false), ), ), diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index f2ebb3134..bceb8fa8a 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -13,6 +13,8 @@ import 'package:provider/provider.dart'; // import 'package:window_manager/window_manager.dart'; import 'common.dart'; +import 'consts.dart'; +import 'models/platform_model.dart'; import 'mobile/pages/home_page.dart'; import 'mobile/pages/server_page.dart'; import 'mobile/pages/settings_page.dart'; @@ -21,25 +23,9 @@ int? windowId; Future main(List args) async { WidgetsFlutterBinding.ensureInitialized(); - // global FFI, use this **ONLY** for global configuration - // for convenience, use global FFI on mobile platform - // focus on multi-ffi on desktop first - await initGlobalFFI(); - // await Firebase.initializeApp(); - if (isAndroid) { - toAndroidChannelInit(); - } - refreshCurrentUser(); - runRustDeskApp(args); -} -ThemeData getCurrentTheme() { - return isDarkTheme() ? MyTheme.darkTheme : MyTheme.darkTheme; -} - -void runRustDeskApp(List args) async { if (!isDesktop) { - runApp(App()); + runMainApp(false); return; } // main window @@ -52,28 +38,62 @@ void runRustDeskApp(List args) async { WindowType wType = type.windowType; switch (wType) { case WindowType.RemoteDesktop: - runApp(GetMaterialApp( - theme: getCurrentTheme(), - home: DesktopRemoteScreen( - params: argument, - ), - )); + runRemoteScreen(argument); break; case WindowType.FileTransfer: - runApp(GetMaterialApp( - theme: getCurrentTheme(), - home: DesktopFileTransferScreen(params: argument))); + runFileTransferScreen(argument); break; default: break; } } else { + runMainApp(true); + } +} + +ThemeData getCurrentTheme() { + return isDarkTheme() ? MyTheme.darkTheme : MyTheme.darkTheme; +} + +Future initEnv(String appType) async { + await platformFFI.init(appType); + // global FFI, use this **ONLY** for global configuration + // for convenience, use global FFI on mobile platform + // focus on multi-ffi on desktop first + await initGlobalFFI(); + // await Firebase.initializeApp(); + if (isAndroid) { + toAndroidChannelInit(); + } + refreshCurrentUser(); +} + +void runMainApp(bool startService) async { + await initEnv(kAppTypeMain); + if (startService) { // await windowManager.ensureInitialized(); // disable tray // initTray(); gFFI.serverModel.startService(); - runApp(App()); } + runApp(App()); +} + +void runRemoteScreen(Map argument) async { + await initEnv(kAppTypeDesktopRemote); + runApp(GetMaterialApp( + theme: getCurrentTheme(), + home: DesktopRemoteScreen( + params: argument, + ), + )); +} + +void runFileTransferScreen(Map argument) async { + await initEnv(kAppTypeDesktopFileTransfer); + runApp(GetMaterialApp( + theme: getCurrentTheme(), + home: DesktopFileTransferScreen(params: argument))); } class App extends StatelessWidget { @@ -108,8 +128,8 @@ class App extends StatelessWidget { builder: FlutterSmartDialog.init( builder: isAndroid ? (_, child) => AccessibilityListener( - child: child, - ) + child: child, + ) : null)), ); } diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index bfdb6fa1a..b9740ed8f 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/models/model.dart'; +import 'package:flutter_hbb/models/platform_model.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; @@ -46,7 +47,7 @@ class AbModel with ChangeNotifier { } Future getApiServer() async { - return await _ffi?.bind.mainGetApiServer() ?? ""; + return await bind.mainGetApiServer() ?? ""; } void reset() { diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index 5bca33303..e86ac1de2 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -9,6 +9,7 @@ import 'package:get/get.dart'; import 'package:path/path.dart' as Path; import 'model.dart'; +import 'platform_model.dart'; enum SortBy { Name, Type, Modified, Size } @@ -50,7 +51,7 @@ class FileModel extends ChangeNotifier { bool get localSortAscending => _localSortAscending; - SortBy getSortStyle(bool isLocal){ + SortBy getSortStyle(bool isLocal) { return isLocal ? _localSortStyle : _remoteSortStyle; } @@ -164,7 +165,7 @@ class FileModel extends ChangeNotifier { // Desktop uses jobTable // id = index + 1 final jobIndex = getJob(id); - if (jobIndex >= 0 && _jobTable.length > jobIndex){ + if (jobIndex >= 0 && _jobTable.length > jobIndex) { final job = _jobTable[jobIndex]; job.fileNum = int.parse(evt['file_num']); job.speed = double.parse(evt['speed']); @@ -203,8 +204,7 @@ class FileModel extends ChangeNotifier { debugPrint("init remote home:${fd.path}"); _currentRemoteDir = fd; } - } - finally {} + } finally {} } _fileFetcher.tryCompleteTask(evt['value'], evt['is_local']); notifyListeners(); @@ -260,7 +260,7 @@ class FileModel extends ChangeNotifier { final id = int.tryParse(evt['id']) ?? 0; if (false == resp) { final jobIndex = getJob(id); - if (jobIndex != -1){ + if (jobIndex != -1) { cancelJob(id); final job = jobTable[jobIndex]; job.state = JobState.done; @@ -274,9 +274,12 @@ class FileModel extends ChangeNotifier { // overwrite need_override = true; } - _ffi.target?.bind.sessionSetConfirmOverrideFile(id: _ffi.target?.id ?? "", - actId: id, fileNum: int.parse(evt['file_num']), - needOverride: need_override, remember: fileConfirmCheckboxRemember, + bind.sessionSetConfirmOverrideFile( + id: _ffi.target?.id ?? "", + actId: id, + fileNum: int.parse(evt['file_num']), + needOverride: need_override, + remember: fileConfirmCheckboxRemember, isUpload: evt['is_upload'] == "true"); } } @@ -288,21 +291,27 @@ class FileModel extends ChangeNotifier { onReady() async { _localOption.home = _ffi.target?.getByName("get_home_dir") ?? ""; - _localOption.showHidden = (await _ffi.target?.bind.sessionGetPeerOption - (id: _ffi.target?.id ?? "", name: "local_show_hidden"))?.isNotEmpty ?? false; + _localOption.showHidden = (await bind.sessionGetPeerOption( + id: _ffi.target?.id ?? "", name: "local_show_hidden")) + ?.isNotEmpty ?? + false; - _remoteOption.showHidden = (await _ffi.target?.bind.sessionGetPeerOption - (id: _ffi.target?.id ?? "", name: "remote_show_hidden"))?.isNotEmpty ?? false; + _remoteOption.showHidden = (await bind.sessionGetPeerOption( + id: _ffi.target?.id ?? "", name: "remote_show_hidden")) + ?.isNotEmpty ?? + false; _remoteOption.isWindows = _ffi.target?.ffiModel.pi.platform == "Windows"; debugPrint("remote platform: ${_ffi.target?.ffiModel.pi.platform}"); await Future.delayed(Duration(milliseconds: 100)); - final local = (await _ffi.target?.bind.sessionGetPeerOption - (id: _ffi.target?.id ?? "", name: "local_dir")) ?? ""; - final remote = (await _ffi.target?.bind.sessionGetPeerOption - (id: _ffi.target?.id ?? "", name: "remote_dir")) ?? ""; + final local = (await bind.sessionGetPeerOption( + id: _ffi.target?.id ?? "", name: "local_dir")) ?? + ""; + final remote = (await bind.sessionGetPeerOption( + id: _ffi.target?.id ?? "", name: "remote_dir")) ?? + ""; openDirectory(local.isEmpty ? _localOption.home : local, isLocal: true); openDirectory(remote.isEmpty ? _remoteOption.home : remote, isLocal: false); await Future.delayed(Duration(seconds: 1)); @@ -313,7 +322,7 @@ class FileModel extends ChangeNotifier { openDirectory(_remoteOption.home, isLocal: false); } // load last transfer jobs - await _ffi.target?.bind.sessionLoadLastTransferJobs(id: '${_ffi.target?.id}'); + await bind.sessionLoadLastTransferJobs(id: '${_ffi.target?.id}'); } onClose() { @@ -327,8 +336,8 @@ class FileModel extends ChangeNotifier { msgMap["remote_dir"] = _currentRemoteDir.path; msgMap["remote_show_hidden"] = _remoteOption.showHidden ? "Y" : ""; final id = _ffi.target?.id ?? ""; - for(final msg in msgMap.entries) { - _ffi.target?.bind.sessionPeerOption(id: id, name: msg.key, value: msg.value); + for (final msg in msgMap.entries) { + bind.sessionPeerOption(id: id, name: msg.key, value: msg.value); } _currentLocalDir.clear(); _currentRemoteDir.clear(); @@ -339,8 +348,9 @@ class FileModel extends ChangeNotifier { Future refresh({bool? isLocal}) async { if (isDesktop) { isLocal = isLocal ?? _isLocal; - await isLocal ? openDirectory(currentLocalDir.path, isLocal: isLocal) : - openDirectory(currentRemoteDir.path, isLocal: isLocal); + await isLocal + ? openDirectory(currentLocalDir.path, isLocal: isLocal) + : openDirectory(currentRemoteDir.path, isLocal: isLocal); } else { await openDirectory(currentDir.path); } @@ -353,7 +363,9 @@ class FileModel extends ChangeNotifier { final isWindows = isLocal ? _localOption.isWindows : _remoteOption.isWindows; // process /C:\ -> C:\ on Windows - if (isLocal ? _localOption.isWindows : _remoteOption.isWindows && path.length > 1 && path[0] == '/') { + if (isLocal + ? _localOption.isWindows + : _remoteOption.isWindows && path.length > 1 && path[0] == '/') { path = path.substring(1); if (path[path.length - 1] != '\\') { path = path + "\\"; @@ -380,7 +392,8 @@ class FileModel extends ChangeNotifier { goToParentDirectory({bool? isLocal}) { isLocal = isLocal ?? _isLocal; - final isWindows = isLocal ? _localOption.isWindows : _remoteOption.isWindows; + final isWindows = + isLocal ? _localOption.isWindows : _remoteOption.isWindows; final currDir = isLocal ? currentLocalDir : currentRemoteDir; var parent = PathUtil.dirname(currDir.path, isWindows); // specially for C:\, D:\, goto '/' @@ -395,12 +408,11 @@ class FileModel extends ChangeNotifier { sendFiles(SelectedItems items, {bool isRemote = false}) { if (isDesktop) { // desktop sendFiles - final toPath = - isRemote ? currentLocalDir.path : currentRemoteDir.path; + final toPath = isRemote ? currentLocalDir.path : currentRemoteDir.path; final isWindows = - isRemote ? _localOption.isWindows : _remoteOption.isWindows; + isRemote ? _localOption.isWindows : _remoteOption.isWindows; final showHidden = - isRemote ? _localOption.showHidden : _remoteOption.showHidden; + isRemote ? _localOption.showHidden : _remoteOption.showHidden; items.items.forEach((from) async { final jobId = ++_jobId; _jobTable.add(JobProgress() @@ -408,11 +420,17 @@ class FileModel extends ChangeNotifier { ..totalSize = from.size ..state = JobState.inProgress ..id = jobId - ..isRemote = isRemote - ); - _ffi.target?.bind.sessionSendFiles(id: '${_ffi.target?.id}', actId: _jobId, path: from.path, to: PathUtil.join(toPath, from.name, isWindows) - ,fileNum: 0, includeHidden: showHidden, isRemote: isRemote); - print("path:${from.path}, toPath:${toPath}, to:${PathUtil.join(toPath, from.name, isWindows)}"); + ..isRemote = isRemote); + bind.sessionSendFiles( + id: '${_ffi.target?.id}', + actId: _jobId, + path: from.path, + to: PathUtil.join(toPath, from.name, isWindows), + fileNum: 0, + includeHidden: showHidden, + isRemote: isRemote); + print( + "path:${from.path}, toPath:${toPath}, to:${PathUtil.join(toPath, from.name, isWindows)}"); }); } else { if (items.isLocal == null) { @@ -421,15 +439,21 @@ class FileModel extends ChangeNotifier { } _jobProgress.state = JobState.inProgress; final toPath = - items.isLocal! ? currentRemoteDir.path : currentLocalDir.path; + items.isLocal! ? currentRemoteDir.path : currentLocalDir.path; final isWindows = - items.isLocal! ? _localOption.isWindows : _remoteOption.isWindows; + items.isLocal! ? _localOption.isWindows : _remoteOption.isWindows; final showHidden = - items.isLocal! ? _localOption.showHidden : _remoteOption.showHidden; + items.isLocal! ? _localOption.showHidden : _remoteOption.showHidden; items.items.forEach((from) async { _jobId++; - await _ffi.target?.bind.sessionSendFiles(id: '${_ffi.target?.getId()}', actId: _jobId, path: from.path, to: PathUtil.join(toPath, from.name, isWindows) - ,fileNum: 0, includeHidden: showHidden, isRemote: !(items.isLocal!)); + await bind.sessionSendFiles( + id: '${_ffi.target?.getId()}', + actId: _jobId, + path: from.path, + to: PathUtil.join(toPath, from.name, isWindows), + fileNum: 0, + includeHidden: showHidden, + isRemote: !(items.isLocal!)); }); } } @@ -626,21 +650,34 @@ class FileModel extends ChangeNotifier { } sendRemoveFile(String path, int fileNum, bool isLocal) { - _ffi.target?.bind.sessionRemoveFile(id: '${_ffi.target?.id}', actId: _jobId, path: path, isRemote: !isLocal, fileNum: fileNum); + bind.sessionRemoveFile( + id: '${_ffi.target?.id}', + actId: _jobId, + path: path, + isRemote: !isLocal, + fileNum: fileNum); } sendRemoveEmptyDir(String path, int fileNum, bool isLocal) { - _ffi.target?.bind.sessionRemoveAllEmptyDirs(id: '${_ffi.target?.id}', actId: _jobId, path: path, isRemote: !isLocal); + bind.sessionRemoveAllEmptyDirs( + id: '${_ffi.target?.id}', + actId: _jobId, + path: path, + isRemote: !isLocal); } createDir(String path, {bool? isLocal}) async { isLocal = isLocal ?? this.isLocal; _jobId++; - _ffi.target?.bind.sessionCreateDir(id: '${_ffi.target?.id}', actId: _jobId, path: path, isRemote: !isLocal); + bind.sessionCreateDir( + id: '${_ffi.target?.id}', + actId: _jobId, + path: path, + isRemote: !isLocal); } cancelJob(int id) async { - _ffi.target?.bind.sessionCancelJob(id: '${_ffi.target?.id}', actId: id); + bind.sessionCancelJob(id: '${_ffi.target?.id}', actId: id); jobReset(); } @@ -650,14 +687,18 @@ class FileModel extends ChangeNotifier { // compatible for mobile logic _currentLocalDir.changeSortStyle(sort, ascending: ascending); _currentRemoteDir.changeSortStyle(sort, ascending: ascending); - _localSortStyle = sort; _localSortAscending = ascending; - _remoteSortStyle = sort; _remoteSortAscending = ascending; + _localSortStyle = sort; + _localSortAscending = ascending; + _remoteSortStyle = sort; + _remoteSortAscending = ascending; } else if (isLocal) { _currentLocalDir.changeSortStyle(sort, ascending: ascending); - _localSortStyle = sort; _localSortAscending = ascending; + _localSortStyle = sort; + _localSortAscending = ascending; } else { _currentRemoteDir.changeSortStyle(sort, ascending: ascending); - _remoteSortStyle = sort; _remoteSortAscending = ascending; + _remoteSortStyle = sort; + _remoteSortAscending = ascending; } notifyListeners(); } @@ -668,7 +709,7 @@ class FileModel extends ChangeNotifier { void updateFolderFiles(Map evt) { // ret: "{\"id\":1,\"num_entries\":12,\"total_size\":1264822.0}" - Map info = json.decode(evt['info']); + Map info = json.decode(evt['info']); int id = info['id']; int num_entries = info['num_entries']; double total_size = info['total_size']; @@ -685,7 +726,7 @@ class FileModel extends ChangeNotifier { void loadLastJob(Map evt) { debugPrint("load last job: ${evt}"); - Map jobDetail = json.decode(evt['value']); + Map jobDetail = json.decode(evt['value']); // int id = int.parse(jobDetail['id']); String remote = jobDetail['remote']; String to = jobDetail['to']; @@ -703,13 +744,14 @@ class FileModel extends ChangeNotifier { ..showHidden = showHidden ..state = JobState.paused; jobTable.add(jobProgress); - _ffi.target?.bind.sessionAddJob(id: '${_ffi.target?.id}', - isRemote: isRemote, - includeHidden: showHidden, - actId: currJobId, - path: isRemote ? remote : to, - to: isRemote ? to: remote, - fileNum: fileNum, + bind.sessionAddJob( + id: '${_ffi.target?.id}', + isRemote: isRemote, + includeHidden: showHidden, + actId: currJobId, + path: isRemote ? remote : to, + to: isRemote ? to : remote, + fileNum: fileNum, ); } @@ -717,9 +759,8 @@ class FileModel extends ChangeNotifier { final jobIndex = getJob(jobId); if (jobIndex != -1) { final job = jobTable[jobIndex]; - _ffi.target?.bind.sessionResumeJob(id: '${_ffi.target?.id}', - actId: job.id, - isRemote: job.isRemote); + bind.sessionResumeJob( + id: '${_ffi.target?.id}', actId: job.id, isRemote: job.isRemote); job.state = JobState.inProgress; } else { debugPrint("jobId ${jobId} is not exists"); @@ -844,12 +885,12 @@ class FileFetcher { String path, bool isLocal, bool showHidden) async { try { if (isLocal) { - final res = await _ffi.bind.sessionReadLocalDirSync( + final res = await bind.sessionReadLocalDirSync( id: id ?? "", path: path, showHidden: showHidden); final fd = FileDirectory.fromJson(jsonDecode(res)); return fd; } else { - await _ffi.bind.sessionReadRemoteDir( + await bind.sessionReadRemoteDir( id: id ?? "", path: path, includeHidden: showHidden); return registerReadTask(isLocal, path); } @@ -862,7 +903,12 @@ class FileFetcher { int id, String path, bool isLocal, bool showHidden) async { // TODO test Recursive is show hidden default? try { - await _ffi.bind.sessionReadDirRecursive(id: _ffi.id, actId: id, path: path, isRemote: !isLocal, showHidden: showHidden); + await bind.sessionReadDirRecursive( + id: _ffi.id, + actId: id, + path: path, + isRemote: !isLocal, + showHidden: showHidden); return registerReadRecursiveTask(id); } catch (e) { return Future.error(e); @@ -1033,7 +1079,9 @@ List _sortList(List list, SortBy sortType, bool ascending) { files.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); // first folders will go to list (if available) then files will go to list. - return ascending ? [...dirs, ...files] : [...dirs.reversed.toList(), ...files.reversed.toList()]; + return ascending + ? [...dirs, ...files] + : [...dirs.reversed.toList(), ...files.reversed.toList()]; } else if (sortType == SortBy.Modified) { // making the list of Path & DateTime List<_PathStat> _pathStat = []; @@ -1065,7 +1113,9 @@ List _sortList(List list, SortBy sortType, bool ascending) { .split('.') .last .compareTo(b.name.toLowerCase().split('.').last)); - return ascending ? [...dirs, ...files]: [...dirs.reversed.toList(), ...files.reversed.toList()]; + return ascending + ? [...dirs, ...files] + : [...dirs.reversed.toList(), ...files.reversed.toList()]; } else if (sortType == SortBy.Size) { // create list of path and size Map _sizeMap = {}; @@ -1090,7 +1140,9 @@ List _sortList(List list, SortBy sortType, bool ascending) { .indexWhere((element) => element.key == a.name) .compareTo( _sizeMapList.indexWhere((element) => element.key == b.name))); - return ascending ? [...dirs, ...files]: [...dirs.reversed.toList(), ...files.reversed.toList()]; + return ascending + ? [...dirs, ...files] + : [...dirs.reversed.toList(), ...files.reversed.toList()]; } return []; } diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 11415eeef..5c83a124c 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -20,8 +20,8 @@ import 'package:tuple/tuple.dart'; import '../common.dart'; import '../mobile/widgets/dialog.dart'; import '../mobile/widgets/overlay.dart'; -import 'native_model.dart' if (dart.library.html) 'web_model.dart'; import 'peer_model.dart'; +import 'platform_model.dart'; typedef HandleMsgBox = void Function(Map evt, String id); bool _waitForImage = false; @@ -29,7 +29,6 @@ bool _waitForImage = false; class FfiModel with ChangeNotifier { PeerInfo _pi = PeerInfo(); Display _display = Display(); - PlatformFFI _platformFFI = PlatformFFI(); var _inputBlocked = false; final _permissions = Map(); @@ -44,12 +43,6 @@ class FfiModel with ChangeNotifier { Display get display => _display; - PlatformFFI get platformFFI => _platformFFI; - - set platformFFI(PlatformFFI value) { - _platformFFI = value; - } - bool? get secure => _secure; bool? get direct => _direct; @@ -71,10 +64,6 @@ class FfiModel with ChangeNotifier { clear(); } - Future init() async { - await _platformFFI.init(); - } - void toggleTouchMode() { if (!isPeerAndroid) { _touchMode = !_touchMode; @@ -280,7 +269,7 @@ class FfiModel with ChangeNotifier { _timer?.cancel(); if (hasRetry) { _timer = Timer(Duration(seconds: _reconnects), () { - parent.target?.bind.sessionReconnect(id: id); + bind.sessionReconnect(id: id); clearPermissions(); showLoading(translate('Connecting...')); }); @@ -306,9 +295,8 @@ class FfiModel with ChangeNotifier { Timer(Duration(milliseconds: 100), showMobileActionsOverlay); } } else { - _touchMode = await parent.target?.bind - .getSessionOption(id: peerId, arg: "touch-mode") != - ''; + _touchMode = + await bind.getSessionOption(id: peerId, arg: "touch-mode") != ''; } if (evt['is_file_transfer'] == "true") { @@ -387,8 +375,7 @@ class ImageModel with ChangeNotifier { } Future.delayed(Duration(milliseconds: 1), () { if (parent.target?.ffiModel.isPeerAndroid ?? false) { - parent.target?.bind - .sessionPeerOption(id: _id, name: "view-style", value: "shrink"); + bind.sessionPeerOption(id: _id, name: "view-style", value: "shrink"); parent.target?.canvasModel.updateViewStyle(); } }); @@ -439,8 +426,7 @@ class CanvasModel with ChangeNotifier { double get tabBarHeight => _tabBarHeight; void updateViewStyle() async { - final s = - await parent.target?.bind.getSessionOption(id: id, arg: 'view-style'); + final s = await bind.getSessionOption(id: id, arg: 'view-style'); if (s == null) { return; } @@ -844,13 +830,6 @@ class FFI { this.userModel = UserModel(WeakReference(this)); } - static FFI newFFI() { - final ffi = FFI(); - // keep platformFFI only once - ffi.ffiModel.platformFFI = gFFI.ffiModel.platformFFI; - return ffi; - } - /// Get the remote id for current client. String getId() { return getByName('remote_id'); // TODO @@ -1008,16 +987,16 @@ class FFI { /// Send **get** command to the Rust core based on [name] and [arg]. /// Return the result as a string. String getByName(String name, [String arg = '']) { - return ffiModel.platformFFI.getByName(name, arg); + return platformFFI.getByName(name, arg); } /// Send **set** command to the Rust core based on [name] and [value]. void setByName(String name, [String value = '']) { - ffiModel.platformFFI.setByName(name, value); + platformFFI.setByName(name, value); } String getOption(String name) { - return ffiModel.platformFFI.getByName("option", name); + return platformFFI.getByName("option", name); } Future getLocalOption(String name) { @@ -1040,11 +1019,9 @@ class FFI { Map res = Map() ..["name"] = name ..["value"] = value; - return ffiModel.platformFFI.setByName('option', jsonEncode(res)); + return platformFFI.setByName('option', jsonEncode(res)); } - RustdeskImpl get bind => ffiModel.platformFFI.ffiBind; - handleMouse(Map evt, {double tabBarHeight = 0.0}) { var type = ''; var isMove = false; @@ -1102,18 +1079,18 @@ class FFI { listenToMouse(bool yesOrNo) { if (yesOrNo) { - ffiModel.platformFFI.startDesktopWebListener(); + platformFFI.startDesktopWebListener(); } else { - ffiModel.platformFFI.stopDesktopWebListener(); + platformFFI.stopDesktopWebListener(); } } void setMethodCallHandler(FMethod callback) { - ffiModel.platformFFI.setMethodCallHandler(callback); + platformFFI.setMethodCallHandler(callback); } Future invokeMethod(String method, [dynamic arguments]) async { - return await ffiModel.platformFFI.invokeMethod(method, arguments); + return await platformFFI.invokeMethod(method, arguments); } Future> getAudioInputs() async { diff --git a/flutter/lib/models/native_model.dart b/flutter/lib/models/native_model.dart index 511aa5ffe..784ffe6c8 100644 --- a/flutter/lib/models/native_model.dart +++ b/flutter/lib/models/native_model.dart @@ -27,17 +27,24 @@ typedef HandleEvent = void Function(Map evt); /// FFI wrapper around the native Rust core. /// Hides the platform differences. class PlatformFFI { - Pointer? _lastRgbaFrame; String _dir = ''; String _homeDir = ''; F2? _getByName; F3? _setByName; var _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; @@ -94,10 +101,8 @@ class PlatformFFI { } /// Init the FFI class, loads the native Rust core library. - Future init() async { - isIOS = Platform.isIOS; - isAndroid = Platform.isAndroid; - isDesktop = Platform.isWindows || Platform.isMacOS || Platform.isLinux; + Future init(String appType) async { + _appType = appType; // if (isDesktop) { // // TODO // return; @@ -111,7 +116,7 @@ class PlatformFFI { : Platform.isMacOS ? DynamicLibrary.open("librustdesk.dylib") : DynamicLibrary.process(); - print('initializing FFI'); + debugPrint('initializing FFI ${_appType}'); try { _getByName = dylib.lookupFunction('get_by_name'); _setByName = @@ -155,7 +160,8 @@ class PlatformFFI { name = macOsInfo.computerName; id = macOsInfo.systemGUID ?? ""; } - print("info1-id:$id,info2-name:$name,dir:$_dir,homeDir:$_homeDir"); + print( + "_appType:$_appType,info1-id:$id,info2-name:$name,dir:$_dir,homeDir:$_homeDir"); setByName('info1', id); setByName('info2', name); setByName('home_dir', _homeDir); @@ -185,17 +191,18 @@ class PlatformFFI { /// Start listening to the Rust core's events and frames. void _startListenEvent(RustdeskImpl rustdeskImpl) { () async { - await for (final message in rustdeskImpl.startGlobalEventStream()) { - if (_eventCallback != null) { - try { - Map event = json.decode(message); - // _tryHandle here may be more flexible than _eventCallback - if (!_tryHandle(event)) { + 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'); } + } catch (e) { + print('json.decode fail(): $e'); } } }(); @@ -212,7 +219,7 @@ class PlatformFFI { void stopDesktopWebListener() {} void setMethodCallHandler(FMethod callback) { - toAndroidChannel.setMethodCallHandler((call) async { + _toAndroidChannel.setMethodCallHandler((call) async { callback(call.method, call.arguments); return null; }); @@ -220,9 +227,6 @@ class PlatformFFI { invokeMethod(String method, [dynamic arguments]) async { if (!isAndroid) return Future(() => false); - return await toAndroidChannel.invokeMethod(method, arguments); + return await _toAndroidChannel.invokeMethod(method, arguments); } } - -final localeName = Platform.localeName; -final toAndroidChannel = MethodChannel("mChannel"); diff --git a/flutter/lib/models/peer_model.dart b/flutter/lib/models/peer_model.dart index eb520f015..5c889e60f 100644 --- a/flutter/lib/models/peer_model.dart +++ b/flutter/lib/models/peer_model.dart @@ -1,6 +1,6 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; -import '../../common.dart'; +import 'platform_model.dart'; class Peer { final String id; @@ -44,11 +44,10 @@ class Peers extends ChangeNotifier { _name = name; _loadEvent = loadEvent; _peers = _initPeers; - gFFI.ffiModel.platformFFI.registerEventHandler(_cbQueryOnlines, _name, - (evt) { + platformFFI.registerEventHandler(_cbQueryOnlines, _name, (evt) { _updateOnlineState(evt); }); - gFFI.ffiModel.platformFFI.registerEventHandler(_loadEvent, _name, (evt) { + platformFFI.registerEventHandler(_loadEvent, _name, (evt) { _updatePeers(evt); }); } @@ -57,8 +56,8 @@ class Peers extends ChangeNotifier { @override void dispose() { - gFFI.ffiModel.platformFFI.unregisterEventHandler(_cbQueryOnlines, _name); - gFFI.ffiModel.platformFFI.unregisterEventHandler(_loadEvent, _name); + platformFFI.unregisterEventHandler(_cbQueryOnlines, _name); + platformFFI.unregisterEventHandler(_loadEvent, _name); super.dispose(); } diff --git a/flutter/lib/models/platform_model.dart b/flutter/lib/models/platform_model.dart new file mode 100644 index 000000000..d2b8fa765 --- /dev/null +++ b/flutter/lib/models/platform_model.dart @@ -0,0 +1,7 @@ +import 'package:flutter_hbb/generated_bridge.dart'; +import 'native_model.dart' if (dart.library.html) 'web_model.dart'; + +final platformFFI = PlatformFFI.instance; +final localeName = PlatformFFI.localeName; + +RustdeskImpl get bind => platformFFI.ffiBind; diff --git a/flutter/lib/models/user_model.dart b/flutter/lib/models/user_model.dart index a842ec36e..539211664 100644 --- a/flutter/lib/models/user_model.dart +++ b/flutter/lib/models/user_model.dart @@ -6,6 +6,7 @@ import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'model.dart'; +import 'platform_model.dart'; class UserModel extends ChangeNotifier { var userName = "".obs; @@ -17,8 +18,7 @@ class UserModel extends ChangeNotifier { if (userName.isNotEmpty) { return userName.value; } - final userInfo = - await parent.target?.bind.mainGetLocalOption(key: 'user_info') ?? "{}"; + final userInfo = await bind.mainGetLocalOption(key: 'user_info') ?? "{}"; if (userInfo.trim().isEmpty) { return ""; } @@ -29,10 +29,6 @@ class UserModel extends ChangeNotifier { Future logOut() async { debugPrint("start logout"); - final bind = parent.target?.bind; - if (bind == null) { - return; - } final url = await bind.mainGetApiServer(); final _ = await http.post(Uri.parse("$url/api/logout"), body: { @@ -55,10 +51,6 @@ class UserModel extends ChangeNotifier { } Future> login(String userName, String pass) async { - final bind = parent.target?.bind; - if (bind == null) { - return {"error": "no context"}; - } final url = await bind.mainGetApiServer(); try { final resp = await http.post(Uri.parse("$url/api/login"), diff --git a/flutter/lib/models/web_model.dart b/flutter/lib/models/web_model.dart index 59a0e610e..d3f1bacad 100644 --- a/flutter/lib/models/web_model.dart +++ b/flutter/lib/models/web_model.dart @@ -20,7 +20,12 @@ class PlatformFFI { context.callMethod('setByName', [name, value]); } - static Future init() async { + PlatformFFI._(); + static final PlatformFFI instance = PlatformFFI._(); + + static get localeName => window.navigator.language; + + static Future init(String _appType) async { isWeb = true; isWebDesktop = !context.callMethod('isMobile'); context.callMethod('init'); @@ -68,5 +73,3 @@ class PlatformFFI { return true; } } - -final localeName = window.navigator.language; diff --git a/flutter/linux/CMakeLists.txt b/flutter/linux/CMakeLists.txt index 28f309c7f..9f6d0ce52 100644 --- a/flutter/linux/CMakeLists.txt +++ b/flutter/linux/CMakeLists.txt @@ -115,9 +115,9 @@ include(flutter/generated_plugins.cmake) # By default, "installing" just makes a relocatable bundle in the build # directory. set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) +#if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) -endif() +#endif() # Start with a clean build bundle directory every time. install(CODE " diff --git a/src/flutter.rs b/src/flutter.rs index edd972f68..a8e0224eb 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -31,10 +31,14 @@ use hbb_common::{ use crate::common::make_fd_to_json; use crate::{client::*, flutter_ffi::EventToUI, make_fd_flutter}; +pub(super) const APP_TYPE_MAIN: &str = "main"; +pub(super) const APP_TYPE_DESKTOP_REMOTE: &str = "remote"; +pub(super) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer"; + lazy_static::lazy_static! { // static ref SESSION: Arc>> = Default::default(); pub static ref SESSIONS: RwLock> = Default::default(); - pub static ref GLOBAL_EVENT_STREAM: RwLock>> = Default::default(); // rust to dart event channel + pub static ref GLOBAL_EVENT_STREAM: RwLock>> = Default::default(); // rust to dart event channel } // pub fn get_session<'a>(id: &str) -> Option<&'a Session> { @@ -786,113 +790,114 @@ impl Connection { vec![("x", &cp.x.to_string()), ("y", &cp.y.to_string())], ); } - Some(message::Union::FileResponse(fr)) => match fr.union { - Some(file_response::Union::Dir(fd)) => { - let mut entries = fd.entries.to_vec(); - if self.session.peer_platform() == "Windows" { - fs::transform_windows_path(&mut entries); - } - let id = fd.id; - self.session.push_event( - "file_dir", - vec![("value", &make_fd_to_json(fd)), ("is_local", "false")], - ); - if let Some(job) = fs::get_job(id, &mut self.write_jobs) { - job.set_files(entries); - } - } - Some(file_response::Union::Block(block)) => { - if let Some(job) = fs::get_job(block.id, &mut self.write_jobs) { - if let Err(_err) = job.write(block, None).await { - // to-do: add "skip" for writing job + Some(message::Union::FileResponse(fr)) => { + match fr.union { + Some(file_response::Union::Dir(fd)) => { + let mut entries = fd.entries.to_vec(); + if self.session.peer_platform() == "Windows" { + fs::transform_windows_path(&mut entries); + } + let id = fd.id; + self.session.push_event( + "file_dir", + vec![("value", &make_fd_to_json(fd)), ("is_local", "false")], + ); + if let Some(job) = fs::get_job(id, &mut self.write_jobs) { + job.set_files(entries); } - self.update_jobs_status(); } - } - Some(file_response::Union::Done(d)) => { - if let Some(job) = fs::get_job(d.id, &mut self.write_jobs) { - job.modify_time(); - fs::remove_job(d.id, &mut self.write_jobs); + Some(file_response::Union::Block(block)) => { + if let Some(job) = fs::get_job(block.id, &mut self.write_jobs) { + if let Err(_err) = job.write(block, None).await { + // to-do: add "skip" for writing job + } + self.update_jobs_status(); + } } - self.handle_job_status(d.id, d.file_num, None); - } - Some(file_response::Union::Error(e)) => { - self.handle_job_status(e.id, e.file_num, Some(e.error)); - } - Some(file_response::Union::Digest(digest)) => { - if digest.is_upload { - if let Some(job) = fs::get_job(digest.id, &mut self.read_jobs) { - if let Some(file) = job.files().get(digest.file_num as usize) { - let read_path = get_string(&job.join(&file.name)); - let overwrite_strategy = job.default_overwrite_strategy(); - if let Some(overwrite) = overwrite_strategy { - let req = FileTransferSendConfirmRequest { - id: digest.id, - file_num: digest.file_num, - union: Some(if overwrite { - file_transfer_send_confirm_request::Union::OffsetBlk(0) - } else { - file_transfer_send_confirm_request::Union::Skip( - true, - ) - }), - ..Default::default() - }; - job.confirm(&req); - let msg = new_send_confirm(req); - allow_err!(peer.send(&msg).await); - } else { - self.handle_override_file_confirm( - digest.id, - digest.file_num, - read_path, - true, - ); + Some(file_response::Union::Done(d)) => { + if let Some(job) = fs::get_job(d.id, &mut self.write_jobs) { + job.modify_time(); + fs::remove_job(d.id, &mut self.write_jobs); + } + self.handle_job_status(d.id, d.file_num, None); + } + Some(file_response::Union::Error(e)) => { + self.handle_job_status(e.id, e.file_num, Some(e.error)); + } + Some(file_response::Union::Digest(digest)) => { + if digest.is_upload { + if let Some(job) = fs::get_job(digest.id, &mut self.read_jobs) { + if let Some(file) = job.files().get(digest.file_num as usize) { + let read_path = get_string(&job.join(&file.name)); + let overwrite_strategy = job.default_overwrite_strategy(); + if let Some(overwrite) = overwrite_strategy { + let req = FileTransferSendConfirmRequest { + id: digest.id, + file_num: digest.file_num, + union: Some(if overwrite { + file_transfer_send_confirm_request::Union::OffsetBlk(0) + } else { + file_transfer_send_confirm_request::Union::Skip( + true, + ) + }), + ..Default::default() + }; + job.confirm(&req); + let msg = new_send_confirm(req); + allow_err!(peer.send(&msg).await); + } else { + self.handle_override_file_confirm( + digest.id, + digest.file_num, + read_path, + true, + ); + } } } - } - } else { - if let Some(job) = fs::get_job(digest.id, &mut self.write_jobs) { - if let Some(file) = job.files().get(digest.file_num as usize) { - let write_path = get_string(&job.join(&file.name)); - let overwrite_strategy = job.default_overwrite_strategy(); - match fs::is_write_need_confirmation(&write_path, &digest) { - Ok(res) => match res { - DigestCheckResult::IsSame => { - let msg= new_send_confirm(FileTransferSendConfirmRequest { + } else { + if let Some(job) = fs::get_job(digest.id, &mut self.write_jobs) { + if let Some(file) = job.files().get(digest.file_num as usize) { + let write_path = get_string(&job.join(&file.name)); + let overwrite_strategy = job.default_overwrite_strategy(); + match fs::is_write_need_confirmation(&write_path, &digest) { + Ok(res) => match res { + DigestCheckResult::IsSame => { + let msg= new_send_confirm(FileTransferSendConfirmRequest { id: digest.id, file_num: digest.file_num, union: Some(file_transfer_send_confirm_request::Union::Skip(true)), ..Default::default() }); - self.session.send_msg(msg); - } - DigestCheckResult::NeedConfirm(digest) => { - if let Some(overwrite) = overwrite_strategy { - let msg = new_send_confirm( - FileTransferSendConfirmRequest { - id: digest.id, - file_num: digest.file_num, - union: Some(if overwrite { - file_transfer_send_confirm_request::Union::OffsetBlk(0) - } else { - file_transfer_send_confirm_request::Union::Skip(true) - }), - ..Default::default() - }, - ); self.session.send_msg(msg); - } else { - self.handle_override_file_confirm( - digest.id, - digest.file_num, - write_path.to_string(), - false, - ); } - } - DigestCheckResult::NoSuchFile => { - let msg = new_send_confirm( + DigestCheckResult::NeedConfirm(digest) => { + if let Some(overwrite) = overwrite_strategy { + let msg = new_send_confirm( + FileTransferSendConfirmRequest { + id: digest.id, + file_num: digest.file_num, + union: Some(if overwrite { + file_transfer_send_confirm_request::Union::OffsetBlk(0) + } else { + file_transfer_send_confirm_request::Union::Skip(true) + }), + ..Default::default() + }, + ); + self.session.send_msg(msg); + } else { + self.handle_override_file_confirm( + digest.id, + digest.file_num, + write_path.to_string(), + false, + ); + } + } + DigestCheckResult::NoSuchFile => { + let msg = new_send_confirm( FileTransferSendConfirmRequest { id: digest.id, file_num: digest.file_num, @@ -900,19 +905,20 @@ impl Connection { ..Default::default() }, ); - self.session.send_msg(msg); + self.session.send_msg(msg); + } + }, + Err(err) => { + println!("error recving digest: {}", err); } - }, - Err(err) => { - println!("error recving digest: {}", err); } } } } } + _ => {} } - _ => {} - }, + } Some(message::Union::Misc(misc)) => match misc.union { Some(misc::Union::AudioFormat(f)) => { self.audio_handler.handle_format(f); // @@ -1513,7 +1519,11 @@ pub mod connection_manager { assert!(h.get("name").is_none()); h.insert("name", name); - if let Some(s) = GLOBAL_EVENT_STREAM.read().unwrap().as_ref() { + if let Some(s) = GLOBAL_EVENT_STREAM + .read() + .unwrap() + .get(super::APP_TYPE_MAIN) + { s.add(serde_json::ser::to_string(&h).unwrap_or("".to_owned())); }; } diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 413e3cde3..2d7f4be65 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -75,11 +75,17 @@ pub enum EventToUI { Rgba(ZeroCopyBuffer>), } -pub fn start_global_event_stream(s: StreamSink) -> ResultType<()> { - let _ = flutter::GLOBAL_EVENT_STREAM.write().unwrap().insert(s); +pub fn start_global_event_stream(s: StreamSink, app_type: String) -> ResultType<()> { + if let Some(_) = flutter::GLOBAL_EVENT_STREAM.write().unwrap().insert(app_type.clone(), s) { + log::warn!("Global event stream of type {} is started before, but now removed", app_type); + } Ok(()) } +pub fn stop_global_event_stream(app_type: String) { + let _ = flutter::GLOBAL_EVENT_STREAM.write().unwrap().remove(&app_type); +} + pub fn host_stop_system_key_propagate(stopped: bool) { #[cfg(windows)] crate::platform::windows::stop_system_key_propagate(stopped); @@ -518,7 +524,7 @@ pub fn main_load_recent_peers() { .drain(..) .map(|(id, _, p)| (id, p.info)) .collect(); - if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().as_ref() { + if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(flutter::APP_TYPE_MAIN) { let data = HashMap::from([ ("name", "load_recent_peers".to_owned()), ( @@ -544,7 +550,7 @@ pub fn main_load_fav_peers() { } }) .collect(); - if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().as_ref() { + if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(flutter::APP_TYPE_MAIN) { let data = HashMap::from([ ("name", "load_fav_peers".to_owned()), ( @@ -558,7 +564,7 @@ pub fn main_load_fav_peers() { } pub fn main_load_lan_peers() { - if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().as_ref() { + if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(flutter::APP_TYPE_MAIN) { let data = HashMap::from([ ("name", "load_lan_peers".to_owned()), ("peers", get_lan_peers()), @@ -1066,7 +1072,7 @@ unsafe extern "C" fn set_by_name(name: *const c_char, value: *const c_char) { } fn handle_query_onlines(onlines: Vec, offlines: Vec) { - if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().as_ref() { + if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(flutter::APP_TYPE_MAIN) { let data = HashMap::from([ ("name", "callback_query_onlines".to_owned()), ("onlines", onlines.join(",")),