diff --git a/src/api.rs b/src/api.rs deleted file mode 100644 index a0ea5df1c..000000000 --- a/src/api.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::ffi::{c_char}; - -use crate::{ - flutter::{FlutterHandler, SESSIONS}, - plugins::PLUGIN_REGISTRAR, - ui_session_interface::Session, -}; - -// API provided by RustDesk. -pub type LoadPluginFunc = fn(*const c_char) -> i32; -pub type UnloadPluginFunc = fn(*const c_char) -> i32; -pub type AddSessionFunc = fn(session_id: String) -> bool; -pub type RemoveSessionFunc = fn(session_id: &String) -> bool; -pub type AddSessionHookFunc = fn(session_id: String, key: String, hook: SessionHook) -> bool; -pub type RemoveSessionHookFunc = fn(session_id: String, key: &String) -> bool; - -/// Hooks for session. -#[derive(Clone)] -pub enum SessionHook { - OnSessionRgba(fn(String, Vec) -> Vec), -} - -// #[repr(C)] -pub struct RustDeskApiTable { - pub(crate) load_plugin: LoadPluginFunc, - pub(crate) unload_plugin: UnloadPluginFunc, - pub add_session: AddSessionFunc, - pub remove_session: RemoveSessionFunc, - pub add_session_hook: AddSessionHookFunc, - pub remove_session_hook: RemoveSessionHookFunc, -} - -fn load_plugin(path: *const c_char) -> i32 { - PLUGIN_REGISTRAR.load_plugin(path) -} - -fn unload_plugin(path: *const c_char) -> i32 { - PLUGIN_REGISTRAR.unload_plugin(path) -} - -fn add_session(session_id: String) -> bool { - // let mut sessions = SESSIONS.write().unwrap(); - // if sessions.contains_key(&session.id) { - // return false; - // } - // let _ = sessions.insert(session.id.to_owned(), session); - // true - false -} - -fn remove_session(session_id: &String) -> bool { - let mut sessions = SESSIONS.write().unwrap(); - if !sessions.contains_key(session_id) { - return false; - } - let _ = sessions.remove(session_id); - true -} - -fn add_session_hook(session_id: String, key: String, hook: SessionHook) -> bool { - let sessions = SESSIONS.read().unwrap(); - if let Some(session) = sessions.get(&session_id) { - return session.add_session_hook(key, hook); - } - false -} - -fn remove_session_hook(session_id: String, key: &String) -> bool { - let sessions = SESSIONS.read().unwrap(); - if let Some(session) = sessions.get(&session_id) { - return session.remove_session_hook(key); - } - false -} - -impl Default for RustDeskApiTable { - fn default() -> Self { - Self { - load_plugin, - unload_plugin, - add_session, - remove_session, - add_session_hook, - remove_session_hook, - } - } -} diff --git a/src/flutter.rs b/src/flutter.rs index f08b6a569..6207eb4e3 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -147,7 +147,7 @@ pub struct FlutterHandler { renderer: Arc>, peer_info: Arc>, #[cfg(not(any(target_os = "android", target_os = "ios")))] - hooks: Arc>>, + hooks: Arc>>, } #[cfg(not(feature = "flutter_texture_render"))] @@ -160,7 +160,7 @@ pub struct FlutterHandler { pub rgba_valid: Arc, peer_info: Arc>, #[cfg(not(any(target_os = "android", target_os = "ios")))] - hooks: Arc>>, + hooks: Arc>>, } #[cfg(feature = "flutter_texture_render")] @@ -294,7 +294,7 @@ impl FlutterHandler { } #[cfg(not(any(target_os = "android", target_os = "ios")))] - pub(crate) fn add_session_hook(&self, key: String, hook: crate::api::SessionHook) -> bool { + pub(crate) fn add_session_hook(&self, key: String, hook: SessionHook) -> bool { let mut hooks = self.hooks.write().unwrap(); if hooks.contains_key(&key) { // Already has the hook with this key. @@ -501,6 +501,15 @@ impl InvokeUiSession for FlutterHandler { #[inline] #[cfg(not(feature = "flutter_texture_render"))] fn on_rgba(&self, rgba: &mut scrap::ImageRgb) { + // Give a chance for plugins or etc to hook a rgba data. + #[cfg(not(any(target_os = "android", target_os = "ios")))] + for (key, hook) in self.hooks.read().unwrap().iter() { + match hook { + SessionHook::OnSessionRgba(cb) => { + cb(key.to_owned(), rgba); + }, + } + } // If the current rgba is not fetched by flutter, i.e., is valid. // We give up sending a new event to flutter. if self.rgba_valid.load(Ordering::Relaxed) { @@ -1056,3 +1065,9 @@ pub fn stop_global_event_stream(app_type: String) { #[no_mangle] unsafe extern "C" fn get_rgba() {} + +/// Hooks for session. +#[derive(Clone)] +pub enum SessionHook { + OnSessionRgba(fn(String, &mut scrap::ImageRgb)), +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index ec70d1179..31ad83bca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,13 +48,6 @@ mod license; #[cfg(not(any(target_os = "android", target_os = "ios")))] mod port_forward; -#[cfg(not(any(target_os = "android", target_os = "ios")))] -#[cfg(any(feature = "flutter"))] -pub mod api; -#[cfg(not(any(target_os = "android", target_os = "ios")))] -#[cfg(any(feature = "flutter"))] -pub mod plugins; - #[cfg(all(feature = "flutter", feature = "plugin_framework"))] #[cfg(not(any(target_os = "android", target_os = "ios")))] pub mod plugin; diff --git a/src/plugin/errno.rs b/src/plugin/errno.rs index db580c0bd..f62a3daac 100644 --- a/src/plugin/errno.rs +++ b/src/plugin/errno.rs @@ -17,6 +17,8 @@ pub const ERR_CALL_NOT_SUPPORTED_METHOD: i32 = 10202; // failed on calling pub const ERR_CALL_INVALID_ARGS: i32 = 10301; pub const ERR_PEER_ID_MISMATCH: i32 = 10302; +// no handlers on calling +pub const ERR_NOT_HANDLED: i32 = 10401; // ====================================================== // errors that should be handled by the plugin diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index 898630910..dea7dbd2d 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -8,6 +8,8 @@ mod errno; pub mod ipc; mod plog; mod plugins; +pub mod native; +mod native_handlers; pub use plugins::{ handle_client_event, handle_listen_event, handle_server_event, handle_ui_event, load_plugin, diff --git a/src/plugin/native.rs b/src/plugin/native.rs new file mode 100644 index 000000000..ce885c77c --- /dev/null +++ b/src/plugin/native.rs @@ -0,0 +1,40 @@ +use std::{ + ffi::{c_char, c_int, c_void}, + os::raw::c_uint, +}; + +use hbb_common::log::error; + +use super::{ + cstr_to_string, + errno::ERR_NOT_HANDLED, + native_handlers::{Callable, NATIVE_HANDLERS_REGISTRAR}, +}; +/// The native returned value from librustdesk native. +/// +/// [Note] +/// The data is owned by librustdesk. +#[repr(C)] +pub struct NativeReturnValue { + pub return_type: c_int, + pub data: *const c_void, +} + +pub(super) extern "C" fn cb_native_data( + method: *const c_char, + json: *const c_char, + raw: *const c_void, + raw_len: usize, +) -> NativeReturnValue { + let ret = match cstr_to_string(method) { + Ok(method) => NATIVE_HANDLERS_REGISTRAR.call(&method, json, raw, raw_len), + Err(err) => { + error!("cb_native_data error: {}", err); + None + } + }; + return ret.unwrap_or(NativeReturnValue { + return_type: ERR_NOT_HANDLED, + data: std::ptr::null(), + }); +} diff --git a/src/plugin/native_handlers/macros.rs b/src/plugin/native_handlers/macros.rs new file mode 100644 index 000000000..82d7e10a6 --- /dev/null +++ b/src/plugin/native_handlers/macros.rs @@ -0,0 +1,27 @@ +#[macro_export] +macro_rules! return_if_not_method { + ($call: ident, $prefix: ident) => { + if $call.starts_with($prefix) { + return None; + } + }; +} + +#[macro_export] +macro_rules! call_if_method { + ($call: ident ,$method: literal, $block: block) => { + if ($call != $method) { + $block + } + }; +} + +#[macro_export] +macro_rules! define_method_prefix { + ($prefix: literal) => { + #[inline] + fn method_prefix(&self) -> &'static str { + $prefix + } + }; +} diff --git a/src/plugin/native_handlers/mod.rs b/src/plugin/native_handlers/mod.rs new file mode 100644 index 000000000..32b6627e2 --- /dev/null +++ b/src/plugin/native_handlers/mod.rs @@ -0,0 +1,124 @@ +use std::{ + ffi::c_void, + sync::{Arc, RwLock}, + vec, +}; + +use hbb_common::libc::c_char; +use lazy_static::lazy_static; +use serde_json::Map; + +use crate::return_if_not_method; + +use self::session::PluginNativeSessionHandler; + +use super::cstr_to_string; + +mod macros; +pub mod session; + +pub type NR = super::native::NativeReturnValue; +pub type PluginNativeHandlerRegistrar = NativeHandlerRegistrar>; + +lazy_static! { + pub static ref NATIVE_HANDLERS_REGISTRAR: Arc = + Arc::new(PluginNativeHandlerRegistrar::default()); +} + +#[derive(Clone)] +pub struct NativeHandlerRegistrar { + handlers: Arc>>, +} + +impl Default for PluginNativeHandlerRegistrar { + fn default() -> Self { + Self { + handlers: Arc::new(RwLock::new(vec![Box::new( + // Add prebuilt native handlers here. + PluginNativeSessionHandler::default(), + )])), + } + } +} + +pub(self) trait PluginNativeHandler { + /// The method prefix handled by this handler.s + fn method_prefix(&self) -> &'static str; + + /// Try to handle the method with the given data. + /// + /// Returns: None for the message does not be handled by this handler. + fn on_message(&self, method: &str, data: &Map) -> Option; + + /// Try to handle the method with the given data and extra void binary data. + /// + /// Returns: None for the message does not be handled by this handler. + fn on_message_raw( + &self, + method: &str, + data: &Map, + raw: *const c_void, + raw_len: usize, + ) -> Option; +} + +pub trait Callable { + fn call( + &self, + method: &String, + json: *const c_char, + raw: *const c_void, + raw_len: usize, + ) -> Option { + None + } +} + +impl Callable for T +where + T: PluginNativeHandler + Send + Sync, +{ + fn call( + &self, + method: &String, + json: *const c_char, + raw: *const c_void, + raw_len: usize, + ) -> Option { + let prefix = self.method_prefix(); + return_if_not_method!(method, prefix); + match cstr_to_string(json) { + Ok(s) => { + if let Ok(json) = serde_json::from_str(s.as_str()) { + let method_suffix = &method[prefix.len()..]; + if raw != std::ptr::null() && raw_len > 0 { + return self.on_message_raw(method_suffix, &json, raw, raw_len); + } else { + return self.on_message(method_suffix, &json); + } + } else { + return None; + } + } + Err(_) => return None, + } + } +} + +impl Callable for PluginNativeHandlerRegistrar { + fn call( + &self, + method: &String, + json: *const c_char, + raw: *const c_void, + raw_len: usize, + ) -> Option { + for handler in self.handlers.read().unwrap().iter() { + let ret = handler.call(method, json, raw, raw_len); + if ret.is_some() { + return ret; + } + } + None + } +} diff --git a/src/plugin/native_handlers/session.rs b/src/plugin/native_handlers/session.rs new file mode 100644 index 000000000..94d00bf05 --- /dev/null +++ b/src/plugin/native_handlers/session.rs @@ -0,0 +1,141 @@ +use std::sync::{atomic::AtomicU64, Arc, RwLock}; + +use crate::{ + call_if_method, define_method_prefix, flutter::FlutterHandler, return_if_not_method, + ui_session_interface::Session, +}; + +use super::PluginNativeHandler; + +#[derive(Default)] +/// Session related handler for librustdesk core. +pub struct PluginNativeSessionHandler { + sessions: Arc>>>, + id: AtomicU64, +} + +lazy_static::lazy_static! { + pub static ref SESSION_HANDLER: Arc = Arc::new(PluginNativeSessionHandler::default()); +} + +impl PluginNativeHandler for PluginNativeSessionHandler { + define_method_prefix!("session_"); + + fn on_message( + &self, + method: &str, + data: &serde_json::Map, + ) -> Option { + match method { + "create_session" => { + return Some(super::NR { + return_type: 1, + data: SESSION_HANDLER.create_session() as _, + }); + } + "add_session_hook" => { + if let Some(id) = data.get("id") { + if let Some(id) = id.as_u64() { + SESSION_HANDLER.add_session_hook(id); + return Some(super::NR { + return_type: 0, + data: std::ptr::null(), + }); + } + } + } + "remove_session_hook" => { + if let Some(id) = data.get("id") { + if let Some(id) = id.as_u64() { + SESSION_HANDLER.remove_session_hook(id); + return Some(super::NR { + return_type: 0, + data: std::ptr::null(), + }); + } + } + } + "remove_session" => { + if let Some(id) = data.get("id") { + if let Some(id) = id.as_u64() { + SESSION_HANDLER.remove_session(id); + return Some(super::NR { + return_type: 0, + data: std::ptr::null(), + }); + } + } + } + _ => {} + } + None + } + + fn on_message_raw( + &self, + method: &str, + data: &serde_json::Map, + raw: *const std::ffi::c_void, + raw_len: usize, + ) -> Option { + None + } +} + +impl PluginNativeSessionHandler { + fn create_session(&self) -> u64 { + let mut sessions = self.sessions.write().unwrap(); + let unique_id = self.id.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + let mut session: Session = Session::default(); + session.id = self.get_hook_key(unique_id); + sessions.push(session); + return unique_id; + } + + fn add_session_hook(&self, session_id: u64) { + let sessions = self.sessions.read().unwrap(); + let session_id = self.get_hook_key(session_id); + for session in sessions.iter() { + if session.id == session_id { + session.ui_handler.add_session_hook( + session_id.to_owned(), + crate::flutter::SessionHook::OnSessionRgba(session_rgba_cb), + ); + } + } + } + + fn remove_session_hook(&self, session_id: u64) { + let sessions = self.sessions.read().unwrap(); + let session_id = self.get_hook_key(session_id); + for session in sessions.iter() { + if session.id == session_id { + session.ui_handler.remove_session_hook(&session_id); + } + } + } + + fn remove_session(&self, session_id: u64) { + let mut sessions = self.sessions.write().unwrap(); + let session_id = self.get_hook_key(session_id); + for i in 0..sessions.len() { + if sessions[i].id == session_id { + sessions.remove(i); + } + } + } + + #[inline] + fn get_hook_key(&self, id: u64) -> String { + format!("{}_{}", self.method_prefix(), id) + } + + // The callback function for rgba data + fn session_rgba_cb(&self, key: String, rgb: &mut scrap::ImageRgb) { + todo!() + } +} + +fn session_rgba_cb(key: String, rgb: &mut scrap::ImageRgb) { + SESSION_HANDLER.session_rgba_cb(key, rgb); +} diff --git a/src/plugin/plugins.rs b/src/plugin/plugins.rs index a17f24980..4441d314d 100644 --- a/src/plugin/plugins.rs +++ b/src/plugin/plugins.rs @@ -14,7 +14,7 @@ use std::{ collections::HashMap, ffi::{c_char, c_void}, path::PathBuf, - sync::{Arc, RwLock}, + sync::{Arc, RwLock}, os::raw::c_uint, }; const METHOD_HANDLE_UI: &[u8; 10] = b"handle_ui\0"; @@ -91,6 +91,14 @@ type CallbackGetId = extern "C" fn() -> *const c_char; /// level: "error", "warn", "info", "debug", "trace". /// msg: The message. type CallbackLog = extern "C" fn(level: *const c_char, msg: *const c_char); + +/// Callback to the librustdesk core. +/// +/// method: the method name of this callback. +/// json: the json data for the parameters. The argument *must* be non-null. +/// raw: the binary data for this call, nullable. +/// raw_len: the length of this binary data, only valid when we pass raw data to `raw`. +type CallbackNative = extern "C" fn(method: *const c_char, json: *const c_char, raw: *const c_void, raw_len: usize) -> super::native::NativeReturnValue; /// The main function of the plugin on the client(self) side. /// /// method: The method. "handle_ui" or "handle_peer" @@ -140,6 +148,7 @@ struct Callbacks { get_conf: CallbackGetConf, get_id: CallbackGetId, log: CallbackLog, + native: CallbackNative } /// The plugin initialize data. @@ -334,6 +343,7 @@ fn load_plugin_path(path: &str) -> ResultType<()> { get_conf: config::cb_get_conf, get_id: config::cb_get_local_peer_id, log: super::plog::plugin_log, + native: super::native::cb_native_data }, }; plugin.init(&init_data, path)?; diff --git a/src/plugins.rs b/src/plugins.rs deleted file mode 100644 index dfbdc7753..000000000 --- a/src/plugins.rs +++ /dev/null @@ -1,201 +0,0 @@ -use std::{ - collections::HashMap, - ffi::{c_char, CStr}, - sync::{Arc, RwLock}, -}; - -use hbb_common::{ - anyhow::Error, - log::{debug, error}, -}; -use lazy_static::lazy_static; -use libloading::{Library, Symbol}; - -lazy_static! { - pub static ref PLUGIN_REGISTRAR: Arc> = - Arc::new(PluginRegistar::::default()); -} -// API needed to be implemented by plugins. -pub type PluginInitFunc = fn() -> i32; -// API needed to be implemented by plugins. -pub type PluginIdFunc = fn() -> *const c_char; -// API needed to be implemented by plugins. -pub type PluginNameFunc = fn() -> *const c_char; -// API needed to be implemented by plugins. -pub type PluginDisposeFunc = fn() -> i32; - -pub trait Plugin { - // Return: the unique ID which identifies this plugin. - fn plugin_id(&self) -> String; - // Return: the name which is human-readable. - fn plugin_name(&self) -> String; - // Return: the virtual table of the plugin. - fn plugin_vt(&self) -> &RustDeskPluginTable; -} - -#[repr(C)] -#[derive(Default, Clone)] -pub struct RustDeskPluginTable { - pub init: Option, // NonNull - pub dispose: Option, // NonNull -} - -pub struct PluginImpl { - vt: RustDeskPluginTable, - pub id: String, - pub name: String, - _inner: Option, -} - -impl Default for PluginImpl { - fn default() -> Self { - Self { - _inner: None, - vt: Default::default(), - id: Default::default(), - name: Default::default(), - } - } -} - -impl Plugin for PluginImpl { - fn plugin_id(&self) -> String { - self.id.to_owned() - } - - fn plugin_name(&self) -> String { - self.name.to_owned() - } - - fn plugin_vt(&self) -> &RustDeskPluginTable { - &self.vt - } -} - -#[derive(Default, Clone)] -pub struct PluginRegistar { - plugins: Arc>>, -} - -impl PluginRegistar

{ - pub fn load_plugin(&self, path: *const c_char) -> i32 { - let p = unsafe { CStr::from_ptr(path) }; - let lib_path = p.to_str().unwrap_or("").to_owned(); - let lib = unsafe { libloading::Library::new(lib_path.as_str()) }; - match lib { - Ok(lib) => match lib.try_into() { - Ok(plugin) => { - let plugin: PluginImpl = plugin; - // try to initialize this plugin - if let Some(init) = plugin.plugin_vt().init { - let init_ret = init(); - if init_ret != 0 { - error!( - "Error when initializing the plugin {} with error code {}.", - plugin.name, init_ret - ); - return init_ret; - } - } - PLUGIN_REGISTRAR - .plugins - .write() - .unwrap() - .insert(lib_path, plugin); - return 0; - } - Err(err) => { - eprintln!("Load plugin failed: {}", err); - } - }, - Err(err) => { - eprintln!("Load plugin failed: {}", err); - } - } - -1 - } - - pub fn unload_plugin(&self, path: *const c_char) -> i32 { - let p = unsafe { CStr::from_ptr(path) }; - let lib_path = p.to_str().unwrap_or("").to_owned(); - match PLUGIN_REGISTRAR.plugins.write().unwrap().remove(&lib_path) { - Some(plugin) => { - if let Some(dispose) = plugin.plugin_vt().dispose { - return dispose(); - } - 0 - } - None => -1, - } - } -} - -impl TryFrom for PluginImpl { - type Error = Error; - - fn try_from(library: Library) -> Result { - let init: Symbol = unsafe { library.get(b"plugin_init")? }; - let dispose: Symbol = unsafe { library.get(b"plugin_dispose")? }; - let id_func: Symbol = unsafe { library.get(b"plugin_id")? }; - let id_string = unsafe { - std::ffi::CStr::from_ptr(id_func()) - .to_str() - .unwrap_or("") - .to_owned() - }; - let name_func: Symbol = unsafe { library.get(b"plugin_name")? }; - let name_string = unsafe { - std::ffi::CStr::from_ptr(name_func()) - .to_str() - .unwrap_or("") - .to_owned() - }; - debug!( - "Successfully loaded the plugin called {} with id {}.", - name_string, id_string - ); - Ok(Self { - vt: RustDeskPluginTable { - init: Some(*init), - dispose: Some(*dispose), - }, - id: id_string, - name: name_string, - _inner: Some(library), - }) - } -} - -#[test] -#[cfg(target_os = "linux")] -fn test_plugin() { - use std::io::Write; - let mut cmd = std::process::Command::new("cargo"); - cmd.current_dir("./examples/custom_plugin"); - // Strip this shared library. - cmd.env("RUSTFLAGS", "-C link-arg=-s"); - cmd.arg("build"); - // Spawn the compiler process. - let mut child = cmd.spawn().unwrap(); - // Wait for the compiler to finish. - let status = child.wait().unwrap(); - assert!(status.success()); - // Load the library. - let lib = unsafe { - Library::new("./examples/custom_plugin/target/debug/libcustom_plugin.so").unwrap() - }; - let plugin: PluginImpl = lib.try_into().unwrap(); - assert!(plugin._inner.is_some()); - assert!(plugin.name == "A Template Rust Plugin"); - assert!(plugin.id == "TemplatePlugin"); - println!( - "plugin vt size: {}", - std::mem::size_of::() - ); - assert!(PLUGIN_REGISTRAR - .plugins - .write() - .unwrap() - .insert("test".to_owned(), plugin) - .is_none()); -}