From 0ef1659b8784608e4a56552514d5ca158015cbf1 Mon Sep 17 00:00:00 2001 From: csf Date: Fri, 5 Aug 2022 20:29:43 +0800 Subject: [PATCH 1/3] fix mobile features --- flutter/lib/common.dart | 17 +++ flutter/lib/desktop/pages/remote_page.dart | 26 ----- flutter/lib/mobile/pages/home_page.dart | 6 +- flutter/lib/mobile/pages/remote_page.dart | 119 +++++++++----------- flutter/lib/mobile/pages/settings_page.dart | 26 ++--- flutter/lib/models/model.dart | 9 +- 6 files changed, 89 insertions(+), 114 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index f49440655..16e8a172e 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -352,6 +352,23 @@ RadioListTile getRadio( ); } +CheckboxListTile getToggle( + String id, void Function(void Function()) setState, option, name) { + final opt = bind.getSessionToggleOptionSync(id: id, arg: option); + return CheckboxListTile( + value: opt, + onChanged: (v) { + setState(() { + bind.sessionToggleOption(id: id, value: option); + }); + if (option == "show-quality-monitor") { + gFFI.qualityMonitorModel.checkShowQualityMonitor(id); + } + }, + dense: true, + title: Text(translate(name))); +} + /// find ffi, tag is Remote ID /// for session specific usage FFI ffi(String? tag) { diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index ed62e5067..bfc193a89 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -887,32 +887,6 @@ class ImagePainter extends CustomPainter { } } -CheckboxListTile getToggle( - String id, void Function(void Function()) setState, option, name) { - final opt = bind.getSessionToggleOptionSync(id: id, arg: option); - return CheckboxListTile( - value: opt, - onChanged: (v) { - setState(() { - bind.sessionToggleOption(id: id, value: option); - }); - }, - dense: true, - title: Text(translate(name))); -} - -RadioListTile getRadio(String name, String toValue, String curValue, - void Function(String?) onChange) { - return RadioListTile( - controlAffinity: ListTileControlAffinity.trailing, - title: Text(translate(name)), - value: toValue, - groupValue: curValue, - onChanged: onChange, - dense: true, - ); -} - void showOptions(String id) async { String quality = await bind.getSessionImageQuality(id: id) ?? 'balanced'; if (quality == '') quality = 'balanced'; diff --git a/flutter/lib/mobile/pages/home_page.dart b/flutter/lib/mobile/pages/home_page.dart index e56434487..6bf0be2c7 100644 --- a/flutter/lib/mobile/pages/home_page.dart +++ b/flutter/lib/mobile/pages/home_page.dart @@ -12,10 +12,10 @@ abstract class PageShape extends Widget { final List appBarActions = []; } -final homeKey = GlobalKey<_HomePageState>(); - class HomePage extends StatefulWidget { - HomePage({Key? key}) : super(key: key); + static final homeKey = GlobalKey<_HomePageState>(); + + HomePage() : super(key: homeKey); @override _HomePageState createState() => _HomePageState(); diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 980f665e7..6ea4ca2e6 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -12,6 +12,7 @@ import 'package:wakelock/wakelock.dart'; import '../../common.dart'; import '../../models/model.dart'; +import '../../models/platform_model.dart'; import '../widgets/dialog.dart'; import '../widgets/gestures.dart'; import '../widgets/overlay.dart'; @@ -135,7 +136,7 @@ class _RemotePageState extends State { if (newValue.length > common) { var s = newValue.substring(common); if (s.length > 1) { - gFFI.setByName('input_string', s); + bind.sessionInputString(id: widget.id, value: s); } else { inputChar(s); } @@ -169,11 +170,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 - gFFI.setByName('input_string', content); + bind.sessionInputString(id: widget.id, value: content); openKeyboard(); return; } - gFFI.setByName('input_string', content); + bind.sessionInputString(id: widget.id, value: content); } else { inputChar(content); } @@ -409,7 +410,7 @@ class _RemotePageState extends State { icon: Icon(Icons.tv), onPressed: () { setState(() => _showEdit = false); - showOptions(); + showOptions(widget.id); }, ) ] + @@ -461,7 +462,7 @@ class _RemotePageState extends State { icon: Icon(Icons.more_vert), onPressed: () { setState(() => _showEdit = false); - showActions(); + showActions(widget.id); }, ), ]), @@ -573,7 +574,7 @@ class _RemotePageState extends State { }, onTwoFingerScaleEnd: (d) { _scale = 1; - gFFI.setByName('peer_option', '{"name": "view-style", "value": ""}'); + bind.sessionPeerOption(id: widget.id, name: "view-style", value: ""); }, onThreeFingerVerticalDragUpdate: gFFI.ffiModel.isPeerAndroid ? null @@ -620,8 +621,9 @@ class _RemotePageState extends State { Widget getBodyForDesktopWithListener(bool keyboard) { var paints = [ImagePaint()]; - if (keyboard || - gFFI.getByName('toggle_option', 'show-remote-cursor') == 'true') { + final cursor = bind.getSessionToggleOptionSync( + id: widget.id, arg: 'show-remote-cursor'); + if (keyboard || cursor) { paints.add(CursorPaint()); } return Container( @@ -649,7 +651,7 @@ class _RemotePageState extends State { return out; } - void showActions() { + void showActions(String id) async { final size = MediaQuery.of(context).size; final x = 120.0; final y = size.height; @@ -668,7 +670,7 @@ class _RemotePageState extends State { style: flatButtonStyle, onPressed: () { Navigator.pop(context); - showSetOSPassword(false); + showSetOSPassword(id, false); }, child: Icon(Icons.edit, color: MyTheme.accent), ) @@ -691,7 +693,8 @@ class _RemotePageState extends State { more.add(PopupMenuItem( child: Text(translate('Insert Lock')), value: 'lock')); if (pi.platform == 'Windows' && - gFFI.getByName('toggle_option', 'privacy-mode') != 'true') { + await bind.getSessionToggleOption(id: id, arg: 'privacy-mode') != + true) { more.add(PopupMenuItem( child: Text(translate((gFFI.ffiModel.inputBlocked ? 'Unb' : 'B') + 'lock user input')), @@ -713,28 +716,29 @@ class _RemotePageState extends State { elevation: 8, ); if (value == 'cad') { - gFFI.setByName('ctrl_alt_del'); + bind.sessionCtrlAltDel(id: widget.id); } else if (value == 'lock') { - gFFI.setByName('lock_screen'); + bind.sessionLockScreen(id: widget.id); } else if (value == 'block-input') { - gFFI.setByName('toggle_option', - (gFFI.ffiModel.inputBlocked ? 'un' : '') + 'block-input'); + bind.sessionToggleOption( + id: widget.id, + value: (gFFI.ffiModel.inputBlocked ? 'un' : '') + 'block-input'); gFFI.ffiModel.inputBlocked = !gFFI.ffiModel.inputBlocked; } else if (value == 'refresh') { - gFFI.setByName('refresh'); + bind.sessionRefresh(id: widget.id); } else if (value == 'paste') { () async { ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); if (data != null && data.text != null) { - gFFI.setByName('input_string', '${data.text}'); + bind.sessionInputString(id: widget.id, value: data.text ?? ""); } }(); } else if (value == 'enter_os_password') { - var password = gFFI.getByName('peer_option', "os-password"); - if (password != "") { - gFFI.setByName('input_os_password', password); + var password = await bind.getSessionOption(id: id, arg: "os-password"); + if (password != null) { + bind.sessionInputOsPassword(id: widget.id, value: password); } else { - showSetOSPassword(true); + showSetOSPassword(id, true); } } else if (value == 'reset_canvas') { gFFI.cursorModel.reset(); @@ -762,8 +766,8 @@ class _RemotePageState extends State { onTouchModeChange: (t) { gFFI.ffiModel.toggleTouchMode(); final v = gFFI.ffiModel.touchMode ? 'Y' : ''; - gFFI.setByName('peer_option', - '{"name": "touch-mode", "value": "$v"}'); + bind.sessionPeerOption( + id: widget.id, name: "touch", value: v); })); })); } @@ -978,23 +982,23 @@ class QualityMonitor extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Speed: ${qualityMonitorModel.data.speed}", + "Speed: ${qualityMonitorModel.data.speed ?? ''}", style: TextStyle(color: MyTheme.grayBg), ), Text( - "FPS: ${qualityMonitorModel.data.fps}", + "FPS: ${qualityMonitorModel.data.fps ?? ''}", style: TextStyle(color: MyTheme.grayBg), ), Text( - "Delay: ${qualityMonitorModel.data.delay} ms", + "Delay: ${qualityMonitorModel.data.delay ?? ''} ms", style: TextStyle(color: MyTheme.grayBg), ), Text( - "Target Bitrate: ${qualityMonitorModel.data.targetBitrate}kb", + "Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb", style: TextStyle(color: MyTheme.grayBg), ), Text( - "Codec: ${qualityMonitorModel.data.codecFormat}", + "Codec: ${qualityMonitorModel.data.codecFormat ?? ''}", style: TextStyle(color: MyTheme.grayBg), ), ], @@ -1003,26 +1007,11 @@ class QualityMonitor extends StatelessWidget { : SizedBox.shrink()))); } -CheckboxListTile getToggle( - void Function(void Function()) setState, option, name) { - return CheckboxListTile( - value: gFFI.getByName('toggle_option', option) == 'true', - onChanged: (v) { - setState(() { - gFFI.setByName('toggle_option', option); - }); - if (option == "show-quality-monitor") { - gFFI.qualityMonitorModel.checkShowQualityMonitor(); - } - }, - dense: true, - title: Text(translate(name))); -} - -void showOptions() { - String quality = gFFI.getByName('image_quality'); +void showOptions(String id) async { + String quality = await bind.getSessionImageQuality(id: id) ?? 'balanced'; if (quality == '') quality = 'balanced'; - String viewStyle = gFFI.getByName('peer_option', 'view-style'); + String viewStyle = + await bind.getSessionOption(id: id, arg: 'view-style') ?? ''; var displays = []; final pi = gFFI.ffiModel.pi; final image = gFFI.ffiModel.getConnectionImage(); @@ -1035,7 +1024,7 @@ void showOptions() { children.add(InkWell( onTap: () { if (i == cur) return; - gFFI.setByName('switch_display', i.toString()); + bind.sessionSwitchDisplay(id: id, value: i); SmartDialog.dismiss(); }, child: Ink( @@ -1064,30 +1053,30 @@ void showOptions() { DialogManager.show((setState, close) { final more = []; if (perms['audio'] != false) { - more.add(getToggle(setState, 'disable-audio', 'Mute')); + more.add(getToggle(id, setState, 'disable-audio', 'Mute')); } if (perms['keyboard'] != false) { if (perms['clipboard'] != false) - more.add(getToggle(setState, 'disable-clipboard', 'Disable clipboard')); + more.add( + getToggle(id, setState, 'disable-clipboard', 'Disable clipboard')); more.add(getToggle( - setState, 'lock-after-session-end', 'Lock after session end')); + id, setState, 'lock-after-session-end', 'Lock after session end')); if (pi.platform == 'Windows') { - more.add(getToggle(setState, 'privacy-mode', 'Privacy mode')); + more.add(getToggle(id, setState, 'privacy-mode', 'Privacy mode')); } } var setQuality = (String? value) { if (value == null) return; setState(() { quality = value; - gFFI.setByName('image_quality', value); + bind.sessionSetImageQuality(id: id, value: value); }); }; var setViewStyle = (String? value) { if (value == null) return; setState(() { viewStyle = value; - gFFI.setByName( - 'peer_option', '{"name": "view-style", "value": "$value"}'); + bind.sessionPeerOption(id: id, name: "view-style", value: value); gFFI.canvasModel.updateViewStyle(); }); }; @@ -1105,9 +1094,10 @@ void showOptions() { getRadio('Balanced', 'balanced', quality, setQuality), getRadio('Optimize reaction time', 'low', quality, setQuality), Divider(color: MyTheme.border), - getToggle(setState, 'show-remote-cursor', 'Show remote cursor'), getToggle( - setState, 'show-quality-monitor', 'Show quality monitor'), + id, setState, 'show-remote-cursor', 'Show remote cursor'), + getToggle(id, setState, 'show-quality-monitor', + 'Show quality monitor'), ] + more), actions: [], @@ -1137,10 +1127,10 @@ void showRestartRemoteDevice(PeerInfo pi, String id) async { if (res == true) gFFI.setByName('restart_remote_device'); } -void showSetOSPassword(bool login) { +void showSetOSPassword(String id, bool login) async { final controller = TextEditingController(); - var password = gFFI.getByName('peer_option', "os-password"); - var autoLogin = gFFI.getByName('peer_option', "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( @@ -1173,12 +1163,11 @@ void showSetOSPassword(bool login) { style: flatButtonStyle, onPressed: () { var text = controller.text.trim(); - gFFI.setByName( - 'peer_option', '{"name": "os-password", "value": "$text"}'); - gFFI.setByName('peer_option', - '{"name": "auto-login", "value": "${autoLogin ? 'Y' : ''}"}'); + bind.sessionPeerOption(id: id, name: "os-password", value: text); + bind.sessionPeerOption( + id: id, name: "auto-login", value: autoLogin ? 'Y' : ''); if (text != "" && login) { - gFFI.setByName('input_os_password', text); + bind.sessionInputOsPassword(id: id, value: text); } close(); }, diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index ab7b2584d..01cf4ae5d 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -9,6 +9,7 @@ import 'package:url_launcher/url_launcher.dart'; import '../../common.dart'; import '../../models/model.dart'; +import '../../models/platform_model.dart'; import '../widgets/dialog.dart'; import 'home_page.dart'; import 'scan_page.dart'; @@ -192,21 +193,18 @@ void showServerSettings() { showServerSettingsWithValue(id, relay, key, api); } -void showLanguageSettings() { +void showLanguageSettings() async { try { final langs = json.decode(gFFI.getByName('langs')) as List; - var lang = gFFI.getByName('local_option', 'lang'); + var lang = await bind.mainGetLocalOption(key: "lang"); DialogManager.show((setState, close) { final setLang = (v) { if (lang != v) { setState(() { lang = v; }); - final msg = Map() - ..['name'] = 'lang' - ..['value'] = v; - gFFI.setByName('local_option', json.encode(msg)); - homeKey.currentState?.refreshPages(); + bind.mainSetLocalOption(key: "lang", value: v); + HomePage.homeKey.currentState?.refreshPages(); Future.delayed(Duration(milliseconds: 200), close); } }; @@ -277,8 +275,8 @@ fetch('http://localhost:21114/api/login', { final body = { 'username': name, 'password': pass, - 'id': gFFI.getByName('server_id'), - 'uuid': gFFI.getByName('uuid') + 'id': bind.mainGetMyId(), + 'uuid': bind.mainGetUuid() }; try { final response = await http.post(Uri.parse('$url/api/login'), @@ -314,10 +312,7 @@ void refreshCurrentUser() async { final token = gFFI.getByName("option", "access_token"); if (token == '') return; final url = getUrl(); - final body = { - 'id': gFFI.getByName('server_id'), - 'uuid': gFFI.getByName('uuid') - }; + final body = {'id': bind.mainGetMyId(), 'uuid': bind.mainGetUuid()}; try { final response = await http.post(Uri.parse('$url/api/currentUser'), headers: { @@ -340,10 +335,7 @@ void logout() async { final token = gFFI.getByName("option", "access_token"); if (token == '') return; final url = getUrl(); - final body = { - 'id': gFFI.getByName('server_id'), - 'uuid': gFFI.getByName('uuid') - }; + final body = {'id': bind.mainGetMyId(), 'uuid': bind.mainGetUuid()}; try { await http.post(Uri.parse('$url/api/logout'), headers: { diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 612dd04ac..f67d0d5fa 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -171,6 +171,8 @@ class FfiModel with ChangeNotifier { parent.target?.serverModel.onClientAuthorized(evt); } else if (name == 'on_client_remove') { parent.target?.serverModel.onClientRemove(evt); + } else if (name == 'update_quality_status') { + parent.target?.qualityMonitorModel.updateQualityStatus(evt); } }; } @@ -807,9 +809,10 @@ class QualityMonitorModel with ChangeNotifier { bool get show => _show; QualityMonitorData get data => _data; - checkShowQualityMonitor() { - final show = - gFFI.getByName('toggle_option', 'show-quality-monitor') == 'true'; + checkShowQualityMonitor(String id) async { + final show = await bind.getSessionToggleOption( + id: id, arg: 'show-quality-monitor') == + true; if (_show != show) { _show = show; notifyListeners(); From c5d062829191809978a5880af10c3f6d729ff7b5 Mon Sep 17 00:00:00 2001 From: csf Date: Mon, 8 Aug 2022 17:53:51 +0800 Subject: [PATCH 2/3] refactor set/getByName "peers" "option" --- flutter/lib/common.dart | 7 + .../lib/desktop/pages/connection_page.dart | 16 +- .../lib/desktop/pages/desktop_home_page.dart | 96 ++-- .../lib/desktop/widgets/peercard_widget.dart | 8 +- flutter/lib/mobile/pages/connection_page.dart | 97 ++-- flutter/lib/mobile/pages/scan_page.dart | 118 +++-- flutter/lib/mobile/pages/settings_page.dart | 11 +- flutter/lib/models/chat_model.dart | 5 +- flutter/lib/models/file_model.dart | 2 +- flutter/lib/models/model.dart | 64 +-- flutter/lib/models/native_model.dart | 15 + flutter/lib/models/server_model.dart | 17 +- flutter/lib/utils/tray_manager.dart | 3 +- src/flutter_ffi.rs | 459 +++++------------- src/ui_interface.rs | 49 +- 15 files changed, 414 insertions(+), 553 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 16e8a172e..861b6b645 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -391,3 +391,10 @@ Future initGlobalFFI() async { // global shared preference await Get.putAsync(() => SharedPreferences.getInstance()); } + +String translate(String name) { + if (name.startsWith('Failed to') && name.contains(': ')) { + return name.split(': ').map((x) => translate(x)).join(': '); + } + return platformFFI.translate(name, localeName); +} diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index e32275373..d992c6c62 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -44,13 +44,22 @@ class _ConnectionPageState extends State { /// Update url. If it's not null, means an update is available. var _updateUrl = ''; - var _menuPos; Timer? _updateTimer; @override void initState() { super.initState(); + if (_idController.text.isEmpty) { + () async { + final lastRemoteId = await bind.mainGetLastRemoteId(); + if (lastRemoteId != _idController.text) { + setState(() { + _idController.text = lastRemoteId; + }); + } + }(); + } _updateTimer = Timer.periodic(Duration(seconds: 1), (timer) { updateStatus(); }); @@ -58,7 +67,6 @@ class _ConnectionPageState extends State { @override Widget build(BuildContext context) { - if (_idController.text.isEmpty) _idController.text = gFFI.getId(); return Container( decoration: BoxDecoration(color: isDarkTheme() ? null : MyTheme.grayBg), child: Column( @@ -428,7 +436,7 @@ class _ConnectionPageState extends State { } updateStatus() async { - svcStopped.value = gFFI.getOption("stop-service") == "Y"; + svcStopped.value = bind.mainGetOption(key: "stop-service") == "Y"; final status = jsonDecode(await bind.mainGetConnectStatus()) as Map; svcStatusCode.value = status["status_num"]; @@ -444,7 +452,7 @@ class _ConnectionPageState extends State { } Future buildAddressBook(BuildContext context) async { - final token = await gFFI.getLocalOption('access_token'); + final token = await bind.mainGetLocalOption(key: 'access_token'); if (token.trim().isEmpty) { return Center( child: InkWell( diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 6dc5e8f2f..3f908c3ce 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -7,7 +7,6 @@ import 'package:flutter/services.dart'; 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'; @@ -156,6 +155,8 @@ class _DesktopHomePageState extends State with TrayListener { }, onTap: () async { final userName = await gFFI.userModel.getUserName(); + final enabledInput = await bind.mainGetOption(key: 'enable-audio'); + final defaultInput = await gFFI.getDefaultAudioInput(); var menu = [ genEnablePopupMenuItem( translate("Enable Keyboard/Mouse"), @@ -173,7 +174,7 @@ class _DesktopHomePageState extends State with TrayListener { translate("Enable TCP Tunneling"), 'enable-tunnel', ), - genAudioInputPopupMenuItem(), + genAudioInputPopupMenuItem(enabledInput != "N", defaultInput), PopupMenuDivider(), PopupMenuItem( child: Text(translate("ID/Relay Server")), @@ -465,49 +466,60 @@ class _DesktopHomePageState extends State with TrayListener { Get.find().setString("darkTheme", choice); } - void onSelectMenu(String value) { - if (value.startsWith('enable-')) { - final option = gFFI.getOption(value); - gFFI.setOption(value, option == "N" ? "" : "N"); - } else if (value.startsWith('allow-')) { - final option = gFFI.getOption(value); + void onSelectMenu(String key) async { + if (key.startsWith('enable-')) { + final option = await bind.mainGetOption(key: key); + bind.mainSetOption(key: key, value: option == "N" ? "" : "N"); + } else if (key.startsWith('allow-')) { + final option = await bind.mainGetOption(key: key); final choice = option == "Y" ? "" : "Y"; - gFFI.setOption(value, choice); + bind.mainSetOption(key: key, value: choice); changeTheme(choice); - } else if (value == "stop-service") { - final option = gFFI.getOption(value); - gFFI.setOption(value, option == "Y" ? "" : "Y"); - } else if (value == "change-id") { + } else if (key == "stop-service") { + final option = await bind.mainGetOption(key: key); + bind.mainSetOption(key: key, value: option == "Y" ? "" : "Y"); + } else if (key == "change-id") { changeId(); - } else if (value == "custom-server") { + } else if (key == "custom-server") { changeServer(); - } else if (value == "whitelist") { + } else if (key == "whitelist") { changeWhiteList(); - } else if (value == "socks5-proxy") { + } else if (key == "socks5-proxy") { changeSocks5Proxy(); - } else if (value == "about") { + } else if (key == "about") { about(); - } else if (value == "logout") { + } else if (key == "logout") { logOut(); - } else if (value == "login") { + } else if (key == "login") { login(); } } - PopupMenuItem genEnablePopupMenuItem(String label, String value) { - final v = gFFI.getOption(value); - final isEnable = value.startsWith('enable-') ? v != "N" : v == "Y"; + PopupMenuItem genEnablePopupMenuItem(String label, String key) { + Future getOptionEnable(String key) async { + final v = await bind.mainGetOption(key: key); + return key.startsWith('enable-') ? v != "N" : v == "Y"; + } + return PopupMenuItem( - child: Row( - children: [ - Offstage(offstage: !isEnable, child: Icon(Icons.check)), - Text( - label, - style: genTextStyle(isEnable), - ), - ], - ), - value: value, + child: FutureBuilder( + future: getOptionEnable(key), + builder: (context, snapshot) { + var enable = false; + if (snapshot.hasData && snapshot.data!) { + enable = true; + } + return Row( + children: [ + Offstage(offstage: !enable, child: Icon(Icons.check)), + Text( + label, + style: genTextStyle(enable), + ), + ], + ); + }), + value: key, ); } @@ -518,10 +530,11 @@ class _DesktopHomePageState extends State with TrayListener { color: Colors.redAccent, decoration: TextDecoration.lineThrough); } - PopupMenuItem genAudioInputPopupMenuItem() { - final _enabledInput = gFFI.getOption('enable-audio'); - var defaultInput = gFFI.getDefaultAudioInput().obs; - var enabled = (_enabledInput != "N").obs; + PopupMenuItem genAudioInputPopupMenuItem( + bool enableInput, String defaultAudioInput) { + final defaultInput = defaultAudioInput.obs; + final enabled = enableInput.obs; + return PopupMenuItem( child: FutureBuilder>( future: gFFI.getAudioInputs(), @@ -569,12 +582,13 @@ class _DesktopHomePageState extends State with TrayListener { alignment: Alignment.centerLeft, child: Text(translate("Audio Input"))), itemBuilder: (context) => inputList, - onSelected: (dev) { + onSelected: (dev) async { if (dev == "Mute") { - gFFI.setOption( - 'enable-audio', _enabledInput == 'N' ? '' : 'N'); - enabled.value = gFFI.getOption('enable-audio') != 'N'; - } else if (dev != gFFI.getDefaultAudioInput()) { + await bind.mainSetOption( + key: 'enable-audio', value: enabled.value ? '' : 'N'); + enabled.value = + await bind.mainGetOption(key: 'enable-audio') != 'N'; + } else if (dev != await gFFI.getDefaultAudioInput()) { gFFI.setDefaultAudioInput(dev); defaultInput.value = dev; } diff --git a/flutter/lib/desktop/widgets/peercard_widget.dart b/flutter/lib/desktop/widgets/peercard_widget.dart index 0782e8426..949c46234 100644 --- a/flutter/lib/desktop/widgets/peercard_widget.dart +++ b/flutter/lib/desktop/widgets/peercard_widget.dart @@ -89,7 +89,8 @@ class _PeerCardState extends State<_PeerCard> children: [ Expanded( child: FutureBuilder( - future: gFFI.getPeerOption(peer.id, 'alias'), + future: bind.mainGetPeerOption( + id: peer.id, key: 'alias'), builder: (_, snapshot) { if (snapshot.hasData) { final name = snapshot.data!.isEmpty @@ -304,7 +305,7 @@ class _PeerCardState extends State<_PeerCard> void _rename(String id) async { var isInProgress = false; - var name = await gFFI.getPeerOption(id, 'alias'); + var name = await bind.mainGetPeerOption(id: id, key: 'alias'); if (widget.type == PeerType.ab) { final peer = gFFI.abModel.peers.firstWhere((p) => id == p['id']); if (peer == null) { @@ -359,7 +360,8 @@ class _PeerCardState extends State<_PeerCard> if (k.currentState != null) { if (k.currentState!.validate()) { k.currentState!.save(); - await gFFI.setPeerOption(id, 'alias', name); + await bind.mainSetPeerOption( + id: id, key: 'alias', value: name); if (widget.type == PeerType.ab) { gFFI.abModel.setPeerOption(id, 'alias', name); await gFFI.abModel.updateAb(); diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index 9722b1a47..69e7b9433 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -7,6 +7,8 @@ import 'package:url_launcher/url_launcher.dart'; import '../../common.dart'; import '../../models/model.dart'; +import '../../models/peer_model.dart'; +import '../../models/platform_model.dart'; import 'home_page.dart'; import 'remote_page.dart'; import 'scan_page.dart'; @@ -41,6 +43,16 @@ class _ConnectionPageState extends State { @override void initState() { super.initState(); + if (_idController.text.isEmpty) { + () async { + final lastRemoteId = await bind.mainGetLastRemoteId(); + if (lastRemoteId != _idController.text) { + setState(() { + _idController.text = lastRemoteId; + }); + } + }(); + } if (isAndroid) { Timer(Duration(seconds: 5), () { _updateUrl = gFFI.getByName('software_update_url'); @@ -52,7 +64,6 @@ class _ConnectionPageState extends State { @override Widget build(BuildContext context) { Provider.of(context); - if (_idController.text.isEmpty) _idController.text = gFFI.getId(); return SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.start, @@ -221,44 +232,52 @@ class _ConnectionPageState extends State { final n = (windowWidth / (minWidth + 2 * space)).floor(); width = windowWidth / n - 2 * space; } - final cards = []; - var peers = gFFI.peers(); - peers.forEach((p) { - cards.add(Container( - width: width, - child: Card( - child: GestureDetector( - onTap: !isWebDesktop ? () => connect('${p.id}') : null, - onDoubleTap: isWebDesktop ? () => connect('${p.id}') : null, - onLongPressStart: (details) { - final x = details.globalPosition.dx; - final y = details.globalPosition.dy; - _menuPos = RelativeRect.fromLTRB(x, y, x, y); - showPeerMenu(context, p.id); - }, - child: ListTile( - contentPadding: const EdgeInsets.only(left: 12), - subtitle: Text('${p.username}@${p.hostname}'), - title: Text('${p.id}'), - leading: Container( - padding: const EdgeInsets.all(6), - child: getPlatformImage('${p.platform}'), - color: str2color('${p.id}${p.platform}', 0x7f)), - trailing: InkWell( - child: Padding( - padding: const EdgeInsets.all(12), - child: Icon(Icons.more_vert)), - onTapDown: (e) { - final x = e.globalPosition.dx; - final y = e.globalPosition.dy; - _menuPos = RelativeRect.fromLTRB(x, y, x, y); - }, - onTap: () { - showPeerMenu(context, p.id); - }), - ))))); - }); - return Wrap(children: cards, spacing: space, runSpacing: space); + return FutureBuilder>( + future: gFFI.peers(), + builder: (context, snapshot) { + final cards = []; + if (snapshot.hasData) { + final peers = snapshot.data!; + peers.forEach((p) { + cards.add(Container( + width: width, + child: Card( + child: GestureDetector( + onTap: + !isWebDesktop ? () => connect('${p.id}') : null, + onDoubleTap: + isWebDesktop ? () => connect('${p.id}') : null, + onLongPressStart: (details) { + final x = details.globalPosition.dx; + final y = details.globalPosition.dy; + _menuPos = RelativeRect.fromLTRB(x, y, x, y); + showPeerMenu(context, p.id); + }, + child: ListTile( + contentPadding: const EdgeInsets.only(left: 12), + subtitle: Text('${p.username}@${p.hostname}'), + title: Text('${p.id}'), + leading: Container( + padding: const EdgeInsets.all(6), + child: getPlatformImage('${p.platform}'), + color: str2color('${p.id}${p.platform}', 0x7f)), + trailing: InkWell( + child: Padding( + padding: const EdgeInsets.all(12), + child: Icon(Icons.more_vert)), + onTapDown: (e) { + final x = e.globalPosition.dx; + final y = e.globalPosition.dy; + _menuPos = RelativeRect.fromLTRB(x, y, x, y); + }, + onTap: () { + showPeerMenu(context, p.id); + }), + ))))); + }); + } + return Wrap(children: cards, spacing: space, runSpacing: space); + }); } /// Show the peer menu and handle user's choice. diff --git a/flutter/lib/mobile/pages/scan_page.dart b/flutter/lib/mobile/pages/scan_page.dart index 2f5a9d991..54ba44892 100644 --- a/flutter/lib/mobile/pages/scan_page.dart +++ b/flutter/lib/mobile/pages/scan_page.dart @@ -9,7 +9,7 @@ import 'package:qr_code_scanner/qr_code_scanner.dart'; import 'package:zxing2/qrcode.dart'; import '../../common.dart'; -import '../../models/model.dart'; +import '../../models/platform_model.dart'; class ScanPage extends StatefulWidget { @override @@ -153,54 +153,80 @@ class _ScanPageState extends State { } void showServerSettingsWithValue( - String id, String relay, String key, String api) { - final formKey = GlobalKey(); - final id0 = gFFI.getByName('option', 'custom-rendezvous-server'); - final relay0 = gFFI.getByName('option', 'relay-server'); - final api0 = gFFI.getByName('option', 'api-server'); - final key0 = gFFI.getByName('option', 'key'); + String id, String relay, String key, String api) async { + Map oldOptions = jsonDecode(await bind.mainGetOptions()); + String id0 = oldOptions['custom-rendezvous-server'] ?? ""; + String relay0 = oldOptions['relay-server'] ?? ""; + String api0 = oldOptions['api-server'] ?? ""; + String key0 = oldOptions['key'] ?? ""; + var isInProgress = false; + final idController = TextEditingController(text: id); + final relayController = TextEditingController(text: relay); + final apiController = TextEditingController(text: api); + + String? idServerMsg; + String? relayServerMsg; + String? apiServerMsg; + DialogManager.show((setState, close) { + Future validate() async { + if (idController.text != id) { + final res = await validateAsync(idController.text); + setState(() => idServerMsg = res); + if (idServerMsg != null) return false; + id = idController.text; + } + if (relayController.text != relay) { + relayServerMsg = await validateAsync(relayController.text); + if (relayServerMsg != null) return false; + relay = relayController.text; + } + if (apiController.text != relay) { + apiServerMsg = await validateAsync(apiController.text); + if (apiServerMsg != null) return false; + api = apiController.text; + } + return true; + } + return CustomAlertDialog( title: Text(translate('ID/Relay Server')), content: Form( - key: formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ TextFormField( - initialValue: id, + controller: idController, decoration: InputDecoration( - labelText: translate('ID Server'), - ), - validator: validate, - onSaved: (String? value) { - if (value != null) id = value.trim(); - }, + labelText: translate('ID Server'), + errorText: idServerMsg), ) ] + (isAndroid ? [ TextFormField( - initialValue: relay, + controller: relayController, decoration: InputDecoration( - labelText: translate('Relay Server'), - ), - validator: validate, - onSaved: (String? value) { - if (value != null) relay = value.trim(); - }, + labelText: translate('Relay Server'), + errorText: relayServerMsg), ) ] : []) + [ TextFormField( - initialValue: api, + controller: apiController, decoration: InputDecoration( labelText: translate('API Server'), ), - validator: validate, - onSaved: (String? value) { - if (value != null) api = value.trim(); + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: (v) { + if (v != null && v.length > 0) { + if (!(v.startsWith('http://') || + v.startsWith("https://"))) { + return translate("invalid_http"); + } + } + return apiServerMsg; }, ), TextFormField( @@ -208,11 +234,13 @@ void showServerSettingsWithValue( decoration: InputDecoration( labelText: 'Key', ), - validator: null, - onSaved: (String? value) { + onChanged: (String? value) { if (value != null) key = value.trim(); }, ), + Offstage( + offstage: !isInProgress, + child: LinearProgressIndicator()) ])), actions: [ TextButton( @@ -224,24 +252,28 @@ void showServerSettingsWithValue( ), TextButton( style: flatButtonStyle, - onPressed: () { - if (formKey.currentState != null && - formKey.currentState!.validate()) { - formKey.currentState!.save(); - if (id != id0) - gFFI.setByName('option', - '{"name": "custom-rendezvous-server", "value": "$id"}'); + onPressed: () async { + setState(() { + idServerMsg = null; + relayServerMsg = null; + apiServerMsg = null; + isInProgress = true; + }); + if (await validate()) { + if (id != id0) { + bind.mainSetOption(key: "custom-rendezvous-server", value: id); + } if (relay != relay0) - gFFI.setByName( - 'option', '{"name": "relay-server", "value": "$relay"}'); - if (key != key0) - gFFI.setByName('option', '{"name": "key", "value": "$key"}'); + bind.mainSetOption(key: "relay-server", value: relay); + if (key != key0) bind.mainSetOption(key: "key", value: key); if (api != api0) - gFFI.setByName( - 'option', '{"name": "api-server", "value": "$api"}'); + bind.mainSetOption(key: "api-server", value: api); gFFI.ffiModel.updateUser(); close(); } + setState(() { + isInProgress = false; + }); }, child: Text(translate('OK')), ), @@ -250,11 +282,11 @@ void showServerSettingsWithValue( }); } -String? validate(value) { +Future validateAsync(String value) async { value = value.trim(); if (value.isEmpty) { return null; } - final res = gFFI.getByName('test_if_valid_server', value); + final res = await bind.mainTestIfValidServer(server: value); return res.isEmpty ? null : res; } diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index 01cf4ae5d..3646b59e9 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -185,11 +185,12 @@ class _SettingsState extends State with WidgetsBindingObserver { } } -void showServerSettings() { - final id = gFFI.getByName('option', 'custom-rendezvous-server'); - final relay = gFFI.getByName('option', 'relay-server'); - final api = gFFI.getByName('option', 'api-server'); - final key = gFFI.getByName('option', 'key'); +void showServerSettings() async { + Map options = jsonDecode(await bind.mainGetOptions()); + String id = options['custom-rendezvous-server'] ?? ""; + String relay = options['relay-server'] ?? ""; + String api = options['api-server'] ?? ""; + String key = options['key'] ?? ""; showServerSettingsWithValue(id, relay, key, api); } diff --git a/flutter/lib/models/chat_model.dart b/flutter/lib/models/chat_model.dart index ad572c164..28ffa65e2 100644 --- a/flutter/lib/models/chat_model.dart +++ b/flutter/lib/models/chat_model.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:dash_chat_2/dash_chat_2.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hbb/models/platform_model.dart'; import '../../mobile/widgets/overlay.dart'; import 'model.dart'; @@ -72,7 +73,7 @@ class ChatModel with ChangeNotifier { } } - receive(int id, String text) { + receive(int id, String text) async { if (text.isEmpty) return; // first message show overlay icon if (chatIconOverlayEntry == null) { @@ -82,7 +83,7 @@ class ChatModel with ChangeNotifier { if (id == clientModeID) { chatUser = ChatUser( firstName: _ffi.target?.ffiModel.pi.username, - id: _ffi.target?.getId() ?? "", + id: await bind.mainGetLastRemoteId(), ); } else { final client = _ffi.target?.serverModel.clients[id]; diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index 459e8c448..45f5ec970 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -444,7 +444,7 @@ class FileModel extends ChangeNotifier { items.items.forEach((from) async { _jobId++; await bind.sessionSendFiles( - id: '${_ffi.target?.getId()}', + id: await bind.mainGetLastRemoteId(), actId: _jobId, path: from.path, to: PathUtil.join(toPath, from.name, isWindows), diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index f67d0d5fa..7ca77f6cd 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -881,11 +881,6 @@ class FFI { this.qualityMonitorModel = QualityMonitorModel(WeakReference(this)); } - /// Get the remote id for current client. - String getId() { - return getByName('remote_id'); // TODO - } - /// Send a mouse tap event(down and up). void tap(MouseButtons button) { sendMouse('down', button); @@ -963,9 +958,9 @@ class FFI { } /// List the saved peers. - List peers() { + Future> peers() async { try { - var str = getByName('peers'); // TODO + var str = await bind.mainGetRecentPeers(); if (str == "") return []; List peers = json.decode(str); return peers @@ -1046,33 +1041,6 @@ class FFI { platformFFI.setByName(name, value); } - String getOption(String name) { - return platformFFI.getByName("option", name); - } - - Future getLocalOption(String name) { - return bind.mainGetLocalOption(key: name); - } - - Future setLocalOption(String key, String value) { - return bind.mainSetLocalOption(key: key, value: value); - } - - Future getPeerOption(String id, String key) { - return bind.mainGetPeerOption(id: id, key: key); - } - - Future setPeerOption(String id, String key, String value) { - return bind.mainSetPeerOption(id: id, key: key, value: value); - } - - void setOption(String name, String value) { - Map res = Map() - ..["name"] = name - ..["value"] = value; - return platformFFI.setByName('option', jsonEncode(res)); - } - handleMouse(Map evt, {double tabBarHeight = 0.0}) { var type = ''; var isMove = false; @@ -1148,8 +1116,8 @@ class FFI { return await bind.mainGetSoundInputs(); } - String getDefaultAudioInput() { - final input = getOption('audio-input'); + Future getDefaultAudioInput() async { + final input = await bind.mainGetOption(key: 'audio-input'); if (input.isEmpty && Platform.isWindows) { return "System Sound"; } @@ -1157,11 +1125,14 @@ class FFI { } void setDefaultAudioInput(String input) { - setOption('audio-input', input); + bind.mainSetOption(key: 'audio-input', value: input); } Future> getHttpHeaders() async { - return {"Authorization": "Bearer " + await getLocalOption("access_token")}; + return { + "Authorization": + "Bearer " + await bind.mainGetLocalOption(key: "access_token") + }; } } @@ -1233,11 +1204,12 @@ void initializeCursorAndCanvas(FFI ffi) async { /// Translate text based on the pre-defined dictionary. /// note: params [FFI?] can be used to replace global FFI implementation /// for example: during global initialization, gFFI not exists yet. -String translate(String name, {FFI? ffi}) { - if (name.startsWith('Failed to') && name.contains(': ')) { - return name.split(': ').map((x) => translate(x)).join(': '); - } - var a = 'translate'; - var b = '{"locale": "$localeName", "text": "$name"}'; - return (ffi ?? gFFI).getByName(a, b); -} +// String translate(String name, {FFI? ffi}) { +// if (name.startsWith('Failed to') && name.contains(': ')) { +// return name.split(': ').map((x) => translate(x)).join(': '); +// } +// var a = 'translate'; +// var b = '{"locale": "$localeName", "text": "$name"}'; +// +// return (ffi ?? gFFI).getByName(a, b); +// } diff --git a/flutter/lib/models/native_model.dart b/flutter/lib/models/native_model.dart index 784ffe6c8..c58577945 100644 --- a/flutter/lib/models/native_model.dart +++ b/flutter/lib/models/native_model.dart @@ -29,6 +29,7 @@ typedef HandleEvent = void Function(Map evt); class PlatformFFI { String _dir = ''; String _homeDir = ''; + F2? _translate; F2? _getByName; F3? _setByName; var _eventHandlers = Map>(); @@ -75,6 +76,19 @@ class PlatformFFI { } } + String translate(String name, String locale) { + if (_translate == null) return ''; + var a = name.toNativeUtf8(); + var b = locale.toNativeUtf8(); + var p = _translate!(a, b); + assert(p != nullptr); + final res = p.toDartString(); + calloc.free(p); + calloc.free(a); + calloc.free(b); + return res; + } + /// Send **get** command to the Rust core based on [name] and [arg]. /// Return the result as a string. String getByName(String name, [String arg = '']) { @@ -118,6 +132,7 @@ class PlatformFFI { : DynamicLibrary.process(); debugPrint('initializing FFI ${_appType}'); try { + _translate = dylib.lookupFunction('translate'); _getByName = dylib.lookupFunction('get_by_name'); _setByName = dylib.lookupFunction, Pointer), F3>( diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index c9147441e..362e47a78 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter_hbb/models/platform_model.dart'; import 'package:wakelock/wakelock.dart'; import '../common.dart'; @@ -57,7 +58,7 @@ class ServerModel with ChangeNotifier { set verificationMethod(String method) { _verificationMethod = method; - gFFI.setOption("verification-method", method); + bind.mainSetOption(key: "verification-method", value: method); } String get temporaryPasswordLength { @@ -70,7 +71,7 @@ class ServerModel with ChangeNotifier { set temporaryPasswordLength(String length) { _temporaryPasswordLength = length; - gFFI.setOption("temporary-password-length", length); + bind.mainSetOption(key: "temporary-password-length", value: length); } TextEditingController get serverId => _serverId; @@ -85,7 +86,7 @@ class ServerModel with ChangeNotifier { ServerModel(this.parent) { () async { - _emptyIdShow = translate("Generating ...", ffi: this.parent.target); + _emptyIdShow = translate("Generating ..."); _serverId = TextEditingController(text: this._emptyIdShow); /** * 1. check android permission @@ -153,11 +154,13 @@ class ServerModel with ChangeNotifier { }); } - updatePasswordModel() { + updatePasswordModel() async { var update = false; final temporaryPassword = gFFI.getByName("temporary_password"); - final verificationMethod = gFFI.getOption("verification-method"); - final temporaryPasswordLength = gFFI.getOption("temporary-password-length"); + final verificationMethod = + await bind.mainGetOption(key: "verification-method"); + final temporaryPasswordLength = + await bind.mainGetOption(key: "temporary-password-length"); final oldPwdText = _serverPasswd.text; if (_serverPasswd.text != temporaryPassword) { _serverPasswd.text = temporaryPassword; @@ -325,7 +328,7 @@ class ServerModel with ChangeNotifier { const maxCount = 10; while (count < maxCount) { await Future.delayed(Duration(seconds: 1)); - final id = parent.target?.getByName("server_id") ?? ""; + final id = await bind.mainGetMyId(); if (id.isEmpty) { continue; } else { diff --git a/flutter/lib/utils/tray_manager.dart b/flutter/lib/utils/tray_manager.dart index d911932e5..f0422f554 100644 --- a/flutter/lib/utils/tray_manager.dart +++ b/flutter/lib/utils/tray_manager.dart @@ -1,8 +1,9 @@ import 'dart:io'; -import 'package:flutter_hbb/models/model.dart'; import 'package:tray_manager/tray_manager.dart'; +import '../common.dart'; + Future initTray({List? extra_item}) async { List items = [ MenuItem(key: "show", label: translate("show rustdesk")), diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 904912715..40f72444a 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -23,10 +23,10 @@ use crate::ui_interface; use crate::ui_interface::{change_id, check_connect_status, is_ok_change_id}; use crate::ui_interface::{ discover, forget_password, get_api_server, get_app_name, get_async_job_status, - get_connect_status, get_fav, get_id, get_lan_peers, get_license, get_local_option, get_options, - get_peer, get_peer_option, get_socks, get_sound_inputs, get_uuid, get_version, - has_rendezvous_service, post_request, set_local_option, set_options, set_peer_option, - set_socks, store_fav, test_if_valid_server, using_public_server, + get_connect_status, get_fav, get_id, get_lan_peers, get_license, get_local_option, get_option, + get_options, get_peer, get_peer_option, get_socks, get_sound_inputs, get_uuid, get_version, + has_rendezvous_service, post_request, set_local_option, set_option, set_options, + set_peer_option, set_socks, store_fav, test_if_valid_server, using_public_server, }; fn initialize(app_dir: &str) { @@ -81,14 +81,24 @@ pub enum EventToUI { } 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); + 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); + let _ = flutter::GLOBAL_EVENT_STREAM + .write() + .unwrap() + .remove(&app_type); } pub fn host_stop_system_key_propagate(stopped: bool) { @@ -113,7 +123,6 @@ pub fn get_session_remember(id: String) -> Option { } } -// TODO sync pub fn get_session_toggle_option(id: String, arg: String) -> Option { if let Some(session) = SESSIONS.read().unwrap().get(&id) { Some(session.get_toggle_option(&arg)) @@ -143,7 +152,6 @@ pub fn get_session_option(id: String, arg: String) -> Option { } } -// void pub fn session_login(id: String, password: String, remember: bool) { if let Some(session) = SESSIONS.read().unwrap().get(&id) { session.login(&password, remember); @@ -409,6 +417,26 @@ pub fn main_get_async_status() -> String { get_async_job_status() } +pub fn main_get_option(key: String) -> String { + get_option(key) +} + +pub fn main_set_option(key: String, value: String) { + if key.eq("custom-rendezvous-server") { + set_option(key, value); + #[cfg(target_os = "android")] + crate::rendezvous_mediator::RendezvousMediator::restart(); + #[cfg(any( + target_os = "android", + target_os = "ios", + feature = "cli" + ))] + crate::common::test_rendezvous_server(); + } else { + set_option(key, value); + } +} + pub fn main_get_options() -> String { get_options() } @@ -452,7 +480,7 @@ pub fn main_store_fav(favs: Vec) { store_fav(favs) } -pub fn main_get_peers(id: String) -> String { +pub fn main_get_peer(id: String) -> String { let conf = get_peer(id); serde_json::to_string(&conf).unwrap_or("".to_string()) } @@ -525,13 +553,30 @@ pub fn main_forget_password(id: String) { forget_password(id) } +// TODO APP_DIR & ui_interface +pub fn main_get_recent_peers() -> String { + if !config::APP_DIR.read().unwrap().is_empty() { + let peers: Vec<(String, config::PeerInfoSerde)> = PeerConfig::peers() + .drain(..) + .map(|(id, _, p)| (id, p.info)) + .collect(); + serde_json::ser::to_string(&peers).unwrap_or("".to_owned()) + } else { + String::new() + } +} + pub fn main_load_recent_peers() { if !config::APP_DIR.read().unwrap().is_empty() { let peers: Vec<(String, config::PeerInfoSerde)> = PeerConfig::peers() .drain(..) .map(|(id, _, p)| (id, p.info)) .collect(); - if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(flutter::APP_TYPE_MAIN) { + 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()), ( @@ -557,7 +602,11 @@ pub fn main_load_fav_peers() { } }) .collect(); - if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(flutter::APP_TYPE_MAIN) { + 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()), ( @@ -571,7 +620,11 @@ pub fn main_load_fav_peers() { } pub fn main_load_lan_peers() { - if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(flutter::APP_TYPE_MAIN) { + 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()), @@ -580,6 +633,25 @@ pub fn main_load_lan_peers() { }; } +pub fn main_get_last_remote_id() -> String { + // if !config::APP_DIR.read().unwrap().is_empty() { + // res = LocalConfig::get_remote_id(); + // } + LocalConfig::get_remote_id() +} + +#[no_mangle] +unsafe extern "C" fn translate(name: *const c_char, locale: *const c_char) -> *const c_char { + let name = CStr::from_ptr(name); + let locale = CStr::from_ptr(locale); + let res = if let (Ok(name), Ok(locale)) = (name.to_str(), locale.to_str()) { + crate::client::translate_locale(name.to_owned(), locale) + } else { + String::new() + }; + CString::from_vec_unchecked(res.into_bytes()).into_raw() +} + /// FFI for **get** commands which are idempotent. /// Return result in c string. /// @@ -594,93 +666,41 @@ unsafe extern "C" fn get_by_name(name: *const c_char, arg: *const c_char) -> *co let name: &CStr = CStr::from_ptr(name); if let Ok(name) = name.to_str() { match name { - "peers" => { - if !config::APP_DIR.read().unwrap().is_empty() { - let peers: Vec<(String, config::PeerInfoSerde)> = PeerConfig::peers() - .drain(..) - .map(|(id, _, p)| (id, p.info)) - .collect(); - res = serde_json::ser::to_string(&peers).unwrap_or("".to_owned()); - } - } - "remote_id" => { - if !config::APP_DIR.read().unwrap().is_empty() { - res = LocalConfig::get_remote_id(); - } - } - // "remember" => { - // res = Session::get_remember().to_string(); - // } - // "toggle_option" => { - // if let Ok(arg) = arg.to_str() { - // if let Some(v) = Session::get_toggle_option(arg) { - // res = v.to_string(); - // } + // "peers" => { + // if !config::APP_DIR.read().unwrap().is_empty() { + // let peers: Vec<(String, config::PeerInfoSerde)> = PeerConfig::peers() + // .drain(..) + // .map(|(id, _, p)| (id, p.info)) + // .collect(); + // res = serde_json::ser::to_string(&peers).unwrap_or("".to_owned()); // } // } - "test_if_valid_server" => { - if let Ok(arg) = arg.to_str() { - res = hbb_common::socket_client::test_if_valid_server(arg); - } - } - "option" => { - if let Ok(arg) = arg.to_str() { - res = ui_interface::get_option(arg.to_owned()); - } - } - // "image_quality" => { - // res = Session::get_image_quality(); + // "remote_id" => { + // if !config::APP_DIR.read().unwrap().is_empty() { + // res = LocalConfig::get_remote_id(); + // } + // } + // "test_if_valid_server" => { + // if let Ok(arg) = arg.to_str() { + // res = hbb_common::socket_client::test_if_valid_server(arg); + // } + // } + // "option" => { + // if let Ok(arg) = arg.to_str() { + // res = ui_interface::get_option(arg.to_owned()); + // } // } "software_update_url" => { res = crate::common::SOFTWARE_UPDATE_URL.lock().unwrap().clone() } - "translate" => { - if let Ok(arg) = arg.to_str() { - if let Ok(m) = serde_json::from_str::>(arg) { - if let Some(locale) = m.get("locale") { - if let Some(text) = m.get("text") { - res = crate::client::translate_locale(text.to_owned(), locale); - } - } - } - } - } - // "peer_option" => { - // if let Ok(arg) = arg.to_str() { - // res = Session::get_option(arg); - // } - // } // File Action "get_home_dir" => { res = fs::get_home_as_string(); } - // "read_local_dir_sync" => { - // if let Ok(value) = arg.to_str() { - // if let Ok(m) = serde_json::from_str::>(value) { - // if let (Some(path), Some(show_hidden)) = - // (m.get("path"), m.get("show_hidden")) - // { - // if let Ok(fd) = - // fs::read_dir(&fs::get_path(path), show_hidden.eq("true")) - // { - // res = make_fd_to_json(fd); - // } - // } - // } - // } - // } // Server Side - "local_option" => { - if let Ok(arg) = arg.to_str() { - res = LocalConfig::get_option(arg); - } - } "langs" => { res = crate::lang::LANGS.to_string(); } - "server_id" => { - res = ui_interface::get_id(); - } "temporary_password" => { res = ui_interface::temporary_password(); } @@ -709,9 +729,6 @@ unsafe extern "C" fn get_by_name(name: *const c_char, arg: *const c_char) -> *co } } } - "uuid" => { - res = base64::encode(get_uuid()); - } _ => { log::error!("Unknown name of get_by_name: {}", name); } @@ -742,69 +759,9 @@ unsafe extern "C" fn set_by_name(name: *const c_char, value: *const c_char) { "info2" => { *crate::common::FLUTTER_INFO2.lock().unwrap() = value.to_owned(); } - // "connect" => { - // Session::start(value, false); - // } - // "connect_file_transfer" => { - // Session::start(value, true); - // } - // "login" => { - // if let Ok(m) = serde_json::from_str::>(value) { - // if let Some(password) = m.get("password") { - // if let Some(remember) = m.get("remember") { - // Session::login(password, remember == "true"); - // } - // } - // } - // } - // "close" => { - // Session::close(); - // } - // "refresh" => { - // Session::refresh(); - // } - // "reconnect" => { - // Session::reconnect(); - // } - // "toggle_option" => { - // Session::toggle_option(value); - // } - // "image_quality" => { - // Session::set_image_quality(value); - // } - // "lock_screen" => { - // Session::lock_screen(); - // } - // "ctrl_alt_del" => { - // Session::ctrl_alt_del(); - // } - // "switch_display" => { - // if let Ok(v) = value.parse::() { - // Session::switch_display(v); - // } - // } "remove" => { PeerConfig::remove(value); } - // "input_key" => { - // if let Ok(m) = serde_json::from_str::>(value) { - // let alt = m.get("alt").is_some(); - // let ctrl = m.get("ctrl").is_some(); - // let shift = m.get("shift").is_some(); - // let command = m.get("command").is_some(); - // let down = m.get("down").is_some(); - // let press = m.get("press").is_some(); - // if let Some(name) = m.get("name") { - // Session::input_key(name, down, press, alt, ctrl, shift, command); - // } - // } - // } - // "input_string" => { - // Session::input_string(value); - // } - // "chat_client_mode" => { - // Session::send_chat(value.to_owned()); - // } // TODO "send_mouse" => { @@ -848,203 +805,29 @@ unsafe extern "C" fn set_by_name(name: *const c_char, value: *const c_char) { } } } - "option" => { - if let Ok(m) = serde_json::from_str::>(value) { - if let Some(name) = m.get("name") { - if let Some(value) = m.get("value") { - ui_interface::set_option(name.to_owned(), value.to_owned()); - if name == "custom-rendezvous-server" { - #[cfg(target_os = "android")] - crate::rendezvous_mediator::RendezvousMediator::restart(); - #[cfg(any( - target_os = "android", - target_os = "ios", - feature = "cli" - ))] - crate::common::test_rendezvous_server(); - } - } - } - } - } - // "peer_option" => { + // "option" => { // if let Ok(m) = serde_json::from_str::>(value) { // if let Some(name) = m.get("name") { // if let Some(value) = m.get("value") { - // Session::set_option(name.to_owned(), value.to_owned()); + // ui_interface::set_option(name.to_owned(), value.to_owned()); + // if name == "custom-rendezvous-server" { + // #[cfg(target_os = "android")] + // crate::rendezvous_mediator::RendezvousMediator::restart(); + // #[cfg(any( + // target_os = "android", + // target_os = "ios", + // feature = "cli" + // ))] + // crate::common::test_rendezvous_server(); + // } // } // } // } // } - "local_option" => { - if let Ok(m) = serde_json::from_str::>(value) { - if let Some(name) = m.get("name") { - if let Some(value) = m.get("value") { - LocalConfig::set_option(name.to_owned(), value.to_owned()); - } - } - } - } - // "input_os_password" => { - // Session::input_os_password(value.to_owned(), true); - // } "restart_remote_device" => { // TODO // Session::restart_remote_device(); } - // // File Action - // "read_remote_dir" => { - // if let Ok(m) = serde_json::from_str::>(value) { - // if let (Some(path), Some(show_hidden), Some(session)) = ( - // m.get("path"), - // m.get("show_hidden"), - // Session::get().read().unwrap().as_ref(), - // ) { - // session.read_remote_dir(path.to_owned(), show_hidden.eq("true")); - // } - // } - // } - // "send_files" => { - // if let Ok(m) = serde_json::from_str::>(value) { - // if let ( - // Some(id), - // Some(path), - // Some(to), - // Some(file_num), - // Some(show_hidden), - // Some(is_remote), - // ) = ( - // m.get("id"), - // m.get("path"), - // m.get("to"), - // m.get("file_num"), - // m.get("show_hidden"), - // m.get("is_remote"), - // ) { - // Session::send_files( - // id.parse().unwrap_or(0), - // path.to_owned(), - // to.to_owned(), - // file_num.parse().unwrap_or(0), - // show_hidden.eq("true"), - // is_remote.eq("true"), - // ); - // } - // } - // } - // "set_confirm_override_file" => { - // if let Ok(m) = serde_json::from_str::>(value) { - // if let ( - // Some(id), - // Some(file_num), - // Some(need_override), - // Some(remember), - // Some(is_upload), - // ) = ( - // m.get("id"), - // m.get("file_num"), - // m.get("need_override"), - // m.get("remember"), - // m.get("is_upload"), - // ) { - // Session::set_confirm_override_file( - // id.parse().unwrap_or(0), - // file_num.parse().unwrap_or(0), - // need_override.eq("true"), - // remember.eq("true"), - // is_upload.eq("true"), - // ); - // } - // } - // } - // ** TODO ** continue - // "remove_file" => { - // if let Ok(m) = serde_json::from_str::>(value) { - // if let ( - // Some(id), - // Some(path), - // Some(file_num), - // Some(is_remote), - // Some(session), - // ) = ( - // m.get("id"), - // m.get("path"), - // m.get("file_num"), - // m.get("is_remote"), - // Session::get().write().unwrap().as_mut(), - // ) { - // session.remove_file( - // id.parse().unwrap_or(0), - // path.to_owned(), - // file_num.parse().unwrap_or(0), - // is_remote.eq("true"), - // ); - // } - // } - // } - // "read_dir_recursive" => { - // if let Ok(m) = serde_json::from_str::>(value) { - // if let (Some(id), Some(path), Some(is_remote), Some(session)) = ( - // m.get("id"), - // m.get("path"), - // m.get("is_remote"), - // Session::get().write().unwrap().as_mut(), - // ) { - // session.remove_dir_all( - // id.parse().unwrap_or(0), - // path.to_owned(), - // is_remote.eq("true"), - // ); - // } - // } - // } - // "remove_all_empty_dirs" => { - // if let Ok(m) = serde_json::from_str::>(value) { - // if let (Some(id), Some(path), Some(is_remote), Some(session)) = ( - // m.get("id"), - // m.get("path"), - // m.get("is_remote"), - // Session::get().write().unwrap().as_mut(), - // ) { - // session.remove_dir( - // id.parse().unwrap_or(0), - // path.to_owned(), - // is_remote.eq("true"), - // ); - // } - // } - // } - // "cancel_job" => { - // if let (Ok(id), Some(session)) = - // (value.parse(), Session::get().write().unwrap().as_mut()) - // { - // session.cancel_job(id); - // } - // } - // "create_dir" => { - // if let Ok(m) = serde_json::from_str::>(value) { - // if let (Some(id), Some(path), Some(is_remote), Some(session)) = ( - // m.get("id"), - // m.get("path"), - // m.get("is_remote"), - // Session::get().write().unwrap().as_mut(), - // ) { - // session.create_dir( - // id.parse().unwrap_or(0), - // path.to_owned(), - // is_remote.eq("true"), - // ); - // } - // } - // } - // Server Side - // "update_password" => { - // if value.is_empty() { - // Config::set_password(&Config::get_auto_password()); - // } else { - // Config::set_password(value); - // } - // } #[cfg(target_os = "android")] "chat_server_mode" => { if let Ok(m) = serde_json::from_str::>(value) { @@ -1105,7 +888,11 @@ 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().get(flutter::APP_TYPE_MAIN) { + 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(",")), diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 2aa4f36ec..cdfd0edce 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -138,10 +138,11 @@ pub fn get_license() -> String { } pub fn get_option(key: String) -> String { - #[cfg(any(target_os = "android", target_os = "ios"))] - return Config::get_option(&key); - #[cfg(not(any(target_os = "android", target_os = "ios")))] - return get_option_(&key); + get_option_(&key) + // #[cfg(any(target_os = "android", target_os = "ios"))] + // return Config::get_option(&key); + // #[cfg(not(any(target_os = "android", target_os = "ios")))] + // return get_option_(&key); } fn get_option_(key: &str) -> String { @@ -250,33 +251,31 @@ pub fn get_sound_inputs() -> Vec { } pub fn set_options(m: HashMap) { + *OPTIONS.lock().unwrap() = m.clone(); #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - *OPTIONS.lock().unwrap() = m.clone(); - ipc::set_options(m).ok(); - } + ipc::set_options(m).ok(); + #[cfg(any(target_os = "android", target_os = "ios"))] + Config::set_options(m); } pub fn set_option(key: String, value: String) { + let mut options = OPTIONS.lock().unwrap(); + #[cfg(target_os = "macos")] + if &key == "stop-service" { + let is_stop = value == "Y"; + if is_stop && crate::platform::macos::uninstall() { + return; + } + } + if value.is_empty() { + options.remove(&key); + } else { + options.insert(key.clone(), value.clone()); + } + #[cfg(not(any(target_os = "android", target_os = "ios")))] + ipc::set_options(options.clone()).ok(); #[cfg(any(target_os = "android", target_os = "ios"))] Config::set_option(key, value); - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - let mut options = OPTIONS.lock().unwrap(); - #[cfg(target_os = "macos")] - if &key == "stop-service" { - let is_stop = value == "Y"; - if is_stop && crate::platform::macos::uninstall() { - return; - } - } - if value.is_empty() { - options.remove(&key); - } else { - options.insert(key.clone(), value.clone()); - } - ipc::set_options(options.clone()).ok(); - } } pub fn install_path() -> String { From e420178750574de079ee604e1e5ff9f46dad1cfe Mon Sep 17 00:00:00 2001 From: csf Date: Mon, 8 Aug 2022 22:27:27 +0800 Subject: [PATCH 3/3] refactor all [setByName] [getByName] to async bridge function --- .../lib/desktop/pages/connection_page.dart | 28 +- .../lib/desktop/pages/desktop_home_page.dart | 12 +- flutter/lib/desktop/pages/remote_page.dart | 5 +- .../lib/desktop/widgets/peercard_widget.dart | 2 +- flutter/lib/mobile/pages/connection_page.dart | 35 +- flutter/lib/mobile/pages/remote_page.dart | 7 +- flutter/lib/mobile/pages/server_page.dart | 18 +- flutter/lib/mobile/pages/settings_page.dart | 86 ++-- flutter/lib/mobile/widgets/dialog.dart | 23 +- flutter/lib/models/chat_model.dart | 11 +- flutter/lib/models/file_model.dart | 2 +- flutter/lib/models/model.dart | 30 +- flutter/lib/models/native_model.dart | 39 +- flutter/lib/models/server_model.dart | 79 ++-- src/client.rs | 2 +- src/common.rs | 6 +- src/flutter_ffi.rs | 413 ++++++------------ src/server/connection.rs | 4 +- 18 files changed, 332 insertions(+), 470 deletions(-) diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index d992c6c62..d1080dbd3 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -825,10 +825,34 @@ class WebMenu extends StatefulWidget { } class _WebMenuState extends State { + String? username; + String url = ""; + + @override + void initState() { + super.initState(); + () async { + final usernameRes = await getUsername(); + final urlRes = await getUrl(); + var update = false; + if (usernameRes != username) { + username = usernameRes; + update = true; + } + if (urlRes != url) { + url = urlRes; + update = true; + } + + if (update) { + setState(() {}); + } + }(); + } + @override Widget build(BuildContext context) { Provider.of(context); - final username = getUsername(); return PopupMenuButton( icon: Icon(Icons.more_vert), itemBuilder: (context) { @@ -846,7 +870,7 @@ class _WebMenuState extends State { value: "server", ) ] + - (getUrl().contains('admin.rustdesk.com') + (url.contains('admin.rustdesk.com') ? >[] : [ PopupMenuItem( diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 3f908c3ce..f02e0fdfd 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -275,9 +275,7 @@ class _DesktopHomePageState extends State with TrayListener { ), IconButton( icon: Icon(Icons.refresh), - onPressed: () { - gFFI.setByName("temporary_password"); - }, + onPressed: () => bind.mainUpdateTemporaryPassword(), ), FutureBuilder( future: buildPasswordPopupMenu(context), @@ -360,7 +358,7 @@ class _DesktopHomePageState extends State with TrayListener { if (gFFI.serverModel.temporaryPasswordLength != e) { gFFI.serverModel.temporaryPasswordLength = e; - gFFI.setByName("temporary_password"); + bind.mainUpdateTemporaryPassword(); } }, )) @@ -1336,8 +1334,8 @@ Future loginDialog() async { return completer.future; } -void setPasswordDialog() { - final pw = gFFI.getByName("permanent_password"); +void setPasswordDialog() async { + final pw = await bind.mainGetPermanentPassword(); final p0 = TextEditingController(text: pw); final p1 = TextEditingController(text: pw); var errMsg0 = ""; @@ -1427,7 +1425,7 @@ void setPasswordDialog() { }); return; } - gFFI.setByName("permanent_password", pass); + bind.mainSetPermanentPassword(password: pass); close(); }, child: Text(translate("OK"))), diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index bfc193a89..a945e3ae3 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -348,8 +348,9 @@ class _RemotePageState extends State if (dy > 0) dy = -1; else if (dy < 0) dy = 1; - _ffi.setByName('send_mouse', - '{"id": "${widget.id}", "type": "wheel", "x": "$dx", "y": "$dy"}'); + bind.sessionSendMouse( + id: widget.id, + msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}'); } }, child: MouseRegion( diff --git a/flutter/lib/desktop/widgets/peercard_widget.dart b/flutter/lib/desktop/widgets/peercard_widget.dart index 949c46234..f4743a7b5 100644 --- a/flutter/lib/desktop/widgets/peercard_widget.dart +++ b/flutter/lib/desktop/widgets/peercard_widget.dart @@ -187,7 +187,7 @@ class _PeerCardState extends State<_PeerCard> elevation: 8, ); if (value == 'remove') { - setState(() => gFFI.setByName('remove', '$id')); + setState(() => bind.mainRemovePeer(id: id)); () async { removePreference(id); }(); diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index 69e7b9433..227bfb630 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -54,8 +54,9 @@ class _ConnectionPageState extends State { }(); } if (isAndroid) { - Timer(Duration(seconds: 5), () { - _updateUrl = gFFI.getByName('software_update_url'); + Timer(Duration(seconds: 5), () async { + _updateUrl = await bind.mainGetSoftwareUpdateUrl(); + ; if (_updateUrl.isNotEmpty) setState(() {}); }); } @@ -299,7 +300,7 @@ class _ConnectionPageState extends State { elevation: 8, ); if (value == 'remove') { - setState(() => gFFI.setByName('remove', '$id')); + setState(() => bind.mainRemovePeer(id: id)); () async { removePreference(id); }(); @@ -315,10 +316,34 @@ class WebMenu extends StatefulWidget { } class _WebMenuState extends State { + String? username; + String url = ""; + + @override + void initState() { + super.initState(); + () async { + final usernameRes = await getUsername(); + final urlRes = await getUrl(); + var update = false; + if (usernameRes != username) { + username = usernameRes; + update = true; + } + if (urlRes != url) { + url = urlRes; + update = true; + } + + if (update) { + setState(() {}); + } + }(); + } + @override Widget build(BuildContext context) { Provider.of(context); - final username = getUsername(); return PopupMenuButton( icon: Icon(Icons.more_vert), itemBuilder: (context) { @@ -336,7 +361,7 @@ class _WebMenuState extends State { value: "server", ) ] + - (getUrl().contains('admin.rustdesk.com') + (url.contains('admin.rustdesk.com') ? >[] : [ PopupMenuItem( diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 6ea4ca2e6..9b938a1ce 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -330,8 +330,9 @@ class _RemotePageState extends State { if (dy > 0) dy = -1; else if (dy < 0) dy = 1; - gFFI.setByName( - 'send_mouse', '{"type": "wheel", "x": "$dx", "y": "$dy"}'); + bind.sessionSendMouse( + id: widget.id, + msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}'); } }, child: MouseRegion( @@ -1124,7 +1125,7 @@ void showRestartRemoteDevice(PeerInfo pi, String id) async { onPressed: () => close(true), child: Text(translate("OK"))), ], )); - if (res == true) gFFI.setByName('restart_remote_device'); + if (res == true) bind.sessionRestartRemoteDevice(id: id); } void showSetOSPassword(String id, bool login) async { diff --git a/flutter/lib/mobile/pages/server_page.dart b/flutter/lib/mobile/pages/server_page.dart index 19753bcac..3abcd70da 100644 --- a/flutter/lib/mobile/pages/server_page.dart +++ b/flutter/lib/mobile/pages/server_page.dart @@ -1,13 +1,10 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:flutter_hbb/mobile/widgets/dialog.dart'; -import 'package:flutter_hbb/models/model.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:provider/provider.dart'; import '../../common.dart'; -import '../../models/model.dart'; +import '../../models/platform_model.dart'; import '../../models/server_model.dart'; import 'home_page.dart'; @@ -99,10 +96,7 @@ class ServerPage extends StatelessWidget implements PageShape { } else if (value == kUsePermanentPassword || value == kUseTemporaryPassword || value == kUseBothPasswords) { - Map msg = Map() - ..["name"] = "verification-method" - ..["value"] = value; - gFFI.setByName('option', jsonEncode(msg)); + bind.mainSetOption(key: "verification-method", value: value); gFFI.serverModel.updatePasswordModel(); } }) @@ -183,9 +177,8 @@ class ServerInfo extends StatelessWidget { ? null : IconButton( icon: const Icon(Icons.refresh), - onPressed: () { - gFFI.setByName("temporary_password"); - })), + onPressed: () => + bind.mainUpdateTemporaryPassword())), onSaved: (String? value) {}, ), ], @@ -406,8 +399,7 @@ class ConnectionManager extends StatelessWidget { MaterialStateProperty.all(Colors.red)), icon: Icon(Icons.close), onPressed: () { - gFFI.setByName( - "close_conn", entry.key.toString()); + bind.serverCloseConnection(connId: entry.key); gFFI.invokeMethod( "cancel_notification", entry.key); }, diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index 3646b59e9..4b8760413 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -31,15 +31,38 @@ class SettingsPage extends StatefulWidget implements PageShape { const url = 'https://rustdesk.com/'; final _hasIgnoreBattery = androidVersion >= 26; var _ignoreBatteryOpt = false; +var _enableAbr = false; class _SettingsState extends State with WidgetsBindingObserver { + String? username; + @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); - if (_hasIgnoreBattery) { - updateIgnoreBatteryStatus(); - } + + () async { + var update = false; + if (_hasIgnoreBattery) { + update = await updateIgnoreBatteryStatus(); + } + + final usernameRes = await getUsername(); + if (usernameRes != username) { + update = true; + username = usernameRes; + } + + final enableAbrRes = await bind.mainGetOption(key: "enable-abr") != "N"; + if (enableAbrRes != _enableAbr) { + update = true; + _enableAbr = enableAbrRes; + } + + if (update) { + setState(() {}); + } + }(); } @override @@ -51,16 +74,18 @@ class _SettingsState extends State with WidgetsBindingObserver { @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { - updateIgnoreBatteryStatus(); + () async { + if (await updateIgnoreBatteryStatus()) { + setState(() {}); + } + }(); } } Future updateIgnoreBatteryStatus() async { final res = await PermissionManager.check("ignore_battery_optimizations"); if (_ignoreBatteryOpt != res) { - setState(() { - _ignoreBatteryOpt = res; - }); + _ignoreBatteryOpt = res; return true; } else { return false; @@ -70,21 +95,15 @@ class _SettingsState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { Provider.of(context); - final username = getUsername(); - final enableAbr = gFFI.getByName("option", "enable-abr") != 'N'; final enhancementsTiles = [ SettingsTile.switchTile( - title: Text(translate('Adaptive Bitrate') + '(beta)'), - initialValue: enableAbr, + title: Text(translate('Adaptive Bitrate') + ' (beta)'), + initialValue: _enableAbr, onToggle: (v) { - final msg = Map() - ..["name"] = "enable-abr" - ..["value"] = ""; - if (!v) { - msg["value"] = "N"; - } - gFFI.setByName("option", json.encode(msg)); - setState(() {}); + bind.mainSetOption(key: "enable-abr", value: v ? "" : "N"); + setState(() { + _enableAbr = !_enableAbr; + }); }, ) ]; @@ -196,7 +215,7 @@ void showServerSettings() async { void showLanguageSettings() async { try { - final langs = json.decode(gFFI.getByName('langs')) as List; + final langs = json.decode(await bind.mainGetLangs()) as List; var lang = await bind.mainGetLocalOption(key: "lang"); DialogManager.show((setState, close) { final setLang = (v) { @@ -297,20 +316,19 @@ String parseResp(String body) { } final token = data['access_token']; if (token != null) { - gFFI.setByName('option', '{"name": "access_token", "value": "$token"}'); + bind.mainSetOption(key: "access_token", value: token); } final info = data['user']; if (info != null) { final value = json.encode(info); - gFFI.setByName( - 'option', json.encode({"name": "user_info", "value": value})); + bind.mainSetOption(key: "user_info", value: value); gFFI.ffiModel.updateUser(); } return ''; } void refreshCurrentUser() async { - final token = gFFI.getByName("option", "access_token"); + final token = await bind.mainGetOption(key: "access_token"); if (token == '') return; final url = getUrl(); final body = {'id': bind.mainGetMyId(), 'uuid': bind.mainGetUuid()}; @@ -333,7 +351,7 @@ void refreshCurrentUser() async { } void logout() async { - final token = gFFI.getByName("option", "access_token"); + final token = await bind.mainGetOption(key: "access_token"); if (token == '') return; final url = getUrl(); final body = {'id': bind.mainGetMyId(), 'uuid': bind.mainGetUuid()}; @@ -350,16 +368,16 @@ void logout() async { resetToken(); } -void resetToken() { - gFFI.setByName('option', '{"name": "access_token", "value": ""}'); - gFFI.setByName('option', '{"name": "user_info", "value": ""}'); +void resetToken() async { + await bind.mainSetOption(key: "access_token", value: ""); + await bind.mainSetOption(key: "user_info", value: ""); gFFI.ffiModel.updateUser(); } -String getUrl() { - var url = gFFI.getByName('option', 'api-server'); +Future getUrl() async { + var url = await bind.mainGetOption(key: "api-server"); if (url == '') { - url = gFFI.getByName('option', 'custom-rendezvous-server'); + url = await bind.mainGetOption(key: "custom-rendezvous-server"); if (url != '') { if (url.contains(':')) { final tmp = url.split(':'); @@ -448,11 +466,11 @@ void showLogin() { }); } -String? getUsername() { - final token = gFFI.getByName("option", "access_token"); +Future getUsername() async { + final token = await bind.mainGetOption(key: "access_token"); String? username; if (token != "") { - final info = gFFI.getByName("option", "user_info"); + final info = await bind.mainGetOption(key: "user_info"); if (info != "") { try { Map tmp = json.decode(info); diff --git a/flutter/lib/mobile/widgets/dialog.dart b/flutter/lib/mobile/widgets/dialog.dart index 3ab0489a9..ddd6816fb 100644 --- a/flutter/lib/mobile/widgets/dialog.dart +++ b/flutter/lib/mobile/widgets/dialog.dart @@ -1,11 +1,9 @@ import 'dart:async'; -import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import '../../common.dart'; -import '../../models/model.dart'; +import '../../models/platform_model.dart'; void clientClose() { msgBox('', 'Close', 'Are you sure to close the connection?'); @@ -22,8 +20,8 @@ void showError({Duration duration = SEC1}) { showToast(translate("Error"), duration: SEC1); } -void setPermanentPasswordDialog() { - final pw = gFFI.getByName("permanent_password"); +void setPermanentPasswordDialog() async { + final pw = await bind.mainGetPermanentPassword(); final p0 = TextEditingController(text: pw); final p1 = TextEditingController(text: pw); var validateLength = false; @@ -103,9 +101,9 @@ void setPermanentPasswordDialog() { }); } -void setTemporaryPasswordLengthDialog() { +void setTemporaryPasswordLengthDialog() async { List lengths = ['6', '8', '10']; - String length = gFFI.getByName('option', 'temporary-password-length'); + String length = await bind.mainGetOption(key: "temporary-password-length"); var index = lengths.indexOf(length); if (index < 0) index = 0; length = lengths[index]; @@ -116,11 +114,8 @@ void setTemporaryPasswordLengthDialog() { setState(() { length = newValue; }); - Map msg = Map() - ..["name"] = "temporary-password-length" - ..["value"] = newValue; - gFFI.setByName("option", jsonEncode(msg)); - gFFI.setByName("temporary_password"); + bind.mainSetOption(key: "temporary-password-length", value: newValue); + bind.mainUpdateTemporaryPassword(); Future.delayed(Duration(milliseconds: 200), () { close(); showSuccess(); @@ -138,9 +133,9 @@ void setTemporaryPasswordLengthDialog() { }, backDismiss: true, clickMaskDismiss: true); } -void enterPasswordDialog(String id) { +void enterPasswordDialog(String id) async { final controller = TextEditingController(); - var remember = gFFI.getByName('remember', id) == 'true'; + var remember = await bind.getSessionRemember(id: id) ?? false; DialogManager.show((setState, close) { return CustomAlertDialog( title: Text(translate('Password Required')), diff --git a/flutter/lib/models/chat_model.dart b/flutter/lib/models/chat_model.dart index 28ffa65e2..52f00aa01 100644 --- a/flutter/lib/models/chat_model.dart +++ b/flutter/lib/models/chat_model.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:dash_chat_2/dash_chat_2.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/models/platform_model.dart'; @@ -106,12 +104,11 @@ class ChatModel with ChangeNotifier { if (message.text.isNotEmpty) { _messages[_currentID]?.insert(message); if (_currentID == clientModeID) { - _ffi.target?.setByName("chat_client_mode", message.text); + if (_ffi.target != null) { + bind.sessionSendChat(id: _ffi.target!.id, text: message.text); + } } else { - final msg = Map() - ..["id"] = _currentID - ..["text"] = message.text; - _ffi.target?.setByName("chat_server_mode", jsonEncode(msg)); + bind.serverSendChat(connId: _currentID, msg: message.text); } } notifyListeners(); diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index 45f5ec970..75f3f8045 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -290,7 +290,7 @@ class FileModel extends ChangeNotifier { } onReady() async { - _localOption.home = _ffi.target?.getByName("get_home_dir") ?? ""; + _localOption.home = await bind.mainGetHomeDir(); _localOption.showHidden = (await bind.sessionGetPeerOption( id: _ffi.target?.id ?? "", name: "local_show_hidden")) .isNotEmpty; diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 7ca77f6cd..c7295f57e 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -889,8 +889,10 @@ class FFI { /// Send scroll event with scroll distance [y]. void scroll(int y) { - setByName('send_mouse', - json.encode(modify({'id': id, 'type': 'wheel', 'y': y.toString()}))); + bind.sessionSendMouse( + id: id, + msg: json + .encode(modify({'id': id, 'type': 'wheel', 'y': y.toString()}))); } /// Reconnect to the remote peer. @@ -916,8 +918,9 @@ class FFI { /// Send mouse press event. void sendMouse(String type, MouseButtons button) { if (!ffiModel.keyboard()) return; - setByName('send_mouse', - json.encode(modify({'id': id, 'type': type, 'buttons': button.value}))); + bind.sessionSendMouse( + id: id, + msg: json.encode(modify({'type': type, 'buttons': button.value}))); } /// Send key stroke event. @@ -953,8 +956,8 @@ class FFI { if (!ffiModel.keyboard()) return; var x2 = x.toInt(); var y2 = y.toInt(); - setByName( - 'send_mouse', json.encode(modify({'id': id, 'x': '$x2', 'y': '$y2'}))); + bind.sessionSendMouse( + id: id, msg: json.encode(modify({'x': '$x2', 'y': '$y2'}))); } /// List the saved peers. @@ -1032,14 +1035,14 @@ 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 platformFFI.getByName(name, arg); - } + // String getByName(String name, [String arg = '']) { + // return platformFFI.getByName(name, arg); + // } /// Send **set** command to the Rust core based on [name] and [value]. - void setByName(String name, [String value = '']) { - platformFFI.setByName(name, value); - } + // void setByName(String name, [String value = '']) { + // platformFFI.setByName(name, value); + // } handleMouse(Map evt, {double tabBarHeight = 0.0}) { var type = ''; @@ -1092,8 +1095,7 @@ class FFI { break; } evt['buttons'] = buttons; - evt['id'] = id; - setByName('send_mouse', json.encode(evt)); + bind.sessionSendMouse(id: id, msg: json.encode(evt)); } listenToMouse(bool yesOrNo) { diff --git a/flutter/lib/models/native_model.dart b/flutter/lib/models/native_model.dart index c58577945..a55ed1d29 100644 --- a/flutter/lib/models/native_model.dart +++ b/flutter/lib/models/native_model.dart @@ -30,8 +30,6 @@ class PlatformFFI { String _dir = ''; String _homeDir = ''; F2? _translate; - F2? _getByName; - F3? _setByName; var _eventHandlers = Map>(); late RustdeskImpl _ffiBind; late String _appType; @@ -89,31 +87,6 @@ class PlatformFFI { return res; } - /// Send **get** command to the Rust core based on [name] and [arg]. - /// Return the result as a string. - String getByName(String name, [String arg = '']) { - if (_getByName == null) return ''; - var a = name.toNativeUtf8(); - var b = arg.toNativeUtf8(); - var p = _getByName!(a, b); - assert(p != nullptr); - var res = p.toDartString(); - calloc.free(p); - calloc.free(a); - calloc.free(b); - return res; - } - - /// Send **set** command to the Rust core based on [name] and [value]. - void setByName(String name, [String value = '']) { - if (_setByName == null) return; - var a = name.toNativeUtf8(); - var b = value.toNativeUtf8(); - _setByName!(a, b); - calloc.free(a); - calloc.free(b); - } - /// Init the FFI class, loads the native Rust core library. Future init(String appType) async { _appType = appType; @@ -133,10 +106,6 @@ class PlatformFFI { debugPrint('initializing FFI ${_appType}'); try { _translate = dylib.lookupFunction('translate'); - _getByName = dylib.lookupFunction('get_by_name'); - _setByName = - dylib.lookupFunction, Pointer), F3>( - 'set_by_name'); _dir = (await getApplicationDocumentsDirectory()).path; _ffiBind = RustdeskImpl(dylib); _startListenEvent(_ffiBind); // global event @@ -177,10 +146,10 @@ class PlatformFFI { } print( "_appType:$_appType,info1-id:$id,info2-name:$name,dir:$_dir,homeDir:$_homeDir"); - setByName('info1', id); - setByName('info2', name); - setByName('home_dir', _homeDir); - setByName('init', _dir); + await _ffiBind.mainDeviceId(id: id); + await _ffiBind.mainDeviceName(name: name); + await _ffiBind.mainSetHomeDir(home: _homeDir); + await _ffiBind.mainInit(appDir: _dir); } catch (e) { print(e); } diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index 362e47a78..6aa7016b2 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -99,42 +99,29 @@ class ServerModel with ChangeNotifier { // audio if (androidVersion < 30 || !await PermissionManager.check("audio")) { _audioOk = false; - parent.target?.setByName( - 'option', - jsonEncode(Map() - ..["name"] = "enable-audio" - ..["value"] = "N")); + bind.mainSetOption(key: "enable-audio", value: "N"); } else { - final audioOption = parent.target?.getByName('option', 'enable-audio'); - _audioOk = audioOption?.isEmpty ?? false; + final audioOption = await bind.mainGetOption(key: 'enable-audio'); + _audioOk = audioOption.isEmpty; } // file if (!await PermissionManager.check("file")) { _fileOk = false; - parent.target?.setByName( - 'option', - jsonEncode(Map() - ..["name"] = "enable-file-transfer" - ..["value"] = "N")); + bind.mainSetOption(key: "enable-file-transfer", value: "N"); } else { final fileOption = - parent.target?.getByName('option', 'enable-file-transfer'); - _fileOk = fileOption?.isEmpty ?? false; + await bind.mainGetOption(key: 'enable-file-transfer'); + _fileOk = fileOption.isEmpty; } - // input (mouse control) - Map res = Map() - ..["name"] = "enable-keyboard" - ..["value"] = 'N'; - parent.target - ?.setByName('option', jsonEncode(res)); // input false by default + // input (mouse control) false by default + bind.mainSetOption(key: "enable-keyboard", value: "N"); notifyListeners(); }(); - Timer.periodic(Duration(seconds: 1), (timer) { - var status = - int.tryParse(parent.target?.getByName('connect_statue') ?? "") ?? 0; + Timer.periodic(Duration(seconds: 1), (timer) async { + var status = await bind.mainGetOnlineStatue(); if (status > 0) { status = 1; } @@ -142,10 +129,8 @@ class ServerModel with ChangeNotifier { _connectStatus = status; notifyListeners(); } - final res = parent.target - ?.getByName('check_clients_length', _clients.length.toString()) ?? - ""; - if (res.isNotEmpty) { + final res = await bind.mainCheckClientsLength(length: _clients.length); + if (res != null) { debugPrint("clients not match!"); updateClientState(res); } @@ -156,7 +141,7 @@ class ServerModel with ChangeNotifier { updatePasswordModel() async { var update = false; - final temporaryPassword = gFFI.getByName("temporary_password"); + final temporaryPassword = await bind.mainGetTemporaryPassword(); final verificationMethod = await bind.mainGetOption(key: "verification-method"); final temporaryPasswordLength = @@ -194,10 +179,7 @@ class ServerModel with ChangeNotifier { } _audioOk = !_audioOk; - Map res = Map() - ..["name"] = "enable-audio" - ..["value"] = _audioOk ? '' : 'N'; - parent.target?.setByName('option', jsonEncode(res)); + bind.mainSetOption(key: "enable-audio", value: _audioOk ? '' : 'N'); notifyListeners(); } @@ -211,10 +193,7 @@ class ServerModel with ChangeNotifier { } _fileOk = !_fileOk; - Map res = Map() - ..["name"] = "enable-file-transfer" - ..["value"] = _fileOk ? '' : 'N'; - parent.target?.setByName('option', jsonEncode(res)); + bind.mainSetOption(key: "enable-file-transfer", value: _fileOk ? '' : 'N'); notifyListeners(); } @@ -284,7 +263,7 @@ class ServerModel with ChangeNotifier { // TODO parent.target?.ffiModel.updateEventListener(""); await parent.target?.invokeMethod("init_service"); - parent.target?.setByName("start_service"); + await bind.mainStartService(); _fetchID(); updateClientState(); if (!Platform.isLinux) { @@ -299,7 +278,7 @@ class ServerModel with ChangeNotifier { // TODO parent.target?.serverModel.closeAll(); await parent.target?.invokeMethod("stop_service"); - parent.target?.setByName("stop_service"); + await bind.mainStopService(); notifyListeners(); if (!Platform.isLinux) { // current linux is not supported @@ -312,9 +291,9 @@ class ServerModel with ChangeNotifier { } Future setPermanentPassword(String newPW) async { - parent.target?.setByName("permanent_password", newPW); + await bind.mainSetPermanentPassword(password: newPW); await Future.delayed(Duration(milliseconds: 500)); - final pw = parent.target?.getByName("permanent_password"); + final pw = await bind.mainGetPermanentPassword(); if (newPW == pw) { return true; } else { @@ -355,10 +334,7 @@ class ServerModel with ChangeNotifier { break; case "input": if (_inputOk != value) { - Map res = Map() - ..["name"] = "enable-keyboard" - ..["value"] = value ? '' : 'N'; - parent.target?.setByName('option', jsonEncode(res)); + bind.mainSetOption(key: "enable-keyboard", value: value ? '' : 'N'); } _inputOk = value; break; @@ -368,8 +344,8 @@ class ServerModel with ChangeNotifier { notifyListeners(); } - updateClientState([String? json]) { - var res = json ?? parent.target?.getByName("clients_state") ?? ""; + updateClientState([String? json]) async { + var res = await bind.mainGetClientsState(); try { final List clientsJson = jsonDecode(res); for (var clientJson in clientsJson) { @@ -451,12 +427,9 @@ class ServerModel with ChangeNotifier { }); } - void sendLoginResponse(Client client, bool res) { - final Map response = Map(); - response["id"] = client.id; - response["res"] = res; + void sendLoginResponse(Client client, bool res) async { if (res) { - parent.target?.setByName("login_res", jsonEncode(response)); + bind.serverLoginRes(connId: client.id, res: res); if (!client.isFileTransfer) { parent.target?.invokeMethod("start_capture"); } @@ -464,7 +437,7 @@ class ServerModel with ChangeNotifier { _clients[client.id]?.authorized = true; notifyListeners(); } else { - parent.target?.setByName("login_res", jsonEncode(response)); + bind.serverLoginRes(connId: client.id, res: res); parent.target?.invokeMethod("cancel_notification", client.id); _clients.remove(client.id); } @@ -496,7 +469,7 @@ class ServerModel with ChangeNotifier { closeAll() { _clients.forEach((id, client) { - parent.target?.setByName("close_conn", id.toString()); + bind.serverCloseConnection(connId: id); }); _clients.clear(); } diff --git a/src/client.rs b/src/client.rs index 7ddfe0969..89d66c6ca 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1280,7 +1280,7 @@ impl LoginConfigHandler { /// Create a [`Message`] for login. fn create_login_msg(&self, password: Vec) -> Message { #[cfg(any(target_os = "android", target_os = "ios"))] - let my_id = Config::get_id_or(crate::common::FLUTTER_INFO1.lock().unwrap().clone()); + let my_id = Config::get_id_or(crate::common::DEVICE_ID.lock().unwrap().clone()); #[cfg(not(any(target_os = "android", target_os = "ios")))] let my_id = Config::get_id(); let mut lr = LoginRequest { diff --git a/src/common.rs b/src/common.rs index 5af811c05..605435956 100644 --- a/src/common.rs +++ b/src/common.rs @@ -28,8 +28,8 @@ lazy_static::lazy_static! { } lazy_static::lazy_static! { - pub static ref FLUTTER_INFO1: Arc> = Default::default(); - pub static ref FLUTTER_INFO2: Arc> = Default::default(); + pub static ref DEVICE_ID: Arc> = Default::default(); + pub static ref DEVICE_NAME: Arc> = Default::default(); } #[inline] @@ -441,7 +441,7 @@ pub fn username() -> String { #[cfg(not(any(target_os = "android", target_os = "ios")))] return whoami::username().trim_end_matches('\0').to_owned(); #[cfg(any(target_os = "android", target_os = "ios"))] - return FLUTTER_INFO2.lock().unwrap().clone(); + return DEVICE_NAME.lock().unwrap().clone(); } #[inline] diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 40f72444a..95cd1abd3 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -26,7 +26,8 @@ use crate::ui_interface::{ get_connect_status, get_fav, get_id, get_lan_peers, get_license, get_local_option, get_option, get_options, get_peer, get_peer_option, get_socks, get_sound_inputs, get_uuid, get_version, has_rendezvous_service, post_request, set_local_option, set_option, set_options, - set_peer_option, set_socks, store_fav, test_if_valid_server, using_public_server, + set_peer_option, set_permanent_password, set_socks, store_fav, test_if_valid_server, + update_temporary_password, using_public_server, }; fn initialize(app_dir: &str) { @@ -235,38 +236,6 @@ pub fn session_send_chat(id: String, text: String) { } } -// if let Some(_type) = m.get("type") { -// mask = match _type.as_str() { -// "down" => 1, -// "up" => 2, -// "wheel" => 3, -// _ => 0, -// }; -// } -// if let Some(buttons) = m.get("buttons") { -// mask |= match buttons.as_str() { -// "left" => 1, -// "right" => 2, -// "wheel" => 4, -// _ => 0, -// } << 3; -// } -// TODO -pub fn session_send_mouse( - id: String, - mask: i32, - x: i32, - y: i32, - alt: bool, - ctrl: bool, - shift: bool, - command: bool, -) { - if let Some(session) = SESSIONS.read().unwrap().get(&id) { - session.send_mouse(mask, x, y, alt, ctrl, shift, command); - } -} - pub fn session_peer_option(id: String, name: String, value: String) { if let Some(session) = SESSIONS.read().unwrap().get(&id) { session.set_option(name, value); @@ -426,11 +395,7 @@ pub fn main_set_option(key: String, value: String) { set_option(key, value); #[cfg(target_os = "android")] crate::rendezvous_mediator::RendezvousMediator::restart(); - #[cfg(any( - target_os = "android", - target_os = "ios", - feature = "cli" - ))] + #[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))] crate::common::test_rendezvous_server(); } else { set_option(key, value); @@ -640,6 +605,143 @@ pub fn main_get_last_remote_id() -> String { LocalConfig::get_remote_id() } +pub fn main_get_software_update_url() -> String { + crate::common::SOFTWARE_UPDATE_URL.lock().unwrap().clone() +} + +pub fn main_get_home_dir() -> String { + fs::get_home_as_string() +} + +pub fn main_get_langs() -> String { + crate::lang::LANGS.to_string() +} + +pub fn main_get_temporary_password() -> String { + ui_interface::temporary_password() +} + +pub fn main_get_permanent_password() -> String { + ui_interface::permanent_password() +} + +pub fn main_get_online_statue() -> i64 { + ONLINE.lock().unwrap().values().max().unwrap_or(&0).clone() +} + +pub fn main_get_clients_state() -> String { + get_clients_state() +} + +pub fn main_check_clients_length(length: usize) -> Option { + if length != get_clients_length() { + Some(get_clients_state()) + } else { + None + } +} + +pub fn main_init(app_dir: String) { + initialize(&app_dir); +} + +pub fn main_device_id(id: String) { + *crate::common::DEVICE_ID.lock().unwrap() = id; +} + +pub fn main_device_name(name: String) { + *crate::common::DEVICE_NAME.lock().unwrap() = name; +} + +pub fn main_remove_peer(id: String) { + PeerConfig::remove(&id); +} + +// TODO +pub fn session_send_mouse(id: String, msg: String) { + if let Ok(m) = serde_json::from_str::>(&msg) { + let alt = m.get("alt").is_some(); + let ctrl = m.get("ctrl").is_some(); + let shift = m.get("shift").is_some(); + let command = m.get("command").is_some(); + let x = m + .get("x") + .map(|x| x.parse::().unwrap_or(0)) + .unwrap_or(0); + let y = m + .get("y") + .map(|x| x.parse::().unwrap_or(0)) + .unwrap_or(0); + let mut mask = 0; + if let Some(_type) = m.get("type") { + mask = match _type.as_str() { + "down" => 1, + "up" => 2, + "wheel" => 3, + _ => 0, + }; + } + if let Some(buttons) = m.get("buttons") { + mask |= match buttons.as_str() { + "left" => 1, + "right" => 2, + "wheel" => 4, + _ => 0, + } << 3; + } + if let Some(session) = SESSIONS.read().unwrap().get(&id) { + session.send_mouse(mask, x, y, alt, ctrl, shift, command); + } + } +} + +pub fn session_restart_remote_device(id: String) { + // TODO + // Session::restart_remote_device(); +} + +pub fn main_set_home_dir(home: String) { + *config::APP_HOME_DIR.write().unwrap() = home; +} + +pub fn main_stop_service() { + #[cfg(target_os = "android")] + { + Config::set_option("stop-service".into(), "Y".into()); + crate::rendezvous_mediator::RendezvousMediator::restart(); + } +} + +pub fn main_start_service() { + #[cfg(target_os = "android")] + { + Config::set_option("stop-service".into(), "".into()); + crate::rendezvous_mediator::RendezvousMediator::restart(); + } + #[cfg(not(target_os = "android"))] + std::thread::spawn(move || start_server(true)); +} + +pub fn main_update_temporary_password() { + update_temporary_password(); +} + +pub fn main_set_permanent_password(password: String) { + set_permanent_password(password); +} + +pub fn server_send_chat(conn_id: i32, msg: String) { + connection_manager::send_chat(conn_id, msg); +} + +pub fn server_login_res(conn_id: i32, res: bool) { + connection_manager::on_login_res(conn_id, res); +} + +pub fn server_close_connection(conn_id: i32) { + connection_manager::close_conn(conn_id); +} + #[no_mangle] unsafe extern "C" fn translate(name: *const c_char, locale: *const c_char) -> *const c_char { let name = CStr::from_ptr(name); @@ -652,241 +754,6 @@ unsafe extern "C" fn translate(name: *const c_char, locale: *const c_char) -> *c CString::from_vec_unchecked(res.into_bytes()).into_raw() } -/// FFI for **get** commands which are idempotent. -/// Return result in c string. -/// -/// # Arguments -/// -/// * `name` - name of the command -/// * `arg` - argument of the command -#[no_mangle] -unsafe extern "C" fn get_by_name(name: *const c_char, arg: *const c_char) -> *const c_char { - let mut res = "".to_owned(); - let arg: &CStr = CStr::from_ptr(arg); - let name: &CStr = CStr::from_ptr(name); - if let Ok(name) = name.to_str() { - match name { - // "peers" => { - // if !config::APP_DIR.read().unwrap().is_empty() { - // let peers: Vec<(String, config::PeerInfoSerde)> = PeerConfig::peers() - // .drain(..) - // .map(|(id, _, p)| (id, p.info)) - // .collect(); - // res = serde_json::ser::to_string(&peers).unwrap_or("".to_owned()); - // } - // } - // "remote_id" => { - // if !config::APP_DIR.read().unwrap().is_empty() { - // res = LocalConfig::get_remote_id(); - // } - // } - // "test_if_valid_server" => { - // if let Ok(arg) = arg.to_str() { - // res = hbb_common::socket_client::test_if_valid_server(arg); - // } - // } - // "option" => { - // if let Ok(arg) = arg.to_str() { - // res = ui_interface::get_option(arg.to_owned()); - // } - // } - "software_update_url" => { - res = crate::common::SOFTWARE_UPDATE_URL.lock().unwrap().clone() - } - // File Action - "get_home_dir" => { - res = fs::get_home_as_string(); - } - // Server Side - "langs" => { - res = crate::lang::LANGS.to_string(); - } - "temporary_password" => { - res = ui_interface::temporary_password(); - } - "permanent_password" => { - res = ui_interface::permanent_password(); - } - "connect_statue" => { - res = ONLINE - .lock() - .unwrap() - .values() - .max() - .unwrap_or(&0) - .clone() - .to_string(); - } - #[cfg(not(any(target_os = "ios")))] - "clients_state" => { - res = get_clients_state(); - } - #[cfg(not(any(target_os = "ios")))] - "check_clients_length" => { - if let Ok(value) = arg.to_str() { - if value.parse::().unwrap_or(usize::MAX) != get_clients_length() { - res = get_clients_state() - } - } - } - _ => { - log::error!("Unknown name of get_by_name: {}", name); - } - } - } - CString::from_vec_unchecked(res.into_bytes()).into_raw() -} - -/// FFI for **set** commands which are not idempotent. -/// -/// # Arguments -/// -/// * `name` - name of the command -/// * `arg` - argument of the command -#[no_mangle] -unsafe extern "C" fn set_by_name(name: *const c_char, value: *const c_char) { - let value: &CStr = CStr::from_ptr(value); - if let Ok(value) = value.to_str() { - let name: &CStr = CStr::from_ptr(name); - if let Ok(name) = name.to_str() { - match name { - "init" => { - initialize(value); - } - "info1" => { - *crate::common::FLUTTER_INFO1.lock().unwrap() = value.to_owned(); - } - "info2" => { - *crate::common::FLUTTER_INFO2.lock().unwrap() = value.to_owned(); - } - "remove" => { - PeerConfig::remove(value); - } - - // TODO - "send_mouse" => { - if let Ok(m) = serde_json::from_str::>(value) { - let id = m.get("id"); - if id.is_none() { - return; - } - let id = id.unwrap(); - let alt = m.get("alt").is_some(); - let ctrl = m.get("ctrl").is_some(); - let shift = m.get("shift").is_some(); - let command = m.get("command").is_some(); - let x = m - .get("x") - .map(|x| x.parse::().unwrap_or(0)) - .unwrap_or(0); - let y = m - .get("y") - .map(|x| x.parse::().unwrap_or(0)) - .unwrap_or(0); - let mut mask = 0; - if let Some(_type) = m.get("type") { - mask = match _type.as_str() { - "down" => 1, - "up" => 2, - "wheel" => 3, - _ => 0, - }; - } - if let Some(buttons) = m.get("buttons") { - mask |= match buttons.as_str() { - "left" => 1, - "right" => 2, - "wheel" => 4, - _ => 0, - } << 3; - } - if let Some(session) = SESSIONS.read().unwrap().get(id) { - session.send_mouse(mask, x, y, alt, ctrl, shift, command); - } - } - } - // "option" => { - // if let Ok(m) = serde_json::from_str::>(value) { - // if let Some(name) = m.get("name") { - // if let Some(value) = m.get("value") { - // ui_interface::set_option(name.to_owned(), value.to_owned()); - // if name == "custom-rendezvous-server" { - // #[cfg(target_os = "android")] - // crate::rendezvous_mediator::RendezvousMediator::restart(); - // #[cfg(any( - // target_os = "android", - // target_os = "ios", - // feature = "cli" - // ))] - // crate::common::test_rendezvous_server(); - // } - // } - // } - // } - // } - "restart_remote_device" => { - // TODO - // Session::restart_remote_device(); - } - #[cfg(target_os = "android")] - "chat_server_mode" => { - if let Ok(m) = serde_json::from_str::>(value) { - if let (Some(Value::Number(id)), Some(Value::String(text))) = - (m.get("id"), m.get("text")) - { - let id = id.as_i64().unwrap_or(0); - connection_manager::send_chat(id as i32, text.to_owned()); - } - } - } - "home_dir" => { - *config::APP_HOME_DIR.write().unwrap() = value.to_owned(); - } - #[cfg(target_os = "android")] - "login_res" => { - if let Ok(m) = serde_json::from_str::>(value) { - if let (Some(Value::Number(id)), Some(Value::Bool(res))) = - (m.get("id"), m.get("res")) - { - let id = id.as_i64().unwrap_or(0); - connection_manager::on_login_res(id as i32, *res); - } - } - } - #[cfg(target_os = "android")] - "stop_service" => { - Config::set_option("stop-service".into(), "Y".into()); - crate::rendezvous_mediator::RendezvousMediator::restart(); - } - "start_service" => { - #[cfg(target_os = "android")] - { - Config::set_option("stop-service".into(), "".into()); - crate::rendezvous_mediator::RendezvousMediator::restart(); - } - #[cfg(not(target_os = "android"))] - std::thread::spawn(move || start_server(true)); - } - #[cfg(target_os = "android")] - "close_conn" => { - if let Ok(id) = value.parse::() { - connection_manager::close_conn(id); - }; - } - "temporary_password" => { - ui_interface::update_temporary_password(); - } - "permanent_password" => { - ui_interface::set_permanent_password(value.to_owned()); - } - _ => { - log::error!("Unknown name of set_by_name: {}", name); - } - } - } - } -} - fn handle_query_onlines(onlines: Vec, offlines: Vec) { if let Some(s) = flutter::GLOBAL_EVENT_STREAM .read() diff --git a/src/server/connection.rs b/src/server/connection.rs index 7d12dce45..346477851 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -5,7 +5,7 @@ use crate::clipboard_file::*; use crate::common::update_clipboard; use crate::video_service; #[cfg(any(target_os = "android", target_os = "ios"))] -use crate::{common::FLUTTER_INFO2, flutter::connection_manager::start_channel}; +use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel}; use crate::{ipc, VERSION}; use hbb_common::{ config::Config, @@ -643,7 +643,7 @@ impl Connection { } #[cfg(target_os = "android")] { - pi.hostname = FLUTTER_INFO2.lock().unwrap().clone(); + pi.hostname = DEVICE_NAME.lock().unwrap().clone(); pi.platform = "Android".into(); } #[cfg(feature = "hwcodec")]