Merge pull request #3076 from Kingtous/master

feat: add url scheme handler for macos
This commit is contained in:
RustDesk 2023-02-05 12:38:01 +08:00 committed by GitHub
commit 4b5724fa76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 211 additions and 48 deletions

41
Cargo.lock generated
View File

@ -1137,7 +1137,7 @@ checksum = "413487ef345ab5cdfbf23e66070741217a701bce70f2f397a54221b4f2b6056a"
dependencies = [
"dconf_rs",
"detect-desktop-environment",
"dirs",
"dirs 4.0.0",
"objc",
"rust-ini",
"web-sys",
@ -1401,6 +1401,16 @@ dependencies = [
"dirs-sys-next",
]
[[package]]
name = "dirs"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
dependencies = [
"cfg-if 0.1.10",
"dirs-sys",
]
[[package]]
name = "dirs"
version = "4.0.0"
@ -1873,6 +1883,19 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fruitbasket"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "898289b8e0528c84fb9b88f15ac9d5109bcaf23e0e49bb6f64deee0d86b6a351"
dependencies = [
"dirs 2.0.2",
"objc",
"objc-foundation",
"objc_id",
"time 0.1.45",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
@ -3657,6 +3680,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
"malloc_buf",
"objc_exception",
]
[[package]]
@ -3670,6 +3694,15 @@ dependencies = [
"objc_id",
]
[[package]]
name = "objc_exception"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
dependencies = [
"cc",
]
[[package]]
name = "objc_id"
version = "0.1.1"
@ -4655,6 +4688,7 @@ dependencies = [
"flexi_logger",
"flutter_rust_bridge",
"flutter_rust_bridge_codegen",
"fruitbasket",
"glib 0.16.5",
"gtk",
"hbb_common",
@ -4673,6 +4707,7 @@ dependencies = [
"mouce",
"num_cpus",
"objc",
"objc_id",
"parity-tokio-ipc",
"rdev",
"repng",
@ -4713,7 +4748,7 @@ name = "rustdesk-portable-packer"
version = "0.1.0"
dependencies = [
"brotli",
"dirs",
"dirs 4.0.0",
"embed-resource",
"md5",
]
@ -6591,7 +6626,7 @@ dependencies = [
"async-trait",
"byteorder",
"derivative",
"dirs",
"dirs 4.0.0",
"enumflags2",
"event-listener",
"futures-core",

View File

@ -106,6 +106,8 @@ core-graphics = "0.22"
include_dir = "0.7.2"
tray-item = "0.7" # looks better than trayicon
dark-light = "0.2"
fruitbasket = "0.10.0"
objc_id = "0.1.1"
[target.'cfg(target_os = "linux")'.dependencies]
psimple = { package = "libpulse-simple-binding", version = "2.25" }

View File

@ -1315,6 +1315,12 @@ StreamSubscription? listenUniLinks() {
///
/// * Returns true if we successfully handle the startup arguments.
bool checkArguments() {
if (kBootArgs.isNotEmpty) {
final ret = parseRustdeskUri(kBootArgs.first);
if (ret) {
return true;
}
}
// bootArgs:[--connect, 362587269, --switch_uuid, e3d531cc-5dce-41e0-bd06-5d4a2b1eec05]
// check connect args
var connectIndex = kBootArgs.indexOf("--connect");
@ -1352,7 +1358,7 @@ bool checkArguments() {
bool parseRustdeskUri(String uriPath) {
final uri = Uri.tryParse(uriPath);
if (uri == null) {
print("uri is not valid: $uriPath");
debugPrint("uri is not valid: $uriPath");
return false;
}
return callUniLinksUriHandler(uri);

View File

@ -11,8 +11,9 @@ const String kPeerPlatformLinux = "Linux";
const String kPeerPlatformMacOS = "Mac OS";
const String kPeerPlatformAndroid = "Android";
/// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)' , 'Desktop CM Page', "Install Page"
/// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)', "Install Page"
const String kAppTypeMain = "main";
const String kAppTypeConnectionManager = "cm";
const String kAppTypeDesktopRemote = "remote";
const String kAppTypeDesktopFileTransfer = "file transfer";
const String kAppTypeDesktopPortForward = "port forward";

View File

@ -211,7 +211,7 @@ void runMultiWindow(
}
void runConnectionManagerScreen(bool hide) async {
await initEnv(kAppTypeMain);
await initEnv(kAppTypeConnectionManager);
_runApp(
'',
const DesktopServerPage(),

View File

@ -199,6 +199,9 @@ class FfiModel with ChangeNotifier {
final peer_id = evt['peer_id'].toString();
await bind.sessionSwitchSides(id: peer_id);
closeConnection(id: peer_id);
} else if (name == "on_url_scheme_received") {
final url = evt['url'].toString();
parseRustdeskUri(url);
}
};
}

View File

@ -8,6 +8,7 @@ import 'package:external_path/external_path.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:win32/win32.dart' as win32;
@ -46,6 +47,8 @@ class PlatformFFI {
static get localeName => Platform.localeName;
static get isMain => instance._appType == kAppTypeMain;
static Future<String> getVersion() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
return packageInfo.version;
@ -112,8 +115,11 @@ class PlatformFFI {
}
_ffiBind = RustdeskImpl(dylib);
if (Platform.isLinux) {
// start dbus service, no need to await
await _ffiBind.mainStartDbusServer();
// Start a dbus service, no need to await
_ffiBind.mainStartDbusServer();
} else if (Platform.isMacOS) {
// Start an ipc server for handling url schemes.
_ffiBind.mainStartIpcUrlServer();
}
_startListenEvent(_ffiBind); // global event
try {

View File

@ -227,7 +227,7 @@
TargetAttributes = {
33CC10EC2044A3C60003C045 = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
LastSwiftMigration = 1420;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.Sandbox = {
@ -463,6 +463,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.14;
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
};
name = Profile;
@ -607,6 +608,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.14;
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
"SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = Runner/bridge_generated.h;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -643,6 +645,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.rustdesk;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
"SWIFT_OBJC_BRIDGING_HEADER[arch=*]" = Runner/bridge_generated.h;
SWIFT_VERSION = 5.0;
};

View File

@ -87,4 +87,3 @@ class MainFlutterWindow: NSWindow {
})
}
}

View File

@ -1,4 +1,6 @@
use hbb_common::log;
use std::future::Future;
use hbb_common::{log, ResultType};
/// shared by flutter and sciter main function
///
@ -346,5 +348,11 @@ fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<Strin
return if res { None } else { Some(Vec::new()) };
}
#[cfg(target_os = "macos")]
return Some(Vec::new());
{
return if let Err(_) = crate::ipc::send_url_scheme(uni_links) {
Some(Vec::new())
} else {
None
}
}
}

View File

@ -1,30 +1,27 @@
use std::{
collections::HashMap,
ffi::{CStr, CString},
os::raw::c_char,
};
use std::{collections::HashMap, ffi::{CStr, CString}, os::raw::c_char, thread};
use std::str::FromStr;
use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer};
use serde_json::json;
use crate::common::is_keyboard_mode_supported;
use hbb_common::message_proto::KeyboardMode;
use hbb_common::ResultType;
use hbb_common::{
config::{self, LocalConfig, PeerConfig, ONLINE},
config::{self, LocalConfig, ONLINE, PeerConfig},
fs, log,
};
use std::str::FromStr;
use hbb_common::message_proto::KeyboardMode;
use hbb_common::ResultType;
// use crate::hbbs_http::account::AuthResult;
use crate::flutter::{self, SESSIONS};
use crate::ui_interface::{self, *};
use crate::{
client::file_trait::FileManager,
common::make_fd_to_json,
flutter::{session_add, session_start_},
};
use crate::common::is_keyboard_mode_supported;
use crate::flutter::{self, SESSIONS};
use crate::ui_interface::{self, *};
// use crate::hbbs_http::account::AuthResult;
fn initialize(app_dir: &str) {
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
#[cfg(target_os = "android")]
@ -1255,16 +1252,32 @@ pub fn main_hide_docker() -> SyncReturn<bool> {
SyncReturn(true)
}
/// Start an ipc server for receiving the url scheme.
///
/// * Should only be called in the main flutter window.
/// * macOS only
pub fn main_start_ipc_url_server() {
#[cfg(target_os = "macos")]
thread::spawn(move || crate::server::start_ipc_url_server());
}
/// Send a url scheme throught the ipc.
///
/// * macOS only
pub fn send_url_scheme(url: String) {
#[cfg(target_os = "macos")]
thread::spawn(move || crate::ui::macos::handle_url_scheme(url));
}
#[cfg(target_os = "android")]
pub mod server_side {
use hbb_common::log;
use jni::{
JNIEnv,
objects::{JClass, JString},
sys::jstring,
JNIEnv,
};
use hbb_common::log;
use crate::start_server;
#[no_mangle]

View File

@ -16,10 +16,10 @@ use hbb_common::{
config::{self, Config, Config2},
futures::StreamExt as _,
futures_util::sink::SinkExt,
log, password_security as password, timeout, tokio,
log, password_security as password, ResultType, timeout,
tokio,
tokio::io::{AsyncRead, AsyncWrite},
tokio_util::codec::Framed,
ResultType,
};
use crate::rendezvous_mediator::RendezvousMediator;
@ -210,6 +210,7 @@ pub enum Data {
DataPortableService(DataPortableService),
SwitchSidesRequest(String),
SwitchSidesBack,
UrlLink(String)
}
#[tokio::main(flavor = "current_thread")]
@ -832,3 +833,9 @@ pub async fn test_rendezvous_server() -> ResultType<()> {
c.send(&Data::TestRendezvousServer).await?;
Ok(())
}
#[tokio::main(flavor = "current_thread")]
pub async fn send_url_scheme(url: String) -> ResultType<()> {
connect(1_000, "_url").await?.send(&Data::UrlLink(url)).await?;
Ok(())
}

View File

@ -1,8 +1,13 @@
use crate::ipc::Data;
use std::{
collections::HashMap,
net::SocketAddr,
sync::{Arc, Mutex, RwLock, Weak},
time::Duration,
};
use bytes::Bytes;
pub use connection::*;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::config::Config2;
use hbb_common::{
allow_err,
anyhow::{anyhow, Context},
@ -12,19 +17,18 @@ use hbb_common::{
message_proto::*,
protobuf::{Enum, Message as _},
rendezvous_proto::*,
ResultType,
socket_client,
sodiumoxide::crypto::{box_, secretbox, sign},
timeout, tokio, ResultType, Stream,
sodiumoxide::crypto::{box_, secretbox, sign}, Stream, timeout, tokio,
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use service::ServiceTmpl;
use hbb_common::config::Config2;
use hbb_common::tcp::new_listener;
use service::{GenericService, Service, Subscriber};
use std::{
collections::HashMap,
net::SocketAddr,
sync::{Arc, Mutex, RwLock, Weak},
time::Duration,
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use service::ServiceTmpl;
use crate::ipc::{connect, Data};
pub mod audio_service;
cfg_if::cfg_if! {
@ -55,8 +59,6 @@ mod service;
mod video_qos;
pub mod video_service;
use hbb_common::tcp::new_listener;
pub type Childs = Arc<Mutex<Vec<std::process::Child>>>;
type ConnMap = HashMap<i32, ConnInner>;
@ -425,6 +427,50 @@ pub async fn start_server(is_server: bool) {
}
}
#[cfg(target_os = "macos")]
#[tokio::main(flavor = "current_thread")]
pub async fn start_ipc_url_server() {
log::debug!("Start an ipc server for listening to url schemes");
match crate::ipc::new_listener("_url").await {
Ok(mut incoming) => {
while let Some(Ok(conn)) = incoming.next().await {
let mut conn = crate::ipc::Connection::new(conn);
match conn.next_timeout(1000).await {
Ok(Some(data)) => {
match data {
Data::UrlLink(url) => {
#[cfg(feature = "flutter")]
{
if let Some(stream) = crate::flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(
crate::flutter::APP_TYPE_MAIN
) {
let mut m = HashMap::new();
m.insert("name", "on_url_scheme_received");
m.insert("url", url.as_str());
stream.add(serde_json::to_string(&m).unwrap());
} else {
log::warn!("No main window app found!");
}
}
}
_ => {
log::warn!("An unexpected data was sent to the ipc url server.")
}
}
}
Err(err) => {
log::error!("{}", err);
}
_ => {}
}
}
}
Err(err) => {
log::error!("{}", err);
}
}
}
#[cfg(target_os = "macos")]
async fn sync_and_watch_config_dir() {
if crate::platform::is_root() {

View File

@ -1,3 +1,5 @@
use std::{ffi::c_void, rc::Rc};
#[cfg(target_os = "macos")]
use cocoa::{
appkit::{NSApp, NSApplication, NSApplicationActivationPolicy::*, NSMenu, NSMenuItem},
@ -8,11 +10,16 @@ use objc::{
class,
declare::ClassDecl,
msg_send,
runtime::{Object, Sel, BOOL},
runtime::{BOOL, Object, Sel},
sel, sel_impl,
};
use sciter::{make_args, Host};
use std::{ffi::c_void, rc::Rc};
use objc::runtime::Class;
use objc_id::WeakId;
use sciter::{Host, make_args};
use hbb_common::{log, tokio};
use crate::ui_cm_interface::start_ipc;
static APP_HANDLER_IVAR: &str = "GoDeskAppHandler";
@ -98,12 +105,21 @@ unsafe fn set_delegate(handler: Option<Box<dyn AppHandler>>) {
sel!(handleMenuItem:),
handle_menu_item as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(sel!(handleEvent:withReplyEvent:), handle_apple_event as extern fn(&Object, Sel, u64, u64));
let decl = decl.register();
let delegate: id = msg_send![decl, alloc];
let () = msg_send![delegate, init];
let state = DelegateState { handler };
let handler_ptr = Box::into_raw(Box::new(state));
(*delegate).set_ivar(APP_HANDLER_IVAR, handler_ptr as *mut c_void);
// Set the url scheme handler
let cls = Class::get("NSAppleEventManager").unwrap();
let manager: *mut Object = msg_send![cls, sharedAppleEventManager];
let _: () = msg_send![manager,
setEventHandler: delegate
andSelector: sel!(handleEvent:withReplyEvent:)
forEventClass: fruitbasket::kInternetEventClass
andEventID: fruitbasket::kAEGetURL];
let () = msg_send![NSApp(), setDelegate: delegate];
}
@ -167,6 +183,24 @@ extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
}
}
/// The function to handle the url scheme sent by the system.
///
/// 1. Try to send the url scheme from ipc.
/// 2. If failed to send the url scheme, we open a new main window to handle this url scheme.
pub fn handle_url_scheme(url: String) {
if let Err(err) = crate::ipc::send_url_scheme(url.clone()) {
log::debug!("Send the url to the existing flutter process failed, {}. Let's open a new program to handle this.", err);
let _ = crate::run_me(vec![url]);
}
}
extern fn handle_apple_event(_this: &Object, _cmd: Sel, event: u64, _reply: u64) {
let event = event as *mut Object;
let url = fruitbasket::parse_url_event(event);
log::debug!("an event was received: {}", url);
std::thread::spawn(move || handle_url_scheme(url));
}
unsafe fn make_menu_item(title: &str, key: &str, tag: u32) -> *mut Object {
let title = NSString::alloc(nil).init_str(title);
let action = sel!(handleMenuItem:);