From ecf8c2664c30f4f0d1ab831e9e1ea0ae42696ff4 Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 18 Apr 2023 23:02:37 +0800 Subject: [PATCH 1/5] plugin, tmp commit Signed-off-by: fufesou --- Cargo.lock | 1 - Cargo.toml | 2 +- libs/hbb_common/protos/message.proto | 6 + src/flutter.rs | 54 +++++++-- src/flutter_ffi.rs | 132 +++++++++------------- src/lib.rs | 1 + src/plugin/callback_msg.rs | 112 ++++++++++++++++++ src/plugin/config.rs | 0 src/plugin/desc.rs | 126 +++++++++++++++++++++ src/plugin/mod.rs | 19 ++++ src/plugin/plugins.rs | 162 +++++++++++++++++++++++++++ src/server.rs | 18 ++- src/server/dbus.rs | 24 ++-- src/ui_session_interface.rs | 8 ++ 14 files changed, 547 insertions(+), 118 deletions(-) create mode 100644 src/plugin/callback_msg.rs create mode 100644 src/plugin/config.rs create mode 100644 src/plugin/desc.rs create mode 100644 src/plugin/mod.rs create mode 100644 src/plugin/plugins.rs diff --git a/Cargo.lock b/Cargo.lock index 299d99c14..45f809680 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5133,7 +5133,6 @@ dependencies = [ "include_dir", "jni 0.19.0", "lazy_static", - "libloading", "libpulse-binding", "libpulse-simple-binding", "mac_address", diff --git a/Cargo.toml b/Cargo.toml index cd76b7d01..d70c202c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ hwcodec = ["scrap/hwcodec"] mediacodec = ["scrap/mediacodec"] linux_headless = ["pam", "users"] virtual_display_driver = ["virtual_display"] +plugin_framework = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -69,7 +70,6 @@ hex = "0.4" reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false } chrono = "0.4" cidr-utils = "0.5" -libloading = "0.7" [target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies] cpal = "0.15" diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index 42d2d7c09..b4eb9bf4b 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -600,6 +600,11 @@ message SwitchSidesResponse { message SwitchBack {} +message Plugin { + string id = 1; + bytes content = 2; +} + message Misc { oneof union { ChatMessage chat_message = 4; @@ -621,6 +626,7 @@ message Misc { SwitchSidesRequest switch_sides_request = 21; SwitchBack switch_back = 22; Resolution change_resolution = 24; + Plugin plugin = 25; } } diff --git a/src/flutter.rs b/src/flutter.rs index 34fe469fc..d2cba9dd7 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -29,20 +29,20 @@ use std::{ /// tag "main" for [Desktop Main Page] and [Mobile (Client and Server)] (the mobile don't need multiple windows, only one global event stream is needed) /// tag "cm" only for [Desktop CM Page] -pub(super) const APP_TYPE_MAIN: &str = "main"; +pub(crate) const APP_TYPE_MAIN: &str = "main"; #[cfg(not(any(target_os = "android", target_os = "ios")))] -pub(super) const APP_TYPE_CM: &str = "cm"; +pub(crate) const APP_TYPE_CM: &str = "cm"; #[cfg(any(target_os = "android", target_os = "ios"))] -pub(super) const APP_TYPE_CM: &str = "main"; +pub(crate) const APP_TYPE_CM: &str = "main"; -pub(super) const APP_TYPE_DESKTOP_REMOTE: &str = "remote"; -pub(super) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer"; -pub(super) const APP_TYPE_DESKTOP_PORT_FORWARD: &str = "port forward"; +pub(crate) const APP_TYPE_DESKTOP_REMOTE: &str = "remote"; +pub(crate) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer"; +pub(crate) const APP_TYPE_DESKTOP_PORT_FORWARD: &str = "port forward"; lazy_static::lazy_static! { pub(crate) static ref CUR_SESSION_ID: RwLock = Default::default(); pub(crate) static ref SESSIONS: RwLock>> = Default::default(); - pub(crate) static ref GLOBAL_EVENT_STREAM: RwLock>> = Default::default(); // rust to dart event channel + static ref GLOBAL_EVENT_STREAM: RwLock>> = Default::default(); // rust to dart event channel } #[cfg(all(target_os = "windows", feature = "flutter_texture_render"))] @@ -249,14 +249,18 @@ impl FlutterHandler { /// /// * `name` - The name of the event. /// * `event` - Fields of the event content. - fn push_event(&self, name: &str, event: Vec<(&str, &str)>) { + pub fn push_event(&self, name: &str, event: Vec<(&str, &str)>) -> Option { let mut h: HashMap<&str, &str> = event.iter().cloned().collect(); assert!(h.get("name").is_none()); h.insert("name", name); let out = serde_json::ser::to_string(&h).unwrap_or("".to_owned()); - if let Some(stream) = &*self.event_stream.read().unwrap() { - stream.add(EventToUI::Event(out)); - } + Some( + self.event_stream + .read() + .unwrap() + .as_ref()? + .add(EventToUI::Event(out)), + ) } pub(crate) fn close_event_stream(&mut self) { @@ -627,7 +631,7 @@ impl InvokeUiSession for FlutterHandler { } fn on_voice_call_closed(&self, reason: &str) { - self.push_event("on_voice_call_closed", [("reason", reason)].into()) + let _res = self.push_event("on_voice_call_closed", [("reason", reason)].into()); } fn on_voice_call_waiting(&self) { @@ -1007,3 +1011,29 @@ pub fn session_register_texture(id: *const char, ptr: usize) { #[no_mangle] #[cfg(not(feature = "flutter_texture_render"))] pub fn session_register_texture(_id: *const char, _ptr: usize) {} + +pub fn push_session_event(peer: &str, name: &str, event: Vec<(&str, &str)>) -> Option { + SESSIONS.read().unwrap().get(peer)?.push_event(name, event) +} + +pub fn push_global_event(channel: &str, event: String) -> Option { + Some(GLOBAL_EVENT_STREAM.read().unwrap().get(channel)?.add(event)) +} + +pub fn start_global_event_stream(s: StreamSink, app_type: String) -> ResultType<()> { + if let Some(_) = GLOBAL_EVENT_STREAM + .write() + .unwrap() + .insert(app_type.clone(), s) + { + log::warn!( + "Global event stream of type {} is started before, but now removed", + app_type + ); + } + Ok(()) +} + +pub fn stop_global_event_stream(app_type: String) { + let _ = GLOBAL_EVENT_STREAM.write().unwrap().remove(&app_type); +} diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 3fdc5e48f..fa1cf6cc8 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -53,27 +53,6 @@ pub enum EventToUI { Rgba, } -pub fn start_global_event_stream(s: StreamSink, app_type: String) -> ResultType<()> { - if let Some(_) = flutter::GLOBAL_EVENT_STREAM - .write() - .unwrap() - .insert(app_type.clone(), s) - { - log::warn!( - "Global event stream of type {} is started before, but now removed", - app_type - ); - } - Ok(()) -} - -pub fn stop_global_event_stream(app_type: String) { - let _ = flutter::GLOBAL_EVENT_STREAM - .write() - .unwrap() - .remove(&app_type); -} - pub fn host_stop_system_key_propagate(_stopped: bool) { #[cfg(windows)] crate::platform::windows::stop_system_key_propagate(_stopped); @@ -338,7 +317,13 @@ pub fn session_handle_flutter_key_event( down_or_up: bool, ) { if let Some(session) = SESSIONS.read().unwrap().get(&id) { - session.handle_flutter_key_event(&name, platform_code, position_code, lock_modes, down_or_up); + session.handle_flutter_key_event( + &name, + platform_code, + position_code, + lock_modes, + down_or_up, + ); } } @@ -739,20 +724,18 @@ pub fn main_load_recent_peers() { .drain(..) .map(|(id, _, p)| peer_to_map(id, p)) .collect(); - if let Some(s) = flutter::GLOBAL_EVENT_STREAM - .read() - .unwrap() - .get(flutter::APP_TYPE_MAIN) - { - let data = HashMap::from([ - ("name", "load_recent_peers".to_owned()), - ( - "peers", - serde_json::ser::to_string(&peers).unwrap_or("".to_owned()), - ), - ]); - s.add(serde_json::ser::to_string(&data).unwrap_or("".to_owned())); - }; + + let data = HashMap::from([ + ("name", "load_recent_peers".to_owned()), + ( + "peers", + serde_json::ser::to_string(&peers).unwrap_or("".to_owned()), + ), + ]); + let _res = flutter::push_global_event( + flutter::APP_TYPE_MAIN, + serde_json::ser::to_string(&data).unwrap_or("".to_owned()), + ); } } @@ -790,38 +773,33 @@ pub fn main_load_fav_peers() { } }) .collect(); - if let Some(s) = flutter::GLOBAL_EVENT_STREAM - .read() - .unwrap() - .get(flutter::APP_TYPE_MAIN) - { - let data = HashMap::from([ - ("name", "load_fav_peers".to_owned()), - ( - "peers", - serde_json::ser::to_string(&peers).unwrap_or("".to_owned()), - ), - ]); - s.add(serde_json::ser::to_string(&data).unwrap_or("".to_owned())); - }; + + let data = HashMap::from([ + ("name", "load_fav_peers".to_owned()), + ( + "peers", + serde_json::ser::to_string(&peers).unwrap_or("".to_owned()), + ), + ]); + let _res = flutter::push_global_event( + flutter::APP_TYPE_MAIN, + serde_json::ser::to_string(&data).unwrap_or("".to_owned()), + ); } } pub fn main_load_lan_peers() { - if let Some(s) = flutter::GLOBAL_EVENT_STREAM - .read() - .unwrap() - .get(flutter::APP_TYPE_MAIN) - { - let data = HashMap::from([ - ("name", "load_lan_peers".to_owned()), - ( - "peers", - serde_json::to_string(&get_lan_peers()).unwrap_or_default(), - ), - ]); - s.add(serde_json::ser::to_string(&data).unwrap_or("".to_owned())); - }; + let data = HashMap::from([ + ("name", "load_lan_peers".to_owned()), + ( + "peers", + serde_json::to_string(&get_lan_peers()).unwrap_or_default(), + ), + ]); + let _res = flutter::push_global_event( + flutter::APP_TYPE_MAIN, + serde_json::ser::to_string(&data).unwrap_or("".to_owned()), + ); } pub fn main_remove_discovered(id: String) { @@ -835,10 +813,9 @@ fn main_broadcast_message(data: &HashMap<&str, &str>) { flutter::APP_TYPE_DESKTOP_PORT_FORWARD, ]; + let event = serde_json::ser::to_string(&data).unwrap_or("".to_owned()); for app in apps { - if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(app) { - s.add(serde_json::ser::to_string(data).unwrap_or("".to_owned())); - }; + let _res = flutter::push_global_event(app, event.clone()); } } @@ -1222,18 +1199,15 @@ unsafe extern "C" fn translate(name: *const c_char, locale: *const c_char) -> *c } fn handle_query_onlines(onlines: Vec, offlines: Vec) { - if let Some(s) = flutter::GLOBAL_EVENT_STREAM - .read() - .unwrap() - .get(flutter::APP_TYPE_MAIN) - { - let data = HashMap::from([ - ("name", "callback_query_onlines".to_owned()), - ("onlines", onlines.join(",")), - ("offlines", offlines.join(",")), - ]); - s.add(serde_json::ser::to_string(&data).unwrap_or("".to_owned())); - }; + let data = HashMap::from([ + ("name", "callback_query_onlines".to_owned()), + ("onlines", onlines.join(",")), + ("offlines", offlines.join(",")), + ]); + let _res = flutter::push_global_event( + flutter::APP_TYPE_MAIN, + serde_json::ser::to_string(&data).unwrap_or("".to_owned()), + ); } pub fn query_onlines(ids: Vec) { diff --git a/src/lib.rs b/src/lib.rs index 3f48be87d..ad9229ab5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,6 +48,7 @@ mod license; #[cfg(not(any(target_os = "android", target_os = "ios")))] mod port_forward; +#[cfg(all(feature = "flutter", feature = "plugin_framework"))] #[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(any(feature = "flutter"))] pub mod api; diff --git a/src/plugin/callback_msg.rs b/src/plugin/callback_msg.rs new file mode 100644 index 000000000..62962cc83 --- /dev/null +++ b/src/plugin/callback_msg.rs @@ -0,0 +1,112 @@ +use super::cstr_to_string; +use crate::flutter::{self, APP_TYPE_CM, APP_TYPE_MAIN, SESSIONS}; +use hbb_common::{lazy_static, libc, log, message_proto::Plugin, ResultType}; +use serde_derive::{Deserialize, Serialize}; +use serde_json; +use std::{ + collections::HashMap, + ffi::{c_char, CStr}, + sync::Arc, +}; + +const MSG_TO_PEER_TARGET: &str = "peer"; +const MSG_TO_UI_TARGET: &str = "ui"; + +#[allow(dead_code)] +const MSG_TO_UI_FLUTTER_CHANNEL_MAIN: u16 = 0x01 << 0; +#[allow(dead_code)] +#[cfg(not(any(target_os = "android", target_os = "ios")))] +const MSG_TO_UI_FLUTTER_CHANNEL_CM: u16 = 0x01 << 1; +#[cfg(any(target_os = "android", target_os = "ios"))] +const MSG_TO_UI_FLUTTER_CHANNEL_CM: u16 = 0x01; +const MSG_TO_UI_FLUTTER_CHANNEL_REMOTE: u16 = 0x01 << 2; +#[allow(dead_code)] +const MSG_TO_UI_FLUTTER_CHANNEL_TRANSFER: u16 = 0x01 << 3; +#[allow(dead_code)] +const MSG_TO_UI_FLUTTER_CHANNEL_FORWARD: u16 = 0x01 << 4; + +lazy_static::lazy_static! { + static ref MSG_TO_UI_FLUTTER_CHANNELS: Arc> = { + let channels = HashMap::from([ + (MSG_TO_UI_FLUTTER_CHANNEL_MAIN, APP_TYPE_MAIN.to_string()), + (MSG_TO_UI_FLUTTER_CHANNEL_CM, APP_TYPE_CM.to_string()), + ]); + Arc::new(channels) + }; +} + +/// Callback to send message to peer or ui. +/// peer, target, id are utf8 strings(null terminated). +/// +/// peer: The peer id. +/// target: "peer" or "ui". +/// id: The id of this plugin. +/// content: The content. +/// len: The length of the content. +pub fn callback_msg( + peer: *const c_char, + target: *const c_char, + id: *const c_char, + content: *const c_char, + len: usize, +) { + macro_rules! callback_msg_field { + ($field: ident) => { + let $field = match cstr_to_string($field) { + Err(e) => { + log::error!("Failed to convert {} to string, {}", stringify!($field), e); + return; + } + Ok(v) => v, + }; + }; + } + callback_msg_field!(peer); + callback_msg_field!(target); + callback_msg_field!(id); + + match &target as _ { + MSG_TO_PEER_TARGET => { + if let Some(session) = SESSIONS.write().unwrap().get_mut(&peer) { + let content_slice = + unsafe { std::slice::from_raw_parts(content as *const u8, len) }; + let content_vec = Vec::from(content_slice); + let plugin = Plugin { + id, + content: bytes::Bytes::from(content_vec), + ..Default::default() + }; + session.send_plugin(plugin); + } + } + MSG_TO_UI_TARGET => { + let content_slice = unsafe { std::slice::from_raw_parts(content as *const u8, len) }; + let channel = u16::from_be_bytes([content_slice[0], content_slice[1]]); + let content = std::string::String::from_utf8(content_slice[2..].to_vec()) + .unwrap_or("".to_string()); + let mut m = HashMap::new(); + m.insert("name", "plugin"); + m.insert("peer", &peer); + m.insert("content", &content); + let event = serde_json::to_string(&m).unwrap_or("".to_string()); + for (k, v) in MSG_TO_UI_FLUTTER_CHANNELS.iter() { + if channel & k != 0 { + let _res = flutter::push_global_event(v as _, event.clone()); + } + } + if channel & MSG_TO_UI_FLUTTER_CHANNEL_REMOTE != 0 + || channel & MSG_TO_UI_FLUTTER_CHANNEL_TRANSFER != 0 + || channel & MSG_TO_UI_FLUTTER_CHANNEL_FORWARD != 0 + { + let _res = flutter::push_session_event( + APP_TYPE_CM, + "plugin", + vec![("peer", &peer), ("content", &content)], + ); + } + } + _ => { + log::error!("Unknown target {}", target); + } + } +} diff --git a/src/plugin/config.rs b/src/plugin/config.rs new file mode 100644 index 000000000..e69de29bb diff --git a/src/plugin/desc.rs b/src/plugin/desc.rs new file mode 100644 index 000000000..8d345b7d2 --- /dev/null +++ b/src/plugin/desc.rs @@ -0,0 +1,126 @@ +use hbb_common::ResultType; +use serde_derive::Deserialize; +use serde_json; +use std::collections::HashMap; +use std::ffi::{c_char, CStr}; + +#[derive(Debug, Deserialize)] +pub struct UiButton { + key: String, + text: String, + icon: String, + tooltip: String, + action: String, // The action to be triggered when the button is clicked. +} + +#[derive(Debug, Deserialize)] +pub struct UiCheckbox { + key: String, + text: String, + tooltip: String, + action: String, // The action to be triggered when the checkbox is checked or unchecked. +} + +#[derive(Debug, Deserialize)] +#[serde(tag = "t", content = "c")] +pub enum UiType { + Button(UiButton), + Checkbox(UiCheckbox), +} + +#[derive(Debug, Deserialize)] +pub struct Location { + core: String, + ui: HashMap, +} + +#[derive(Debug, Deserialize)] +pub struct ConfigItem { + key: String, + value: String, + default: String, + description: String, +} + +#[derive(Debug, Deserialize)] +pub struct Configs { + pub local: Vec, + pub session: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct Config { + pub host: Configs, + pub client: Configs, +} + +#[derive(Debug, Deserialize)] +pub struct Desc { + id: String, + name: String, + version: String, + description: String, + author: String, + home: String, + license: String, + published: String, + released: String, + github: String, + location: Location, + config: Config, +} + +impl Desc { + pub fn from_cstr(s: *const c_char) -> ResultType { + let s = unsafe { CStr::from_ptr(s) }; + Ok(serde_json::from_str(s.to_str()?)?) + } + + pub fn id(&self) -> &str { + &self.id + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn version(&self) -> &str { + &self.version + } + + pub fn description(&self) -> &str { + &self.description + } + + pub fn author(&self) -> &str { + &self.author + } + + pub fn home(&self) -> &str { + &self.home + } + + pub fn license(&self) -> &str { + &self.license + } + + pub fn published(&self) -> &str { + &self.published + } + + pub fn released(&self) -> &str { + &self.released + } + + pub fn github(&self) -> &str { + &self.github + } + + pub fn location(&self) -> &Location { + &self.location + } + + pub fn config(&self) -> &Config { + &self.config + } +} diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs new file mode 100644 index 000000000..ab233a503 --- /dev/null +++ b/src/plugin/mod.rs @@ -0,0 +1,19 @@ +use hbb_common::{dlopen::symbor::Library, log, ResultType}; +use std::{ + ffi::{c_char, CStr}, + path::Path, +}; + +mod callback_msg; +mod config; +pub mod desc; +mod plugins; + +pub use plugins::load_plugins; + +#[inline] +fn cstr_to_string(cstr: *const c_char) -> ResultType { + Ok(String::from_utf8(unsafe { + CStr::from_ptr(cstr).to_bytes().to_vec() + })?) +} diff --git a/src/plugin/plugins.rs b/src/plugin/plugins.rs new file mode 100644 index 000000000..d93ac5938 --- /dev/null +++ b/src/plugin/plugins.rs @@ -0,0 +1,162 @@ +use std::{ + collections::HashMap, + ffi::{c_char, CStr}, + path::PathBuf, + sync::{Arc, RwLock}, +}; + +use super::{callback_msg, desc::Desc}; +use hbb_common::{ + anyhow::Error, + bail, + dlopen::symbor::Library, + lazy_static, libc, log, + log::{debug, error}, + ResultType, +}; + +lazy_static::lazy_static! { + pub static ref PLUGINS: Arc>> = Default::default(); +} + +/// Initialize the plugins. +/// Return 0 if success. +pub type PluginFuncInit = fn() -> i32; +/// Reset the plugin. +/// Return 0 if success. +pub type PluginFuncReset = fn() -> i32; +/// Clear the plugin. +/// Return 0 if success. +pub type PluginFuncClear = fn() -> i32; +/// Get the description of the plugin. +/// Return the description. The plugin allocate memory with `libc::malloc` and return the pointer. +pub type PluginFuncDesc = fn() -> *const c_char; +/// Callback to send message to peer or ui. +/// peer, target, id are utf8 strings(null terminated). +/// +/// peer: The peer id. +/// target: "peer" or "ui". +/// id: The id of this plugin. +/// content: The content. +/// len: The length of the content. +type PluginFuncCallbackMsg = fn( + peer: *const c_char, + target: *const c_char, + id: *const c_char, + content: *const c_char, + len: usize, +); +pub type PluginFuncSetCallbackMsg = fn(PluginFuncCallbackMsg); +/// The main function of the plugin. +/// method: The method. "handle_ui" or "handle_peer" +/// args: The arguments. +/// out: The output. The plugin allocate memory with `libc::malloc` and set the pointer to `out`. +/// out_len: The length of the output. +/// Return 0 if success. +pub type PluginFuncCall = fn( + method: *const c_char, + args: *const c_char, + out: *mut *mut c_char, + out_len: *mut usize, +) -> i32; + +macro_rules! make_plugin { + ($($field:ident : $tp:ty),+) => { + pub struct Plugin { + _lib: Library, + path: String, + desc: Option, + $($field: $tp),+ + } + + impl Plugin { + fn new(path: &str) -> ResultType { + let lib = match Library::open(path) { + Ok(lib) => lib, + Err(e) => { + bail!("Failed to load library {}, {}", path, e); + } + }; + + $(let $field = match unsafe { lib.symbol::<$tp>(stringify!($field)) } { + Ok(m) => { + log::info!("method found {}", stringify!($field)); + *m + }, + Err(e) => { + bail!("Failed to load {} func {}, {}", path, stringify!($field), e); + } + } + ;)+ + + Ok(Self { + _lib: lib, + path: path.to_string(), + desc: None, + $( $field ),+ + }) + } + } + } +} + +make_plugin!( + fn_init: PluginFuncInit, + fn_reset: PluginFuncReset, + fn_clear: PluginFuncClear, + fn_desc: PluginFuncDesc, + fn_set_cb_msg: PluginFuncSetCallbackMsg, + fn_call: PluginFuncCall +); + +pub fn load_plugins(dir: &str) -> ResultType<()> { + for entry in std::fs::read_dir(dir)? { + match entry { + Ok(entry) => { + let path = entry.path(); + if path.is_file() { + let path = path.to_str().unwrap_or(""); + if path.ends_with(".so") { + if let Err(e) = load_plugin(path) { + log::error!("{e}"); + } + } + } + } + Err(e) => { + log::error!("Failed to read dir entry, {}", e); + } + } + } + Ok(()) +} + +pub fn unload_plugin(id: &str) { + if let Some(plugin) = PLUGINS.write().unwrap().remove(id) { + let _ret = (plugin.fn_clear)(); + } +} + +pub fn reload_plugin(id: &str) -> ResultType<()> { + let path = match PLUGINS.read().unwrap().get(id) { + Some(plugin) => plugin.path.clone(), + None => bail!("Plugin {} not found", id), + }; + unload_plugin(id); + load_plugin(&path) +} + +fn load_plugin(path: &str) -> ResultType<()> { + let mut plugin = Plugin::new(path)?; + let desc = (plugin.fn_desc)(); + let desc_res = Desc::from_cstr(desc); + unsafe { + libc::free(desc as _); + } + let desc = desc_res?; + let id = desc.id().to_string(); + (plugin.fn_set_cb_msg)(callback_msg::callback_msg); + plugin.desc = Some(desc); + PLUGINS.write().unwrap().insert(id, plugin); + Ok(()) +} diff --git a/src/server.rs b/src/server.rs index 9c7fb2260..6a0371757 100644 --- a/src/server.rs +++ b/src/server.rs @@ -451,17 +451,13 @@ pub async fn start_ipc_url_server() { Ok(Some(data)) => match data { #[cfg(feature = "flutter")] Data::UrlLink(url) => { - 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!"); + let mut m = HashMap::new(); + m.insert("name", "on_url_scheme_received"); + m.insert("url", url.as_str()); + let event = serde_json::to_string(&m).unwrap(); + match crate::flutter::push_global_event(crate::flutter::APP_TYPE_MAIN, event) { + None => log::warn!("No main window app found!"), + Some(..) => {} } } _ => { diff --git a/src/server/dbus.rs b/src/server/dbus.rs index 081db3e8f..79aa62a1e 100644 --- a/src/server/dbus.rs +++ b/src/server/dbus.rs @@ -72,21 +72,17 @@ fn handle_client_message(builder: &mut IfaceBuilder<()>) { #[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"), - ("uni_links", _uni_links.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."); + let data = HashMap::from([ + ("name", "new_connection"), + ("uni_links", _uni_links.as_str()), + ]); + let event = serde_json::ser::to_string(&data).unwrap_or("".to_string()); + match crate::flutter::push_global_event(flutter::APP_TYPE_MAIN, event) { + None => log::error!("failed to find main event stream"), + Some(false) => { + log::error!("failed to add dbus message to flutter global dbus stream.") } - } else { - log::error!("failed to find main event stream"); + Some(true) => {} } } return Ok((DBUS_METHOD_RETURN_SUCCESS.to_string(),)); diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index db0aab0ae..2747a6f00 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -240,6 +240,14 @@ impl Session { self.send(Data::Message(msg)); } + pub fn send_plugin(&self, plugin: Plugin) { + let mut misc = Misc::new(); + misc.set_plugin(plugin); + let mut msg_out = Message::new(); + msg_out.set_misc(misc); + self.send(Data::Message(msg_out)); + } + pub fn get_audit_server(&self, typ: String) -> String { if self.lc.read().unwrap().conn_id <= 0 || LocalConfig::get_option("access_token").is_empty() From b9b2f76ae02671a83551918eadb4033379bf9615 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 19 Apr 2023 11:21:37 +0800 Subject: [PATCH 2/5] tmp commit Signed-off-by: fufesou --- src/plugin/callback_msg.rs | 13 +-- src/plugin/config.rs | 196 +++++++++++++++++++++++++++++++++++++ src/plugin/desc.rs | 31 +++--- src/plugin/mod.rs | 7 +- src/plugin/plugins.rs | 48 ++++++--- 5 files changed, 251 insertions(+), 44 deletions(-) diff --git a/src/plugin/callback_msg.rs b/src/plugin/callback_msg.rs index 62962cc83..c3a5a24d6 100644 --- a/src/plugin/callback_msg.rs +++ b/src/plugin/callback_msg.rs @@ -1,13 +1,8 @@ use super::cstr_to_string; use crate::flutter::{self, APP_TYPE_CM, APP_TYPE_MAIN, SESSIONS}; -use hbb_common::{lazy_static, libc, log, message_proto::Plugin, ResultType}; -use serde_derive::{Deserialize, Serialize}; +use hbb_common::{lazy_static, log, message_proto::Plugin}; use serde_json; -use std::{ - collections::HashMap, - ffi::{c_char, CStr}, - sync::Arc, -}; +use std::{collections::HashMap, ffi::c_char, sync::Arc}; const MSG_TO_PEER_TARGET: &str = "peer"; const MSG_TO_UI_TARGET: &str = "ui"; @@ -85,7 +80,7 @@ pub fn callback_msg( let content = std::string::String::from_utf8(content_slice[2..].to_vec()) .unwrap_or("".to_string()); let mut m = HashMap::new(); - m.insert("name", "plugin"); + m.insert("name", "plugin_event"); m.insert("peer", &peer); m.insert("content", &content); let event = serde_json::to_string(&m).unwrap_or("".to_string()); @@ -99,7 +94,7 @@ pub fn callback_msg( || channel & MSG_TO_UI_FLUTTER_CHANNEL_FORWARD != 0 { let _res = flutter::push_session_event( - APP_TYPE_CM, + &peer, "plugin", vec![("peer", &peer), ("content", &content)], ); diff --git a/src/plugin/config.rs b/src/plugin/config.rs index e69de29bb..7a9228425 100644 --- a/src/plugin/config.rs +++ b/src/plugin/config.rs @@ -0,0 +1,196 @@ +use super::desc::ConfigItem; +use hbb_common::{bail, config::Config as HbbConfig, lazy_static, ResultType}; +use serde_derive::{Deserialize, Serialize}; +use std::{ + ops::{Deref, DerefMut}, + sync::{Arc, Mutex}, + {collections::HashMap, path::PathBuf}, +}; + +lazy_static::lazy_static! { + static ref CONFIG_LOCAL: Arc>> = Default::default(); + static ref CONFIG_LOCAL_ITEMS: Arc>>> = Default::default(); + static ref CONFIG_PEERS: Arc>> = Default::default(); + static ref CONFIG_PEER_ITEMS: Arc>>> = Default::default(); +} + +#[derive(Debug, Default, Serialize, Deserialize)] +struct LocalConfig(HashMap); +#[derive(Debug, Default, Serialize, Deserialize)] +struct PeerConfig(HashMap); +type PeersConfig = HashMap; + +#[inline] +fn path_plugins(id: &str) -> PathBuf { + HbbConfig::path("plugins").join(id) +} + +impl Deref for LocalConfig { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for LocalConfig { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Deref for PeerConfig { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for PeerConfig { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl LocalConfig { + #[inline] + fn path(id: &str) -> PathBuf { + path_plugins(id).join("local.toml") + } + + #[inline] + pub fn load(id: &str) { + let mut conf = hbb_common::config::load_path::(Self::path(id)); + if let Some(items) = CONFIG_LOCAL_ITEMS.lock().unwrap().get(id) { + for item in items { + if !conf.contains_key(&item.key) { + conf.insert(item.key.to_owned(), item.default.to_owned()); + } + } + } + CONFIG_LOCAL.lock().unwrap().insert(id.to_owned(), conf); + } + + #[inline] + fn save(id: &str) -> ResultType<()> { + match CONFIG_LOCAL.lock().unwrap().get(id) { + Some(config) => hbb_common::config::store_path(Self::path(id), config), + None => bail!("No such plugin {}", id), + } + } + + #[inline] + pub fn get(id: &str, key: &str) -> Option { + if let Some(conf) = CONFIG_LOCAL.lock().unwrap().get(id) { + return conf.get(key).map(|s| s.to_owned()); + } + Self::load(id); + CONFIG_LOCAL + .lock() + .unwrap() + .get(id)? + .get(key) + .map(|s| s.to_owned()) + } + + #[inline] + pub fn set(id: &str, key: &str, value: &str) -> ResultType<()> { + match CONFIG_LOCAL.lock().unwrap().get_mut(id) { + Some(config) => { + config.insert(key.to_owned(), value.to_owned()); + hbb_common::config::store_path(Self::path(id), config) + } + None => bail!("No such plugin {}", id), + } + } +} + +impl PeerConfig { + #[inline] + fn path(id: &str, peer: &str) -> PathBuf { + path_plugins(id) + .join("peers") + .join(format!("{}.toml", peer)) + } + + #[inline] + pub fn load(id: &str, peer: &str) { + let mut conf = hbb_common::config::load_path::(Self::path(id, peer)); + if let Some(items) = CONFIG_PEER_ITEMS.lock().unwrap().get(id) { + for item in items { + if !conf.contains_key(&item.key) { + conf.insert(item.key.to_owned(), item.default.to_owned()); + } + } + } + match CONFIG_PEERS.lock().unwrap().get_mut(id) { + Some(peers) => { + peers.insert(peer.to_owned(), conf); + } + None => { + let mut peers = HashMap::new(); + peers.insert(peer.to_owned(), conf); + CONFIG_PEERS.lock().unwrap().insert(id.to_owned(), peers); + } + } + } + + #[inline] + fn save(id: &str, peer: &str) -> ResultType<()> { + match CONFIG_PEERS.lock().unwrap().get(id) { + Some(peers) => match peers.get(peer) { + Some(config) => hbb_common::config::store_path(Self::path(id, peer), config), + None => bail!("No such peer {}", peer), + }, + None => bail!("No such plugin {}", id), + } + } + + #[inline] + pub fn get(id: &str, peer: &str, key: &str) -> Option { + if let Some(peers) = CONFIG_PEERS.lock().unwrap().get(id) { + if let Some(conf) = peers.get(peer) { + return conf.get(key).map(|s| s.to_owned()); + } + } + Self::load(id, peer); + CONFIG_PEERS + .lock() + .unwrap() + .get(id)? + .get(peer)? + .get(key) + .map(|s| s.to_owned()) + } + + #[inline] + pub fn set(id: &str, peer: &str, key: &str, value: &str) -> ResultType<()> { + match CONFIG_PEERS.lock().unwrap().get_mut(id) { + Some(peers) => match peers.get_mut(peer) { + Some(config) => { + config.insert(key.to_owned(), value.to_owned()); + hbb_common::config::store_path(Self::path(id, peer), config) + } + None => bail!("No such peer {}", peer), + }, + None => bail!("No such plugin {}", id), + } + } +} + +#[inline] +pub(super) fn set_local_items(id: &str, items: &Vec) { + CONFIG_LOCAL_ITEMS + .lock() + .unwrap() + .insert(id.to_owned(), items.clone()); +} + +#[inline] +pub(super) fn set_peer_items(id: &str, items: &Vec) { + CONFIG_PEER_ITEMS + .lock() + .unwrap() + .insert(id.to_owned(), items.clone()); +} diff --git a/src/plugin/desc.rs b/src/plugin/desc.rs index 8d345b7d2..26eda187a 100644 --- a/src/plugin/desc.rs +++ b/src/plugin/desc.rs @@ -1,10 +1,10 @@ use hbb_common::ResultType; -use serde_derive::Deserialize; +use serde_derive::{Deserialize, Serialize}; use serde_json; use std::collections::HashMap; use std::ffi::{c_char, CStr}; -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct UiButton { key: String, text: String, @@ -13,7 +13,7 @@ pub struct UiButton { action: String, // The action to be triggered when the button is clicked. } -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct UiCheckbox { key: String, text: String, @@ -21,7 +21,7 @@ pub struct UiCheckbox { action: String, // The action to be triggered when the checkbox is checked or unchecked. } -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(tag = "t", content = "c")] pub enum UiType { Button(UiButton), @@ -30,28 +30,21 @@ pub enum UiType { #[derive(Debug, Deserialize)] pub struct Location { - core: String, - ui: HashMap, + pub ui: HashMap, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, Deserialize)] pub struct ConfigItem { - key: String, - value: String, - default: String, - description: String, -} - -#[derive(Debug, Deserialize)] -pub struct Configs { - pub local: Vec, - pub session: Vec, + pub key: String, + pub value: String, + pub default: String, + pub description: String, } #[derive(Debug, Deserialize)] pub struct Config { - pub host: Configs, - pub client: Configs, + pub local: Vec, + pub peer: Vec, } #[derive(Debug, Deserialize)] diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index ab233a503..9e3e5993d 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -1,8 +1,5 @@ -use hbb_common::{dlopen::symbor::Library, log, ResultType}; -use std::{ - ffi::{c_char, CStr}, - path::Path, -}; +use hbb_common::ResultType; +use std::ffi::{c_char, CStr}; mod callback_msg; mod config; diff --git a/src/plugin/plugins.rs b/src/plugin/plugins.rs index d93ac5938..a26f7a77c 100644 --- a/src/plugin/plugins.rs +++ b/src/plugin/plugins.rs @@ -1,19 +1,13 @@ use std::{ collections::HashMap, - ffi::{c_char, CStr}, - path::PathBuf, + ffi::c_char, + path::Path, sync::{Arc, RwLock}, }; use super::{callback_msg, desc::Desc}; -use hbb_common::{ - anyhow::Error, - bail, - dlopen::symbor::Library, - lazy_static, libc, log, - log::{debug, error}, - ResultType, -}; +use crate::flutter; +use hbb_common::{bail, dlopen::symbor::Library, lazy_static, libc, log, ResultType}; lazy_static::lazy_static! { pub static ref PLUGINS: Arc>> = Default::default(); @@ -109,7 +103,7 @@ make_plugin!( fn_call: PluginFuncCall ); -pub fn load_plugins(dir: &str) -> ResultType<()> { +pub fn load_plugins>(dir: P) -> ResultType<()> { for entry in std::fs::read_dir(dir)? { match entry { Ok(entry) => { @@ -156,7 +150,39 @@ fn load_plugin(path: &str) -> ResultType<()> { let desc = desc_res?; let id = desc.id().to_string(); (plugin.fn_set_cb_msg)(callback_msg::callback_msg); + update_config(&desc); + reload_ui(&desc); plugin.desc = Some(desc); PLUGINS.write().unwrap().insert(id, plugin); Ok(()) } + +fn update_config(desc: &Desc) { + super::config::set_local_items(desc.id(), &desc.config().local); + super::config::set_peer_items(desc.id(), &desc.config().peer); +} + +fn reload_ui(desc: &Desc) { + for (location, ui) in desc.location().ui.iter() { + let v: Vec<&str> = location.split('|').collect(); + // The first element is the "client" or "host". + // The second element is the "main", "remote", "cm", "file transfer", "port forward". + if v.len() >= 2 { + let available_channels = vec![ + flutter::APP_TYPE_MAIN, + flutter::APP_TYPE_DESKTOP_REMOTE, + flutter::APP_TYPE_CM, + flutter::APP_TYPE_DESKTOP_FILE_TRANSFER, + flutter::APP_TYPE_DESKTOP_PORT_FORWARD, + ]; + if available_channels.contains(&v[1]) { + if let Ok(ui) = serde_json::to_string(&ui) { + let mut m = HashMap::new(); + m.insert("name", "plugin_reload"); + m.insert("ui", &ui); + flutter::push_global_event(v[1], serde_json::to_string(&m).unwrap()); + } + } + } + } +} From e0b3ef6c4bb0c44fc29ba2bf9e208dcff942f812 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 19 Apr 2023 11:31:49 +0800 Subject: [PATCH 3/5] fix missed ffi method Signed-off-by: fufesou --- src/flutter_ffi.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index fa1cf6cc8..0386c5ad3 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -48,6 +48,13 @@ fn initialize(app_dir: &str) { } } +pub fn start_global_event_stream(s: StreamSink, app_type: String) -> ResultType<()> { + super::flutter::start_global_event_stream(s, app_type) +} + +pub fn stop_global_event_stream(app_type: String) { + super::flutter::stop_global_event_stream(app_type) +} pub enum EventToUI { Event(String), Rgba, From e194e70f1a78b22a28e048884986ace0e81dca52 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 19 Apr 2023 11:34:52 +0800 Subject: [PATCH 4/5] remove warn Signed-off-by: fufesou --- src/server/dbus.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/dbus.rs b/src/server/dbus.rs index 79aa62a1e..b01b11c9d 100644 --- a/src/server/dbus.rs +++ b/src/server/dbus.rs @@ -71,7 +71,7 @@ fn handle_client_message(builder: &mut IfaceBuilder<()>) { move |_, _, (_uni_links,): (String,)| { #[cfg(feature = "flutter")] { - use crate::flutter::{self, APP_TYPE_MAIN}; + use crate::flutter; let data = HashMap::from([ ("name", "new_connection"), ("uni_links", _uni_links.as_str()), From 601372520022636d5939f4edd7158ab2768cff7c Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 19 Apr 2023 12:06:01 +0800 Subject: [PATCH 5/5] remove warns Signed-off-by: fufesou --- Cargo.lock | 1 + Cargo.toml | 1 + src/client.rs | 8 +++++--- src/client/io_loop.rs | 5 ++++- src/lib.rs | 5 ++++- src/ui_session_interface.rs | 23 +++++++++++++++-------- 6 files changed, 30 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45f809680..299d99c14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5133,6 +5133,7 @@ dependencies = [ "include_dir", "jni 0.19.0", "lazy_static", + "libloading", "libpulse-binding", "libpulse-simple-binding", "mac_address", diff --git a/Cargo.toml b/Cargo.toml index d70c202c6..784b83537 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,6 +70,7 @@ hex = "0.4" reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false } chrono = "0.4" cidr-utils = "0.5" +libloading = "0.7" [target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies] cpal = "0.15" diff --git a/src/client.rs b/src/client.rs index 1dd525984..1c6f12108 100644 --- a/src/client.rs +++ b/src/client.rs @@ -53,9 +53,7 @@ use scrap::{ ImageFormat, }; -use crate::{ - common::{self, is_keyboard_mode_supported}, -}; +use crate::common::{self, is_keyboard_mode_supported}; #[cfg(not(any(target_os = "android", target_os = "ios")))] use crate::common::{check_clipboard, ClipboardContext, CLIPBOARD_INTERVAL}; @@ -73,6 +71,8 @@ pub const MILLI1: Duration = Duration::from_millis(1); pub const SEC30: Duration = Duration::from_secs(30); pub const VIDEO_QUEUE_SIZE: usize = 120; +#[cfg(all(target_os = "linux", feature = "linux_headless"))] +#[cfg(not(any(feature = "flatpak", feature = "appimage")))] pub const LOGIN_MSG_DESKTOP_NOT_INITED: &str = "Desktop env is not inited"; pub const LOGIN_MSG_DESKTOP_SESSION_NOT_READY: &str = "Desktop session not ready"; pub const LOGIN_MSG_DESKTOP_XSESSION_FAILED: &str = "Desktop xsession failed"; @@ -88,7 +88,9 @@ pub const LOGIN_MSG_PASSWORD_EMPTY: &str = "Empty Password"; pub const LOGIN_MSG_PASSWORD_WRONG: &str = "Wrong Password"; pub const LOGIN_MSG_NO_PASSWORD_ACCESS: &str = "No Password Access"; pub const LOGIN_MSG_OFFLINE: &str = "Offline"; +#[cfg(target_os = "linux")] pub const SCRAP_UBUNTU_HIGHER_REQUIRED: &str = "Wayland requires Ubuntu 21.04 or higher version."; +#[cfg(target_os = "linux")] pub const SCRAP_OTHER_VERSION_OR_X11_REQUIRED: &str = "Wayland requires higher version of linux distro. Please try X11 desktop or change your OS."; pub const SCRAP_X11_REQUIRED: &str = "x11 expected"; diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 919347e5f..eccc837f0 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -362,7 +362,10 @@ impl Remote { }); return Some(tx); } - None + #[cfg(target_os = "ios")] + { + None + } } async fn handle_msg_from_ui(&mut self, data: Data, peer: &mut Stream) -> bool { diff --git a/src/lib.rs b/src/lib.rs index ad9229ab5..ec70d1179 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,6 @@ mod license; #[cfg(not(any(target_os = "android", target_os = "ios")))] mod port_forward; -#[cfg(all(feature = "flutter", feature = "plugin_framework"))] #[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(any(feature = "flutter"))] pub mod api; @@ -56,6 +55,10 @@ pub mod api; #[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; + mod tray; mod ui_cm_interface; diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 2747a6f00..779119221 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -14,10 +14,12 @@ use rdev::{Event, EventType::*, KeyCode}; use uuid::Uuid; use hbb_common::config::{Config, LocalConfig, PeerConfig}; +#[cfg(not(feature = "flutter"))] +use hbb_common::fs; use hbb_common::rendezvous_proto::ConnType; use hbb_common::tokio::{self, sync::mpsc}; use hbb_common::{allow_err, message_proto::*}; -use hbb_common::{fs, get_version_number, log, Stream}; +use hbb_common::{get_version_number, log, Stream}; use crate::client::io_loop::Remote; use crate::client::{ @@ -25,7 +27,7 @@ use crate::client::{ input_os_password, load_config, send_mouse, start_video_audio_threads, FileManager, Key, LoginConfigHandler, QualityStatus, KEY_MAP, }; -use crate::common::{self, GrabState}; +use crate::common::GrabState; use crate::keyboard; use crate::{client::Data, client::Interface}; @@ -155,6 +157,7 @@ impl Session { self.lc.read().unwrap().get_toggle_option(&name) } + #[cfg(not(feature = "flutter"))] pub fn is_privacy_mode_supported(&self) -> bool { self.lc.read().unwrap().is_privacy_mode_supported() } @@ -198,6 +201,7 @@ impl Session { self.lc.read().unwrap().remember } + #[cfg(not(feature = "flutter"))] pub fn set_write_override( &mut self, job_id: i32, @@ -240,6 +244,8 @@ impl Session { self.send(Data::Message(msg)); } + #[cfg(all(feature = "flutter", feature = "plugin_framework"))] + #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn send_plugin(&self, plugin: Plugin) { let mut misc = Misc::new(); misc.set_plugin(plugin); @@ -270,6 +276,8 @@ impl Session { }); } + #[cfg(not(feature = "flutter"))] + #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn is_xfce(&self) -> bool { #[cfg(not(any(target_os = "ios")))] return crate::platform::is_xfce(); @@ -277,11 +285,6 @@ impl Session { false } - pub fn get_supported_keyboard_modes(&self) -> Vec { - let version = self.get_peer_version(); - common::get_supported_keyboard_modes(version) - } - pub fn remove_port_forward(&self, port: i32) { let mut config = self.load_config(); config.port_forwards = config @@ -310,6 +313,7 @@ impl Session { self.send(Data::AddPortForward(pf)); } + #[cfg(not(feature = "flutter"))] pub fn get_id(&self) -> String { self.id.clone() } @@ -369,6 +373,7 @@ impl Session { input_os_password(pass, activate, self.clone()); } + #[cfg(not(feature = "flutter"))] pub fn get_chatbox(&self) -> String { #[cfg(feature = "inline")] return crate::ui::inline::get_chatbox(); @@ -545,7 +550,8 @@ impl Session { position_code: i32, lock_modes: i32, down_or_up: bool, - ) {} + ) { + } #[cfg(not(any(target_os = "ios")))] pub fn handle_flutter_key_event( @@ -675,6 +681,7 @@ impl Session { })); } + #[cfg(not(feature = "flutter"))] pub fn get_icon_path(&self, file_type: i32, ext: String) -> String { let mut path = Config::icon_path(); if file_type == FileType::DirLink as i32 {