From 3d7736836f372f77063468a2873798b5ab13e223 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 11 Oct 2022 19:52:03 +0800 Subject: [PATCH] feat: add dbus and cli connect support --- Cargo.lock | 11 +++ Cargo.toml | 2 + build.py | 4 + flutter/lib/common.dart | 14 +++ .../lib/desktop/pages/desktop_home_page.dart | 3 + flutter/lib/main.dart | 2 + flutter/lib/models/model.dart | 4 + flutter/lib/models/native_model.dart | 4 + flutter/pubspec.lock | 14 +-- res/PKGBUILD | 1 + res/pacman_install | 3 + res/rpm-suse.spec | 4 + res/rpm.spec | 4 + res/rustdesk-link.desktop | 11 +++ src/core_main.rs | 42 ++++++++- src/flutter_ffi.rs | 11 +++ src/server.rs | 2 + src/server/dbus.rs | 92 +++++++++++++++++++ 18 files changed, 219 insertions(+), 9 deletions(-) create mode 100644 res/rustdesk-link.desktop create mode 100644 src/server/dbus.rs diff --git a/Cargo.lock b/Cargo.lock index 48bfd0e20..159eee6e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1229,6 +1229,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "dbus-crossroads" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "554114296d012b33fdaf362a733db6dc5f73c4c9348b8b620ddd42e61b406e30" +dependencies = [ + "dbus", +] + [[package]] name = "dconf_rs" version = "0.3.0" @@ -4361,6 +4370,8 @@ dependencies = [ "ctrlc", "dark-light", "dasp", + "dbus", + "dbus-crossroads", "default-net", "dispatch", "enigo", diff --git a/Cargo.toml b/Cargo.toml index f04b36b5c..8cf338647 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,6 +110,8 @@ rust-pulsectl = { git = "https://github.com/open-trade/pulsectl" } async-process = "1.3" mouce = { git="https://github.com/fufesou/mouce.git" } evdev = { git="https://github.com/fufesou/evdev" } +dbus = "0.9" +dbus-crossroads = "0.5" [target.'cfg(target_os = "android")'.dependencies] diff --git a/build.py b/build.py index 85dab7090..3e9fe813d 100755 --- a/build.py +++ b/build.py @@ -165,6 +165,8 @@ def build_flutter_deb(version): 'cp ../res/128x128@2x.png tmpdeb/usr/share/rustdesk/files/rustdesk.png') os.system( 'cp ../res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop') + os.system( + 'cp ../res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop') os.system( 'cp ../res/com.rustdesk.RustDesk.policy tmpdeb/usr/share/polkit-1/actions/') os.system("echo \"#!/bin/sh\" >> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit") @@ -333,6 +335,8 @@ def main(): 'cp res/128x128@2x.png tmpdeb/usr/share/rustdesk/files/rustdesk.png') os.system( 'cp res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop') + os.system( + 'cp res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop') os.system('cp -a res/DEBIAN/* tmpdeb/DEBIAN/') os.system('strip tmpdeb/usr/bin/rustdesk') os.system('mkdir -p tmpdeb/usr/lib/rustdesk') diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 7be5807a6..6f41aa27a 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -10,6 +10,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; +import 'package:flutter_hbb/main.dart'; import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:get/get.dart'; @@ -1128,6 +1129,19 @@ Future restoreWindowPosition(WindowType type, {int? windowId}) async { return false; } +void checkArguments() { + // check connect args + final connectIndex = bootArgs.indexOf("--connect"); + if (connectIndex == -1) { + return; + } + String? peerId = bootArgs.length < connectIndex + 1 ? null: bootArgs[connectIndex + 1]; + if (peerId != null) { + rustDeskWinManager.newRemoteDesktop(peerId); + bootArgs.removeAt(connectIndex); bootArgs.removeAt(connectIndex); + } +} + /// Connect to a peer with [id]. /// If [isFileTransfer], starts a session only for file transfer. /// If [isTcpTunneling], starts a session only for tcp tunneling. diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 017bf5436..4bdbbbaac 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -452,6 +452,9 @@ class _DesktopHomePageState extends State } } }); + Future.delayed(Duration.zero, () { + checkArguments(); + }); } @override diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 27b14be09..549a07517 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -24,10 +24,12 @@ import 'mobile/pages/server_page.dart'; import 'models/platform_model.dart'; int? windowId; +late List bootArgs; Future main(List args) async { WidgetsFlutterBinding.ensureInitialized(); debugPrint("launch args: $args"); + bootArgs = args; if (!isDesktop) { runMobileApp(); diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 6c800147e..b57d1fbc1 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -14,6 +14,7 @@ import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/file_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/models/user_model.dart'; +import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:tuple/tuple.dart'; import 'package:flutter_custom_cursor/flutter_custom_cursor.dart'; @@ -176,6 +177,9 @@ class FfiModel with ChangeNotifier { updateBlockInputState(evt, peerId); } else if (name == 'update_privacy_mode') { updatePrivacyMode(evt, peerId); + } else if (name == 'new_connection') { + final remoteId = evt['peer_id']; + rustDeskWinManager.newRemoteDesktop(remoteId); } }; } diff --git a/flutter/lib/models/native_model.dart b/flutter/lib/models/native_model.dart index 54895f947..5c724f59b 100644 --- a/flutter/lib/models/native_model.dart +++ b/flutter/lib/models/native_model.dart @@ -113,6 +113,10 @@ class PlatformFFI { debugPrint('Failed to get documents directory: $e'); } _ffiBind = RustdeskImpl(dylib); + if (Platform.isLinux) { + // start dbus service, no need to await + await _ffiBind.mainStartDbusServer(); + } _startListenEvent(_ffiBind); // global event try { if (isAndroid) { diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index b3308bb9d..b7971eb92 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -140,7 +140,7 @@ packages: name: characters url: "https://pub.flutter-io.cn" source: hosted - version: "1.2.0" + version: "1.2.1" charcode: dependency: transitive description: @@ -161,7 +161,7 @@ packages: name: clock url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.0" + version: "1.1.1" code_builder: dependency: transitive description: @@ -369,8 +369,8 @@ packages: dependency: "direct main" description: path: "." - ref: "4a950fd3a5a228bf5381070a4c803919d5787c07" - resolved-ref: "4a950fd3a5a228bf5381070a4c803919d5787c07" + ref: dec2166e881c47d922e1edc484d10d2cd5c2103b + resolved-ref: dec2166e881c47d922e1edc484d10d2cd5c2103b url: "https://github.com/Kingtous/rustdesk_flutter_custom_cursor" source: git version: "0.0.1" @@ -602,7 +602,7 @@ packages: name: material_color_utilities url: "https://pub.flutter-io.cn" source: hosted - version: "0.1.4" + version: "0.1.5" menu_base: dependency: transitive description: @@ -616,7 +616,7 @@ packages: name: meta url: "https://pub.flutter-io.cn" source: hosted - version: "1.7.0" + version: "1.8.0" mime: dependency: transitive description: @@ -693,7 +693,7 @@ packages: name: path url: "https://pub.flutter-io.cn" source: hosted - version: "1.8.1" + version: "1.8.2" path_drawing: dependency: transitive description: diff --git a/res/PKGBUILD b/res/PKGBUILD index 90e00df19..ff9ab1a28 100644 --- a/res/PKGBUILD +++ b/res/PKGBUILD @@ -30,5 +30,6 @@ package() { pushd ${pkgdir} && ln -s /usr/lib/rustdesk/rustdesk usr/bin/rustdesk && popd install -Dm 644 $HBB/res/rustdesk.service -t "${pkgdir}/usr/share/rustdesk/files" install -Dm 644 $HBB/res/rustdesk.desktop -t "${pkgdir}/usr/share/rustdesk/files" + install -Dm 644 $HBB/res/rustdesk-link.desktop -t "${pkgdir}/usr/share/rustdesk/files" install -Dm 644 $HBB/res/128x128@2x.png "${pkgdir}/usr/share/rustdesk/files/rustdesk.png" } diff --git a/res/pacman_install b/res/pacman_install index eeef34028..bcd69077d 100644 --- a/res/pacman_install +++ b/res/pacman_install @@ -7,6 +7,7 @@ post_install() { # do something here cp /usr/share/rustdesk/files/rustdesk.service /etc/systemd/system/rustdesk.service cp /usr/share/rustdesk/files/rustdesk.desktop /usr/share/applications/ + cp /usr/share/rustdesk/files/rustdesk-link.desktop /usr/share/applications/ systemctl daemon-reload systemctl enable rustdesk systemctl start rustdesk @@ -24,6 +25,7 @@ pre_upgrade() { post_upgrade() { cp /usr/share/rustdesk/files/rustdesk.service /etc/systemd/system/rustdesk.service cp /usr/share/rustdesk/files/rustdesk.desktop /usr/share/applications/ + cp /usr/share/rustdesk/files/rustdesk-link.desktop /usr/share/applications/ systemctl daemon-reload systemctl enable rustdesk systemctl start rustdesk @@ -40,5 +42,6 @@ pre_remove() { # arg 1: the old package version post_remove() { rm /usr/share/applications/rustdesk.desktop || true + rm /usr/share/applications/rustdesk-link.desktop || true update-desktop-database } diff --git a/res/rpm-suse.spec b/res/rpm-suse.spec index a22171242..85f01e37e 100644 --- a/res/rpm-suse.spec +++ b/res/rpm-suse.spec @@ -25,6 +25,7 @@ install $HBB/libsciter-gtk.so %{buildroot}/usr/lib/rustdesk/libsciter-gtk.so install $HBB/res/rustdesk.service %{buildroot}/usr/share/rustdesk/files/ install $HBB/res/128x128@2x.png %{buildroot}/usr/share/rustdesk/files/rustdesk.png install $HBB/res/rustdesk.desktop %{buildroot}/usr/share/rustdesk/files/ +install $HBB/res/rustdesk-link.desktop %{buildroot}/usr/share/rustdesk/files/ %files /usr/bin/rustdesk @@ -32,6 +33,7 @@ install $HBB/res/rustdesk.desktop %{buildroot}/usr/share/rustdesk/files/ /usr/share/rustdesk/files/rustdesk.service /usr/share/rustdesk/files/rustdesk.png /usr/share/rustdesk/files/rustdesk.desktop +/usr/share/rustdesk/files/rustdesk-link.desktop %changelog # let's skip this for now @@ -52,6 +54,7 @@ esac %post cp /usr/share/rustdesk/files/rustdesk.service /etc/systemd/system/rustdesk.service cp /usr/share/rustdesk/files/rustdesk.desktop /usr/share/applications/ +cp /usr/share/rustdesk/files/rustdesk-link.desktop /usr/share/applications/ systemctl daemon-reload systemctl enable rustdesk systemctl start rustdesk @@ -75,6 +78,7 @@ case "$1" in 0) # for uninstall rm /usr/share/applications/rustdesk.desktop || true + rm /usr/share/applications/rustdesk-link.desktop || true update-desktop-database ;; 1) diff --git a/res/rpm.spec b/res/rpm.spec index 8e04eaac5..80887de96 100644 --- a/res/rpm.spec +++ b/res/rpm.spec @@ -25,6 +25,7 @@ install $HBB/libsciter-gtk.so %{buildroot}/usr/lib/rustdesk/libsciter-gtk.so install $HBB/res/rustdesk.service %{buildroot}/usr/share/rustdesk/files/ install $HBB/res/128x128@2x.png %{buildroot}/usr/share/rustdesk/files/rustdesk.png install $HBB/res/rustdesk.desktop %{buildroot}/usr/share/rustdesk/files/ +install $HBB/res/rustdesk-link.desktop %{buildroot}/usr/share/rustdesk/files/ %files /usr/bin/rustdesk @@ -32,6 +33,7 @@ install $HBB/res/rustdesk.desktop %{buildroot}/usr/share/rustdesk/files/ /usr/share/rustdesk/files/rustdesk.service /usr/share/rustdesk/files/rustdesk.png /usr/share/rustdesk/files/rustdesk.desktop +/usr/share/rustdesk/files/rustdesk-link.desktop /usr/share/rustdesk/files/__pycache__/* %changelog @@ -53,6 +55,7 @@ esac %post cp /usr/share/rustdesk/files/rustdesk.service /etc/systemd/system/rustdesk.service cp /usr/share/rustdesk/files/rustdesk.desktop /usr/share/applications/ +cp /usr/share/rustdesk/files/rustdesk-link.desktop /usr/share/applications/ systemctl daemon-reload systemctl enable rustdesk systemctl start rustdesk @@ -76,6 +79,7 @@ case "$1" in 0) # for uninstall rm /usr/share/applications/rustdesk.desktop || true + rm /usr/share/applications/rustdesk-link.desktop || true update-desktop-database ;; 1) diff --git a/res/rustdesk-link.desktop b/res/rustdesk-link.desktop new file mode 100644 index 000000000..9ab6f495e --- /dev/null +++ b/res/rustdesk-link.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=RustDeskURL Scheme Handler +NoDisplay=true +MimeType=x-scheme-handler/rustdesk; +TryExec=rustdesk +Exec=rustdesk --connect "%u" +Icon=rustdesk +Terminal=false +Type=Application +StartupNotify=false +Version=1.5 diff --git a/src/core_main.rs b/src/core_main.rs index ac05b20bf..416c2965f 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -1,3 +1,5 @@ +use std::env::Args; + use hbb_common::log; // shared by flutter and sciter main function @@ -11,6 +13,7 @@ pub fn core_main() -> Option> { let mut is_setup = false; let mut _is_elevate = false; let mut _is_run_as_system = false; + let mut _is_connect = false; for arg in std::env::args() { // to-do: how to pass to flutter? if i == 0 && crate::common::is_setup(&arg) { @@ -20,14 +23,16 @@ pub fn core_main() -> Option> { _is_elevate = true; } else if arg == "--run-as-system" { _is_run_as_system = true; + } else if arg == "--connect" { + _is_connect = true; } else { args.push(arg); } } i += 1; } - if args.contains(&"--install".to_string()) { - is_setup = true; + if _is_connect { + return core_main_invoke_new_connection(std::env::args()); } if is_setup { if args.is_empty() { @@ -208,3 +213,36 @@ fn import_config(path: &str) { } } } + +/// invoke a new connection +/// +/// [Note] +/// this is for invoke new connection from dbus +fn core_main_invoke_new_connection(mut args: Args) -> Option> { + args + .position(|element| { + return element == "--connect"; + }) + .unwrap(); + let peer_id = args.next().unwrap_or("".to_string()); + if peer_id.is_empty() { + eprintln!("please provide a valid peer id"); + return None; + } + #[cfg(target_os = "linux")] + { + use crate::dbus::invoke_new_connection; + + match invoke_new_connection(peer_id) { + Ok(()) => { + return None; + } + Err(err) => { + log::error!("{}", err.as_ref()); + // return Some to invoke this new connection by self + return Some(Vec::new()); + } + } + } + return None; +} diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index f788c679d..873248cca 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -801,6 +801,17 @@ pub fn main_is_release() -> bool { is_release() } +pub fn main_start_dbus_server() { + #[cfg(target_os = "linux")] + { + use crate::dbus::start_dbus_server; + // spawn new thread to start dbus server + std::thread::spawn(|| { + let _ = start_dbus_server(); + }); + } +} + 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(); diff --git a/src/server.rs b/src/server.rs index 12b5afe56..cb3fd7c9d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -30,6 +30,8 @@ mod clipboard_service; mod wayland; #[cfg(target_os = "linux")] pub mod uinput; +#[cfg(target_os = "linux")] +pub mod dbus; pub mod input_service; } else { mod clipboard_service { diff --git a/src/server/dbus.rs b/src/server/dbus.rs new file mode 100644 index 000000000..a7b21f0dc --- /dev/null +++ b/src/server/dbus.rs @@ -0,0 +1,92 @@ +/// Url handler based on dbus +/// +/// Note: +/// On linux, we use dbus to communicate multiple rustdesk process. +/// [Flutter]: handle uni links for linux +use dbus::blocking::Connection; +use dbus_crossroads::{Crossroads, IfaceBuilder}; +use hbb_common::{log}; +use std::{error::Error, fmt, time::Duration, collections::HashMap}; + +const DBUS_NAME: &str = "org.rustdesk.rustdesk"; +const DBUS_PREFIX: &str = "/dbus"; +const DBUS_METHOD_NEW_CONNECTION: &str = "NewConnection"; +const DBUS_METHOD_NEW_CONNECTION_ID: &str = "id"; +const DBUS_METHOD_RETURN: &str = "ret"; +const DBUS_METHOD_RETURN_SUCCESS: &str = "ok"; +const DBUS_TIMEOUT: Duration = Duration::from_secs(5); + +#[derive(Debug)] +struct DbusError(String); + +impl fmt::Display for DbusError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "RustDesk DBus Error: {}", self.0) + } +} + +impl Error for DbusError {} + +/// invoke new connection from dbus +/// +/// [Tips]: +/// How to test by CLI: +/// - use dbus-send command: +/// `dbus-send --session --print-reply --dest=org.rustdesk.rustdesk /dbus org.rustdesk.rustdesk.NewConnection string:'PEER_ID'` +pub fn invoke_new_connection(peer_id: String) -> Result<(), Box> { + let conn = Connection::new_session()?; + let proxy = conn.with_proxy(DBUS_NAME, DBUS_PREFIX, DBUS_TIMEOUT); + let (ret,): (String,) = proxy.method_call(DBUS_NAME, DBUS_METHOD_NEW_CONNECTION, (peer_id,))?; + if ret != DBUS_METHOD_RETURN_SUCCESS { + log::error!("error on call new connection to dbus server"); + return Err(Box::new(DbusError("not success".to_string()))); + } + Ok(()) +} + +/// start dbus server +/// +/// [Blocking]: +/// The function will block current thread to serve dbus server. +/// So it's suitable to spawn a new thread dedicated to dbus server. +pub fn start_dbus_server() -> Result<(), Box> { + let conn: Connection = Connection::new_session()?; + let _ = conn.request_name(DBUS_NAME, false, true, false)?; + let mut cr = Crossroads::new(); + let token = cr.register(DBUS_NAME, handle_client_message); + cr.insert(DBUS_PREFIX, &[token], ()); + cr.serve(&conn)?; + Ok(()) +} + +fn handle_client_message(builder: &mut IfaceBuilder<()>) { + // register new connection dbus + builder.method( + DBUS_METHOD_NEW_CONNECTION, + (DBUS_METHOD_NEW_CONNECTION_ID,), + (DBUS_METHOD_RETURN,), + move |_, _, (peer_id,): (String,)| { + #[cfg(feature = "flutter")] + { + use crate::flutter::{self, APP_TYPE_MAIN}; + + if let Some(stream) = flutter::GLOBAL_EVENT_STREAM + .write() + .unwrap() + .get(APP_TYPE_MAIN) + { + let data = HashMap::from([ + ("name", "new_connection"), + ("peer_id", peer_id.as_str()) + ]); + if !stream.add(serde_json::ser::to_string(&data).unwrap_or("".to_string())) { + log::error!("failed to add dbus message to flutter global dbus stream."); + } + } else { + log::error!("failed to find main event stream"); + } + } + return Ok((DBUS_METHOD_RETURN_SUCCESS.to_string(),)); + }, + ); +}