From 4f74acba76ad8a9da3bc41719b71ede3ec4d3cc4 Mon Sep 17 00:00:00 2001 From: csf Date: Wed, 21 Dec 2022 15:14:43 +0900 Subject: [PATCH 1/3] add ServerConfig, update server config import and export --- flutter/lib/common.dart | 39 +++++++++++++++ .../desktop/pages/desktop_setting_page.dart | 47 +++++++++---------- 2 files changed, 62 insertions(+), 24 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 15d058b87..8e8e50ae9 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1510,3 +1510,42 @@ Pointer getOSVERSIONINFOEXPointer() { bool get kUseCompatibleUiMode => Platform.isWindows && const [WindowsTarget.w7].contains(windowsBuildNumber.windowsVersion); + +class ServerConfig { + late String idServer; + late String relayServer; + late String apiServer; + late String key; + + ServerConfig( + {String? idServer, String? relayServer, String? apiServer, String? key}) { + this.idServer = idServer?.trim() ?? ''; + this.relayServer = relayServer?.trim() ?? ''; + this.apiServer = apiServer?.trim() ?? ''; + this.key = key?.trim() ?? ''; + } + + /// throw decoding failure + ServerConfig.decode(String msg) { + final input = msg.split('').reversed.join(''); + final bytes = base64Decode(base64.normalize(input)); + final json = jsonDecode(utf8.decode(bytes)); + + idServer = json['host'] ?? ''; + relayServer = json['relay'] ?? ''; + apiServer = json['api'] ?? ''; + key = json['key'] ?? ''; + } + + String encode() { + Map config = {}; + config['host'] = idServer.trim(); + config['relay'] = relayServer.trim(); + config['api'] = apiServer.trim(); + config['key'] = key.trim(); + return base64Encode(Uint8List.fromList(jsonEncode(config).codeUnits)) + .split('') + .reversed + .join(); + } +} diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 613f19810..a45de24b0 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -958,23 +958,17 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { import() { Clipboard.getData(Clipboard.kTextPlain).then((value) { - TextEditingController mytext = TextEditingController(); - String? aNullableString = ''; - aNullableString = value?.text; - mytext.text = aNullableString.toString(); - if (mytext.text.isNotEmpty) { + final text = value?.text; + if (text != null && text.isNotEmpty) { try { - Map config = jsonDecode(mytext.text); - if (config.containsKey('IdServer')) { - String id = config['IdServer'] ?? ''; - String relay = config['RelayServer'] ?? ''; - String api = config['ApiServer'] ?? ''; - String key = config['Key'] ?? ''; - idController.text = id; - relayController.text = relay; - apiController.text = api; - keyController.text = key; - Future success = set(id, relay, api, key); + final sc = ServerConfig.decode(text); + if (sc.idServer.isNotEmpty) { + idController.text = sc.idServer; + relayController.text = sc.relayServer; + apiController.text = sc.apiServer; + keyController.text = sc.key; + Future success = + set(sc.idServer, sc.relayServer, sc.apiServer, sc.key); success.then((value) { if (value) { showToast( @@ -996,12 +990,15 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { } export() { - Map config = {}; - config['IdServer'] = idController.text.trim(); - config['RelayServer'] = relayController.text.trim(); - config['ApiServer'] = apiController.text.trim(); - config['Key'] = keyController.text.trim(); - Clipboard.setData(ClipboardData(text: jsonEncode(config))); + final text = ServerConfig( + idServer: idController.text, + relayServer: relayController.text, + apiServer: apiController.text, + key: keyController.text) + .encode(); + debugPrint("ServerConfig export: $text"); + + Clipboard.setData(ClipboardData(text: text)); showToast(translate('Export server configuration successfully')); } @@ -1106,8 +1103,10 @@ class _AboutState extends State<_About> { const SizedBox( height: 8.0, ), - Text(translate('Version') + ': $version').marginSymmetric(vertical: 4.0), - Text(translate('Build Date') + ': $buildDate').marginSymmetric(vertical: 4.0), + Text(translate('Version') + ': $version') + .marginSymmetric(vertical: 4.0), + Text(translate('Build Date') + ': $buildDate') + .marginSymmetric(vertical: 4.0), InkWell( onTap: () { launchUrlString('https://rustdesk.com/privacy'); From f5cc55ab3db9e3db05381dcacf2fe181c22d2f31 Mon Sep 17 00:00:00 2001 From: csf Date: Wed, 21 Dec 2022 15:41:07 +0900 Subject: [PATCH 2/3] refactor mobile import ServerConfig --- flutter/lib/mobile/pages/scan_page.dart | 160 ++---------------------- flutter/lib/mobile/widgets/dialog.dart | 144 +++++++++++++++++++++ 2 files changed, 151 insertions(+), 153 deletions(-) diff --git a/flutter/lib/mobile/pages/scan_page.dart b/flutter/lib/mobile/pages/scan_page.dart index 810bcbca3..32208f6a4 100644 --- a/flutter/lib/mobile/pages/scan_page.dart +++ b/flutter/lib/mobile/pages/scan_page.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -9,11 +8,11 @@ import 'package:qr_code_scanner/qr_code_scanner.dart'; import 'package:zxing2/qrcode.dart'; import '../../common.dart'; -import '../../models/platform_model.dart'; +import '../widgets/dialog.dart'; class ScanPage extends StatefulWidget { @override - _ScanPageState createState() => _ScanPageState(); + State createState() => _ScanPageState(); } class _ScanPageState extends State { @@ -42,9 +41,9 @@ class _ScanPageState extends State { icon: Icon(Icons.image_search), iconSize: 32.0, onPressed: () async { - final ImagePicker _picker = ImagePicker(); + final ImagePicker picker = ImagePicker(); final XFile? file = - await _picker.pickImage(source: ImageSource.gallery); + await picker.pickImage(source: ImageSource.gallery); if (file != null) { var image = img.decodeNamedImage( File(file.path).readAsBytesSync(), file.path)!; @@ -139,158 +138,13 @@ class _ScanPageState extends State { return; } try { - Map values = json.decode(data.substring(7)); - var host = values['host'] != null ? values['host'] as String : ''; - var key = values['key'] != null ? values['key'] as String : ''; - var api = values['api'] != null ? values['api'] as String : ''; + final sc = ServerConfig.decode(data.substring(7)); Timer(Duration(milliseconds: 60), () { - showServerSettingsWithValue(host, '', key, api, gFFI.dialogManager); + showServerSettingsWithValue(sc.idServer, sc.relayServer, sc.key, + sc.apiServer, gFFI.dialogManager); }); } catch (e) { showToast('Invalid QR code'); } } } - -void showServerSettingsWithValue(String id, String relay, String key, - String api, OverlayDialogManager dialogManager) 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( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextFormField( - controller: idController, - decoration: InputDecoration( - labelText: translate('ID Server'), - errorText: idServerMsg), - ) - ] + - (isAndroid - ? [ - TextFormField( - controller: relayController, - decoration: InputDecoration( - labelText: translate('Relay Server'), - errorText: relayServerMsg), - ) - ] - : []) + - [ - TextFormField( - controller: apiController, - decoration: InputDecoration( - labelText: translate('API Server'), - ), - 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( - initialValue: key, - decoration: InputDecoration( - labelText: 'Key', - ), - onChanged: (String? value) { - if (value != null) key = value.trim(); - }, - ), - Offstage( - offstage: !isInProgress, - child: LinearProgressIndicator()) - ])), - actions: [ - TextButton( - style: flatButtonStyle, - onPressed: () { - close(); - }, - child: Text(translate('Cancel')), - ), - TextButton( - style: flatButtonStyle, - onPressed: () async { - setState(() { - idServerMsg = null; - relayServerMsg = null; - apiServerMsg = null; - isInProgress = true; - }); - if (await validate()) { - if (id != id0) { - if (id0.isNotEmpty) { - await gFFI.userModel.logOut(); - } - bind.mainSetOption(key: "custom-rendezvous-server", value: id); - } - if (relay != relay0) { - bind.mainSetOption(key: "relay-server", value: relay); - } - if (key != key0) bind.mainSetOption(key: "key", value: key); - if (api != api0) { - bind.mainSetOption(key: "api-server", value: api); - } - close(); - } - setState(() { - isInProgress = false; - }); - }, - child: Text(translate('OK')), - ), - ], - ); - }); -} - -Future validateAsync(String value) async { - value = value.trim(); - if (value.isEmpty) { - return null; - } - final res = await bind.mainTestIfValidServer(server: value); - return res.isEmpty ? null : res; -} diff --git a/flutter/lib/mobile/widgets/dialog.dart b/flutter/lib/mobile/widgets/dialog.dart index 96f96658a..8cfaf6a02 100644 --- a/flutter/lib/mobile/widgets/dialog.dart +++ b/flutter/lib/mobile/widgets/dialog.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:flutter/material.dart'; import '../../common.dart'; @@ -236,6 +237,149 @@ void wrongPasswordDialog(String id, OverlayDialogManager dialogManager) { ])); } +void showServerSettingsWithValue(String id, String relay, String key, + String api, OverlayDialogManager dialogManager) 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( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + controller: idController, + decoration: InputDecoration( + labelText: translate('ID Server'), + errorText: idServerMsg), + ) + ] + + (isAndroid + ? [ + TextFormField( + controller: relayController, + decoration: InputDecoration( + labelText: translate('Relay Server'), + errorText: relayServerMsg), + ) + ] + : []) + + [ + TextFormField( + controller: apiController, + decoration: InputDecoration( + labelText: translate('API Server'), + ), + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: (v) { + if (v != null && v.isNotEmpty) { + if (!(v.startsWith('http://') || + v.startsWith("https://"))) { + return translate("invalid_http"); + } + } + return apiServerMsg; + }, + ), + TextFormField( + initialValue: key, + decoration: InputDecoration( + labelText: 'Key', + ), + onChanged: (String? value) { + if (value != null) key = value.trim(); + }, + ), + Offstage( + offstage: !isInProgress, + child: LinearProgressIndicator()) + ])), + actions: [ + TextButton( + style: flatButtonStyle, + onPressed: () { + close(); + }, + child: Text(translate('Cancel')), + ), + TextButton( + style: flatButtonStyle, + onPressed: () async { + setState(() { + idServerMsg = null; + relayServerMsg = null; + apiServerMsg = null; + isInProgress = true; + }); + if (await validate()) { + if (id != id0) { + if (id0.isNotEmpty) { + await gFFI.userModel.logOut(); + } + bind.mainSetOption(key: "custom-rendezvous-server", value: id); + } + if (relay != relay0) { + bind.mainSetOption(key: "relay-server", value: relay); + } + if (key != key0) bind.mainSetOption(key: "key", value: key); + if (api != api0) { + bind.mainSetOption(key: "api-server", value: api); + } + close(); + } + setState(() { + isInProgress = false; + }); + }, + child: Text(translate('OK')), + ), + ], + ); + }); +} + +Future validateAsync(String value) async { + value = value.trim(); + if (value.isEmpty) { + return null; + } + final res = await bind.mainTestIfValidServer(server: value); + return res.isEmpty ? null : res; +} + class PasswordWidget extends StatefulWidget { PasswordWidget({Key? key, required this.controller, this.autoFocus = true}) : super(key: key); From cba6a3e0ee0bc2db41de8b2d4d94f934f09d6e71 Mon Sep 17 00:00:00 2001 From: csf Date: Wed, 21 Dec 2022 16:24:01 +0900 Subject: [PATCH 3/3] refactor to use ServerConfig --- flutter/lib/common.dart | 13 ++++- flutter/lib/mobile/pages/scan_page.dart | 3 +- flutter/lib/mobile/pages/settings_page.dart | 6 +- flutter/lib/mobile/widgets/dialog.dart | 64 ++++++++++----------- 4 files changed, 44 insertions(+), 42 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 8e8e50ae9..46ba90d66 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1525,7 +1525,9 @@ class ServerConfig { this.key = key?.trim() ?? ''; } - /// throw decoding failure + /// decode from shared string (from user shared or rustdesk-server generated) + /// also see [encode] + /// throw when decoding failure ServerConfig.decode(String msg) { final input = msg.split('').reversed.join(''); final bytes = base64Decode(base64.normalize(input)); @@ -1537,6 +1539,8 @@ class ServerConfig { key = json['key'] ?? ''; } + /// encode to shared string + /// also see [ServerConfig.decode] String encode() { Map config = {}; config['host'] = idServer.trim(); @@ -1548,4 +1552,11 @@ class ServerConfig { .reversed .join(); } + + /// from local options + ServerConfig.fromOptions(Map options) + : idServer = options['custom-rendezvous-server'] ?? "", + relayServer = options['relay-server'] ?? "", + apiServer = options['api-server'] ?? "", + key = options['key'] ?? ""; } diff --git a/flutter/lib/mobile/pages/scan_page.dart b/flutter/lib/mobile/pages/scan_page.dart index 32208f6a4..8778d78f7 100644 --- a/flutter/lib/mobile/pages/scan_page.dart +++ b/flutter/lib/mobile/pages/scan_page.dart @@ -140,8 +140,7 @@ class _ScanPageState extends State { try { final sc = ServerConfig.decode(data.substring(7)); Timer(Duration(milliseconds: 60), () { - showServerSettingsWithValue(sc.idServer, sc.relayServer, sc.key, - sc.apiServer, gFFI.dialogManager); + showServerSettingsWithValue(sc, gFFI.dialogManager); }); } catch (e) { showToast('Invalid QR code'); diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index 8c7cdb5c7..9637ecb40 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -391,11 +391,7 @@ class _SettingsState extends State with WidgetsBindingObserver { void showServerSettings(OverlayDialogManager dialogManager) 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, dialogManager); + showServerSettingsWithValue(ServerConfig.fromOptions(options), dialogManager); } void showLanguageSettings(OverlayDialogManager dialogManager) async { diff --git a/flutter/lib/mobile/widgets/dialog.dart b/flutter/lib/mobile/widgets/dialog.dart index 8cfaf6a02..d70902513 100644 --- a/flutter/lib/mobile/widgets/dialog.dart +++ b/flutter/lib/mobile/widgets/dialog.dart @@ -237,17 +237,16 @@ void wrongPasswordDialog(String id, OverlayDialogManager dialogManager) { ])); } -void showServerSettingsWithValue(String id, String relay, String key, - String api, OverlayDialogManager dialogManager) async { +void showServerSettingsWithValue( + ServerConfig serverConfig, OverlayDialogManager dialogManager) 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'] ?? ""; + final oldCfg = ServerConfig.fromOptions(oldOptions); + var isInProgress = false; - final idController = TextEditingController(text: id); - final relayController = TextEditingController(text: relay); - final apiController = TextEditingController(text: api); + final idCtrl = TextEditingController(text: serverConfig.idServer); + final relayCtrl = TextEditingController(text: serverConfig.relayServer); + final apiCtrl = TextEditingController(text: serverConfig.apiServer); + final keyCtrl = TextEditingController(text: serverConfig.key); String? idServerMsg; String? relayServerMsg; @@ -255,21 +254,18 @@ void showServerSettingsWithValue(String id, String relay, String key, dialogManager.show((setState, close) { Future validate() async { - if (idController.text != id) { - final res = await validateAsync(idController.text); + if (idCtrl.text != oldCfg.idServer) { + final res = await validateAsync(idCtrl.text); setState(() => idServerMsg = res); if (idServerMsg != null) return false; - id = idController.text; } - if (relayController.text != relay) { - relayServerMsg = await validateAsync(relayController.text); + if (relayCtrl.text != oldCfg.relayServer) { + relayServerMsg = await validateAsync(relayCtrl.text); if (relayServerMsg != null) return false; - relay = relayController.text; } - if (apiController.text != relay) { - apiServerMsg = await validateAsync(apiController.text); + if (apiCtrl.text != oldCfg.apiServer) { + apiServerMsg = await validateAsync(apiCtrl.text); if (apiServerMsg != null) return false; - api = apiController.text; } return true; } @@ -281,7 +277,7 @@ void showServerSettingsWithValue(String id, String relay, String key, mainAxisSize: MainAxisSize.min, children: [ TextFormField( - controller: idController, + controller: idCtrl, decoration: InputDecoration( labelText: translate('ID Server'), errorText: idServerMsg), @@ -290,7 +286,7 @@ void showServerSettingsWithValue(String id, String relay, String key, (isAndroid ? [ TextFormField( - controller: relayController, + controller: relayCtrl, decoration: InputDecoration( labelText: translate('Relay Server'), errorText: relayServerMsg), @@ -299,7 +295,7 @@ void showServerSettingsWithValue(String id, String relay, String key, : []) + [ TextFormField( - controller: apiController, + controller: apiCtrl, decoration: InputDecoration( labelText: translate('API Server'), ), @@ -315,13 +311,10 @@ void showServerSettingsWithValue(String id, String relay, String key, }, ), TextFormField( - initialValue: key, + controller: keyCtrl, decoration: InputDecoration( labelText: 'Key', ), - onChanged: (String? value) { - if (value != null) key = value.trim(); - }, ), Offstage( offstage: !isInProgress, @@ -345,18 +338,21 @@ void showServerSettingsWithValue(String id, String relay, String key, isInProgress = true; }); if (await validate()) { - if (id != id0) { - if (id0.isNotEmpty) { + if (idCtrl.text != oldCfg.idServer) { + if (oldCfg.idServer.isNotEmpty) { await gFFI.userModel.logOut(); } - bind.mainSetOption(key: "custom-rendezvous-server", value: id); + bind.mainSetOption( + key: "custom-rendezvous-server", value: idCtrl.text); } - if (relay != relay0) { - bind.mainSetOption(key: "relay-server", value: relay); + if (relayCtrl.text != oldCfg.relayServer) { + bind.mainSetOption(key: "relay-server", value: relayCtrl.text); } - if (key != key0) bind.mainSetOption(key: "key", value: key); - if (api != api0) { - bind.mainSetOption(key: "api-server", value: api); + if (keyCtrl.text != oldCfg.key) { + bind.mainSetOption(key: "key", value: keyCtrl.text); + } + if (apiCtrl.text != oldCfg.apiServer) { + bind.mainSetOption(key: "api-server", value: apiCtrl.text); } close(); } @@ -429,7 +425,7 @@ class _PasswordWidgetState extends State { color: Theme.of(context).primaryColorDark, ), onPressed: () { - // Update the state i.e. toogle the state of passwordVisible variable + // Update the state i.e. toggle the state of passwordVisible variable setState(() { _passwordVisible = !_passwordVisible; });