Merge pull request #3989 from Kingtous/feat/plugins
feat: add the plugin registrar and api tables for both sides of rustdesk and plugins
This commit is contained in:
		
						commit
						295e5bbd14
					
				
							
								
								
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -5146,6 +5146,7 @@ dependencies = [ | |||||||
|  "include_dir", |  "include_dir", | ||||||
|  "jni 0.19.0", |  "jni 0.19.0", | ||||||
|  "lazy_static", |  "lazy_static", | ||||||
|  |  "libloading", | ||||||
|  "libpulse-binding", |  "libpulse-binding", | ||||||
|  "libpulse-simple-binding", |  "libpulse-simple-binding", | ||||||
|  "mac_address", |  "mac_address", | ||||||
|  | |||||||
| @ -70,6 +70,7 @@ hex = "0.4" | |||||||
| reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false } | reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false } | ||||||
| chrono = "0.4" | chrono = "0.4" | ||||||
| cidr-utils = "0.5" | cidr-utils = "0.5" | ||||||
|  | libloading = "0.7" | ||||||
| 
 | 
 | ||||||
| [target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies] | [target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies] | ||||||
| cpal = "0.14" | cpal = "0.14" | ||||||
|  | |||||||
							
								
								
									
										35
									
								
								src/api.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/api.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | |||||||
|  | use crate::plugins::PLUGIN_REGISTRAR; | ||||||
|  | 
 | ||||||
|  | // API provided by RustDesk.
 | ||||||
|  | pub type LoadPluginFunc = fn(*const i8) -> i32; | ||||||
|  | pub type UnloadPluginFunc = fn(*const i8) -> i32; | ||||||
|  | 
 | ||||||
|  | #[repr(C)] | ||||||
|  | pub struct RustDeskApiTable { | ||||||
|  |     pub register_plugin: LoadPluginFunc, | ||||||
|  |     pub unload_plugin: UnloadPluginFunc, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[no_mangle] | ||||||
|  | fn load_plugin(path: *const i8) -> i32 { | ||||||
|  |     PLUGIN_REGISTRAR.load_plugin(path) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[no_mangle] | ||||||
|  | fn unload_plugin(path: *const i8) -> i32 { | ||||||
|  |     PLUGIN_REGISTRAR.unload_plugin(path) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[no_mangle] | ||||||
|  | fn get_api_table() -> RustDeskApiTable { | ||||||
|  |     RustDeskApiTable::default() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Default for RustDeskApiTable { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self { | ||||||
|  |             register_plugin: load_plugin, | ||||||
|  |             unload_plugin: unload_plugin, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -43,6 +43,11 @@ mod license; | |||||||
| #[cfg(not(any(target_os = "android", target_os = "ios")))] | #[cfg(not(any(target_os = "android", target_os = "ios")))] | ||||||
| mod port_forward; | mod port_forward; | ||||||
| 
 | 
 | ||||||
|  | #[cfg(not(any(target_os = "android", target_os = "ios")))] | ||||||
|  | mod plugins; | ||||||
|  | #[cfg(not(any(target_os = "android", target_os = "ios")))] | ||||||
|  | mod api; | ||||||
|  | 
 | ||||||
| mod tray; | mod tray; | ||||||
| 
 | 
 | ||||||
| mod ui_cm_interface; | mod ui_cm_interface; | ||||||
|  | |||||||
							
								
								
									
										186
									
								
								src/plugins.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								src/plugins.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,186 @@ | |||||||
|  | use std::{ | ||||||
|  |     collections::HashMap, | ||||||
|  |     ffi::{c_char, CStr}, | ||||||
|  |     sync::{Arc, RwLock}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | use hbb_common::{anyhow::Error, log::debug}; | ||||||
|  | use lazy_static::lazy_static; | ||||||
|  | use libloading::{Library, Symbol}; | ||||||
|  | 
 | ||||||
|  | lazy_static! { | ||||||
|  |     pub static ref PLUGIN_REGISTRAR: Arc<PluginRegistar<PluginImpl>> = | ||||||
|  |         Arc::new(PluginRegistar::<PluginImpl>::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<PluginInitFunc>, | ||||||
|  |     pub dispose: Option<PluginDisposeFunc>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub struct PluginImpl { | ||||||
|  |     vt: RustDeskPluginTable, | ||||||
|  |     pub id: String, | ||||||
|  |     pub name: String, | ||||||
|  |     _inner: Option<Library>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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<P: Plugin> { | ||||||
|  |     plugins: Arc<RwLock<HashMap<String, P>>>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<P: Plugin> PluginRegistar<P> { | ||||||
|  |     pub fn load_plugin(&self, path: *const i8) -> 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) => { | ||||||
|  |                     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 i8) -> 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(_) => 0, | ||||||
|  |             None => -1, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl TryFrom<Library> for PluginImpl { | ||||||
|  |     type Error = Error; | ||||||
|  | 
 | ||||||
|  |     fn try_from(library: Library) -> Result<Self, Self::Error> { | ||||||
|  |         let init: Symbol<PluginInitFunc> = unsafe { library.get(b"plugin_init")? }; | ||||||
|  |         let dispose: Symbol<PluginDisposeFunc> = unsafe { library.get(b"plugin_dispose")? }; | ||||||
|  |         let id_func: Symbol<PluginIdFunc> = 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<PluginNameFunc> = 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 code = " | ||||||
|  |     const char* plugin_name(){return \"test_name\";};
 | ||||||
|  |     const char* plugin_id(){return \"test_id\"; }
 | ||||||
|  |     int plugin_init() {return 0;} | ||||||
|  |     int plugin_dispose() {return 0;} | ||||||
|  |     ";
 | ||||||
|  |     let mut f = std::fs::File::create("test.c").unwrap(); | ||||||
|  |     f.write_all(code.as_bytes()).unwrap(); | ||||||
|  |     f.flush().unwrap(); | ||||||
|  |     let mut cmd = std::process::Command::new("cc"); | ||||||
|  |     cmd.arg("-fPIC") | ||||||
|  |         .arg("-shared") | ||||||
|  |         .arg("test.c") | ||||||
|  |         .arg("-o") | ||||||
|  |         .arg("libtest.so"); | ||||||
|  |     // 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("./libtest.so").unwrap() }; | ||||||
|  |     let plugin: PluginImpl = lib.try_into().unwrap(); | ||||||
|  |     assert!(plugin._inner.is_some()); | ||||||
|  |     assert!(plugin.name == "test_name"); | ||||||
|  |     assert!(plugin.id == "test_id"); | ||||||
|  |     assert!(PLUGIN_REGISTRAR | ||||||
|  |         .plugins | ||||||
|  |         .write() | ||||||
|  |         .unwrap() | ||||||
|  |         .insert("test".to_owned(), plugin) | ||||||
|  |         .is_none()); | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user