Merge pull request #4901 from 21pages/urilink

flutter: file transfer/port forward/rdp command line support
This commit is contained in:
RustDesk 2023-07-07 18:12:32 +08:00 committed by GitHub
commit daa59af5be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 200 additions and 115 deletions

View File

@ -1547,7 +1547,7 @@ Future<bool> initUniLinks() async {
if (initialLink == null) {
return false;
}
return parseRustdeskUri(initialLink);
return handleUriLink(uriString: initialLink);
} catch (err) {
debugPrintStack(label: "$err");
return false;
@ -1568,7 +1568,7 @@ StreamSubscription? listenUniLinks({handleByFlutter = true}) {
debugPrint("A uri was received: $uri.");
if (uri != null) {
if (handleByFlutter) {
callUniLinksUriHandler(uri);
handleUriLink(uri: uri);
} else {
bind.sendUrlScheme(url: uri.toString());
}
@ -1581,90 +1581,142 @@ StreamSubscription? listenUniLinks({handleByFlutter = true}) {
return sub;
}
/// Handle command line arguments
///
/// * Returns true if we successfully handle the startup arguments.
bool checkArguments() {
if (kBootArgs.isNotEmpty) {
final ret = parseRustdeskUri(kBootArgs.first);
if (ret) {
return true;
enum UriLinkType {
remoteDesktop,
fileTransfer,
portForward,
rdp,
}
// uri link handler
bool handleUriLink({List<String>? cmdArgs, Uri? uri, String? uriString}) {
List<String>? args;
if (cmdArgs != null) {
args = cmdArgs;
if (args.isNotEmpty && args[0].startsWith(kUniLinksPrefix)) {
final uri = Uri.tryParse(args[0]);
if (uri != null) {
args = urlLinkToCmdArgs(uri);
}
}
} else if (uri != null) {
args = urlLinkToCmdArgs(uri);
} else if (uriString != null) {
final uri = Uri.tryParse(uriString);
if (uri != null) {
args = urlLinkToCmdArgs(uri);
}
}
// bootArgs:[--connect, 362587269, --switch_uuid, e3d531cc-5dce-41e0-bd06-5d4a2b1eec05]
// check connect args
var connectIndex = kBootArgs.indexOf("--connect");
if (connectIndex == -1) {
return false;
}
String? id =
kBootArgs.length <= connectIndex + 1 ? null : kBootArgs[connectIndex + 1];
String? password =
kBootArgs.length <= connectIndex + 2 ? null : kBootArgs[connectIndex + 2];
if (password != null && password.startsWith("--")) {
password = null;
}
final switchUuidIndex = kBootArgs.indexOf("--switch_uuid");
String? switchUuid = kBootArgs.length <= switchUuidIndex + 1
? null
: kBootArgs[switchUuidIndex + 1];
if (id != null) {
if (id.startsWith(kUniLinksPrefix)) {
return parseRustdeskUri(id);
} else {
// remove "--connect xxx" in the `bootArgs` array
kBootArgs.removeAt(connectIndex);
kBootArgs.removeAt(connectIndex);
// fallback to peer id
Future.delayed(Duration.zero, () {
rustDeskWinManager.newRemoteDesktop(id,
password: password, switch_uuid: switchUuid);
});
return true;
if (args == null) return false;
UriLinkType? type;
String? id;
String? password;
String? switchUuid;
bool? forceRelay;
for (int i = 0; i < args.length; i++) {
switch (args[i]) {
case '--connect':
case '--play':
type = UriLinkType.remoteDesktop;
id = args[i + 1];
i++;
break;
case '--file-transfer':
type = UriLinkType.fileTransfer;
id = args[i + 1];
i++;
break;
case '--port-forward':
type = UriLinkType.portForward;
id = args[i + 1];
i++;
break;
case '--rdp':
type = UriLinkType.rdp;
id = args[i + 1];
i++;
break;
case '--password':
password = args[i + 1];
i++;
break;
case '--switch_uuid':
switchUuid = args[i + 1];
i++;
break;
case '--relay':
forceRelay = true;
break;
default:
break;
}
}
if (type != null && id != null) {
switch (type) {
case UriLinkType.remoteDesktop:
Future.delayed(Duration.zero, () {
rustDeskWinManager.newRemoteDesktop(id!,
password: password,
switch_uuid: switchUuid,
forceRelay: forceRelay);
});
break;
case UriLinkType.fileTransfer:
Future.delayed(Duration.zero, () {
rustDeskWinManager.newFileTransfer(id!,
password: password, forceRelay: forceRelay);
});
break;
case UriLinkType.portForward:
Future.delayed(Duration.zero, () {
rustDeskWinManager.newPortForward(id!, false,
password: password, forceRelay: forceRelay);
});
break;
case UriLinkType.rdp:
Future.delayed(Duration.zero, () {
rustDeskWinManager.newPortForward(id!, true,
password: password, forceRelay: forceRelay);
});
break;
}
return true;
}
return false;
}
/// Parse `rustdesk://` unilinks
///
/// Returns true if we successfully handle the uri provided.
/// [Functions]
/// 1. New Connection: rustdesk://connection/new/your_peer_id
bool parseRustdeskUri(String uriPath) {
final uri = Uri.tryParse(uriPath);
if (uri == null) {
debugPrint("uri is not valid: $uriPath");
return false;
}
return callUniLinksUriHandler(uri);
}
/// uri handler
///
/// Returns true if we successfully handle the uri provided.
bool callUniLinksUriHandler(Uri uri) {
debugPrint("uni links called: $uri");
// new connection
String peerId;
List<String>? urlLinkToCmdArgs(Uri uri) {
String? command;
String? id;
if (uri.authority == "connection" && uri.path.startsWith("/new/")) {
peerId = uri.path.substring("/new/".length);
} else if (uri.authority == "connect") {
peerId = uri.path.substring(1);
} else if (uri.authority.length > 2 && uri.path.length <= 1) {
// "/" or ""
peerId = uri.authority;
} else {
return false;
// For compatibility
command = '--connect';
id = uri.path.substring("/new/".length);
} else if (['connect', "play", 'file-transfer', 'port-forward', 'rdp']
.contains(uri.authority)) {
command = '--${uri.authority}';
if (uri.path.length > 1) {
id = uri.path.substring(1);
}
}
var param = uri.queryParameters;
String? switch_uuid = param["switch_uuid"];
String? password = param["password"];
Future.delayed(Duration.zero, () {
rustDeskWinManager.newRemoteDesktop(peerId,
password: password, switch_uuid: switch_uuid);
});
return true;
List<String> args = List.empty(growable: true);
if (command != null && id != null) {
args.add(command);
args.add(id);
var param = uri.queryParameters;
String? password = param["password"];
if (password != null) args.addAll(['--password', password]);
String? switch_uuid = param["switch_uuid"];
if (switch_uuid != null) args.addAll(['--switch_uuid', switch_uuid]);
if (param["relay"] != null) args.add("--relay");
return args;
}
return null;
}
connectMainDesktop(String id,

View File

@ -52,10 +52,12 @@ class FileManagerPage extends StatefulWidget {
const FileManagerPage(
{Key? key,
required this.id,
required this.password,
required this.tabController,
this.forceRelay})
: super(key: key);
final String id;
final String? password;
final bool? forceRelay;
final DesktopTabController tabController;
@ -79,7 +81,10 @@ class _FileManagerPageState extends State<FileManagerPage>
void initState() {
super.initState();
_ffi = FFI();
_ffi.start(widget.id, isFileTransfer: true, forceRelay: widget.forceRelay);
_ffi.start(widget.id,
isFileTransfer: true,
password: widget.password,
forceRelay: widget.forceRelay);
WidgetsBinding.instance.addPostFrameCallback((_) {
_ffi.dialogManager
.showLoading(translate('Connecting...'), onCancel: closeConnection);

View File

@ -44,6 +44,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
page: FileManagerPage(
key: ValueKey(params['id']),
id: params['id'],
password: params['password'],
tabController: tabController,
forceRelay: params['forceRelay'],
)));
@ -72,6 +73,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
page: FileManagerPage(
key: ValueKey(id),
id: id,
password: args['password'],
tabController: tabController,
forceRelay: args['forceRelay'],
)));

View File

@ -28,11 +28,13 @@ class PortForwardPage extends StatefulWidget {
const PortForwardPage(
{Key? key,
required this.id,
required this.password,
required this.tabController,
required this.isRDP,
this.forceRelay})
: super(key: key);
final String id;
final String password;
final DesktopTabController tabController;
final bool isRDP;
final bool? forceRelay;
@ -55,6 +57,7 @@ class _PortForwardPageState extends State<PortForwardPage>
_ffi = FFI();
_ffi.start(widget.id,
isPortForward: true,
password: widget.password,
forceRelay: widget.forceRelay,
isRdp: widget.isRDP);
Get.put(_ffi, tag: 'pf_${widget.id}');

View File

@ -43,6 +43,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
page: PortForwardPage(
key: ValueKey(params['id']),
id: params['id'],
password: params['password'],
tabController: tabController,
isRDP: isRDP,
forceRelay: params['forceRelay'],
@ -77,6 +78,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
page: PortForwardPage(
key: ValueKey(args['id']),
id: id,
password: args['password'],
isRDP: isRDP,
tabController: tabController,
forceRelay: args['forceRelay'],

View File

@ -134,7 +134,7 @@ void runMainApp(bool startService) async {
// Check the startup argument, if we successfully handle the argument, we keep the main window hidden.
final handledByUniLinks = await initUniLinks();
debugPrint("handled by uni links: $handledByUniLinks");
if (handledByUniLinks || checkArguments()) {
if (handledByUniLinks || handleUriLink(cmdArgs: kBootArgs)) {
windowManager.hide();
} else {
windowManager.show();

View File

@ -248,7 +248,7 @@ class FfiModel with ChangeNotifier {
onUrlSchemeReceived(Map<String, dynamic> evt) {
final url = evt['url'].toString().trim();
if (url.startsWith(kUniLinksPrefix) && parseRustdeskUri(url)) {
if (url.startsWith(kUniLinksPrefix) && handleUriLink(uriString: url)) {
return;
}
switch (url) {

View File

@ -84,10 +84,12 @@ class RustDeskMultiWindowManager {
}
}
Future<dynamic> newFileTransfer(String remoteId, {bool? forceRelay}) async {
Future<dynamic> newFileTransfer(String remoteId,
{String? password, bool? forceRelay}) async {
var msg = jsonEncode({
"type": WindowType.FileTransfer.index,
"id": remoteId,
"password": password,
"forceRelay": forceRelay,
});
@ -117,11 +119,12 @@ class RustDeskMultiWindowManager {
}
Future<dynamic> newPortForward(String remoteId, bool isRDP,
{bool? forceRelay}) async {
{String? password, bool? forceRelay}) async {
final msg = jsonEncode({
"type": WindowType.PortForward.index,
"id": remoteId,
"isRDP": isRDP,
"password": password,
"forceRelay": forceRelay,
});

View File

@ -19,15 +19,23 @@ pub fn core_main() -> Option<Vec<String>> {
let mut _is_elevate = false;
let mut _is_run_as_system = false;
let mut _is_quick_support = false;
let mut _is_flutter_connect = false;
let mut _is_flutter_invoke_new_connection = false;
let mut arg_exe = Default::default();
for arg in std::env::args() {
if i == 0 {
arg_exe = arg;
} else if i > 0 {
#[cfg(feature = "flutter")]
if arg == "--connect" || arg == "--play" {
_is_flutter_connect = true;
if [
"--connect",
"--play",
"--file-transfer",
"--port-forward",
"--rdp",
]
.contains(&arg.as_str())
{
_is_flutter_invoke_new_connection = true;
}
if arg == "--elevate" {
_is_elevate = true;
@ -63,7 +71,7 @@ pub fn core_main() -> Option<Vec<String>> {
}
}
#[cfg(feature = "flutter")]
if _is_flutter_connect {
if _is_flutter_invoke_new_connection {
return core_main_invoke_new_connection(std::env::args());
}
let click_setup = cfg!(windows) && args.is_empty() && crate::common::is_setup(&arg_exe);
@ -318,38 +326,48 @@ fn import_config(path: &str) {
/// If it returns [`Some`], then the process will continue, and flutter gui will be started.
#[cfg(feature = "flutter")]
fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<String>> {
args.position(|element| {
return element == "--connect" || element == "--play";
})?;
let mut peer_id = args.next().unwrap_or("".to_string());
if peer_id.is_empty() {
eprintln!("please provide a valid peer id");
return None;
}
let app_name = crate::get_app_name();
let ext = format!(".{}", app_name.to_lowercase());
if peer_id.ends_with(&ext) {
peer_id = peer_id.replace(&ext, "");
}
let mut switch_uuid = None;
while let Some(item) = args.next() {
if item == "--switch_uuid" {
switch_uuid = args.next();
let mut authority = None;
let mut id = None;
let mut param_array = vec![];
while let Some(arg) = args.next() {
match arg.as_str() {
"--connect" | "--play" | "--file-transfer" | "--port-forward" | "--rdp" => {
authority = Some((&arg.to_string()[2..]).to_owned());
id = args.next();
}
"--password" => {
if let Some(password) = args.next() {
param_array.push(format!("password={password}"));
}
}
"--relay" => {
param_array.push(format!("relay=true"));
}
// inner
"--switch_uuid" => {
if let Some(switch_uuid) = args.next() {
param_array.push(format!("switch_uuid={switch_uuid}"));
}
}
_ => {}
}
}
let mut param_array = vec![];
if switch_uuid.is_some() {
let switch_uuid = switch_uuid.map_or("".to_string(), |p| format!("switch_uuid={}", p));
param_array.push(switch_uuid);
let mut uni_links = Default::default();
if let Some(authority) = authority {
if let Some(mut id) = id {
let app_name = crate::get_app_name();
let ext = format!(".{}", app_name.to_lowercase());
if id.ends_with(&ext) {
id = id.replace(&ext, "");
}
let params = param_array.join("&");
let params_flag = if params.is_empty() { "" } else { "?" };
uni_links = format!("rustdesk://{}/{}{}{}", authority, id, params_flag, params);
}
}
if uni_links.is_empty() {
return None;
}
let params = param_array.join("&");
let params_flag = if params.is_empty() { "" } else { "?" };
#[allow(unused)]
let uni_links = format!(
"rustdesk://connection/new/{}{}{}",
peer_id, params_flag, params
);
#[cfg(target_os = "linux")]
return try_send_by_dbus(uni_links);