feat: ip whitelist, id/relay server/ socks5 proxy, about page

This commit is contained in:
Kingtous 2022-07-18 18:20:00 +08:00
parent b1382c2d57
commit 08043732a8
4 changed files with 769 additions and 132 deletions

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart' hide MenuItem; 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:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tray_manager/tray_manager.dart'; import 'package:tray_manager/tray_manager.dart';
import 'package:url_launcher/url_launcher_string.dart';
class DesktopHomePage extends StatefulWidget { class DesktopHomePage extends StatefulWidget {
DesktopHomePage({Key? key}) : super(key: key); DesktopHomePage({Key? key}) : super(key: key);
@ -105,33 +107,78 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
children: [ children: [
Text( Text(
translate("ID"), translate("ID"),
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500), style: TextStyle(
fontSize: 18, fontWeight: FontWeight.w500),
), ),
PopupMenuButton( PopupMenuButton(
padding: EdgeInsets.all(4.0), padding: EdgeInsets.all(4.0),
itemBuilder: (context) => [ itemBuilder: (context) => [
genEnablePopupMenuItem(translate("Enable Keyboard/Mouse"), 'enable-keyboard',), genEnablePopupMenuItem(
genEnablePopupMenuItem(translate("Enable Clipboard"), 'enable-clipboard',), translate("Enable Keyboard/Mouse"),
genEnablePopupMenuItem(translate("Enable File Transfer"), 'enable-file-transfer',), 'enable-keyboard',
genEnablePopupMenuItem(translate("Enable TCP Tunneling"), 'enable-tunnel',), ),
genAudioInputPopupMenuItem(), genEnablePopupMenuItem(
// TODO: Audio Input translate("Enable Clipboard"),
PopupMenuItem(child: Text(translate("ID/Relay Server")), value: 'custom-server',), 'enable-clipboard',
PopupMenuItem(child: Text(translate("IP Whitelisting")), value: 'whitelist',), ),
PopupMenuItem(child: Text(translate("Socks5 Proxy")), value: 'Socks5 Proxy',), genEnablePopupMenuItem(
// sep translate("Enable File Transfer"),
genEnablePopupMenuItem(translate("Enable Service"), 'stop-service',), 'enable-file-transfer',
// TODO: direct server ),
genEnablePopupMenuItem(translate("Always connected via relay"),'allow-always-relay',), genEnablePopupMenuItem(
genEnablePopupMenuItem(translate("Start ID/relay service"),'stop-rendezvous-service',), translate("Enable TCP Tunneling"),
PopupMenuItem(child: Text(translate("Change ID")), value: 'change-id',), 'enable-tunnel',
genEnablePopupMenuItem(translate("Dark Theme"), 'allow-darktheme',), ),
PopupMenuItem(child: Text(translate("About")), value: 'about',), genAudioInputPopupMenuItem(),
], onSelected: onSelectMenu,) // 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( TextFormField(
controller: model.serverId, controller: model.serverId,
decoration: InputDecoration(
enabled: false,
),
), ),
], ],
), ),
@ -268,80 +315,111 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
gFFI.setOption(value, option == "Y" ? "" : "Y"); gFFI.setOption(value, option == "Y" ? "" : "Y");
} else if (value == "change-id") { } else if (value == "change-id") {
changeId(); changeId();
} else if (value == "custom-server") {
changeServer();
} else if (value == "whitelist") {
changeWhiteList();
} else if (value == "socks5-proxy") {
changeSocks5Proxy();
} else if (value == "about") {
about();
} }
} }
PopupMenuItem<String> genEnablePopupMenuItem(String label, String value) { PopupMenuItem<String> genEnablePopupMenuItem(String label, String value) {
final isEnable = final isEnable = label.startsWith('enable-')
label.startsWith('enable-') ? gFFI.getOption(value) != "N" : gFFI.getOption(value) != "Y"; ? gFFI.getOption(value) != "N"
return PopupMenuItem(child: Row( : gFFI.getOption(value) != "Y";
children: [ return PopupMenuItem(
Offstage(offstage: !isEnable, child: Icon(Icons.check)), child: Row(
Text(label, style: genTextStyle(isEnable),), children: [
], Offstage(offstage: !isEnable, child: Icon(Icons.check)),
), value: value,); Text(
label,
style: genTextStyle(isEnable),
),
],
),
value: value,
);
} }
TextStyle genTextStyle(bool isPositive) { TextStyle genTextStyle(bool isPositive) {
return isPositive ? TextStyle() : TextStyle( return isPositive
color: Colors.redAccent, ? TextStyle()
decoration: TextDecoration.lineThrough : TextStyle(
); color: Colors.redAccent, decoration: TextDecoration.lineThrough);
} }
PopupMenuItem<String> genAudioInputPopupMenuItem() { PopupMenuItem<String> genAudioInputPopupMenuItem() {
final _enabledInput = gFFI.getOption('enable-audio'); final _enabledInput = gFFI.getOption('enable-audio');
var defaultInput = gFFI.getDefaultAudioInput().obs; var defaultInput = gFFI.getDefaultAudioInput().obs;
var enabled = (_enabledInput != "N").obs; var enabled = (_enabledInput != "N").obs;
return PopupMenuItem(child: FutureBuilder<List<String>>( return PopupMenuItem(
future: gFFI.getAudioInputs(), child: FutureBuilder<List<String>>(
builder: (context, snapshot) { future: gFFI.getAudioInputs(),
if (snapshot.hasData) { builder: (context, snapshot) {
final inputs = snapshot.data!; if (snapshot.hasData) {
if (Platform.isWindows) { final inputs = snapshot.data!;
inputs.insert(0, translate("System Sound")); if (Platform.isWindows) {
} inputs.insert(0, translate("System Sound"));
var inputList = inputs.map((e) => PopupMenuItem( }
child: Row( var inputList = inputs
children: [ .map((e) => PopupMenuItem(
Obx(()=> Offstage(offstage: defaultInput.value != e, child: Icon(Icons.check))), child: Row(
Expanded(child: Tooltip( children: [
message: e, Obx(() => Offstage(
child: Text("$e",maxLines: 1, overflow: TextOverflow.ellipsis,))), offstage: defaultInput.value != e,
], child: Icon(Icons.check))),
), Expanded(
value: e, child: Tooltip(
)).toList(); message: e,
inputList.insert(0, PopupMenuItem( child: Text(
child: Row( "$e",
children: [ maxLines: 1,
Obx(()=> Offstage(offstage: enabled.value, child: Icon(Icons.check))), overflow: TextOverflow.ellipsis,
Expanded(child: Text(translate("Mute"))), ))),
], ],
), ),
value: "Mute", value: e,
)); ))
return PopupMenuButton<String>( .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<String>(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
child: Container( child: Container(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(translate("Audio Input"))), child: Text(translate("Audio Input"))),
itemBuilder: (context) => inputList, itemBuilder: (context) => inputList,
onSelected: (dev) { onSelected: (dev) {
if (dev == "Mute") { if (dev == "Mute") {
gFFI.setOption('enable-audio', _enabledInput == 'N' ? '': 'N'); gFFI.setOption(
enabled.value = gFFI.getOption('enable-audio') != 'N'; 'enable-audio', _enabledInput == 'N' ? '' : 'N');
} else if (dev != gFFI.getDefaultAudioInput()) { enabled.value = gFFI.getOption('enable-audio') != 'N';
gFFI.setDefaultAudioInput(dev); } else if (dev != gFFI.getDefaultAudioInput()) {
defaultInput.value = dev; gFFI.setDefaultAudioInput(dev);
} defaultInput.value = dev;
}
}, },
); );
} else { } else {
return Text("..."); return Text("...");
} }
}, },
), value: 'audio-input',); ),
value: 'audio-input',
);
} }
/// change local ID /// change local ID
@ -349,27 +427,30 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
var newId = ""; var newId = "";
var msg = ""; var msg = "";
var isInProgress = false; var isInProgress = false;
DialogManager.show( (setState, close) { DialogManager.show((setState, close) {
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate("Change ID")), title: Text(translate("Change ID")),
content: Column( content: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(translate("id_change_tip")), Text(translate("id_change_tip")),
SizedBox(height: 8.0,), SizedBox(
height: 8.0,
),
Row( Row(
children: [ children: [
Text("ID:").marginOnly(bottom: 16.0), Text("ID:").marginOnly(bottom: 16.0),
SizedBox(width: 24.0,), SizedBox(
width: 24.0,
),
Expanded( Expanded(
child: TextField( child: TextField(
onChanged: (s) { onChanged: (s) {
newId = s; newId = s;
}, },
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg) errorText: msg.isEmpty ? null : translate(msg)),
),
inputFormatters: [ inputFormatters: [
LengthLimitingTextInputFormatter(16), LengthLimitingTextInputFormatter(16),
// FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true) // FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true)
@ -379,34 +460,546 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
), ),
], ],
), ),
SizedBox(height: 4.0,), SizedBox(
Offstage( height: 4.0,
offstage: !isInProgress, ),
child: LinearProgressIndicator()) Offstage(offstage: !isInProgress, child: LinearProgressIndicator())
], ],
), actions: [ ),
TextButton(onPressed: (){ actions: [
close(); TextButton(
}, child: Text("取消")), onPressed: () {
TextButton(onPressed: () async { close();
setState(() { },
msg = ""; child: Text(translate("Cancel"))),
isInProgress = true; TextButton(
gFFI.bind.mainChangeId(newId: newId); onPressed: () async {
}); setState(() {
msg = "";
isInProgress = true;
gFFI.bind.mainChangeId(newId: newId);
});
var status = await gFFI.bind.mainGetAsyncStatus(); var status = await gFFI.bind.mainGetAsyncStatus();
while (status == " "){ while (status == " ") {
await Future.delayed(Duration(milliseconds: 100)); await Future.delayed(Duration(milliseconds: 100));
status = await gFFI.bind.mainGetAsyncStatus(); status = await gFFI.bind.mainGetAsyncStatus();
} }
setState(() { if (status.isEmpty) {
isInProgress = false; // ok
msg = translate(status); close();
}); return;
}
setState(() {
isInProgress = false;
msg = translate(status);
});
},
child: Text(translate("OK"))),
],
);
});
}
}, child: Text("确定")), void changeServer() async {
], Map<String, dynamic> 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<String, dynamic> 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 &copy; 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"))),
],
); );
}); });
} }

View File

@ -19,7 +19,10 @@ use crate::flutter::connection_manager::{self, get_clients_length, get_clients_s
use crate::flutter::{self, Session, SESSIONS}; use crate::flutter::{self, Session, SESSIONS};
use crate::start_server; use crate::start_server;
use crate::ui_interface; 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) { fn initialize(app_dir: &str) {
*config::APP_DIR.write().unwrap() = app_dir.to_owned(); *config::APP_DIR.write().unwrap() = app_dir.to_owned();
@ -391,6 +394,41 @@ pub fn main_get_async_status() -> String {
get_async_job_status() get_async_job_status()
} }
pub fn main_get_options() -> String {
get_options()
}
pub fn main_set_options(json: String) {
let map: HashMap<String, String> = 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<String> {
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. /// FFI for **get** commands which are idempotent.
/// Return result in c string. /// Return result in c string.
/// ///

View File

@ -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::{ use std::{
collections::HashMap, collections::HashMap,
iter::FromIterator, iter::FromIterator,
sync::{Arc, Mutex}, 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! { lazy_static::lazy_static! {
// stupid workaround for https://sciter.com/forums/topic/crash-on-latest-tis-mac-sdk-sometimes/ // stupid workaround for https://sciter.com/forums/topic/crash-on-latest-tis-mac-sdk-sometimes/
static ref STUPID_VALUES: Mutex<Vec<Arc<Vec<Value>>>> = Default::default(); static ref STUPID_VALUES: Mutex<Vec<Arc<Vec<Value>>>> = Default::default();
@ -227,7 +230,7 @@ impl UI {
} }
fn get_options(&self) -> Value { fn get_options(&self) -> Value {
let hashmap = get_options(); let hashmap: HashMap<String, String> = serde_json::from_str(&get_options()).unwrap();
let mut m = Value::map(); let mut m = Value::map();
for (k, v) in hashmap { for (k, v) in hashmap {
m.set_item(k, v); m.set_item(k, v);

View File

@ -1,5 +1,10 @@
use crate::common::SOFTWARE_UPDATE_URL; use std::{
use crate::ipc; collections::HashMap,
process::Child,
sync::{Arc, Mutex},
time::SystemTime,
};
use hbb_common::{ use hbb_common::{
allow_err, allow_err,
config::{self, Config, LocalConfig, PeerConfig, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT}, config::{self, Config, LocalConfig, PeerConfig, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
@ -11,12 +16,9 @@ use hbb_common::{
tcp::FramedStream, tcp::FramedStream,
tokio::{self, sync::mpsc, time}, tokio::{self, sync::mpsc, time},
}; };
use std::{
collections::HashMap, use crate::common::SOFTWARE_UPDATE_URL;
process::Child, use crate::ipc;
sync::{Arc, Mutex},
time::SystemTime,
};
type Message = RendezvousMessage; type Message = RendezvousMessage;
@ -72,7 +74,9 @@ pub fn goto_install() {
pub fn install_me(_options: String, _path: String, silent: bool, debug: bool) { pub fn install_me(_options: String, _path: String, silent: bool, debug: bool) {
#[cfg(windows)] #[cfg(windows)]
std::thread::spawn(move || { 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); 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() crate::get_custom_rendezvous_server(get_option_("custom-rendezvous-server")).is_empty()
} }
pub fn get_options() -> HashMap<String, String> { pub fn get_options() -> String {
// TODO Vec<(String,String)>
let options = OPTIONS.lock().unwrap(); let options = OPTIONS.lock().unwrap();
let mut m = HashMap::new(); let mut m = serde_json::Map::new();
for (k, v) in options.iter() { 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 { pub fn test_if_valid_server(host: String) -> String {