diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 305155f0e..2152a60c3 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart' hide MenuItem; @@ -9,6 +10,7 @@ import 'package:flutter_hbb/models/model.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:tray_manager/tray_manager.dart'; +import 'package:url_launcher/url_launcher_string.dart'; class DesktopHomePage extends StatefulWidget { DesktopHomePage({Key? key}) : super(key: key); @@ -105,33 +107,78 @@ class _DesktopHomePageState extends State with TrayListener { children: [ Text( translate("ID"), - style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500), + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.w500), ), PopupMenuButton( padding: EdgeInsets.all(4.0), - itemBuilder: (context) => [ - genEnablePopupMenuItem(translate("Enable Keyboard/Mouse"), 'enable-keyboard',), - genEnablePopupMenuItem(translate("Enable Clipboard"), 'enable-clipboard',), - genEnablePopupMenuItem(translate("Enable File Transfer"), 'enable-file-transfer',), - genEnablePopupMenuItem(translate("Enable TCP Tunneling"), 'enable-tunnel',), - genAudioInputPopupMenuItem(), - // TODO: Audio Input - PopupMenuItem(child: Text(translate("ID/Relay Server")), value: 'custom-server',), - PopupMenuItem(child: Text(translate("IP Whitelisting")), value: 'whitelist',), - PopupMenuItem(child: Text(translate("Socks5 Proxy")), value: 'Socks5 Proxy',), - // sep - genEnablePopupMenuItem(translate("Enable Service"), 'stop-service',), - // TODO: direct server - genEnablePopupMenuItem(translate("Always connected via relay"),'allow-always-relay',), - genEnablePopupMenuItem(translate("Start ID/relay service"),'stop-rendezvous-service',), - PopupMenuItem(child: Text(translate("Change ID")), value: 'change-id',), - genEnablePopupMenuItem(translate("Dark Theme"), 'allow-darktheme',), - PopupMenuItem(child: Text(translate("About")), value: 'about',), - ], onSelected: onSelectMenu,) + itemBuilder: (context) => [ + genEnablePopupMenuItem( + translate("Enable Keyboard/Mouse"), + 'enable-keyboard', + ), + genEnablePopupMenuItem( + translate("Enable Clipboard"), + 'enable-clipboard', + ), + genEnablePopupMenuItem( + translate("Enable File Transfer"), + 'enable-file-transfer', + ), + genEnablePopupMenuItem( + translate("Enable TCP Tunneling"), + 'enable-tunnel', + ), + genAudioInputPopupMenuItem(), + // TODO: Audio Input + PopupMenuItem( + child: Text(translate("ID/Relay Server")), + value: 'custom-server', + ), + PopupMenuItem( + child: Text(translate("IP Whitelisting")), + value: 'whitelist', + ), + PopupMenuItem( + child: Text(translate("Socks5 Proxy")), + value: 'socks5-proxy', + ), + // sep + genEnablePopupMenuItem( + translate("Enable Service"), + 'stop-service', + ), + // TODO: direct server + genEnablePopupMenuItem( + translate("Always connected via relay"), + 'allow-always-relay', + ), + genEnablePopupMenuItem( + translate("Start ID/relay service"), + 'stop-rendezvous-service', + ), + PopupMenuItem( + child: Text(translate("Change ID")), + value: 'change-id', + ), + genEnablePopupMenuItem( + translate("Dark Theme"), + 'allow-darktheme', + ), + PopupMenuItem( + child: Text(translate("About")), + value: 'about', + ), + ], + onSelected: onSelectMenu, + ) ], ), TextFormField( controller: model.serverId, + decoration: InputDecoration( + enabled: false, + ), ), ], ), @@ -268,80 +315,111 @@ class _DesktopHomePageState extends State with TrayListener { gFFI.setOption(value, option == "Y" ? "" : "Y"); } else if (value == "change-id") { changeId(); + } else if (value == "custom-server") { + changeServer(); + } else if (value == "whitelist") { + changeWhiteList(); + } else if (value == "socks5-proxy") { + changeSocks5Proxy(); + } else if (value == "about") { + about(); } } PopupMenuItem genEnablePopupMenuItem(String label, String value) { - final isEnable = - label.startsWith('enable-') ? gFFI.getOption(value) != "N" : gFFI.getOption(value) != "Y"; - return PopupMenuItem(child: Row( - children: [ - Offstage(offstage: !isEnable, child: Icon(Icons.check)), - Text(label, style: genTextStyle(isEnable),), - ], - ), value: value,); + final isEnable = label.startsWith('enable-') + ? gFFI.getOption(value) != "N" + : gFFI.getOption(value) != "Y"; + return PopupMenuItem( + child: Row( + children: [ + Offstage(offstage: !isEnable, child: Icon(Icons.check)), + Text( + label, + style: genTextStyle(isEnable), + ), + ], + ), + value: value, + ); } TextStyle genTextStyle(bool isPositive) { - return isPositive ? TextStyle() : TextStyle( - color: Colors.redAccent, - decoration: TextDecoration.lineThrough - ); + return isPositive + ? TextStyle() + : TextStyle( + color: Colors.redAccent, decoration: TextDecoration.lineThrough); } PopupMenuItem genAudioInputPopupMenuItem() { final _enabledInput = gFFI.getOption('enable-audio'); var defaultInput = gFFI.getDefaultAudioInput().obs; var enabled = (_enabledInput != "N").obs; - return PopupMenuItem(child: FutureBuilder>( - future: gFFI.getAudioInputs(), - builder: (context, snapshot) { - if (snapshot.hasData) { - final inputs = snapshot.data!; - if (Platform.isWindows) { - inputs.insert(0, translate("System Sound")); - } - var inputList = inputs.map((e) => PopupMenuItem( - child: Row( - children: [ - Obx(()=> Offstage(offstage: defaultInput.value != e, child: Icon(Icons.check))), - Expanded(child: Tooltip( - message: e, - child: Text("$e",maxLines: 1, overflow: TextOverflow.ellipsis,))), - ], - ), - value: e, - )).toList(); - inputList.insert(0, PopupMenuItem( - child: Row( - children: [ - Obx(()=> Offstage(offstage: enabled.value, child: Icon(Icons.check))), - Expanded(child: Text(translate("Mute"))), - ], - ), - value: "Mute", - )); - return PopupMenuButton( + return PopupMenuItem( + child: FutureBuilder>( + future: gFFI.getAudioInputs(), + builder: (context, snapshot) { + if (snapshot.hasData) { + final inputs = snapshot.data!; + if (Platform.isWindows) { + inputs.insert(0, translate("System Sound")); + } + var inputList = inputs + .map((e) => PopupMenuItem( + child: Row( + children: [ + Obx(() => Offstage( + offstage: defaultInput.value != e, + child: Icon(Icons.check))), + Expanded( + child: Tooltip( + message: e, + child: Text( + "$e", + maxLines: 1, + overflow: TextOverflow.ellipsis, + ))), + ], + ), + value: e, + )) + .toList(); + inputList.insert( + 0, + PopupMenuItem( + child: Row( + children: [ + Obx(() => Offstage( + offstage: enabled.value, child: Icon(Icons.check))), + Expanded(child: Text(translate("Mute"))), + ], + ), + value: "Mute", + )); + return PopupMenuButton( padding: EdgeInsets.zero, child: Container( alignment: Alignment.centerLeft, child: Text(translate("Audio Input"))), itemBuilder: (context) => inputList, onSelected: (dev) { - if (dev == "Mute") { - gFFI.setOption('enable-audio', _enabledInput == 'N' ? '': 'N'); - enabled.value = gFFI.getOption('enable-audio') != 'N'; - } else if (dev != gFFI.getDefaultAudioInput()) { - gFFI.setDefaultAudioInput(dev); - defaultInput.value = dev; - } + if (dev == "Mute") { + gFFI.setOption( + 'enable-audio', _enabledInput == 'N' ? '' : 'N'); + enabled.value = gFFI.getOption('enable-audio') != 'N'; + } else if (dev != gFFI.getDefaultAudioInput()) { + gFFI.setDefaultAudioInput(dev); + defaultInput.value = dev; + } }, - ); - } else { - return Text("..."); - } - }, - ), value: 'audio-input',); + ); + } else { + return Text("..."); + } + }, + ), + value: 'audio-input', + ); } /// change local ID @@ -349,57 +427,579 @@ class _DesktopHomePageState extends State with TrayListener { var newId = ""; var msg = ""; var isInProgress = false; - DialogManager.show( (setState, close) { + DialogManager.show((setState, close) { return CustomAlertDialog( title: Text(translate("Change ID")), content: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(translate("id_change_tip")), - Offstage( - offstage: msg.isEmpty, - child: Text(msg, style: TextStyle(color: Colors.grey),)).marginOnly(bottom: 4.0), - TextField( - onChanged: (s) { - newId = s; - }, - decoration: InputDecoration( - border: OutlineInputBorder() - ), - inputFormatters: [ - LengthLimitingTextInputFormatter(16), - // FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true) - ], - maxLength: 16, + SizedBox( + height: 8.0, ), - SizedBox(height: 4.0,), - Offstage( - offstage: !isInProgress, - child: LinearProgressIndicator()) + Row( + children: [ + Text("ID:").marginOnly(bottom: 16.0), + SizedBox( + width: 24.0, + ), + Expanded( + child: TextField( + onChanged: (s) { + newId = s; + }, + decoration: InputDecoration( + border: OutlineInputBorder(), + errorText: msg.isEmpty ? null : translate(msg)), + inputFormatters: [ + LengthLimitingTextInputFormatter(16), + // FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true) + ], + maxLength: 16, + ), + ), + ], + ), + SizedBox( + height: 4.0, + ), + Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) ], - ), actions: [ - TextButton(onPressed: (){ - close(); - }, child: Text("取消")), - TextButton(onPressed: () async { - setState(() { - msg = ""; - isInProgress = true; - gFFI.bind.mainChangeId(newId: newId); - }); + ), + actions: [ + TextButton( + onPressed: () { + close(); + }, + child: Text(translate("Cancel"))), + TextButton( + onPressed: () async { + setState(() { + msg = ""; + isInProgress = true; + gFFI.bind.mainChangeId(newId: newId); + }); - var status = await gFFI.bind.mainGetAsyncStatus(); - while (status == " "){ - await Future.delayed(Duration(milliseconds: 100)); - status = await gFFI.bind.mainGetAsyncStatus(); - } - setState(() { - isInProgress = false; - msg = translate(status); - }); + var status = await gFFI.bind.mainGetAsyncStatus(); + while (status == " ") { + await Future.delayed(Duration(milliseconds: 100)); + status = await gFFI.bind.mainGetAsyncStatus(); + } + if (status.isEmpty) { + // ok + close(); + return; + } + setState(() { + isInProgress = false; + msg = translate(status); + }); + }, + child: Text(translate("OK"))), + ], + ); + }); + } - }, child: Text("确定")), - ], + void changeServer() async { + Map oldOptions = + jsonDecode(await gFFI.bind.mainGetOptions()); + print("${oldOptions}"); + String idServer = oldOptions['custom-rendezvous-server'] ?? ""; + var idServerMsg = ""; + String relayServer = oldOptions['relay-server'] ?? ""; + var relayServerMsg = ""; + String apiServer = oldOptions['api-server'] ?? ""; + var apiServerMsg = ""; + var key = oldOptions['key'] ?? ""; + + var isInProgress = false; + DialogManager.show((setState, close) { + return CustomAlertDialog( + title: Text(translate("ID/Relay Server")), + content: ConstrainedBox( + constraints: BoxConstraints(minWidth: 500), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 8.0, + ), + Row( + children: [ + ConstrainedBox( + constraints: BoxConstraints(minWidth: 100), + child: Text("${translate('ID Server')}:") + .marginOnly(bottom: 16.0)), + SizedBox( + width: 24.0, + ), + Expanded( + child: TextField( + onChanged: (s) { + idServer = s; + }, + decoration: InputDecoration( + border: OutlineInputBorder(), + errorText: + idServerMsg.isNotEmpty ? idServerMsg : null), + controller: TextEditingController(text: idServer), + ), + ), + ], + ), + SizedBox( + height: 8.0, + ), + Row( + children: [ + ConstrainedBox( + constraints: BoxConstraints(minWidth: 100), + child: Text("${translate('Relay Server')}:") + .marginOnly(bottom: 16.0)), + SizedBox( + width: 24.0, + ), + Expanded( + child: TextField( + onChanged: (s) { + relayServer = s; + }, + decoration: InputDecoration( + border: OutlineInputBorder(), + errorText: relayServerMsg.isNotEmpty + ? relayServerMsg + : null), + controller: TextEditingController(text: relayServer), + ), + ), + ], + ), + SizedBox( + height: 8.0, + ), + Row( + children: [ + ConstrainedBox( + constraints: BoxConstraints(minWidth: 100), + child: Text("${translate('API Server')}:") + .marginOnly(bottom: 16.0)), + SizedBox( + width: 24.0, + ), + Expanded( + child: TextField( + onChanged: (s) { + apiServer = s; + }, + decoration: InputDecoration( + border: OutlineInputBorder(), + errorText: + apiServerMsg.isNotEmpty ? apiServerMsg : null), + controller: TextEditingController(text: apiServer), + ), + ), + ], + ), + SizedBox( + height: 8.0, + ), + Row( + children: [ + ConstrainedBox( + constraints: BoxConstraints(minWidth: 100), + child: Text("${translate('Key')}:") + .marginOnly(bottom: 16.0)), + SizedBox( + width: 24.0, + ), + Expanded( + child: TextField( + onChanged: (s) { + key = s; + }, + decoration: InputDecoration( + border: OutlineInputBorder(), + ), + controller: TextEditingController(text: key), + ), + ), + ], + ), + SizedBox( + height: 4.0, + ), + Offstage( + offstage: !isInProgress, child: LinearProgressIndicator()) + ], + ), + ), + actions: [ + TextButton( + onPressed: () { + close(); + }, + child: Text(translate("Cancel"))), + TextButton( + onPressed: () async { + setState(() { + [idServerMsg, relayServerMsg, apiServerMsg] + .forEach((element) { + element = ""; + }); + isInProgress = true; + }); + final cancel = () { + setState(() { + isInProgress = false; + }); + }; + idServer = idServer.trim(); + relayServer = relayServer.trim(); + apiServer = apiServer.trim(); + key = key.trim(); + + if (idServer.isNotEmpty) { + idServerMsg = translate( + await gFFI.bind.mainTestIfValidServer(server: idServer)); + if (idServerMsg.isEmpty) { + oldOptions['custom-rendezvous-server'] = idServer; + } else { + cancel(); + return; + } + } else { + oldOptions['custom-rendezvous-server'] = ""; + } + + if (relayServer.isNotEmpty) { + relayServerMsg = translate(await gFFI.bind + .mainTestIfValidServer(server: relayServer)); + if (relayServerMsg.isEmpty) { + oldOptions['relay-server'] = relayServer; + } else { + cancel(); + return; + } + } else { + oldOptions['relay-server'] = ""; + } + + if (apiServer.isNotEmpty) { + if (apiServer.startsWith('http://') || + apiServer.startsWith("https://")) { + oldOptions['api-server'] = apiServer; + return; + } else { + apiServerMsg = translate("invalid_http"); + cancel(); + return; + } + } else { + oldOptions['api-server'] = ""; + } + // ok + oldOptions['key'] = key; + await gFFI.bind.mainSetOptions(json: jsonEncode(oldOptions)); + close(); + }, + child: Text(translate("OK"))), + ], + ); + }); + } + + void changeWhiteList() async { + Map oldOptions = + jsonDecode(await gFFI.bind.mainGetOptions()); + var newWhiteList = ((oldOptions['whitelist'] ?? "") as String).split(','); + var newWhiteListField = newWhiteList.join('\n'); + var msg = ""; + var isInProgress = false; + DialogManager.show((setState, close) { + return CustomAlertDialog( + title: Text(translate("IP Whitelisting")), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(translate("whitelist_sep")), + SizedBox( + height: 8.0, + ), + Row( + children: [ + Expanded( + child: TextField( + onChanged: (s) { + newWhiteListField = s; + }, + maxLines: null, + decoration: InputDecoration( + border: OutlineInputBorder(), + errorText: msg.isEmpty ? null : translate(msg), + ), + controller: TextEditingController(text: newWhiteListField), + ), + ), + ], + ), + SizedBox( + height: 4.0, + ), + Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) + ], + ), + actions: [ + TextButton( + onPressed: () { + close(); + }, + child: Text(translate("Cancel"))), + TextButton( + onPressed: () async { + setState(() { + msg = ""; + isInProgress = true; + }); + newWhiteListField = newWhiteListField.trim(); + var newWhiteList = ""; + if (newWhiteListField.isEmpty) { + // pass + } else { + final ips = + newWhiteListField.trim().split(RegExp(r"[\s,;\n]+")); + // test ip + final ipMatch = RegExp(r"^\d+\.\d+\.\d+\.\d+$"); + for (final ip in ips) { + if (!ipMatch.hasMatch(ip)) { + msg = translate("Invalid IP") + " $ip"; + setState(() { + isInProgress = false; + }); + return; + } + } + newWhiteList = ips.join(','); + } + oldOptions['whitelist'] = newWhiteList; + await gFFI.bind.mainSetOptions(json: jsonEncode(oldOptions)); + close(); + }, + child: Text(translate("OK"))), + ], + ); + }); + } + + void changeSocks5Proxy() async { + var socks = await gFFI.bind.mainGetSocks(); + + String proxy = ""; + String proxyMsg = ""; + String username = ""; + String password = ""; + if (socks.length == 3) { + proxy = socks[0]; + username = socks[1]; + password = socks[2]; + } + + var isInProgress = false; + DialogManager.show((setState, close) { + return CustomAlertDialog( + title: Text(translate("Socks5 Proxy")), + content: ConstrainedBox( + constraints: BoxConstraints(minWidth: 500), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 8.0, + ), + Row( + children: [ + ConstrainedBox( + constraints: BoxConstraints(minWidth: 100), + child: Text("${translate('Hostname')}:") + .marginOnly(bottom: 16.0)), + SizedBox( + width: 24.0, + ), + Expanded( + child: TextField( + onChanged: (s) { + proxy = s; + }, + decoration: InputDecoration( + border: OutlineInputBorder(), + errorText: + proxyMsg.isNotEmpty ? proxyMsg : null), + controller: TextEditingController(text: proxy), + ), + ), + ], + ), + SizedBox( + height: 8.0, + ), + Row( + children: [ + ConstrainedBox( + constraints: BoxConstraints(minWidth: 100), + child: Text("${translate('Username')}:") + .marginOnly(bottom: 16.0)), + SizedBox( + width: 24.0, + ), + Expanded( + child: TextField( + onChanged: (s) { + username = s; + }, + decoration: InputDecoration( + border: OutlineInputBorder(), + ), + controller: TextEditingController(text: username), + ), + ), + ], + ), + SizedBox( + height: 8.0, + ), + Row( + children: [ + ConstrainedBox( + constraints: BoxConstraints(minWidth: 100), + child: Text("${translate('Password')}:") + .marginOnly(bottom: 16.0)), + SizedBox( + width: 24.0, + ), + Expanded( + child: TextField( + onChanged: (s) { + password = s; + }, + decoration: InputDecoration( + border: OutlineInputBorder(), + ), + controller: TextEditingController(text: password), + ), + ), + ], + ), + SizedBox( + height: 8.0, + ), + Offstage( + offstage: !isInProgress, child: LinearProgressIndicator()) + ], + ), + ), + actions: [ + TextButton( + onPressed: () { + close(); + }, + child: Text(translate("Cancel"))), + TextButton( + onPressed: () async { + setState(() { + proxyMsg = ""; + isInProgress = true; + }); + final cancel = () { + setState(() { + isInProgress = false; + }); + }; + proxy = proxy.trim(); + username = username.trim(); + password = password.trim(); + + if (proxy.isNotEmpty) { + proxyMsg = translate( + await gFFI.bind.mainTestIfValidServer(server: proxy)); + if (proxyMsg.isEmpty) { + // ignore + } else { + cancel(); + return; + } + } + await gFFI.bind.mainSetSocks(proxy: proxy, username: username, password: password); + close(); + }, + child: Text(translate("OK"))), + ], + ); + }); + } + + void about() async { + final appName = await gFFI.bind.mainGetAppName(); + final license = await gFFI.bind.mainGetLicense(); + final version = await gFFI.bind.mainGetVersion(); + final linkStyle = TextStyle( + decoration: TextDecoration.underline + ); + DialogManager.show((setState, close) { + return CustomAlertDialog( + title: Text("About $appName"), + content: ConstrainedBox( + constraints: BoxConstraints(minWidth: 500), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 8.0, + ), + Text("Version: $version").marginSymmetric(vertical: 4.0), + InkWell( + onTap: () { + launchUrlString("https://rustdesk.com/privacy"); + }, + child: Text("Privacy Statement", style: linkStyle,).marginSymmetric(vertical: 4.0)), + InkWell( + onTap: () { + launchUrlString("https://rustdesk.com"); + } + ,child: Text("Website",style: linkStyle,).marginSymmetric(vertical: 4.0)), + Container( + decoration: BoxDecoration( + color: Color(0xFF2c8cff) + ), + padding: EdgeInsets.symmetric(vertical: 24, horizontal: 8), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Copyright © 2022 Purslane Ltd.\n$license", style: TextStyle( + color: Colors.white + ),), + Text("Made with heart in this chaotic world!", style: TextStyle( + fontWeight: FontWeight.w800, + color: Colors.white + ),) + ], + ), + ), + ], + ), + ).marginSymmetric(vertical: 4.0) + ], + ), + ), + actions: [ + TextButton( + onPressed: () async { + close(); + }, + child: Text(translate("OK"))), + ], ); }); } diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 60d8fd4b5..432ba3969 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -19,7 +19,10 @@ use crate::flutter::connection_manager::{self, get_clients_length, get_clients_s use crate::flutter::{self, Session, SESSIONS}; use crate::start_server; use crate::ui_interface; -use crate::ui_interface::{change_id, get_async_job_status, get_sound_inputs, is_ok_change_id}; +use crate::ui_interface::{ + change_id, get_app_name, get_async_job_status, get_license, get_options, get_socks, + get_sound_inputs, get_version, is_ok_change_id, set_options, set_socks, test_if_valid_server, +}; fn initialize(app_dir: &str) { *config::APP_DIR.write().unwrap() = app_dir.to_owned(); @@ -391,6 +394,41 @@ pub fn main_get_async_status() -> String { get_async_job_status() } +pub fn main_get_options() -> String { + get_options() +} + +pub fn main_set_options(json: String) { + let map: HashMap = serde_json::from_str(&json).unwrap_or(HashMap::new()); + if !map.is_empty() { + set_options(map) + } +} + +pub fn main_test_if_valid_server(server: String) -> String { + test_if_valid_server(server) +} + +pub fn main_set_socks(proxy: String, username: String, password: String) { + set_socks(proxy, username, password) +} + +pub fn main_get_socks() -> Vec { + get_socks() +} + +pub fn main_get_app_name() -> String { + get_app_name() +} + +pub fn main_get_license() -> String { + get_license() +} + +pub fn main_get_version() -> String { + get_version() +} + /// FFI for **get** commands which are idempotent. /// Return result in c string. /// diff --git a/src/ui.rs b/src/ui.rs index 7a6fd0219..713b57122 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,20 +1,23 @@ -mod cm; -#[cfg(feature = "inline")] -mod inline; -#[cfg(target_os = "macos")] -mod macos; -#[cfg(target_os = "windows")] -pub mod win_privacy; -pub mod remote; -use crate::ui_interface::*; -use hbb_common::{allow_err, config::PeerConfig, log}; -use sciter::Value; use std::{ collections::HashMap, iter::FromIterator, sync::{Arc, Mutex}, }; +use sciter::Value; + +use hbb_common::{allow_err, config::PeerConfig, log}; + +use crate::ui_interface::*; + +mod cm; +#[cfg(feature = "inline")] +mod inline; +#[cfg(target_os = "macos")] +mod macos; +pub mod remote; +#[cfg(target_os = "windows")] +pub mod win_privacy; lazy_static::lazy_static! { // stupid workaround for https://sciter.com/forums/topic/crash-on-latest-tis-mac-sdk-sometimes/ static ref STUPID_VALUES: Mutex>>> = Default::default(); @@ -227,7 +230,7 @@ impl UI { } fn get_options(&self) -> Value { - let hashmap = get_options(); + let hashmap: HashMap = serde_json::from_str(&get_options()).unwrap(); let mut m = Value::map(); for (k, v) in hashmap { m.set_item(k, v); diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 90e39636d..7eaf938d1 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -1,5 +1,10 @@ -use crate::common::SOFTWARE_UPDATE_URL; -use crate::ipc; +use std::{ + collections::HashMap, + process::Child, + sync::{Arc, Mutex}, + time::SystemTime, +}; + use hbb_common::{ allow_err, config::{self, Config, LocalConfig, PeerConfig, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT}, @@ -11,12 +16,9 @@ use hbb_common::{ tcp::FramedStream, tokio::{self, sync::mpsc, time}, }; -use std::{ - collections::HashMap, - process::Child, - sync::{Arc, Mutex}, - time::SystemTime, -}; + +use crate::common::SOFTWARE_UPDATE_URL; +use crate::ipc; type Message = RendezvousMessage; @@ -72,7 +74,9 @@ pub fn goto_install() { pub fn install_me(_options: String, _path: String, silent: bool, debug: bool) { #[cfg(windows)] std::thread::spawn(move || { - allow_err!(crate::platform::windows::install_me(&_options, _path, silent, debug)); + allow_err!(crate::platform::windows::install_me( + &_options, _path, silent, debug + )); std::process::exit(0); }); } @@ -185,14 +189,13 @@ pub fn using_public_server() -> bool { crate::get_custom_rendezvous_server(get_option_("custom-rendezvous-server")).is_empty() } -pub fn get_options() -> HashMap { - // TODO Vec<(String,String)> +pub fn get_options() -> String { let options = OPTIONS.lock().unwrap(); - let mut m = HashMap::new(); + let mut m = serde_json::Map::new(); for (k, v) in options.iter() { - m.insert(k.into(), v.into()); + m.insert(k.into(), v.to_owned().into()); } - m + serde_json::to_string(&m).unwrap() } pub fn test_if_valid_server(host: String) -> String {