flutter_desktop: fix global envet stream shading && refactor platform ffi

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou 2022-08-03 22:03:31 +08:00
parent d3bc0ca073
commit 7a2de5d280
20 changed files with 476 additions and 398 deletions

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -7,15 +8,16 @@ import 'package:get/instance_manager.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'models/model.dart'; import 'models/model.dart';
import 'models/platform_model.dart';
final globalKey = GlobalKey<NavigatorState>(); final globalKey = GlobalKey<NavigatorState>();
final navigationBarKey = GlobalKey(); final navigationBarKey = GlobalKey();
var isAndroid = false; var isAndroid = Platform.isAndroid;
var isIOS = false; var isIOS = Platform.isIOS;
var isWeb = false; var isWeb = false;
var isWebDesktop = false; var isWebDesktop = false;
var isDesktop = false; var isDesktop = Platform.isWindows || Platform.isMacOS || Platform.isLinux;
var version = ""; var version = "";
int androidVersion = 0; int androidVersion = 0;
@ -119,9 +121,9 @@ class DialogManager {
static Future<T?> show<T>(DialogBuilder builder, static Future<T?> show<T>(DialogBuilder builder,
{bool clickMaskDismiss = false, {bool clickMaskDismiss = false,
bool backDismiss = false, bool backDismiss = false,
String? tag, String? tag,
bool useAnimation = true}) async { bool useAnimation = true}) async {
final t; final t;
if (tag != null) { if (tag != null) {
t = tag; t = tag;
@ -146,10 +148,11 @@ class DialogManager {
} }
class CustomAlertDialog extends StatelessWidget { class CustomAlertDialog extends StatelessWidget {
CustomAlertDialog({required this.title, CustomAlertDialog(
required this.content, {required this.title,
required this.actions, required this.content,
this.contentPadding}); required this.actions,
this.contentPadding});
final Widget title; final Widget title;
final Widget content; final Widget content;
@ -162,7 +165,7 @@ class CustomAlertDialog extends StatelessWidget {
scrollable: true, scrollable: true,
title: title, title: title,
contentPadding: contentPadding:
EdgeInsets.symmetric(horizontal: contentPadding ?? 25, vertical: 10), EdgeInsets.symmetric(horizontal: contentPadding ?? 25, vertical: 10),
content: content, content: content,
actions: actions, actions: actions,
); );
@ -364,9 +367,8 @@ Future<void> initGlobalFFI() async {
_globalFFI = FFI(); _globalFFI = FFI();
// after `put`, can also be globally found by Get.find<FFI>(); // after `put`, can also be globally found by Get.find<FFI>();
Get.put(_globalFFI, permanent: true); Get.put(_globalFFI, permanent: true);
await _globalFFI.ffiModel.init();
// trigger connection status updater // trigger connection status updater
await _globalFFI.bind.mainCheckConnectStatus(); await bind.mainCheckConnectStatus();
// global shared preference // global shared preference
await Get.putAsync(() => SharedPreferences.getInstance()); await Get.putAsync(() => SharedPreferences.getInstance());
} }

View File

@ -1 +1,4 @@
double kDesktopRemoteTabBarHeight = 48.0; double kDesktopRemoteTabBarHeight = 48.0;
String kAppTypeMain = "main";
String kAppTypeDesktopRemote = "remote";
String kAppTypeDesktopFileTransfer = "file transfer";

View File

@ -16,6 +16,7 @@ import '../../mobile/pages/home_page.dart';
import '../../mobile/pages/scan_page.dart'; import '../../mobile/pages/scan_page.dart';
import '../../mobile/pages/settings_page.dart'; import '../../mobile/pages/settings_page.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart';
// enum RemoteType { recently, favorite, discovered, addressBook } // enum RemoteType { recently, favorite, discovered, addressBook }
@ -428,10 +429,10 @@ class _ConnectionPageState extends State<ConnectionPage> {
updateStatus() async { updateStatus() async {
svcStopped.value = gFFI.getOption("stop-service") == "Y"; svcStopped.value = gFFI.getOption("stop-service") == "Y";
final status = jsonDecode(await gFFI.bind.mainGetConnectStatus()) final status =
as Map<String, dynamic>; jsonDecode(await bind.mainGetConnectStatus()) as Map<String, dynamic>;
svcStatusCode.value = status["status_num"]; svcStatusCode.value = status["status_num"];
svcIsUsingPublicServer.value = await gFFI.bind.mainIsUsingPublicServer(); svcIsUsingPublicServer.value = await bind.mainIsUsingPublicServer();
} }
handleLogin() { handleLogin() {
@ -906,13 +907,13 @@ class _PeerTabbedPageState extends State<_PeerTabbedPage>
if (_tabController.indexIsChanging) { if (_tabController.indexIsChanging) {
switch (_tabController.index) { switch (_tabController.index) {
case 0: case 0:
gFFI.bind.mainLoadRecentPeers(); bind.mainLoadRecentPeers();
break; break;
case 1: case 1:
gFFI.bind.mainLoadFavPeers(); bind.mainLoadFavPeers();
break; break;
case 2: case 2:
gFFI.bind.mainDiscover(); bind.mainDiscover();
break; break;
case 3: case 3:
break; break;

View File

@ -8,6 +8,7 @@ import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/desktop/pages/connection_page.dart'; import 'package:flutter_hbb/desktop/pages/connection_page.dart';
import 'package:flutter_hbb/desktop/widgets/titlebar_widget.dart'; import 'package:flutter_hbb/desktop/widgets/titlebar_widget.dart';
import 'package:flutter_hbb/models/model.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:flutter_hbb/models/server_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -630,13 +631,13 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
setState(() { setState(() {
msg = ""; msg = "";
isInProgress = true; isInProgress = true;
gFFI.bind.mainChangeId(newId: newId); bind.mainChangeId(newId: newId);
}); });
var status = await gFFI.bind.mainGetAsyncStatus(); var status = await bind.mainGetAsyncStatus();
while (status == " ") { while (status == " ") {
await Future.delayed(Duration(milliseconds: 100)); await Future.delayed(Duration(milliseconds: 100));
status = await gFFI.bind.mainGetAsyncStatus(); status = await bind.mainGetAsyncStatus();
} }
if (status.isEmpty) { if (status.isEmpty) {
// ok // ok
@ -655,8 +656,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
} }
void changeServer() async { void changeServer() async {
Map<String, dynamic> oldOptions = Map<String, dynamic> oldOptions = jsonDecode(await bind.mainGetOptions());
jsonDecode(await gFFI.bind.mainGetOptions());
print("${oldOptions}"); print("${oldOptions}");
String idServer = oldOptions['custom-rendezvous-server'] ?? ""; String idServer = oldOptions['custom-rendezvous-server'] ?? "";
var idServerMsg = ""; var idServerMsg = "";
@ -814,7 +814,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
if (idServer.isNotEmpty) { if (idServer.isNotEmpty) {
idServerMsg = translate( idServerMsg = translate(
await gFFI.bind.mainTestIfValidServer(server: idServer)); await bind.mainTestIfValidServer(server: idServer));
if (idServerMsg.isEmpty) { if (idServerMsg.isEmpty) {
oldOptions['custom-rendezvous-server'] = idServer; oldOptions['custom-rendezvous-server'] = idServer;
} else { } else {
@ -826,8 +826,8 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
} }
if (relayServer.isNotEmpty) { if (relayServer.isNotEmpty) {
relayServerMsg = translate(await gFFI.bind relayServerMsg = translate(
.mainTestIfValidServer(server: relayServer)); await bind.mainTestIfValidServer(server: relayServer));
if (relayServerMsg.isEmpty) { if (relayServerMsg.isEmpty) {
oldOptions['relay-server'] = relayServer; oldOptions['relay-server'] = relayServer;
} else { } else {
@ -853,7 +853,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
} }
// ok // ok
oldOptions['key'] = key; oldOptions['key'] = key;
await gFFI.bind.mainSetOptions(json: jsonEncode(oldOptions)); await bind.mainSetOptions(json: jsonEncode(oldOptions));
close(); close();
}, },
child: Text(translate("OK"))), child: Text(translate("OK"))),
@ -863,8 +863,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
} }
void changeWhiteList() async { void changeWhiteList() async {
Map<String, dynamic> oldOptions = Map<String, dynamic> oldOptions = jsonDecode(await bind.mainGetOptions());
jsonDecode(await gFFI.bind.mainGetOptions());
var newWhiteList = ((oldOptions['whitelist'] ?? "") as String).split(','); var newWhiteList = ((oldOptions['whitelist'] ?? "") as String).split(',');
var newWhiteListField = newWhiteList.join('\n'); var newWhiteListField = newWhiteList.join('\n');
var msg = ""; var msg = "";
@ -935,7 +934,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
newWhiteList = ips.join(','); newWhiteList = ips.join(',');
} }
oldOptions['whitelist'] = newWhiteList; oldOptions['whitelist'] = newWhiteList;
await gFFI.bind.mainSetOptions(json: jsonEncode(oldOptions)); await bind.mainSetOptions(json: jsonEncode(oldOptions));
close(); close();
}, },
child: Text(translate("OK"))), child: Text(translate("OK"))),
@ -945,7 +944,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
} }
void changeSocks5Proxy() async { void changeSocks5Proxy() async {
var socks = await gFFI.bind.mainGetSocks(); var socks = await bind.mainGetSocks();
String proxy = ""; String proxy = "";
String proxyMsg = ""; String proxyMsg = "";
@ -1072,7 +1071,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
if (proxy.isNotEmpty) { if (proxy.isNotEmpty) {
proxyMsg = translate( proxyMsg = translate(
await gFFI.bind.mainTestIfValidServer(server: proxy)); await bind.mainTestIfValidServer(server: proxy));
if (proxyMsg.isEmpty) { if (proxyMsg.isEmpty) {
// ignore // ignore
} else { } else {
@ -1080,7 +1079,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
return; return;
} }
} }
await gFFI.bind.mainSetSocks( await bind.mainSetSocks(
proxy: proxy, username: username, password: password); proxy: proxy, username: username, password: password);
close(); close();
}, },
@ -1091,9 +1090,9 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
} }
void about() async { void about() async {
final appName = await gFFI.bind.mainGetAppName(); final appName = await bind.mainGetAppName();
final license = await gFFI.bind.mainGetLicense(); final license = await bind.mainGetLicense();
final version = await gFFI.bind.mainGetVersion(); final version = await bind.mainGetVersion();
final linkStyle = TextStyle(decoration: TextDecoration.underline); final linkStyle = TextStyle(decoration: TextDecoration.underline);
DialogManager.show((setState, close) { DialogManager.show((setState, close) {
return CustomAlertDialog( return CustomAlertDialog(

View File

@ -11,6 +11,7 @@ import 'package:wakelock/wakelock.dart';
import '../../common.dart'; import '../../common.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart';
class FileManagerPage extends StatefulWidget { class FileManagerPage extends StatefulWidget {
FileManagerPage({Key? key, required this.id}) : super(key: key); FileManagerPage({Key? key, required this.id}) : super(key: key);
@ -37,7 +38,7 @@ class _FileManagerPageState extends State<FileManagerPage>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
Get.put(FFI.newFFI()..connect(widget.id, isFileTransfer: true), Get.put(FFI()..connect(widget.id, isFileTransfer: true),
tag: 'ft_${widget.id}'); tag: 'ft_${widget.id}');
// _ffi.ffiModel.updateEventListener(widget.id); // _ffi.ffiModel.updateEventListener(widget.id);
if (!Platform.isLinux) { if (!Platform.isLinux) {
@ -464,13 +465,15 @@ class _FileManagerPageState extends State<FileManagerPage>
decoration: BoxDecoration(color: Colors.blue), decoration: BoxDecoration(color: Colors.blue),
padding: EdgeInsets.all(8.0), padding: EdgeInsets.all(8.0),
child: FutureBuilder<String>( child: FutureBuilder<String>(
future: _ffi.bind.sessionGetPlatform( future: bind.sessionGetPlatform(
id: _ffi.id, isRemote: !isLocal), id: _ffi.id, isRemote: !isLocal),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) { if (snapshot.hasData && snapshot.data!.isNotEmpty) {
return getPlatformImage('${snapshot.data}'); return getPlatformImage('${snapshot.data}');
} else { } else {
return CircularProgressIndicator(color: Colors.white,); return CircularProgressIndicator(
color: Colors.white,
);
} }
})), })),
Text(isLocal Text(isLocal
@ -505,21 +508,25 @@ class _FileManagerPageState extends State<FileManagerPage>
border: Border.all(color: Colors.black12)), border: Border.all(color: Colors.black12)),
child: TextField( child: TextField(
decoration: InputDecoration( decoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
isDense: true, isDense: true,
prefix: Padding(padding: EdgeInsets.only(left: 4.0)), prefix:
suffix: DropdownButton<String>( Padding(padding: EdgeInsets.only(left: 4.0)),
isDense: true, suffix: DropdownButton<String>(
underline: Offstage(), isDense: true,
items: [ underline: Offstage(),
// TODO: favourite items: [
DropdownMenuItem(child: Text('/'), value: '/',) // TODO: favourite
], onChanged: (path) { DropdownMenuItem(
if (path is String && path.isNotEmpty){ child: Text('/'),
model.openDirectory(path, isLocal: isLocal); value: '/',
} )
}) ],
), onChanged: (path) {
if (path is String && path.isNotEmpty) {
model.openDirectory(path, isLocal: isLocal);
}
})),
controller: TextEditingController( controller: TextEditingController(
text: isLocal text: isLocal
? model.currentLocalDir.path ? model.currentLocalDir.path

View File

@ -16,10 +16,10 @@ import 'package:wakelock/wakelock.dart';
// import 'package:window_manager/window_manager.dart'; // import 'package:window_manager/window_manager.dart';
import '../../common.dart'; import '../../common.dart';
import '../../consts.dart';
import '../../mobile/widgets/dialog.dart'; import '../../mobile/widgets/dialog.dart';
import '../../mobile/widgets/overlay.dart'; import '../../mobile/widgets/overlay.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart';
final initText = '\1' * 1024; final initText = '\1' * 1024;
@ -59,8 +59,6 @@ class _RemotePageState extends State<RemotePage>
var ffitmp = FFI(); var ffitmp = FFI();
ffitmp.canvasModel.tabBarHeight = super.widget.tabBarHeight; ffitmp.canvasModel.tabBarHeight = super.widget.tabBarHeight;
final ffi = Get.put(ffitmp, tag: widget.id); 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); ffi.connect(widget.id, tabBarHeight: super.widget.tabBarHeight);
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
@ -157,7 +155,7 @@ class _RemotePageState extends State<RemotePage>
if (newValue.length > common) { if (newValue.length > common) {
var s = newValue.substring(common); var s = newValue.substring(common);
if (s.length > 1) { if (s.length > 1) {
_ffi.bind.sessionInputString(id: widget.id, value: s); bind.sessionInputString(id: widget.id, value: s);
} else { } else {
inputChar(s); inputChar(s);
} }
@ -191,11 +189,11 @@ class _RemotePageState extends State<RemotePage>
content == '' || content == '' ||
content == '【】')) { content == '【】')) {
// can not only input content[0], because when input ], [ are also auo insert, which cause ] never be input // 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(); openKeyboard();
return; return;
} }
_ffi.bind.sessionInputString(id: widget.id, value: content); bind.sessionInputString(id: widget.id, value: content);
} else { } else {
inputChar(content); inputChar(content);
} }
@ -509,8 +507,8 @@ class _RemotePageState extends State<RemotePage>
id: widget.id, id: widget.id,
) )
]; ];
final cursor = _ffi.bind final cursor = bind.getSessionToggleOptionSync(
.getSessionToggleOptionSync(id: widget.id, arg: 'show-remote-cursor'); id: widget.id, arg: 'show-remote-cursor');
if (keyboard || cursor) { if (keyboard || cursor) {
paints.add(CursorPaint( paints.add(CursorPaint(
id: widget.id, id: widget.id,
@ -519,10 +517,10 @@ class _RemotePageState extends State<RemotePage>
paints.add(getHelpTools()); paints.add(getHelpTools());
return MouseRegion( return MouseRegion(
onEnter: (evt) { onEnter: (evt) {
_ffi.bind.hostStopSystemKeyPropagate(stopped: false); bind.hostStopSystemKeyPropagate(stopped: false);
}, },
onExit: (evt) { onExit: (evt) {
_ffi.bind.hostStopSystemKeyPropagate(stopped: true); bind.hostStopSystemKeyPropagate(stopped: true);
}, },
child: Container( child: Container(
color: MyTheme.canvasColor, color: MyTheme.canvasColor,
@ -601,7 +599,7 @@ class _RemotePageState extends State<RemotePage>
more.add(PopupMenuItem<String>( more.add(PopupMenuItem<String>(
child: Text(translate('Insert Lock')), value: 'lock')); child: Text(translate('Insert Lock')), value: 'lock'));
if (pi.platform == 'Windows' && if (pi.platform == 'Windows' &&
await _ffi.bind.getSessionToggleOption(id: id, arg: 'privacy-mode') != await bind.getSessionToggleOption(id: id, arg: 'privacy-mode') !=
true) { true) {
more.add(PopupMenuItem<String>( more.add(PopupMenuItem<String>(
child: Text(translate((_ffi.ffiModel.inputBlocked ? 'Unb' : 'B') + child: Text(translate((_ffi.ffiModel.inputBlocked ? 'Unb' : 'B') +
@ -617,28 +615,27 @@ class _RemotePageState extends State<RemotePage>
elevation: 8, elevation: 8,
); );
if (value == 'cad') { if (value == 'cad') {
_ffi.bind.sessionCtrlAltDel(id: widget.id); bind.sessionCtrlAltDel(id: widget.id);
} else if (value == 'lock') { } else if (value == 'lock') {
_ffi.bind.sessionLockScreen(id: widget.id); bind.sessionLockScreen(id: widget.id);
} else if (value == 'block-input') { } else if (value == 'block-input') {
_ffi.bind.sessionToggleOption( bind.sessionToggleOption(
id: widget.id, id: widget.id,
value: (_ffi.ffiModel.inputBlocked ? 'un' : '') + 'block-input'); value: (_ffi.ffiModel.inputBlocked ? 'un' : '') + 'block-input');
_ffi.ffiModel.inputBlocked = !_ffi.ffiModel.inputBlocked; _ffi.ffiModel.inputBlocked = !_ffi.ffiModel.inputBlocked;
} else if (value == 'refresh') { } else if (value == 'refresh') {
_ffi.bind.sessionRefresh(id: widget.id); bind.sessionRefresh(id: widget.id);
} else if (value == 'paste') { } else if (value == 'paste') {
() async { () async {
ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
if (data != null && data.text != null) { 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') { } else if (value == 'enter_os_password') {
var password = var password = await bind.getSessionOption(id: id, arg: "os-password");
await _ffi.bind.getSessionOption(id: id, arg: "os-password");
if (password != null) { if (password != null) {
_ffi.bind.sessionInputOsPassword(id: widget.id, value: password); bind.sessionInputOsPassword(id: widget.id, value: password);
} else { } else {
showSetOSPassword(widget.id, true); showSetOSPassword(widget.id, true);
} }
@ -666,7 +663,7 @@ class _RemotePageState extends State<RemotePage>
onTouchModeChange: (t) { onTouchModeChange: (t) {
_ffi.ffiModel.toggleTouchMode(); _ffi.ffiModel.toggleTouchMode();
final v = _ffi.ffiModel.touchMode ? 'Y' : ''; final v = _ffi.ffiModel.touchMode ? 'Y' : '';
_ffi.bind.sessionPeerOption( bind.sessionPeerOption(
id: widget.id, name: "touch-mode", value: v); id: widget.id, name: "touch-mode", value: v);
})); }));
})); }));
@ -892,12 +889,12 @@ class ImagePainter extends CustomPainter {
CheckboxListTile getToggle( CheckboxListTile getToggle(
String id, void Function(void Function()) setState, option, name) { 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( return CheckboxListTile(
value: opt, value: opt,
onChanged: (v) { onChanged: (v) {
setState(() { setState(() {
ffi(id).bind.sessionToggleOption(id: id, value: option); bind.sessionToggleOption(id: id, value: option);
}); });
}, },
dense: true, dense: true,
@ -917,11 +914,10 @@ RadioListTile<String> getRadio(String name, String toValue, String curValue,
} }
void showOptions(String id) async { void showOptions(String id) async {
String quality = String quality = await bind.getSessionImageQuality(id: id) ?? 'balanced';
await ffi(id).bind.getSessionImageQuality(id: id) ?? 'balanced';
if (quality == '') quality = 'balanced'; if (quality == '') quality = 'balanced';
String viewStyle = String viewStyle =
await ffi(id).bind.getSessionOption(id: id, arg: 'view-style') ?? ''; await bind.getSessionOption(id: id, arg: 'view-style') ?? '';
var displays = <Widget>[]; var displays = <Widget>[];
final pi = ffi(id).ffiModel.pi; final pi = ffi(id).ffiModel.pi;
final image = ffi(id).ffiModel.getConnectionImage(); final image = ffi(id).ffiModel.getConnectionImage();
@ -934,7 +930,7 @@ void showOptions(String id) async {
children.add(InkWell( children.add(InkWell(
onTap: () { onTap: () {
if (i == cur) return; if (i == cur) return;
ffi(id).bind.sessionSwitchDisplay(id: id, value: i); bind.sessionSwitchDisplay(id: id, value: i);
SmartDialog.dismiss(); SmartDialog.dismiss();
}, },
child: Ink( child: Ink(
@ -979,16 +975,14 @@ void showOptions(String id) async {
if (value == null) return; if (value == null) return;
setState(() { setState(() {
quality = value; quality = value;
ffi(id).bind.sessionSetImageQuality(id: id, value: value); bind.sessionSetImageQuality(id: id, value: value);
}); });
}; };
var setViewStyle = (String? value) { var setViewStyle = (String? value) {
if (value == null) return; if (value == null) return;
setState(() { setState(() {
viewStyle = value; 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(); ffi(id).canvasModel.updateViewStyle();
}); });
}; };
@ -1018,10 +1012,8 @@ void showOptions(String id) async {
void showSetOSPassword(String id, bool login) async { void showSetOSPassword(String id, bool login) async {
final controller = TextEditingController(); final controller = TextEditingController();
var password = var password = await bind.getSessionOption(id: id, arg: "os-password") ?? "";
await ffi(id).bind.getSessionOption(id: id, arg: "os-password") ?? ""; var autoLogin = await bind.getSessionOption(id: id, arg: "auto-login") != "";
var autoLogin =
await ffi(id).bind.getSessionOption(id: id, arg: "auto-login") != "";
controller.text = password; controller.text = password;
DialogManager.show((setState, close) { DialogManager.show((setState, close) {
return CustomAlertDialog( return CustomAlertDialog(
@ -1054,13 +1046,11 @@ void showSetOSPassword(String id, bool login) async {
style: flatButtonStyle, style: flatButtonStyle,
onPressed: () { onPressed: () {
var text = controller.text.trim(); var text = controller.text.trim();
ffi(id) bind.sessionPeerOption(id: id, name: "os-password", value: text);
.bind bind.sessionPeerOption(
.sessionPeerOption(id: id, name: "os-password", value: text);
ffi(id).bind.sessionPeerOption(
id: id, name: "auto-login", value: autoLogin ? 'Y' : ''); id: id, name: "auto-login", value: autoLogin ? 'Y' : '');
if (text != "" && login) { if (text != "" && login) {
ffi(id).bind.sessionInputOsPassword(id: id, value: text); bind.sessionInputOsPassword(id: id, value: text);
} }
close(); close();
}, },

View File

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/foundation.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 'package:window_manager/window_manager.dart';
import '../../models/peer_model.dart'; import '../../models/peer_model.dart';
import '../../models/platform_model.dart';
import '../../common.dart'; import '../../common.dart';
import 'peercard_widget.dart'; import 'peercard_widget.dart';
@ -116,7 +116,7 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
if (!setEquals(_curPeers, _lastQueryPeers)) { if (!setEquals(_curPeers, _lastQueryPeers)) {
if (now.difference(_lastChangeTime) > Duration(seconds: 1)) { if (now.difference(_lastChangeTime) > Duration(seconds: 1)) {
if (_curPeers.length > 0) { if (_curPeers.length > 0) {
gFFI.ffiModel.platformFFI.ffiBind platformFFI.ffiBind
.queryOnlines(ids: _curPeers.toList(growable: false)); .queryOnlines(ids: _curPeers.toList(growable: false));
_lastQueryPeers = {..._curPeers}; _lastQueryPeers = {..._curPeers};
_lastQueryTime = DateTime.now(); _lastQueryTime = DateTime.now();
@ -127,7 +127,7 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
if (_queryCoun < _maxQueryCount) { if (_queryCoun < _maxQueryCount) {
if (now.difference(_lastQueryTime) > Duration(seconds: 20)) { if (now.difference(_lastQueryTime) > Duration(seconds: 20)) {
if (_curPeers.length > 0) { if (_curPeers.length > 0) {
gFFI.ffiModel.platformFFI.ffiBind platformFFI.ffiBind
.queryOnlines(ids: _curPeers.toList(growable: false)); .queryOnlines(ids: _curPeers.toList(growable: false));
_lastQueryTime = DateTime.now(); _lastQueryTime = DateTime.now();
_queryCoun += 1; _queryCoun += 1;
@ -169,7 +169,7 @@ class RecentPeerWidget extends BasePeerWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final widget = super.build(context); final widget = super.build(context);
gFFI.bind.mainLoadRecentPeers(); bind.mainLoadRecentPeers();
return widget; return widget;
} }
} }
@ -186,7 +186,7 @@ class FavoritePeerWidget extends BasePeerWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final widget = super.build(context); final widget = super.build(context);
gFFI.bind.mainLoadFavPeers(); bind.mainLoadFavPeers();
return widget; return widget;
} }
} }
@ -203,7 +203,7 @@ class DiscoveredPeerWidget extends BasePeerWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final widget = super.build(context); final widget = super.build(context);
gFFI.bind.mainLoadLanPeers(); bind.mainLoadLanPeers();
return widget; return widget;
} }
} }

View File

@ -5,9 +5,11 @@ import 'package:get/get.dart';
import '../../common.dart'; import '../../common.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart';
import '../../models/peer_model.dart'; import '../../models/peer_model.dart';
typedef PopupMenuItemsFunc = Future<List<PopupMenuItem<String>>> Function(); typedef PopupMenuItemsFunc = Future<List<PopupMenuItem<String>>> Function();
enum PeerType { recent, fav, discovered, ab } enum PeerType { recent, fav, discovered, ab }
class _PeerCard extends StatefulWidget { class _PeerCard extends StatefulWidget {
@ -15,10 +17,11 @@ class _PeerCard extends StatefulWidget {
final PopupMenuItemsFunc popupMenuItemsFunc; final PopupMenuItemsFunc popupMenuItemsFunc;
final PeerType type; final PeerType type;
_PeerCard({required this.peer, _PeerCard(
required this.popupMenuItemsFunc, {required this.peer,
Key? key, required this.popupMenuItemsFunc,
required this.type}) Key? key,
required this.type})
: super(key: key); : super(key: key);
@override @override
@ -54,9 +57,10 @@ class _PeerCardState extends State<_PeerCard>
)); ));
} }
Widget _buildPeerTile(BuildContext context, Peer peer, Rx<BoxDecoration?> deco) { Widget _buildPeerTile(
BuildContext context, Peer peer, Rx<BoxDecoration?> deco) {
return Obx( return Obx(
() => Container( () => Container(
decoration: deco.value, decoration: deco.value,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -135,7 +139,7 @@ class _PeerCardState extends State<_PeerCard>
child: CircleAvatar( child: CircleAvatar(
radius: 5, radius: 5,
backgroundColor: backgroundColor:
peer.online ? Colors.green : Colors.yellow)), peer.online ? Colors.green : Colors.yellow)),
Text('${peer.id}') Text('${peer.id}')
]), ]),
InkWell( InkWell(
@ -183,12 +187,13 @@ class _PeerCardState extends State<_PeerCard>
); );
if (value == 'remove') { if (value == 'remove') {
setState(() => gFFI.setByName('remove', '$id')); setState(() => gFFI.setByName('remove', '$id'));
() async { () async {
removePreference(id); removePreference(id);
}(); }();
} else if (value == 'file') { } else if (value == 'file') {
_connect(id, isFileTransfer: true); _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); _connect(id, isFileTransfer: false);
} else if (value == 'ab-delete') { } else if (value == 'ab-delete') {
gFFI.abModel.deletePeer(id); gFFI.abModel.deletePeer(id);
@ -199,7 +204,7 @@ class _PeerCardState extends State<_PeerCard>
} else if (value == 'rename') { } else if (value == 'rename') {
_rename(id); _rename(id);
} else if (value == 'unremember-password') { } 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( child: GestureDetector(
onTap: onTap, onTap: onTap,
child: Obx( child: Obx(
() => Container( () => Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: rxTags.contains(tagName) ? Colors.blue : null, color: rxTags.contains(tagName) ? Colors.blue : null,
border: Border.all(color: MyTheme.darkGray), border: Border.all(color: MyTheme.darkGray),
@ -264,12 +269,12 @@ class _PeerCardState extends State<_PeerCard>
child: Wrap( child: Wrap(
children: tags children: tags
.map((e) => _buildTag(e, selectedTag, onTap: () { .map((e) => _buildTag(e, selectedTag, onTap: () {
if (selectedTag.contains(e)) { if (selectedTag.contains(e)) {
selectedTag.remove(e); selectedTag.remove(e);
} else { } else {
selectedTag.add(e); selectedTag.add(e);
} }
})) }))
.toList(growable: false), .toList(growable: false),
), ),
), ),

View File

@ -13,6 +13,8 @@ import 'package:provider/provider.dart';
// import 'package:window_manager/window_manager.dart'; // import 'package:window_manager/window_manager.dart';
import 'common.dart'; import 'common.dart';
import 'consts.dart';
import 'models/platform_model.dart';
import 'mobile/pages/home_page.dart'; import 'mobile/pages/home_page.dart';
import 'mobile/pages/server_page.dart'; import 'mobile/pages/server_page.dart';
import 'mobile/pages/settings_page.dart'; import 'mobile/pages/settings_page.dart';
@ -21,25 +23,9 @@ int? windowId;
Future<Null> main(List<String> args) async { Future<Null> main(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized(); 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<String> args) async {
if (!isDesktop) { if (!isDesktop) {
runApp(App()); runMainApp(false);
return; return;
} }
// main window // main window
@ -52,28 +38,62 @@ void runRustDeskApp(List<String> args) async {
WindowType wType = type.windowType; WindowType wType = type.windowType;
switch (wType) { switch (wType) {
case WindowType.RemoteDesktop: case WindowType.RemoteDesktop:
runApp(GetMaterialApp( runRemoteScreen(argument);
theme: getCurrentTheme(),
home: DesktopRemoteScreen(
params: argument,
),
));
break; break;
case WindowType.FileTransfer: case WindowType.FileTransfer:
runApp(GetMaterialApp( runFileTransferScreen(argument);
theme: getCurrentTheme(),
home: DesktopFileTransferScreen(params: argument)));
break; break;
default: default:
break; break;
} }
} else { } else {
runMainApp(true);
}
}
ThemeData getCurrentTheme() {
return isDarkTheme() ? MyTheme.darkTheme : MyTheme.darkTheme;
}
Future<void> 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(); // await windowManager.ensureInitialized();
// disable tray // disable tray
// initTray(); // initTray();
gFFI.serverModel.startService(); gFFI.serverModel.startService();
runApp(App());
} }
runApp(App());
}
void runRemoteScreen(Map<String, dynamic> argument) async {
await initEnv(kAppTypeDesktopRemote);
runApp(GetMaterialApp(
theme: getCurrentTheme(),
home: DesktopRemoteScreen(
params: argument,
),
));
}
void runFileTransferScreen(Map<String, dynamic> argument) async {
await initEnv(kAppTypeDesktopFileTransfer);
runApp(GetMaterialApp(
theme: getCurrentTheme(),
home: DesktopFileTransferScreen(params: argument)));
} }
class App extends StatelessWidget { class App extends StatelessWidget {
@ -108,8 +128,8 @@ class App extends StatelessWidget {
builder: FlutterSmartDialog.init( builder: FlutterSmartDialog.init(
builder: isAndroid builder: isAndroid
? (_, child) => AccessibilityListener( ? (_, child) => AccessibilityListener(
child: child, child: child,
) )
: null)), : null)),
); );
} }

View File

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/models/model.dart'; import 'package:flutter_hbb/models/model.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -46,7 +47,7 @@ class AbModel with ChangeNotifier {
} }
Future<String> getApiServer() async { Future<String> getApiServer() async {
return await _ffi?.bind.mainGetApiServer() ?? ""; return await bind.mainGetApiServer() ?? "";
} }
void reset() { void reset() {

View File

@ -9,6 +9,7 @@ import 'package:get/get.dart';
import 'package:path/path.dart' as Path; import 'package:path/path.dart' as Path;
import 'model.dart'; import 'model.dart';
import 'platform_model.dart';
enum SortBy { Name, Type, Modified, Size } enum SortBy { Name, Type, Modified, Size }
@ -50,7 +51,7 @@ class FileModel extends ChangeNotifier {
bool get localSortAscending => _localSortAscending; bool get localSortAscending => _localSortAscending;
SortBy getSortStyle(bool isLocal){ SortBy getSortStyle(bool isLocal) {
return isLocal ? _localSortStyle : _remoteSortStyle; return isLocal ? _localSortStyle : _remoteSortStyle;
} }
@ -164,7 +165,7 @@ class FileModel extends ChangeNotifier {
// Desktop uses jobTable // Desktop uses jobTable
// id = index + 1 // id = index + 1
final jobIndex = getJob(id); final jobIndex = getJob(id);
if (jobIndex >= 0 && _jobTable.length > jobIndex){ if (jobIndex >= 0 && _jobTable.length > jobIndex) {
final job = _jobTable[jobIndex]; final job = _jobTable[jobIndex];
job.fileNum = int.parse(evt['file_num']); job.fileNum = int.parse(evt['file_num']);
job.speed = double.parse(evt['speed']); job.speed = double.parse(evt['speed']);
@ -203,8 +204,7 @@ class FileModel extends ChangeNotifier {
debugPrint("init remote home:${fd.path}"); debugPrint("init remote home:${fd.path}");
_currentRemoteDir = fd; _currentRemoteDir = fd;
} }
} } finally {}
finally {}
} }
_fileFetcher.tryCompleteTask(evt['value'], evt['is_local']); _fileFetcher.tryCompleteTask(evt['value'], evt['is_local']);
notifyListeners(); notifyListeners();
@ -260,7 +260,7 @@ class FileModel extends ChangeNotifier {
final id = int.tryParse(evt['id']) ?? 0; final id = int.tryParse(evt['id']) ?? 0;
if (false == resp) { if (false == resp) {
final jobIndex = getJob(id); final jobIndex = getJob(id);
if (jobIndex != -1){ if (jobIndex != -1) {
cancelJob(id); cancelJob(id);
final job = jobTable[jobIndex]; final job = jobTable[jobIndex];
job.state = JobState.done; job.state = JobState.done;
@ -274,9 +274,12 @@ class FileModel extends ChangeNotifier {
// overwrite // overwrite
need_override = true; need_override = true;
} }
_ffi.target?.bind.sessionSetConfirmOverrideFile(id: _ffi.target?.id ?? "", bind.sessionSetConfirmOverrideFile(
actId: id, fileNum: int.parse(evt['file_num']), id: _ffi.target?.id ?? "",
needOverride: need_override, remember: fileConfirmCheckboxRemember, actId: id,
fileNum: int.parse(evt['file_num']),
needOverride: need_override,
remember: fileConfirmCheckboxRemember,
isUpload: evt['is_upload'] == "true"); isUpload: evt['is_upload'] == "true");
} }
} }
@ -288,21 +291,27 @@ class FileModel extends ChangeNotifier {
onReady() async { onReady() async {
_localOption.home = _ffi.target?.getByName("get_home_dir") ?? ""; _localOption.home = _ffi.target?.getByName("get_home_dir") ?? "";
_localOption.showHidden = (await _ffi.target?.bind.sessionGetPeerOption _localOption.showHidden = (await bind.sessionGetPeerOption(
(id: _ffi.target?.id ?? "", name: "local_show_hidden"))?.isNotEmpty ?? false; id: _ffi.target?.id ?? "", name: "local_show_hidden"))
?.isNotEmpty ??
false;
_remoteOption.showHidden = (await _ffi.target?.bind.sessionGetPeerOption _remoteOption.showHidden = (await bind.sessionGetPeerOption(
(id: _ffi.target?.id ?? "", name: "remote_show_hidden"))?.isNotEmpty ?? false; id: _ffi.target?.id ?? "", name: "remote_show_hidden"))
?.isNotEmpty ??
false;
_remoteOption.isWindows = _ffi.target?.ffiModel.pi.platform == "Windows"; _remoteOption.isWindows = _ffi.target?.ffiModel.pi.platform == "Windows";
debugPrint("remote platform: ${_ffi.target?.ffiModel.pi.platform}"); debugPrint("remote platform: ${_ffi.target?.ffiModel.pi.platform}");
await Future.delayed(Duration(milliseconds: 100)); await Future.delayed(Duration(milliseconds: 100));
final local = (await _ffi.target?.bind.sessionGetPeerOption final local = (await bind.sessionGetPeerOption(
(id: _ffi.target?.id ?? "", name: "local_dir")) ?? ""; id: _ffi.target?.id ?? "", name: "local_dir")) ??
final remote = (await _ffi.target?.bind.sessionGetPeerOption "";
(id: _ffi.target?.id ?? "", name: "remote_dir")) ?? ""; final remote = (await bind.sessionGetPeerOption(
id: _ffi.target?.id ?? "", name: "remote_dir")) ??
"";
openDirectory(local.isEmpty ? _localOption.home : local, isLocal: true); openDirectory(local.isEmpty ? _localOption.home : local, isLocal: true);
openDirectory(remote.isEmpty ? _remoteOption.home : remote, isLocal: false); openDirectory(remote.isEmpty ? _remoteOption.home : remote, isLocal: false);
await Future.delayed(Duration(seconds: 1)); await Future.delayed(Duration(seconds: 1));
@ -313,7 +322,7 @@ class FileModel extends ChangeNotifier {
openDirectory(_remoteOption.home, isLocal: false); openDirectory(_remoteOption.home, isLocal: false);
} }
// load last transfer jobs // load last transfer jobs
await _ffi.target?.bind.sessionLoadLastTransferJobs(id: '${_ffi.target?.id}'); await bind.sessionLoadLastTransferJobs(id: '${_ffi.target?.id}');
} }
onClose() { onClose() {
@ -327,8 +336,8 @@ class FileModel extends ChangeNotifier {
msgMap["remote_dir"] = _currentRemoteDir.path; msgMap["remote_dir"] = _currentRemoteDir.path;
msgMap["remote_show_hidden"] = _remoteOption.showHidden ? "Y" : ""; msgMap["remote_show_hidden"] = _remoteOption.showHidden ? "Y" : "";
final id = _ffi.target?.id ?? ""; final id = _ffi.target?.id ?? "";
for(final msg in msgMap.entries) { for (final msg in msgMap.entries) {
_ffi.target?.bind.sessionPeerOption(id: id, name: msg.key, value: msg.value); bind.sessionPeerOption(id: id, name: msg.key, value: msg.value);
} }
_currentLocalDir.clear(); _currentLocalDir.clear();
_currentRemoteDir.clear(); _currentRemoteDir.clear();
@ -339,8 +348,9 @@ class FileModel extends ChangeNotifier {
Future refresh({bool? isLocal}) async { Future refresh({bool? isLocal}) async {
if (isDesktop) { if (isDesktop) {
isLocal = isLocal ?? _isLocal; isLocal = isLocal ?? _isLocal;
await isLocal ? openDirectory(currentLocalDir.path, isLocal: isLocal) : await isLocal
openDirectory(currentRemoteDir.path, isLocal: isLocal); ? openDirectory(currentLocalDir.path, isLocal: isLocal)
: openDirectory(currentRemoteDir.path, isLocal: isLocal);
} else { } else {
await openDirectory(currentDir.path); await openDirectory(currentDir.path);
} }
@ -353,7 +363,9 @@ class FileModel extends ChangeNotifier {
final isWindows = final isWindows =
isLocal ? _localOption.isWindows : _remoteOption.isWindows; isLocal ? _localOption.isWindows : _remoteOption.isWindows;
// process /C:\ -> C:\ on Windows // 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); path = path.substring(1);
if (path[path.length - 1] != '\\') { if (path[path.length - 1] != '\\') {
path = path + "\\"; path = path + "\\";
@ -380,7 +392,8 @@ class FileModel extends ChangeNotifier {
goToParentDirectory({bool? isLocal}) { goToParentDirectory({bool? isLocal}) {
isLocal = isLocal ?? _isLocal; isLocal = isLocal ?? _isLocal;
final isWindows = isLocal ? _localOption.isWindows : _remoteOption.isWindows; final isWindows =
isLocal ? _localOption.isWindows : _remoteOption.isWindows;
final currDir = isLocal ? currentLocalDir : currentRemoteDir; final currDir = isLocal ? currentLocalDir : currentRemoteDir;
var parent = PathUtil.dirname(currDir.path, isWindows); var parent = PathUtil.dirname(currDir.path, isWindows);
// specially for C:\, D:\, goto '/' // specially for C:\, D:\, goto '/'
@ -395,12 +408,11 @@ class FileModel extends ChangeNotifier {
sendFiles(SelectedItems items, {bool isRemote = false}) { sendFiles(SelectedItems items, {bool isRemote = false}) {
if (isDesktop) { if (isDesktop) {
// desktop sendFiles // desktop sendFiles
final toPath = final toPath = isRemote ? currentLocalDir.path : currentRemoteDir.path;
isRemote ? currentLocalDir.path : currentRemoteDir.path;
final isWindows = final isWindows =
isRemote ? _localOption.isWindows : _remoteOption.isWindows; isRemote ? _localOption.isWindows : _remoteOption.isWindows;
final showHidden = final showHidden =
isRemote ? _localOption.showHidden : _remoteOption.showHidden; isRemote ? _localOption.showHidden : _remoteOption.showHidden;
items.items.forEach((from) async { items.items.forEach((from) async {
final jobId = ++_jobId; final jobId = ++_jobId;
_jobTable.add(JobProgress() _jobTable.add(JobProgress()
@ -408,11 +420,17 @@ class FileModel extends ChangeNotifier {
..totalSize = from.size ..totalSize = from.size
..state = JobState.inProgress ..state = JobState.inProgress
..id = jobId ..id = jobId
..isRemote = isRemote ..isRemote = isRemote);
); bind.sessionSendFiles(
_ffi.target?.bind.sessionSendFiles(id: '${_ffi.target?.id}', actId: _jobId, path: from.path, to: PathUtil.join(toPath, from.name, isWindows) id: '${_ffi.target?.id}',
,fileNum: 0, includeHidden: showHidden, isRemote: isRemote); actId: _jobId,
print("path:${from.path}, toPath:${toPath}, to:${PathUtil.join(toPath, from.name, isWindows)}"); 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 { } else {
if (items.isLocal == null) { if (items.isLocal == null) {
@ -421,15 +439,21 @@ class FileModel extends ChangeNotifier {
} }
_jobProgress.state = JobState.inProgress; _jobProgress.state = JobState.inProgress;
final toPath = final toPath =
items.isLocal! ? currentRemoteDir.path : currentLocalDir.path; items.isLocal! ? currentRemoteDir.path : currentLocalDir.path;
final isWindows = final isWindows =
items.isLocal! ? _localOption.isWindows : _remoteOption.isWindows; items.isLocal! ? _localOption.isWindows : _remoteOption.isWindows;
final showHidden = final showHidden =
items.isLocal! ? _localOption.showHidden : _remoteOption.showHidden; items.isLocal! ? _localOption.showHidden : _remoteOption.showHidden;
items.items.forEach((from) async { items.items.forEach((from) async {
_jobId++; _jobId++;
await _ffi.target?.bind.sessionSendFiles(id: '${_ffi.target?.getId()}', actId: _jobId, path: from.path, to: PathUtil.join(toPath, from.name, isWindows) await bind.sessionSendFiles(
,fileNum: 0, includeHidden: showHidden, isRemote: !(items.isLocal!)); 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) { 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) { 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 { createDir(String path, {bool? isLocal}) async {
isLocal = isLocal ?? this.isLocal; isLocal = isLocal ?? this.isLocal;
_jobId++; _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 { cancelJob(int id) async {
_ffi.target?.bind.sessionCancelJob(id: '${_ffi.target?.id}', actId: id); bind.sessionCancelJob(id: '${_ffi.target?.id}', actId: id);
jobReset(); jobReset();
} }
@ -650,14 +687,18 @@ class FileModel extends ChangeNotifier {
// compatible for mobile logic // compatible for mobile logic
_currentLocalDir.changeSortStyle(sort, ascending: ascending); _currentLocalDir.changeSortStyle(sort, ascending: ascending);
_currentRemoteDir.changeSortStyle(sort, ascending: ascending); _currentRemoteDir.changeSortStyle(sort, ascending: ascending);
_localSortStyle = sort; _localSortAscending = ascending; _localSortStyle = sort;
_remoteSortStyle = sort; _remoteSortAscending = ascending; _localSortAscending = ascending;
_remoteSortStyle = sort;
_remoteSortAscending = ascending;
} else if (isLocal) { } else if (isLocal) {
_currentLocalDir.changeSortStyle(sort, ascending: ascending); _currentLocalDir.changeSortStyle(sort, ascending: ascending);
_localSortStyle = sort; _localSortAscending = ascending; _localSortStyle = sort;
_localSortAscending = ascending;
} else { } else {
_currentRemoteDir.changeSortStyle(sort, ascending: ascending); _currentRemoteDir.changeSortStyle(sort, ascending: ascending);
_remoteSortStyle = sort; _remoteSortAscending = ascending; _remoteSortStyle = sort;
_remoteSortAscending = ascending;
} }
notifyListeners(); notifyListeners();
} }
@ -668,7 +709,7 @@ class FileModel extends ChangeNotifier {
void updateFolderFiles(Map<String, dynamic> evt) { void updateFolderFiles(Map<String, dynamic> evt) {
// ret: "{\"id\":1,\"num_entries\":12,\"total_size\":1264822.0}" // ret: "{\"id\":1,\"num_entries\":12,\"total_size\":1264822.0}"
Map<String,dynamic> info = json.decode(evt['info']); Map<String, dynamic> info = json.decode(evt['info']);
int id = info['id']; int id = info['id'];
int num_entries = info['num_entries']; int num_entries = info['num_entries'];
double total_size = info['total_size']; double total_size = info['total_size'];
@ -685,7 +726,7 @@ class FileModel extends ChangeNotifier {
void loadLastJob(Map<String, dynamic> evt) { void loadLastJob(Map<String, dynamic> evt) {
debugPrint("load last job: ${evt}"); debugPrint("load last job: ${evt}");
Map<String,dynamic> jobDetail = json.decode(evt['value']); Map<String, dynamic> jobDetail = json.decode(evt['value']);
// int id = int.parse(jobDetail['id']); // int id = int.parse(jobDetail['id']);
String remote = jobDetail['remote']; String remote = jobDetail['remote'];
String to = jobDetail['to']; String to = jobDetail['to'];
@ -703,13 +744,14 @@ class FileModel extends ChangeNotifier {
..showHidden = showHidden ..showHidden = showHidden
..state = JobState.paused; ..state = JobState.paused;
jobTable.add(jobProgress); jobTable.add(jobProgress);
_ffi.target?.bind.sessionAddJob(id: '${_ffi.target?.id}', bind.sessionAddJob(
isRemote: isRemote, id: '${_ffi.target?.id}',
includeHidden: showHidden, isRemote: isRemote,
actId: currJobId, includeHidden: showHidden,
path: isRemote ? remote : to, actId: currJobId,
to: isRemote ? to: remote, path: isRemote ? remote : to,
fileNum: fileNum, to: isRemote ? to : remote,
fileNum: fileNum,
); );
} }
@ -717,9 +759,8 @@ class FileModel extends ChangeNotifier {
final jobIndex = getJob(jobId); final jobIndex = getJob(jobId);
if (jobIndex != -1) { if (jobIndex != -1) {
final job = jobTable[jobIndex]; final job = jobTable[jobIndex];
_ffi.target?.bind.sessionResumeJob(id: '${_ffi.target?.id}', bind.sessionResumeJob(
actId: job.id, id: '${_ffi.target?.id}', actId: job.id, isRemote: job.isRemote);
isRemote: job.isRemote);
job.state = JobState.inProgress; job.state = JobState.inProgress;
} else { } else {
debugPrint("jobId ${jobId} is not exists"); debugPrint("jobId ${jobId} is not exists");
@ -844,12 +885,12 @@ class FileFetcher {
String path, bool isLocal, bool showHidden) async { String path, bool isLocal, bool showHidden) async {
try { try {
if (isLocal) { if (isLocal) {
final res = await _ffi.bind.sessionReadLocalDirSync( final res = await bind.sessionReadLocalDirSync(
id: id ?? "", path: path, showHidden: showHidden); id: id ?? "", path: path, showHidden: showHidden);
final fd = FileDirectory.fromJson(jsonDecode(res)); final fd = FileDirectory.fromJson(jsonDecode(res));
return fd; return fd;
} else { } else {
await _ffi.bind.sessionReadRemoteDir( await bind.sessionReadRemoteDir(
id: id ?? "", path: path, includeHidden: showHidden); id: id ?? "", path: path, includeHidden: showHidden);
return registerReadTask(isLocal, path); return registerReadTask(isLocal, path);
} }
@ -862,7 +903,12 @@ class FileFetcher {
int id, String path, bool isLocal, bool showHidden) async { int id, String path, bool isLocal, bool showHidden) async {
// TODO test Recursive is show hidden default? // TODO test Recursive is show hidden default?
try { 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); return registerReadRecursiveTask(id);
} catch (e) { } catch (e) {
return Future.error(e); return Future.error(e);
@ -1033,7 +1079,9 @@ List<Entry> _sortList(List<Entry> list, SortBy sortType, bool ascending) {
files.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); 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. // 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) { } else if (sortType == SortBy.Modified) {
// making the list of Path & DateTime // making the list of Path & DateTime
List<_PathStat> _pathStat = []; List<_PathStat> _pathStat = [];
@ -1065,7 +1113,9 @@ List<Entry> _sortList(List<Entry> list, SortBy sortType, bool ascending) {
.split('.') .split('.')
.last .last
.compareTo(b.name.toLowerCase().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) { } else if (sortType == SortBy.Size) {
// create list of path and size // create list of path and size
Map<String, int> _sizeMap = {}; Map<String, int> _sizeMap = {};
@ -1090,7 +1140,9 @@ List<Entry> _sortList(List<Entry> list, SortBy sortType, bool ascending) {
.indexWhere((element) => element.key == a.name) .indexWhere((element) => element.key == a.name)
.compareTo( .compareTo(
_sizeMapList.indexWhere((element) => element.key == b.name))); _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 []; return [];
} }

View File

@ -20,8 +20,8 @@ import 'package:tuple/tuple.dart';
import '../common.dart'; import '../common.dart';
import '../mobile/widgets/dialog.dart'; import '../mobile/widgets/dialog.dart';
import '../mobile/widgets/overlay.dart'; import '../mobile/widgets/overlay.dart';
import 'native_model.dart' if (dart.library.html) 'web_model.dart';
import 'peer_model.dart'; import 'peer_model.dart';
import 'platform_model.dart';
typedef HandleMsgBox = void Function(Map<String, dynamic> evt, String id); typedef HandleMsgBox = void Function(Map<String, dynamic> evt, String id);
bool _waitForImage = false; bool _waitForImage = false;
@ -29,7 +29,6 @@ bool _waitForImage = false;
class FfiModel with ChangeNotifier { class FfiModel with ChangeNotifier {
PeerInfo _pi = PeerInfo(); PeerInfo _pi = PeerInfo();
Display _display = Display(); Display _display = Display();
PlatformFFI _platformFFI = PlatformFFI();
var _inputBlocked = false; var _inputBlocked = false;
final _permissions = Map<String, bool>(); final _permissions = Map<String, bool>();
@ -44,12 +43,6 @@ class FfiModel with ChangeNotifier {
Display get display => _display; Display get display => _display;
PlatformFFI get platformFFI => _platformFFI;
set platformFFI(PlatformFFI value) {
_platformFFI = value;
}
bool? get secure => _secure; bool? get secure => _secure;
bool? get direct => _direct; bool? get direct => _direct;
@ -71,10 +64,6 @@ class FfiModel with ChangeNotifier {
clear(); clear();
} }
Future<void> init() async {
await _platformFFI.init();
}
void toggleTouchMode() { void toggleTouchMode() {
if (!isPeerAndroid) { if (!isPeerAndroid) {
_touchMode = !_touchMode; _touchMode = !_touchMode;
@ -280,7 +269,7 @@ class FfiModel with ChangeNotifier {
_timer?.cancel(); _timer?.cancel();
if (hasRetry) { if (hasRetry) {
_timer = Timer(Duration(seconds: _reconnects), () { _timer = Timer(Duration(seconds: _reconnects), () {
parent.target?.bind.sessionReconnect(id: id); bind.sessionReconnect(id: id);
clearPermissions(); clearPermissions();
showLoading(translate('Connecting...')); showLoading(translate('Connecting...'));
}); });
@ -306,9 +295,8 @@ class FfiModel with ChangeNotifier {
Timer(Duration(milliseconds: 100), showMobileActionsOverlay); Timer(Duration(milliseconds: 100), showMobileActionsOverlay);
} }
} else { } else {
_touchMode = await parent.target?.bind _touchMode =
.getSessionOption(id: peerId, arg: "touch-mode") != await bind.getSessionOption(id: peerId, arg: "touch-mode") != '';
'';
} }
if (evt['is_file_transfer'] == "true") { if (evt['is_file_transfer'] == "true") {
@ -387,8 +375,7 @@ class ImageModel with ChangeNotifier {
} }
Future.delayed(Duration(milliseconds: 1), () { Future.delayed(Duration(milliseconds: 1), () {
if (parent.target?.ffiModel.isPeerAndroid ?? false) { if (parent.target?.ffiModel.isPeerAndroid ?? false) {
parent.target?.bind bind.sessionPeerOption(id: _id, name: "view-style", value: "shrink");
.sessionPeerOption(id: _id, name: "view-style", value: "shrink");
parent.target?.canvasModel.updateViewStyle(); parent.target?.canvasModel.updateViewStyle();
} }
}); });
@ -439,8 +426,7 @@ class CanvasModel with ChangeNotifier {
double get tabBarHeight => _tabBarHeight; double get tabBarHeight => _tabBarHeight;
void updateViewStyle() async { void updateViewStyle() async {
final s = final s = await bind.getSessionOption(id: id, arg: 'view-style');
await parent.target?.bind.getSessionOption(id: id, arg: 'view-style');
if (s == null) { if (s == null) {
return; return;
} }
@ -844,13 +830,6 @@ class FFI {
this.userModel = UserModel(WeakReference(this)); 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. /// Get the remote id for current client.
String getId() { String getId() {
return getByName('remote_id'); // TODO return getByName('remote_id'); // TODO
@ -1008,16 +987,16 @@ class FFI {
/// Send **get** command to the Rust core based on [name] and [arg]. /// Send **get** command to the Rust core based on [name] and [arg].
/// Return the result as a string. /// Return the result as a string.
String getByName(String name, [String arg = '']) { 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]. /// Send **set** command to the Rust core based on [name] and [value].
void setByName(String name, [String value = '']) { void setByName(String name, [String value = '']) {
ffiModel.platformFFI.setByName(name, value); platformFFI.setByName(name, value);
} }
String getOption(String name) { String getOption(String name) {
return ffiModel.platformFFI.getByName("option", name); return platformFFI.getByName("option", name);
} }
Future<String> getLocalOption(String name) { Future<String> getLocalOption(String name) {
@ -1040,11 +1019,9 @@ class FFI {
Map<String, String> res = Map() Map<String, String> res = Map()
..["name"] = name ..["name"] = name
..["value"] = value; ..["value"] = value;
return ffiModel.platformFFI.setByName('option', jsonEncode(res)); return platformFFI.setByName('option', jsonEncode(res));
} }
RustdeskImpl get bind => ffiModel.platformFFI.ffiBind;
handleMouse(Map<String, dynamic> evt, {double tabBarHeight = 0.0}) { handleMouse(Map<String, dynamic> evt, {double tabBarHeight = 0.0}) {
var type = ''; var type = '';
var isMove = false; var isMove = false;
@ -1102,18 +1079,18 @@ class FFI {
listenToMouse(bool yesOrNo) { listenToMouse(bool yesOrNo) {
if (yesOrNo) { if (yesOrNo) {
ffiModel.platformFFI.startDesktopWebListener(); platformFFI.startDesktopWebListener();
} else { } else {
ffiModel.platformFFI.stopDesktopWebListener(); platformFFI.stopDesktopWebListener();
} }
} }
void setMethodCallHandler(FMethod callback) { void setMethodCallHandler(FMethod callback) {
ffiModel.platformFFI.setMethodCallHandler(callback); platformFFI.setMethodCallHandler(callback);
} }
Future<bool> invokeMethod(String method, [dynamic arguments]) async { Future<bool> invokeMethod(String method, [dynamic arguments]) async {
return await ffiModel.platformFFI.invokeMethod(method, arguments); return await platformFFI.invokeMethod(method, arguments);
} }
Future<List<String>> getAudioInputs() async { Future<List<String>> getAudioInputs() async {

View File

@ -27,17 +27,24 @@ typedef HandleEvent = void Function(Map<String, dynamic> evt);
/// FFI wrapper around the native Rust core. /// FFI wrapper around the native Rust core.
/// Hides the platform differences. /// Hides the platform differences.
class PlatformFFI { class PlatformFFI {
Pointer<RgbaFrame>? _lastRgbaFrame;
String _dir = ''; String _dir = '';
String _homeDir = ''; String _homeDir = '';
F2? _getByName; F2? _getByName;
F3? _setByName; F3? _setByName;
var _eventHandlers = Map<String, Map<String, HandleEvent>>(); var _eventHandlers = Map<String, Map<String, HandleEvent>>();
late RustdeskImpl _ffiBind; late RustdeskImpl _ffiBind;
late String _appType;
void Function(Map<String, dynamic>)? _eventCallback; void Function(Map<String, dynamic>)? _eventCallback;
PlatformFFI._();
static final PlatformFFI instance = PlatformFFI._();
final _toAndroidChannel = MethodChannel("mChannel");
RustdeskImpl get ffiBind => _ffiBind; RustdeskImpl get ffiBind => _ffiBind;
static get localeName => Platform.localeName;
static Future<String> getVersion() async { static Future<String> getVersion() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform(); PackageInfo packageInfo = await PackageInfo.fromPlatform();
return packageInfo.version; return packageInfo.version;
@ -94,10 +101,8 @@ class PlatformFFI {
} }
/// Init the FFI class, loads the native Rust core library. /// Init the FFI class, loads the native Rust core library.
Future<Null> init() async { Future<Null> init(String appType) async {
isIOS = Platform.isIOS; _appType = appType;
isAndroid = Platform.isAndroid;
isDesktop = Platform.isWindows || Platform.isMacOS || Platform.isLinux;
// if (isDesktop) { // if (isDesktop) {
// // TODO // // TODO
// return; // return;
@ -111,7 +116,7 @@ class PlatformFFI {
: Platform.isMacOS : Platform.isMacOS
? DynamicLibrary.open("librustdesk.dylib") ? DynamicLibrary.open("librustdesk.dylib")
: DynamicLibrary.process(); : DynamicLibrary.process();
print('initializing FFI'); debugPrint('initializing FFI ${_appType}');
try { try {
_getByName = dylib.lookupFunction<F2, F2>('get_by_name'); _getByName = dylib.lookupFunction<F2, F2>('get_by_name');
_setByName = _setByName =
@ -155,7 +160,8 @@ class PlatformFFI {
name = macOsInfo.computerName; name = macOsInfo.computerName;
id = macOsInfo.systemGUID ?? ""; 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('info1', id);
setByName('info2', name); setByName('info2', name);
setByName('home_dir', _homeDir); setByName('home_dir', _homeDir);
@ -185,17 +191,18 @@ class PlatformFFI {
/// Start listening to the Rust core's events and frames. /// Start listening to the Rust core's events and frames.
void _startListenEvent(RustdeskImpl rustdeskImpl) { void _startListenEvent(RustdeskImpl rustdeskImpl) {
() async { () async {
await for (final message in rustdeskImpl.startGlobalEventStream()) { await for (final message
if (_eventCallback != null) { in rustdeskImpl.startGlobalEventStream(appType: _appType)) {
try { try {
Map<String, dynamic> event = json.decode(message); Map<String, dynamic> event = json.decode(message);
// _tryHandle here may be more flexible than _eventCallback // _tryHandle here may be more flexible than _eventCallback
if (!_tryHandle(event)) { if (!_tryHandle(event)) {
if (_eventCallback != null) {
_eventCallback!(event); _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 stopDesktopWebListener() {}
void setMethodCallHandler(FMethod callback) { void setMethodCallHandler(FMethod callback) {
toAndroidChannel.setMethodCallHandler((call) async { _toAndroidChannel.setMethodCallHandler((call) async {
callback(call.method, call.arguments); callback(call.method, call.arguments);
return null; return null;
}); });
@ -220,9 +227,6 @@ class PlatformFFI {
invokeMethod(String method, [dynamic arguments]) async { invokeMethod(String method, [dynamic arguments]) async {
if (!isAndroid) return Future<bool>(() => false); if (!isAndroid) return Future<bool>(() => false);
return await toAndroidChannel.invokeMethod(method, arguments); return await _toAndroidChannel.invokeMethod(method, arguments);
} }
} }
final localeName = Platform.localeName;
final toAndroidChannel = MethodChannel("mChannel");

View File

@ -1,6 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import '../../common.dart'; import 'platform_model.dart';
class Peer { class Peer {
final String id; final String id;
@ -44,11 +44,10 @@ class Peers extends ChangeNotifier {
_name = name; _name = name;
_loadEvent = loadEvent; _loadEvent = loadEvent;
_peers = _initPeers; _peers = _initPeers;
gFFI.ffiModel.platformFFI.registerEventHandler(_cbQueryOnlines, _name, platformFFI.registerEventHandler(_cbQueryOnlines, _name, (evt) {
(evt) {
_updateOnlineState(evt); _updateOnlineState(evt);
}); });
gFFI.ffiModel.platformFFI.registerEventHandler(_loadEvent, _name, (evt) { platformFFI.registerEventHandler(_loadEvent, _name, (evt) {
_updatePeers(evt); _updatePeers(evt);
}); });
} }
@ -57,8 +56,8 @@ class Peers extends ChangeNotifier {
@override @override
void dispose() { void dispose() {
gFFI.ffiModel.platformFFI.unregisterEventHandler(_cbQueryOnlines, _name); platformFFI.unregisterEventHandler(_cbQueryOnlines, _name);
gFFI.ffiModel.platformFFI.unregisterEventHandler(_loadEvent, _name); platformFFI.unregisterEventHandler(_loadEvent, _name);
super.dispose(); super.dispose();
} }

View File

@ -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;

View File

@ -6,6 +6,7 @@ import 'package:get/get.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'model.dart'; import 'model.dart';
import 'platform_model.dart';
class UserModel extends ChangeNotifier { class UserModel extends ChangeNotifier {
var userName = "".obs; var userName = "".obs;
@ -17,8 +18,7 @@ class UserModel extends ChangeNotifier {
if (userName.isNotEmpty) { if (userName.isNotEmpty) {
return userName.value; return userName.value;
} }
final userInfo = final userInfo = await bind.mainGetLocalOption(key: 'user_info') ?? "{}";
await parent.target?.bind.mainGetLocalOption(key: 'user_info') ?? "{}";
if (userInfo.trim().isEmpty) { if (userInfo.trim().isEmpty) {
return ""; return "";
} }
@ -29,10 +29,6 @@ class UserModel extends ChangeNotifier {
Future<void> logOut() async { Future<void> logOut() async {
debugPrint("start logout"); debugPrint("start logout");
final bind = parent.target?.bind;
if (bind == null) {
return;
}
final url = await bind.mainGetApiServer(); final url = await bind.mainGetApiServer();
final _ = await http.post(Uri.parse("$url/api/logout"), final _ = await http.post(Uri.parse("$url/api/logout"),
body: { body: {
@ -55,10 +51,6 @@ class UserModel extends ChangeNotifier {
} }
Future<Map<String, dynamic>> login(String userName, String pass) async { Future<Map<String, dynamic>> login(String userName, String pass) async {
final bind = parent.target?.bind;
if (bind == null) {
return {"error": "no context"};
}
final url = await bind.mainGetApiServer(); final url = await bind.mainGetApiServer();
try { try {
final resp = await http.post(Uri.parse("$url/api/login"), final resp = await http.post(Uri.parse("$url/api/login"),

View File

@ -20,7 +20,12 @@ class PlatformFFI {
context.callMethod('setByName', [name, value]); context.callMethod('setByName', [name, value]);
} }
static Future<Null> init() async { PlatformFFI._();
static final PlatformFFI instance = PlatformFFI._();
static get localeName => window.navigator.language;
static Future<Null> init(String _appType) async {
isWeb = true; isWeb = true;
isWebDesktop = !context.callMethod('isMobile'); isWebDesktop = !context.callMethod('isMobile');
context.callMethod('init'); context.callMethod('init');
@ -68,5 +73,3 @@ class PlatformFFI {
return true; return true;
} }
} }
final localeName = window.navigator.language;

View File

@ -115,9 +115,9 @@ include(flutter/generated_plugins.cmake)
# By default, "installing" just makes a relocatable bundle in the build # By default, "installing" just makes a relocatable bundle in the build
# directory. # directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 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) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif() #endif()
# Start with a clean build bundle directory every time. # Start with a clean build bundle directory every time.
install(CODE " install(CODE "

View File

@ -31,10 +31,14 @@ use hbb_common::{
use crate::common::make_fd_to_json; use crate::common::make_fd_to_json;
use crate::{client::*, flutter_ffi::EventToUI, make_fd_flutter}; 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! { lazy_static::lazy_static! {
// static ref SESSION: Arc<RwLock<Option<Session>>> = Default::default(); // static ref SESSION: Arc<RwLock<Option<Session>>> = Default::default();
pub static ref SESSIONS: RwLock<HashMap<String,Session>> = Default::default(); pub static ref SESSIONS: RwLock<HashMap<String,Session>> = Default::default();
pub static ref GLOBAL_EVENT_STREAM: RwLock<Option<StreamSink<String>>> = Default::default(); // rust to dart event channel pub static ref GLOBAL_EVENT_STREAM: RwLock<HashMap<String, StreamSink<String>>> = Default::default(); // rust to dart event channel
} }
// pub fn get_session<'a>(id: &str) -> Option<&'a Session> { // 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())], vec![("x", &cp.x.to_string()), ("y", &cp.y.to_string())],
); );
} }
Some(message::Union::FileResponse(fr)) => match fr.union { Some(message::Union::FileResponse(fr)) => {
Some(file_response::Union::Dir(fd)) => { match fr.union {
let mut entries = fd.entries.to_vec(); Some(file_response::Union::Dir(fd)) => {
if self.session.peer_platform() == "Windows" { let mut entries = fd.entries.to_vec();
fs::transform_windows_path(&mut entries); if self.session.peer_platform() == "Windows" {
} fs::transform_windows_path(&mut entries);
let id = fd.id; }
self.session.push_event( let id = fd.id;
"file_dir", self.session.push_event(
vec![("value", &make_fd_to_json(fd)), ("is_local", "false")], "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); 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
} }
self.update_jobs_status();
} }
} Some(file_response::Union::Block(block)) => {
Some(file_response::Union::Done(d)) => { if let Some(job) = fs::get_job(block.id, &mut self.write_jobs) {
if let Some(job) = fs::get_job(d.id, &mut self.write_jobs) { if let Err(_err) = job.write(block, None).await {
job.modify_time(); // to-do: add "skip" for writing job
fs::remove_job(d.id, &mut self.write_jobs); }
self.update_jobs_status();
}
} }
self.handle_job_status(d.id, d.file_num, None); Some(file_response::Union::Done(d)) => {
} if let Some(job) = fs::get_job(d.id, &mut self.write_jobs) {
Some(file_response::Union::Error(e)) => { job.modify_time();
self.handle_job_status(e.id, e.file_num, Some(e.error)); fs::remove_job(d.id, &mut self.write_jobs);
} }
Some(file_response::Union::Digest(digest)) => { self.handle_job_status(d.id, d.file_num, None);
if digest.is_upload { }
if let Some(job) = fs::get_job(digest.id, &mut self.read_jobs) { Some(file_response::Union::Error(e)) => {
if let Some(file) = job.files().get(digest.file_num as usize) { self.handle_job_status(e.id, e.file_num, Some(e.error));
let read_path = get_string(&job.join(&file.name)); }
let overwrite_strategy = job.default_overwrite_strategy(); Some(file_response::Union::Digest(digest)) => {
if let Some(overwrite) = overwrite_strategy { if digest.is_upload {
let req = FileTransferSendConfirmRequest { if let Some(job) = fs::get_job(digest.id, &mut self.read_jobs) {
id: digest.id, if let Some(file) = job.files().get(digest.file_num as usize) {
file_num: digest.file_num, let read_path = get_string(&job.join(&file.name));
union: Some(if overwrite { let overwrite_strategy = job.default_overwrite_strategy();
file_transfer_send_confirm_request::Union::OffsetBlk(0) if let Some(overwrite) = overwrite_strategy {
} else { let req = FileTransferSendConfirmRequest {
file_transfer_send_confirm_request::Union::Skip( id: digest.id,
true, file_num: digest.file_num,
) union: Some(if overwrite {
}), file_transfer_send_confirm_request::Union::OffsetBlk(0)
..Default::default() } else {
}; file_transfer_send_confirm_request::Union::Skip(
job.confirm(&req); true,
let msg = new_send_confirm(req); )
allow_err!(peer.send(&msg).await); }),
} else { ..Default::default()
self.handle_override_file_confirm( };
digest.id, job.confirm(&req);
digest.file_num, let msg = new_send_confirm(req);
read_path, allow_err!(peer.send(&msg).await);
true, } else {
); self.handle_override_file_confirm(
digest.id,
digest.file_num,
read_path,
true,
);
}
} }
} }
} } else {
} else { if let Some(job) = fs::get_job(digest.id, &mut self.write_jobs) {
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) {
if let Some(file) = job.files().get(digest.file_num as usize) { let write_path = get_string(&job.join(&file.name));
let write_path = get_string(&job.join(&file.name)); let overwrite_strategy = job.default_overwrite_strategy();
let overwrite_strategy = job.default_overwrite_strategy(); match fs::is_write_need_confirmation(&write_path, &digest) {
match fs::is_write_need_confirmation(&write_path, &digest) { Ok(res) => match res {
Ok(res) => match res { DigestCheckResult::IsSame => {
DigestCheckResult::IsSame => { let msg= new_send_confirm(FileTransferSendConfirmRequest {
let msg= new_send_confirm(FileTransferSendConfirmRequest {
id: digest.id, id: digest.id,
file_num: digest.file_num, file_num: digest.file_num,
union: Some(file_transfer_send_confirm_request::Union::Skip(true)), union: Some(file_transfer_send_confirm_request::Union::Skip(true)),
..Default::default() ..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); self.session.send_msg(msg);
} else {
self.handle_override_file_confirm(
digest.id,
digest.file_num,
write_path.to_string(),
false,
);
} }
} DigestCheckResult::NeedConfirm(digest) => {
DigestCheckResult::NoSuchFile => { if let Some(overwrite) = overwrite_strategy {
let msg = new_send_confirm( 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 { FileTransferSendConfirmRequest {
id: digest.id, id: digest.id,
file_num: digest.file_num, file_num: digest.file_num,
@ -900,19 +905,20 @@ impl Connection {
..Default::default() ..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(message::Union::Misc(misc)) => match misc.union {
Some(misc::Union::AudioFormat(f)) => { Some(misc::Union::AudioFormat(f)) => {
self.audio_handler.handle_format(f); // self.audio_handler.handle_format(f); //
@ -1513,7 +1519,11 @@ pub mod connection_manager {
assert!(h.get("name").is_none()); assert!(h.get("name").is_none());
h.insert("name", name); 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())); s.add(serde_json::ser::to_string(&h).unwrap_or("".to_owned()));
}; };
} }

View File

@ -75,11 +75,17 @@ pub enum EventToUI {
Rgba(ZeroCopyBuffer<Vec<u8>>), Rgba(ZeroCopyBuffer<Vec<u8>>),
} }
pub fn start_global_event_stream(s: StreamSink<String>) -> ResultType<()> { pub fn start_global_event_stream(s: StreamSink<String>, app_type: String) -> ResultType<()> {
let _ = flutter::GLOBAL_EVENT_STREAM.write().unwrap().insert(s); 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(()) 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) { pub fn host_stop_system_key_propagate(stopped: bool) {
#[cfg(windows)] #[cfg(windows)]
crate::platform::windows::stop_system_key_propagate(stopped); crate::platform::windows::stop_system_key_propagate(stopped);
@ -518,7 +524,7 @@ pub fn main_load_recent_peers() {
.drain(..) .drain(..)
.map(|(id, _, p)| (id, p.info)) .map(|(id, _, p)| (id, p.info))
.collect(); .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([ let data = HashMap::from([
("name", "load_recent_peers".to_owned()), ("name", "load_recent_peers".to_owned()),
( (
@ -544,7 +550,7 @@ pub fn main_load_fav_peers() {
} }
}) })
.collect(); .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([ let data = HashMap::from([
("name", "load_fav_peers".to_owned()), ("name", "load_fav_peers".to_owned()),
( (
@ -558,7 +564,7 @@ pub fn main_load_fav_peers() {
} }
pub fn main_load_lan_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([ let data = HashMap::from([
("name", "load_lan_peers".to_owned()), ("name", "load_lan_peers".to_owned()),
("peers", get_lan_peers()), ("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<String>, offlines: Vec<String>) { fn handle_query_onlines(onlines: Vec<String>, offlines: Vec<String>) {
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([ let data = HashMap::from([
("name", "callback_query_onlines".to_owned()), ("name", "callback_query_onlines".to_owned()),
("onlines", onlines.join(",")), ("onlines", onlines.join(",")),