refact: remove feature plugin-framework (#15854)

* refact: remove feature plugin-framework

Signed-off-by: fufesou <linlong1266@gmail.com>

* refact: remove unused translations

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: delete settings tab observable with correct type

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou
2026-08-14 14:31:13 +08:00
committed by GitHub
parent d829d1410a
commit d1da05c4db
93 changed files with 8 additions and 5251 deletions

View File

@@ -1437,14 +1437,6 @@ impl<T: InvokeUiSession> Remote<T> {
#[cfg(all(feature = "flutter", feature = "unix-file-copy-paste"))]
crate::flutter::update_file_clipboard_required();
// on connection established client
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
crate::plugin::handle_listen_event(
crate::plugin::EVENT_ON_CONN_CLIENT.to_owned(),
self.handler.get_id(),
);
}
if self.handler.is_file_transfer() {
@@ -1988,26 +1980,6 @@ impl<T: InvokeUiSession> Remote<T> {
);
}
}
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Some(misc::Union::PluginRequest(p)) => {
allow_err!(crate::plugin::handle_server_event(
&p.id,
&self.handler.get_id(),
&p.content
));
// to-do: show message box on UI when error occurs?
}
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Some(misc::Union::PluginFailure(p)) => {
let name = if p.name.is_empty() {
"plugin".to_string()
} else {
p.name
};
self.handler.msgbox("custom-nocancel", &name, &p.msg, "");
}
Some(misc::Union::SupportedEncoding(e)) => {
log::info!("update supported encoding:{:?}", e);
self.handler.lc.write().unwrap().supported_encoding = e;

View File

@@ -190,9 +190,6 @@ pub fn core_main() -> Option<Vec<String>> {
crate::platform::elevate_or_run_as_system(click_setup, _is_elevate, _is_run_as_system);
return None;
}
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
init_plugins(&args);
if args.is_empty() || crate::common::is_empty_uni_link(&args[0]) {
#[cfg(target_os = "macos")]
{
@@ -737,22 +734,6 @@ pub fn core_main() -> Option<Vec<String>> {
crate::platform::gtk_sudo::exec();
}
return None;
} else {
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if args[0] == "--plugin-install" {
if args.len() == 2 {
crate::plugin::change_uninstall_plugin(&args[1], false);
} else if args.len() == 3 {
crate::plugin::install_plugin_with_url(&args[1], &args[2]);
}
return None;
} else if args[0] == "--plugin-uninstall" {
if args.len() == 2 {
crate::plugin::change_uninstall_plugin(&args[1], true);
}
return None;
}
}
}
//_async_logger_holder.map(|x| x.flush());
@@ -762,23 +743,6 @@ pub fn core_main() -> Option<Vec<String>> {
return Some(args);
}
#[inline]
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn init_plugins(args: &Vec<String>) {
if args.is_empty() || "--server" == (&args[0] as &str) {
#[cfg(debug_assertions)]
let load_plugins = true;
#[cfg(not(debug_assertions))]
let load_plugins = crate::platform::is_installed();
if load_plugins {
crate::plugin::init();
}
} else if "--service" == (&args[0] as &str) {
hbb_common::allow_err!(crate::plugin::remove_uninstalled());
}
}
fn import_config(path: &str) {
use hbb_common::{config::*, get_exe_time, get_modified_time};
let path2 = path.replace(".toml", "2.toml");

View File

@@ -225,8 +225,6 @@ pub struct FlutterHandler {
session_handlers: Arc<RwLock<HashMap<SessionID, SessionHandler>>>,
display_rgbas: Arc<RwLock<HashMap<usize, RgbaData>>>,
peer_info: Arc<RwLock<PeerInfo>>,
#[cfg(not(any(target_os = "android", target_os = "ios")))]
hooks: Arc<RwLock<HashMap<String, SessionHook>>>,
use_texture_render: Arc<AtomicBool>,
}
@@ -236,8 +234,6 @@ impl Default for FlutterHandler {
session_handlers: Default::default(),
display_rgbas: Default::default(),
peer_info: Default::default(),
#[cfg(not(any(target_os = "android", target_os = "ios")))]
hooks: Default::default(),
use_texture_render: Arc::new(
AtomicBool::new(crate::ui_interface::use_texture_render()),
),
@@ -636,30 +632,6 @@ impl FlutterHandler {
serde_json::ser::to_string(&msg_vec).unwrap_or("".to_owned())
}
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
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.
return false;
}
let _ = hooks.insert(key, hook);
true
}
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub(crate) fn remove_session_hook(&self, key: &String) -> bool {
let mut hooks = self.hooks.write().unwrap();
if !hooks.contains_key(key) {
// The hook with this key does not found.
return false;
}
let _ = hooks.remove(key);
true
}
pub fn update_use_texture_render(&self) {
self.use_texture_render
.store(crate::ui_interface::use_texture_render(), Ordering::Relaxed);
@@ -1194,15 +1166,6 @@ impl InvokeUiSession for FlutterHandler {
impl FlutterHandler {
#[inline]
fn on_rgba_soft_render(&self, display: usize, 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.
let mut rgba_write_lock = self.display_rgbas.write().unwrap();
@@ -1963,12 +1926,6 @@ pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) {
}
}
/// Hooks for session.
#[derive(Clone)]
pub enum SessionHook {
OnSessionRgba(fn(String, &mut scrap::ImageRgb)),
}
#[inline]
pub fn get_cur_session() -> Option<FlutterSession> {
sessions::get_session_by_session_id(&*CUR_SESSION_ID.read().unwrap())

View File

@@ -12,9 +12,6 @@ use crate::{
ui_interface::{self, *},
};
use flutter_rust_bridge::{StreamSink, SyncReturn};
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::allow_err;
use hbb_common::{
config::{self, LocalConfig, PeerConfig, PeerInfoSerde},
fs, lazy_static, log,
@@ -2522,180 +2519,6 @@ pub fn send_url_scheme(_url: String) {
std::thread::spawn(move || crate::handle_url_scheme(_url));
}
#[inline]
pub fn plugin_event(_id: String, _peer: String, _event: Vec<u8>) {
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
allow_err!(crate::plugin::handle_ui_event(&_id, &_peer, &_event));
}
}
pub fn plugin_register_event_stream(_id: String, _event2ui: StreamSink<EventToUI>) {
#[cfg(feature = "plugin_framework")]
{
crate::plugin::native_handlers::session::session_register_event_stream(_id, _event2ui);
}
}
#[inline]
pub fn plugin_get_session_option(
_id: String,
_peer: String,
_key: String,
) -> SyncReturn<Option<String>> {
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
SyncReturn(crate::plugin::PeerConfig::get(&_id, &_peer, &_key))
}
#[cfg(any(
not(feature = "plugin_framework"),
target_os = "android",
target_os = "ios"
))]
{
SyncReturn(None)
}
}
#[inline]
pub fn plugin_set_session_option(_id: String, _peer: String, _key: String, _value: String) {
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
let _res = crate::plugin::PeerConfig::set(&_id, &_peer, &_key, &_value);
}
}
#[inline]
pub fn plugin_get_shared_option(_id: String, _key: String) -> SyncReturn<Option<String>> {
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
SyncReturn(crate::plugin::ipc::get_config(&_id, &_key).unwrap_or(None))
}
#[cfg(any(
not(feature = "plugin_framework"),
target_os = "android",
target_os = "ios"
))]
{
SyncReturn(None)
}
}
#[inline]
pub fn plugin_set_shared_option(_id: String, _key: String, _value: String) {
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
allow_err!(crate::plugin::ipc::set_config(&_id, &_key, _value));
}
}
#[inline]
pub fn plugin_reload(_id: String) {
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
allow_err!(crate::plugin::ipc::reload_plugin(&_id,));
allow_err!(crate::plugin::reload_plugin(&_id));
}
}
#[inline]
pub fn plugin_enable(_id: String, _v: bool) -> SyncReturn<()> {
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
allow_err!(crate::plugin::ipc::set_manager_plugin_config(
&_id,
"enabled",
_v.to_string()
));
if _v {
allow_err!(crate::plugin::load_plugin(&_id));
} else {
crate::plugin::unload_plugin(&_id);
}
}
SyncReturn(())
}
pub fn plugin_is_enabled(_id: String) -> SyncReturn<bool> {
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
SyncReturn(
match crate::plugin::ipc::get_manager_plugin_config(&_id, "enabled") {
Ok(Some(enabled)) => bool::from_str(&enabled).unwrap_or(false),
_ => false,
},
)
}
#[cfg(any(
not(feature = "plugin_framework"),
target_os = "android",
target_os = "ios"
))]
{
SyncReturn(false)
}
}
pub fn plugin_feature_is_enabled() -> SyncReturn<bool> {
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
#[cfg(debug_assertions)]
let enabled = true;
#[cfg(not(debug_assertions))]
let enabled = is_installed();
SyncReturn(enabled)
}
#[cfg(any(
not(feature = "plugin_framework"),
target_os = "android",
target_os = "ios"
))]
{
SyncReturn(false)
}
}
pub fn plugin_sync_ui(_sync_to: String) {
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
if plugin_feature_is_enabled().0 {
crate::plugin::sync_ui(_sync_to);
}
}
}
pub fn plugin_list_reload() {
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
crate::plugin::load_plugin_list();
}
}
pub fn plugin_install(_id: String, _b: bool) {
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
if _b {
if let Err(e) = crate::plugin::install_plugin(&_id) {
log::error!("Failed to install plugin '{}': {}", _id, e);
}
} else {
crate::plugin::uninstall_plugin(&_id, true);
}
}
}
pub fn is_support_multi_ui_session(version: String) -> SyncReturn<bool> {
SyncReturn(crate::common::is_support_multi_ui_session(&version))
}

View File

@@ -19,9 +19,6 @@ pub(crate) use ipc_drm::DrmConn;
#[cfg(all(target_os = "linux", feature = "drm"))]
pub(crate) use ipc_drm::connect_drm;
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::plugin::ipc::Plugin;
use crate::{
common::{is_server, CheckTestNatType},
privacy_mode,
@@ -404,9 +401,6 @@ pub enum Data {
StartVoiceCall,
VoiceCallResponse(bool),
CloseVoiceCall(String),
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Plugin(Plugin),
#[cfg(windows)]
SyncWinCpuUsage(Option<f64>),
FileTransferLog((String, String)),
@@ -1076,9 +1070,6 @@ async fn handle(data: Data, stream: &mut Connection) {
.await
);
}
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Data::Plugin(plugin) => crate::plugin::ipc::handle_plugin(plugin, stream).await,
#[cfg(windows)]
Data::ControlledSessionCount(_) => {
allow_err!(

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "البصمة"),
("Copy Fingerprint", "نسخ البصمة"),
("no fingerprints", "لا توجد بصمات اصابع"),
("Select a peer", "اختر قرين"),
("Select peers", "اختر الاقران"),
("Plugins", "الاضافات"),
("Uninstall", "الغاء التثبيت"),
("Update", "تحديث"),
("Enable", "تفعيل"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Адбітак"),
("Copy Fingerprint", "Капіяваць адбітак"),
("no fingerprints", "адбіткі адсутнічаюць"),
("Select a peer", "Выберыце абанента"),
("Select peers", "Выберыце абанентаў"),
("Plugins", "Убудовы"),
("Uninstall", "Выдаліць"),
("Update", "Абнавіць"),
("Enable", "Уключыць"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Пръстов отпечатък"),
("Copy Fingerprint", "Копиране на пръстов отпечатък"),
("no fingerprints", "Няма пръстови отпечатъци"),
("Select a peer", "Избери отдалечена страна"),
("Select peers", "Избери отдалечени страни"),
("Plugins", "Плъгини"),
("Uninstall", "Премахни"),
("Update", "Обновяване"),
("Enable", "Позволяване"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Empremta"),
("Copy Fingerprint", "Copia l'empremta"),
("no fingerprints", "Cap empremta"),
("Select a peer", "Seleccioneu un client"),
("Select peers", "Seleccioneu els clients"),
("Plugins", "Complements"),
("Uninstall", "Desinstal·la"),
("Update", "Actualitza"),
("Enable", "Activa"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "指纹"),
("Copy Fingerprint", "复制指纹"),
("no fingerprints", "没有指纹"),
("Select a peer", "选择一个被控端"),
("Select peers", "选择被控"),
("Plugins", "插件"),
("Uninstall", "卸载"),
("Update", "更新"),
("Enable", "启用"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Otisk"),
("Copy Fingerprint", "Kopírovat otisk"),
("no fingerprints", "žádný otisk"),
("Select a peer", "Výběr protistrany"),
("Select peers", "Vybrat protistrany"),
("Plugins", "Pluginy"),
("Uninstall", "Odinstalovat"),
("Update", "Aktualizovat"),
("Enable", "Povolit"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Fingeraftryk"),
("Copy Fingerprint", "Kopiér fingeraftryk"),
("no fingerprints", "Ingen fingeraftryk"),
("Select a peer", "Vælg en peer"),
("Select peers", "Vælg peers"),
("Plugins", "Plugins"),
("Uninstall", "Afinstallér"),
("Update", "Opdatér"),
("Enable", "Aktivér"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Fingerabdruck"),
("Copy Fingerprint", "Fingerabdruck kopieren"),
("no fingerprints", "Keine Fingerabdrücke"),
("Select a peer", "Gegenstelle auswählen"),
("Select peers", "Gegenstellen auswählen"),
("Plugins", "Plugins"),
("Uninstall", "Deinstallieren"),
("Update", "Update"),
("Enable", "Aktivieren"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Δακτυλικό αποτύπωμα"),
("Copy Fingerprint", "Αντιγραφή δακτυλικού αποτυπώματος"),
("no fingerprints", "χωρίς δακτυλικά αποτυπώματα"),
("Select a peer", "Επιλέξτε έναν σταθμό"),
("Select peers", "Επιλέξτε σταθμούς"),
("Plugins", "Επεκτάσεις"),
("Uninstall", "Κατάργηση εγκατάστασης"),
("Update", "Ενημέρωση"),
("Enable", "Ενεργοποίηση"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Fingrospuro"),
("Copy Fingerprint", "Kopii fingrospuron"),
("no fingerprints", "Neniuj fingrospuroj"),
("Select a peer", "Elekti samulon"),
("Select peers", "Elekti samulojn"),
("Plugins", "Kromprogramoj"),
("Uninstall", "Malinstali"),
("Update", "Ĝisdatigi"),
("Enable", "Ebligi"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Huella digital"),
("Copy Fingerprint", "Copiar huella digital"),
("no fingerprints", "sin huellas digitales"),
("Select a peer", "Seleccionar un par"),
("Select peers", "Seleccionar pares"),
("Plugins", "Complementos"),
("Uninstall", "Desinstalar"),
("Update", "Actualizar"),
("Enable", "Habilitar"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Sõrmejälg"),
("Copy Fingerprint", "Kopeeri sõrmejälg"),
("no fingerprints", "Sõrmejäljed puuduvad"),
("Select a peer", "Vali partner"),
("Select peers", "Vali partnerid"),
("Plugins", "Pluginad"),
("Uninstall", "Desinstalli"),
("Update", "Uuenda"),
("Enable", "Luba"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Hatz-marka"),
("Copy Fingerprint", "Kopiatu hatz-marka"),
("no fingerprints", "hatz-markarik ez"),
("Select a peer", "Hautatu parekidea"),
("Select peers", "Hautatu parekideak"),
("Plugins", "Pluginak"),
("Uninstall", "Desinstalatu"),
("Update", "Eguneratu"),
("Enable", "Gaitu"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "\n اثر انگشت"),
("Copy Fingerprint", "کپی کردن اثر انگشت"),
("no fingerprints", "بدون اثر انگشت"),
("Select a peer", "یک همتا را انتخاب کنید"),
("Select peers", "همتایان را انتخاب کنید"),
("Plugins", "پلاگین ها"),
("Uninstall", "حذف نصب"),
("Update", "به روز رسانی"),
("Enable", "فعال کردن"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Sormenjälki"),
("Copy Fingerprint", "Kopioi sormenjälki"),
("no fingerprints", "Ei sormenjälkiä"),
("Select a peer", "Valitse vastapää"),
("Select peers", "Valitse useita vastapään laitteita"),
("Plugins", "Laajennukset"),
("Uninstall", "Poista asennus"),
("Update", "Päivitä"),
("Enable", "Ota käyttöön"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Empreinte numérique"),
("Copy Fingerprint", "Copier lempreinte numérique"),
("no fingerprints", "Aucune empreinte numérique"),
("Select a peer", "Sélectionnez lappareil distant"),
("Select peers", "Sélectionnez les appareils distants"),
("Plugins", "Plugins"),
("Uninstall", "Désinstaller"),
("Update", "Mettre à jour"),
("Enable", "Activer"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "ანაბეჭდი"),
("Copy Fingerprint", "ანაბეჭდის კოპირება"),
("no fingerprints", "ანაბეჭდები არ არის"),
("Select a peer", "აირჩიეთ დისტანციური კვანძი"),
("Select peers", "აირჩიეთ დისტანციური კვანძები"),
("Plugins", "დანამატები"),
("Uninstall", "წაშლა"),
("Update", "განახლება"),
("Enable", "ჩართვა"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "ફિંગરપ્રિન્ટ"),
("Copy Fingerprint", "ફિંગરપ્રિન્ટ કોપી કરો"),
("no fingerprints", "કોઈ ફિંગરપ્રિન્ટ નથી"),
("Select a peer", "એક પીઅર પસંદ કરો"),
("Select peers", "પીઅર્સ પસંદ કરો"),
("Plugins", "પ્લગઇન્સ"),
("Uninstall", "અનઇન્સ્ટોલ કરો"),
("Update", "અપડેટ કરો"),
("Enable", "સક્ષમ કરો"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "טביעת אצבע"),
("Copy Fingerprint", "העתק טביעת אצבע"),
("no fingerprints", "אין טביעות אצבע"),
("Select a peer", "בחר עמית"),
("Select peers", "בחר עמיתים"),
("Plugins", "תוספים"),
("Uninstall", "הסר"),
("Update", "עדכן"),
("Enable", "פועל"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "फिंगरप्रिंट"),
("Copy Fingerprint", "फिंगरप्रिंट कॉपी करें"),
("no fingerprints", "कोई फिंगरप्रिंट नहीं"),
("Select a peer", "एक पीयर (Peer) चुनें"),
("Select peers", "पीयर्स चुनें"),
("Plugins", "प्लगइन्स"),
("Uninstall", "अनइंस्टॉल करें"),
("Update", "अपडेट करें"),
("Enable", "सक्षम करें"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Otisak"),
("Copy Fingerprint", "Kopirat otisak"),
("no fingerprints", "nema otiska"),
("Select a peer", "Izbor druge strane"),
("Select peers", "Odaberite druge strane"),
("Plugins", "Dodaci"),
("Uninstall", "Deinstaliraj"),
("Update", "Ažuriraj"),
("Enable", "Dopustiti"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Ujjlenyomat"),
("Copy Fingerprint", "Ujjlenyomat másolása"),
("no fingerprints", "nincsenek ujjlenyomatok"),
("Select a peer", "Egy távoli állomás kiválasztása"),
("Select peers", "Távoli állomások kiválasztása"),
("Plugins", "Beépülő modulok"),
("Uninstall", "Eltávolítás"),
("Update", "Frissítés"),
("Enable", "Engedélyezés"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Sidik jari"),
("Copy Fingerprint", "Salin sidik jari"),
("no fingerprints", "Tidak ada sidik jari"),
("Select a peer", "Pilih rekan"),
("Select peers", "Pilih rekan-rekan"),
("Plugins", "Plugin"),
("Uninstall", "Hapus instalasi"),
("Update", "Perbarui"),
("Enable", "Aktifkan"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Firma digitale"),
("Copy Fingerprint", "Copia firma digitale"),
("no fingerprints", "Nessuna firma digitale"),
("Select a peer", "Seleziona dispositivo remoto"),
("Select peers", "Seleziona dispositivi remoti"),
("Plugins", "Plugin"),
("Uninstall", "Disinstalla"),
("Update", "Aggiorna"),
("Enable", "Abilita"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "フィンガープリント"),
("Copy Fingerprint", "フィンガープリントをコピー"),
("no fingerprints", "フィンガープリントがありません"),
("Select a peer", "リモートコンピューターを選択"),
("Select peers", "複数のリモートコンピューターを選択"),
("Plugins", "プラグイン"),
("Uninstall", "アンインストール"),
("Update", "更新"),
("Enable", "有効"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "지문"),
("Copy Fingerprint", "지문 복사"),
("no fingerprints", "지문이 없습니다"),
("Select a peer", "피어 선택"),
("Select peers", "피어 선택"),
("Plugins", "플러그인"),
("Uninstall", "설치 제거"),
("Update", "업데이트"),
("Enable", "허용"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Саусақ ізі"),
("Copy Fingerprint", "Саусақ ізін көшіру"),
("no fingerprints", "Саусақ іздері жоқ"),
("Select a peer", "Пир таңдау"),
("Select peers", "Пирлерді таңдау"),
("Plugins", "Плагиндер"),
("Uninstall", "Жою"),
("Update", "Жаңарту"),
("Enable", "Қосу"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Kontrolinis kodas"),
("Copy Fingerprint", "Kopijuoti kontrolinį kodą"),
("no fingerprints", "Nėra kontrolinių kodų"),
("Select a peer", "Pasirinkite įrenginį"),
("Select peers", "Pasirinkite įrenginius"),
("Plugins", "Papildiniai"),
("Uninstall", "Pašalinti"),
("Update", "Atnaujinti"),
("Enable", "Įgalinti"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Pirkstu nospiedums"),
("Copy Fingerprint", "Kopēt pirkstu nospiedumu"),
("no fingerprints", "nav pirkstu nospiedumu"),
("Select a peer", "Atlasīt līdzīgu"),
("Select peers", "Atlasīt līdzīgus"),
("Plugins", "Spraudņi"),
("Uninstall", "Atinstalēt"),
("Update", "Atjaunināt"),
("Enable", "Iespējot"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "ഫിംഗർപ്രിന്റ്"),
("Copy Fingerprint", "ഫിംഗർപ്രിന്റ് കോപ്പി ചെയ്യുക"),
("no fingerprints", "ഫിംഗർപ്രിന്റുകൾ ഇല്ല"),
("Select a peer", "ഒരാളെ തിരഞ്ഞെടുക്കുക"),
("Select peers", "തിരഞ്ഞെടുക്കുക"),
("Plugins", "പ്ലഗിനുകൾ"),
("Uninstall", "അൺഇൻസ്റ്റാൾ ചെയ്യുക"),
("Update", "അപ്ഡേറ്റ് ചെയ്യുക"),
("Enable", "പ്രവർത്തനക്ഷമമാക്കുക"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Fingeravtrykk"),
("Copy Fingerprint", "Kopier fingeravtrykk"),
("no fingerprints", "Ingen fingeravtrykk"),
("Select a peer", "Velg en motpart"),
("Select peers", "Velg motparter"),
("Plugins", "Programtillegg"),
("Uninstall", "Avinstaller"),
("Update", "Oppdater"),
("Enable", "Aktiver"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Vingerafdruk"),
("Copy Fingerprint", "Vingerafdruk kopiëren"),
("no fingerprints", "geen vingerafdrukken"),
("Select a peer", "Selecteer een peer"),
("Select peers", "Selecteer peers"),
("Plugins", "Plugins"),
("Uninstall", "Verwijderen"),
("Update", "Bijwerken"),
("Enable", "Activeren"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Sygnatura"),
("Copy Fingerprint", "Skopiuj sygnaturę"),
("no fingerprints", "brak sygnatur"),
("Select a peer", "Wybierz zdalne urządzenie"),
("Select peers", "Wybierz zdalne urządzenia"),
("Plugins", "Wtyczki"),
("Uninstall", "Odinstaluj"),
("Update", "Aktualizuj"),
("Enable", "Włącz"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Impressão digital"),
("Copy Fingerprint", "Copiar impressão digital"),
("no fingerprints", "Sem impressões digitais"),
("Select a peer", "Selecionar um destino"),
("Select peers", "Selecionar destinos"),
("Plugins", "Plugins"),
("Uninstall", "Desinstalar"),
("Update", "Atualizar"),
("Enable", "Ativar"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Impressão Digital"),
("Copy Fingerprint", "Copiar Impressão Digital"),
("no fingerprints", "sem Impressões Digitais"),
("Select a peer", "Selecione um parceiro"),
("Select peers", "Selecione parceiros"),
("Plugins", "Plugins"),
("Uninstall", "Desinstalar"),
("Update", "Atualizar"),
("Enable", "Habilitar"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Amprentă digitală"),
("Copy Fingerprint", "Copiază amprenta digitală"),
("no fingerprints", "Nicio amprentă digitală"),
("Select a peer", "Selectează un dispozitiv pereche"),
("Select peers", "Selectează dispozitive pereche"),
("Plugins", "Pluginuri"),
("Uninstall", "Dezinstalează"),
("Update", "Actualizează"),
("Enable", "Activează"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Отпечаток"),
("Copy Fingerprint", "Копировать отпечаток"),
("no fingerprints", "отпечатки отсутствуют"),
("Select a peer", "Выберите удалённый узел"),
("Select peers", "Выберите удалённые узлы"),
("Plugins", "Плагины"),
("Uninstall", "Удалить"),
("Update", "Обновить"),
("Enable", "Включить"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Firma digitale"),
("Copy Fingerprint", "Còpia firma digitale"),
("no fingerprints", "Peruna firma digitale"),
("Select a peer", "Seletziona su dispositivu remotu"),
("Select peers", "Seletziona sos dispositivos remotos"),
("Plugins", "Cumplementos"),
("Uninstall", "Disinstalla"),
("Update", "Atualiza"),
("Enable", "Abìlita"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Odtlačok prsta"),
("Copy Fingerprint", "Kopírovať odtlačok prsta"),
("no fingerprints", "žiadne odtlačky prstov"),
("Select a peer", "Výber partnera"),
("Select peers", "Výber partnerov"),
("Plugins", "Pluginy"),
("Uninstall", "Odinštalovať"),
("Update", "Aktualizovať"),
("Enable", "Povoliť"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Prstni odtis"),
("Copy Fingerprint", "Kopiraj prstni odtis"),
("no fingerprints", "ni prstnega odtisa"),
("Select a peer", "Izberite partnerja"),
("Select peers", "Izberite partnerje"),
("Plugins", "Vključki"),
("Uninstall", "Odstrani"),
("Update", "Posodobi"),
("Enable", "Omogoči"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Gjurma e gishtit"),
("Copy Fingerprint", "Kopjo gjurmën e gishtit"),
("no fingerprints", "Nuk ka gjurmë gishtash"),
("Select a peer", "Zgjidh një peer"),
("Select peers", "Zgjidh peer-at"),
("Plugins", "Shtojcat"),
("Uninstall", "Çinstalo"),
("Update", "Përditëso"),
("Enable", "Aktivizo"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Otisak"),
("Copy Fingerprint", "Kopiraj otisak"),
("no fingerprints", "Nema otisaka"),
("Select a peer", "Izaberi klijenta"),
("Select peers", "Izaberi klijente"),
("Plugins", "Dodaci"),
("Uninstall", "Deinstaliraj"),
("Update", "Ažuriraj"),
("Enable", "Omogući"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Fingeravtryck"),
("Copy Fingerprint", "Kopiera fingeravtryck"),
("no fingerprints", "inga fingeravtryck"),
("Select a peer", "Välj en klient"),
("Select peers", "Välj klienter"),
("Plugins", "Plugin"),
("Uninstall", "Avinstallera"),
("Update", "Uppdatera"),
("Enable", "Aktivera"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "கைரேகை"),
("Copy Fingerprint", "கைரேகை நகல்"),
("no fingerprints", "கைரேகைகள் இல்லை"),
("Select a peer", "பியர் தேர்வு"),
("Select peers", "பியர்கள் தேர்வு"),
("Plugins", "இணைப்புகள்"),
("Uninstall", "நிறுவல் நீக்கு"),
("Update", "புதுப்பி"),
("Enable", "இயக்கு"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", ""),
("Copy Fingerprint", ""),
("no fingerprints", ""),
("Select a peer", ""),
("Select peers", ""),
("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "ลายนิ้วมือ"),
("Copy Fingerprint", "คัดลอกลายนิ้วมือ"),
("no fingerprints", "ไม่มีลายนิ้วมือ"),
("Select a peer", "เลือกผู้ใช้งาน"),
("Select peers", "เลือกผู้ใช้งาน"),
("Plugins", "ปลั๊กอิน"),
("Uninstall", "ถอนการติดตั้ง"),
("Update", "อัปเดต"),
("Enable", "เปิดใช้งาน"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Parmak İzi"),
("Copy Fingerprint", "Parmak İzini Kopyala"),
("no fingerprints", "parmak izi yok"),
("Select a peer", "Bir cihaz seçin"),
("Select peers", "Cihazları seçin"),
("Plugins", "Eklentiler"),
("Uninstall", "Kaldır"),
("Update", "Güncelle"),
("Enable", "Etkinleştir"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "指紋"),
("Copy Fingerprint", "複製指紋"),
("no fingerprints", "沒有指紋"),
("Select a peer", "選擇夥伴"),
("Select peers", "選擇夥伴"),
("Plugins", "外掛程式"),
("Uninstall", "解除安裝"),
("Update", "更新"),
("Enable", "啟用"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Відбитки пальців"),
("Copy Fingerprint", "Копіювати відбитки пальців"),
("no fingerprints", "немає відбитків пальців"),
("Select a peer", "Оберіть віддалений пристрій"),
("Select peers", "Оберіть віддалені пристрої"),
("Plugins", "Плагіни"),
("Uninstall", "Видалити"),
("Update", "Оновити"),
("Enable", "Увімкнути"),

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Dấu vân tay"),
("Copy Fingerprint", "Sao chép fingerprint"),
("no fingerprints", "không có fingerprint"),
("Select a peer", "Chọn một đối tác"),
("Select peers", "Chọn các đối tác"),
("Plugins", "Plugin"),
("Uninstall", "Gỡ cài đặt"),
("Update", "Cập nhật"),
("Enable", "Bật"),

View File

@@ -46,10 +46,6 @@ mod lang;
#[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")))]
pub mod plugin;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
mod tray;

View File

@@ -1,44 +0,0 @@
// External support for callback.
// 1. Support block input for some plugins.
// -----------------------------------------------------------------------------
use super::*;
const EXT_SUPPORT_BLOCK_INPUT: &str = "block-input";
pub(super) fn ext_support_callback(
id: &str,
peer: &str,
msg: &super::callback_msg::MsgToExtSupport,
) -> PluginReturn {
match &msg.r#type as _ {
EXT_SUPPORT_BLOCK_INPUT => {
// let supported_plugins = [];
// let supported = supported_plugins.contains(&id);
let supported = true;
if supported {
if msg.data.len() != 1 {
return PluginReturn::new(
errno::ERR_CALLBACK_INVALID_ARGS,
"Invalid data length",
);
}
let block = msg.data[0] != 0;
if crate::server::plugin_block_input(peer, block) == block {
PluginReturn::success()
} else {
PluginReturn::new(errno::ERR_CALLBACK_FAILED, "")
}
} else {
PluginReturn::new(
errno::ERR_CALLBACK_PLUGIN_ID,
&format!("This operation is not supported for plugin '{}', please contact the RustDesk team for support.", id),
)
}
}
_ => PluginReturn::new(
errno::ERR_CALLBACK_TARGET_TYPE,
&format!("Unknown target type '{}'", &msg.r#type),
),
}
}

View File

@@ -1,411 +0,0 @@
use super::*;
use crate::hbbs_http::create_http_client;
use crate::{
flutter::{self, APP_TYPE_CM, APP_TYPE_MAIN, SESSIONS},
ui_interface::get_api_server,
};
use hbb_common::{lazy_static, log, message_proto::PluginRequest};
use serde_derive::{Deserialize, Serialize};
use serde_json;
use std::{
collections::HashMap,
ffi::{c_char, c_void},
sync::Arc,
thread,
time::Duration,
};
const MSG_TO_RUSTDESK_TARGET: &str = "rustdesk";
const MSG_TO_PEER_TARGET: &str = "peer";
const MSG_TO_UI_TARGET: &str = "ui";
const MSG_TO_CONFIG_TARGET: &str = "config";
const MSG_TO_EXT_SUPPORT_TARGET: &str = "ext-support";
const MSG_TO_RUSTDESK_SIGNATURE_VERIFICATION: &str = "signature_verification";
#[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<HashMap<u16, String>> = {
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)
};
}
#[derive(Deserialize)]
pub struct MsgToRustDesk {
pub r#type: String,
pub data: Vec<u8>,
}
#[derive(Deserialize)]
pub struct SignatureVerification {
pub version: String,
pub data: Vec<u8>,
}
#[derive(Debug, Deserialize)]
struct ConfigToUi {
channel: u16,
location: String,
}
#[derive(Debug, Deserialize)]
struct MsgToConfig {
r#type: String,
key: String,
value: String,
#[serde(skip_serializing_if = "Option::is_none")]
ui: Option<ConfigToUi>, // If not None, send msg to ui.
}
#[derive(Debug, Deserialize)]
pub(super) struct MsgToExtSupport {
pub r#type: String,
pub data: Vec<u8>,
}
#[derive(Debug, Serialize)]
struct PluginSignReq {
plugin_id: String,
version: String,
msg: Vec<u8>,
}
#[derive(Debug, Deserialize)]
struct PluginSignResp {
signed_msg: Vec<u8>,
}
macro_rules! cb_msg_field {
($field: ident) => {
let $field = match cstr_to_string($field) {
Err(e) => {
let msg = format!("Failed to convert {} to string, {}", stringify!($field), e);
log::error!("{}", &msg);
return PluginReturn::new(errno::ERR_CALLBACK_INVALID_ARGS, &msg);
}
Ok(v) => v,
};
};
}
macro_rules! early_return_value {
($e:expr, $code: ident, $($arg:tt)*) => {
match $e {
Err(e) => return PluginReturn::new(
errno::$code,
&format!("Failed to {} '{}'", format_args!($($arg)*), e),
),
Ok(v) => v,
}
};
}
/// 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.
///
/// Return null ptr if success.
/// Return the error message if failed. `i32-String` without dash, i32 is a signed little-endian number, the String is utf8 string.
/// The plugin allocate memory with `libc::malloc` and return the pointer.
#[no_mangle]
pub(super) extern "C" fn cb_msg(
peer: *const c_char,
target: *const c_char,
id: *const c_char,
content: *const c_void,
len: usize,
) -> PluginReturn {
cb_msg_field!(target);
cb_msg_field!(id);
match &target as _ {
MSG_TO_PEER_TARGET => {
cb_msg_field!(peer);
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 request = PluginRequest {
id,
content: bytes::Bytes::from(content_vec),
..Default::default()
};
session.send_plugin_request(request);
PluginReturn::success()
} else {
PluginReturn::new(
errno::ERR_CALLBACK_PEER_NOT_FOUND,
&format!("Failed to find session for peer '{}'", peer),
)
}
}
MSG_TO_UI_TARGET => {
cb_msg_field!(peer);
let content_slice = unsafe { std::slice::from_raw_parts(content as *const u8, len) };
let channel = u16::from_le_bytes([content_slice[0], content_slice[1]]);
let content = std::string::String::from_utf8(content_slice[2..].to_vec())
.unwrap_or("".to_string());
push_event_to_ui(channel, &peer, &content);
PluginReturn::success()
}
MSG_TO_CONFIG_TARGET => {
cb_msg_field!(peer);
let s = early_return_value!(
std::str::from_utf8(unsafe { std::slice::from_raw_parts(content as _, len) }),
ERR_CALLBACK_INVALID_MSG,
"parse msg string"
);
// No need to merge the msgs. Handling the msg one by one is ok.
let msg = early_return_value!(
serde_json::from_str::<MsgToConfig>(s),
ERR_CALLBACK_INVALID_MSG,
"parse msg '{}'",
s
);
match &msg.r#type as _ {
config::CONFIG_TYPE_SHARED => {
let _r = early_return_value!(
config::SharedConfig::set(&id, &msg.key, &msg.value),
ERR_CALLBACK_INVALID_MSG,
"set local config"
);
if let Some(ui) = &msg.ui {
// No need to set the peer id for location config.
push_option_to_ui(ui.channel, &id, "", &msg, ui);
}
PluginReturn::success()
}
config::CONFIG_TYPE_PEER => {
let _r = early_return_value!(
config::PeerConfig::set(&id, &peer, &msg.key, &msg.value),
ERR_CALLBACK_INVALID_MSG,
"set peer config"
);
if let Some(ui) = &msg.ui {
push_option_to_ui(ui.channel, &id, &peer, &msg, ui);
}
PluginReturn::success()
}
_ => PluginReturn::new(
errno::ERR_CALLBACK_TARGET_TYPE,
&format!("Unknown target type '{}'", &msg.r#type),
),
}
}
MSG_TO_EXT_SUPPORT_TARGET => {
cb_msg_field!(peer);
let s = early_return_value!(
std::str::from_utf8(unsafe { std::slice::from_raw_parts(content as _, len) }),
ERR_CALLBACK_INVALID_MSG,
"parse msg string"
);
let msg = early_return_value!(
serde_json::from_str::<MsgToExtSupport>(s),
ERR_CALLBACK_INVALID_MSG,
"parse msg '{}'",
s
);
super::callback_ext::ext_support_callback(&id, &peer, &msg)
}
MSG_TO_RUSTDESK_TARGET => handle_msg_to_rustdesk(id, content, len),
_ => PluginReturn::new(
errno::ERR_CALLBACK_TARGET,
&format!("Unknown target '{}'", target),
),
}
}
#[inline]
fn is_peer_channel(channel: u16) -> bool {
channel & MSG_TO_UI_FLUTTER_CHANNEL_REMOTE != 0
|| channel & MSG_TO_UI_FLUTTER_CHANNEL_TRANSFER != 0
|| channel & MSG_TO_UI_FLUTTER_CHANNEL_FORWARD != 0
}
fn handle_msg_to_rustdesk(id: String, content: *const c_void, len: usize) -> PluginReturn {
let s = early_return_value!(
std::str::from_utf8(unsafe { std::slice::from_raw_parts(content as _, len) }),
ERR_CALLBACK_INVALID_MSG,
"parse msg string"
);
let msg_to_rustdesk = early_return_value!(
serde_json::from_str::<MsgToRustDesk>(s),
ERR_CALLBACK_INVALID_MSG,
"parse msg '{}'",
s
);
match &msg_to_rustdesk.r#type as &str {
MSG_TO_RUSTDESK_SIGNATURE_VERIFICATION => request_plugin_sign(id, msg_to_rustdesk),
t => PluginReturn::new(
errno::ERR_CALLBACK_TARGET_TYPE,
&format!(
"Unknown target type '{}' for target {}",
t, MSG_TO_RUSTDESK_TARGET
),
),
}
}
fn request_plugin_sign(id: String, msg_to_rustdesk: MsgToRustDesk) -> PluginReturn {
let signature_data = early_return_value!(
std::str::from_utf8(&msg_to_rustdesk.data),
ERR_CALLBACK_INVALID_MSG,
"parse signature data string"
);
let signature_data = early_return_value!(
serde_json::from_str::<SignatureVerification>(signature_data),
ERR_CALLBACK_INVALID_MSG,
"parse signature data '{}'",
signature_data
);
thread::spawn(move || {
let sign_url = format!("{}/lic/web/api/plugin-sign", get_api_server());
let client = create_http_client();
let req = PluginSignReq {
plugin_id: id.clone(),
version: signature_data.version,
msg: signature_data.data,
};
match client
.post(sign_url)
.json(&req)
.timeout(Duration::from_secs(10))
.send()
{
Ok(response) => match response.json::<PluginSignResp>() {
Ok(sign_resp) => {
match super::plugins::plugin_call(
&id,
super::plugins::METHOD_HANDLE_SIGNATURE_VERIFICATION,
"",
&sign_resp.signed_msg,
) {
Ok(..) => {
match super::plugins::plugin_call_get_return(
&id,
super::plugins::METHOD_HANDLE_STATUS,
"",
&[],
) {
Ok(ret) => {
debug_assert!(!ret.msg.is_null(), "msg is null");
if ret.msg.is_null() {
// unreachable
log::error!(
"The returned message pointer of plugin status is null, plugin id: '{}', code: {}",
id,
ret.code,
);
return;
}
let msg = cstr_to_string(ret.msg).unwrap_or_default();
free_c_ptr(ret.msg as _);
if ret.code == super::errno::ERR_SUCCESS {
log::info!("Plugin '{}' status: '{}'", id, msg);
} else {
log::error!(
"Failed to handle plugin event, id: {}, method: {}, code: {}, msg: {}",
id,
std::string::String::from_utf8(super::plugins::METHOD_HANDLE_STATUS.to_vec()).unwrap_or_default(),
ret.code,
msg
);
}
}
Err(e) => {
log::error!(
"Failed to call status for plugin '{}': {}",
&id,
e
);
}
}
}
Err(e) => {
log::error!(
"Failed to call signature verification for plugin '{}': {}",
&id,
e
);
}
}
}
Err(e) => {
log::error!("Failed to decode response for plugin '{}': {}", &id, e);
}
},
Err(e) => {
log::error!("Failed to request sign for plugin '{}', {}", &id, e);
}
}
});
PluginReturn::success()
}
fn push_event_to_ui(channel: u16, peer: &str, content: &str) {
let mut m = HashMap::new();
m.insert("name", MSG_TO_UI_TYPE_PLUGIN_EVENT);
m.insert("peer", &peer);
m.insert("content", &content);
let event = serde_json::to_string(&m).unwrap_or("".to_string());
// Send to main and cm
for (k, v) in MSG_TO_UI_FLUTTER_CHANNELS.iter() {
if channel & k != 0 {
let _res = flutter::push_global_event(v as _, event.to_string());
}
}
if !peer.is_empty() && is_peer_channel(channel) {
let _res = flutter::push_session_event(
&peer,
MSG_TO_UI_TYPE_PLUGIN_EVENT,
vec![("peer", &peer), ("content", &content)],
);
}
}
fn push_option_to_ui(channel: u16, id: &str, peer: &str, msg: &MsgToConfig, ui: &ConfigToUi) {
let v = [
("id", id),
("location", &ui.location),
("key", &msg.key),
("value", &msg.value),
];
// Send main and cm
let mut m = HashMap::from(v);
m.insert("name", MSG_TO_UI_TYPE_PLUGIN_OPTION);
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.to_string());
}
}
// Send remote, transfer and forward
if !peer.is_empty() && is_peer_channel(channel) {
let mut v = v.to_vec();
v.push(("peer", &peer));
let _res = flutter::push_session_event(&peer, MSG_TO_UI_TYPE_PLUGIN_OPTION, v);
}
}

View File

@@ -1,363 +0,0 @@
use super::{cstr_to_string, str_to_cstr_ret};
use hbb_common::{allow_err, bail, config::Config as HbbConfig, lazy_static, log, ResultType};
use serde_derive::{Deserialize, Serialize};
use std::{
collections::HashMap,
ffi::c_char,
fs,
ops::{Deref, DerefMut},
path::PathBuf,
ptr,
str::FromStr,
sync::{Arc, Mutex},
};
lazy_static::lazy_static! {
static ref CONFIG_SHARED: Arc<Mutex<HashMap<String, SharedConfig>>> = Default::default();
static ref CONFIG_PEERS: Arc<Mutex<HashMap<String, PeersConfig>>> = Default::default();
static ref CONFIG_MANAGER: Arc<Mutex<ManagerConfig>> = {
let conf = hbb_common::config::load_path::<ManagerConfig>(ManagerConfig::path());
Arc::new(Mutex::new(conf))
};
}
use crate::ui_interface::get_id;
pub(super) const CONFIG_TYPE_SHARED: &str = "shared";
pub(super) const CONFIG_TYPE_PEER: &str = "peer";
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct SharedConfig(HashMap<String, String>);
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct PeerConfig(HashMap<String, String>);
type PeersConfig = HashMap<String, PeerConfig>;
#[inline]
fn path_plugins(id: &str) -> PathBuf {
HbbConfig::path("plugins").join(id)
}
pub fn remove(id: &str) {
CONFIG_SHARED.lock().unwrap().remove(id);
CONFIG_PEERS.lock().unwrap().remove(id);
// allow_err is Ok here.
allow_err!(ManagerConfig::remove_plugin(id));
if let Err(e) = fs::remove_dir_all(path_plugins(id)) {
log::error!("Failed to remove plugin '{}' directory: {}", id, e);
}
}
impl Deref for SharedConfig {
type Target = HashMap<String, String>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for SharedConfig {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Deref for PeerConfig {
type Target = HashMap<String, String>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for PeerConfig {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl SharedConfig {
#[inline]
fn path(id: &str) -> PathBuf {
path_plugins(id).join("shared.toml")
}
#[inline]
fn load(id: &str) {
let mut lock = CONFIG_SHARED.lock().unwrap();
if lock.contains_key(id) {
return;
}
let conf = hbb_common::config::load_path::<HashMap<String, String>>(Self::path(id));
let mut conf = SharedConfig(conf);
if let Some(desc_conf) = super::plugins::get_desc_conf(id) {
for item in desc_conf.shared.iter() {
if !conf.contains_key(&item.key) {
conf.insert(item.key.to_owned(), item.default.to_owned());
}
}
}
lock.insert(id.to_owned(), conf);
}
#[inline]
fn load_if_not_exists(id: &str) {
if CONFIG_SHARED.lock().unwrap().contains_key(id) {
return;
}
Self::load(id);
}
#[inline]
pub fn get(id: &str, key: &str) -> Option<String> {
Self::load_if_not_exists(id);
CONFIG_SHARED
.lock()
.unwrap()
.get(id)?
.get(key)
.map(|s| s.to_owned())
}
#[inline]
pub fn set(id: &str, key: &str, value: &str) -> ResultType<()> {
Self::load_if_not_exists(id);
match CONFIG_SHARED.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 => {
// unreachable
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]
fn load(id: &str, peer: &str) {
let mut lock = CONFIG_PEERS.lock().unwrap();
if let Some(peers) = lock.get(id) {
if peers.contains_key(peer) {
return;
}
}
let conf = hbb_common::config::load_path::<HashMap<String, String>>(Self::path(id, peer));
let mut conf = PeerConfig(conf);
if let Some(desc_conf) = super::plugins::get_desc_conf(id) {
for item in desc_conf.peer.iter() {
if !conf.contains_key(&item.key) {
conf.insert(item.key.to_owned(), item.default.to_owned());
}
}
}
if let Some(peers) = lock.get_mut(id) {
peers.insert(peer.to_owned(), conf);
return;
}
let mut peers = HashMap::new();
peers.insert(peer.to_owned(), conf);
lock.insert(id.to_owned(), peers);
}
#[inline]
fn load_if_not_exists(id: &str, peer: &str) {
if let Some(peers) = CONFIG_PEERS.lock().unwrap().get(id) {
if peers.contains_key(peer) {
return;
}
}
Self::load(id, peer);
}
#[inline]
pub fn get(id: &str, peer: &str, key: &str) -> Option<String> {
Self::load_if_not_exists(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<()> {
Self::load_if_not_exists(id, peer);
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 => {
// unreachable
bail!("No such peer {}", peer)
}
},
None => {
// unreachable
bail!("No such plugin {}", id)
}
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PluginStatus {
pub enabled: bool,
}
const MANAGER_VERSION: &str = "0.1.0";
#[derive(Debug, Serialize, Deserialize)]
pub struct ManagerConfig {
pub version: String,
#[serde(default)]
pub options: HashMap<String, String>,
#[serde(default)]
pub plugins: HashMap<String, PluginStatus>,
}
impl Default for ManagerConfig {
fn default() -> Self {
Self {
version: MANAGER_VERSION.to_owned(),
options: HashMap::new(),
plugins: HashMap::new(),
}
}
}
// Do not care about the `store_path` error, no need to store the old value and restore if failed.
impl ManagerConfig {
#[inline]
fn path() -> PathBuf {
HbbConfig::path("plugins").join("manager.toml")
}
#[inline]
pub fn get_option(key: &str) -> Option<String> {
CONFIG_MANAGER
.lock()
.unwrap()
.options
.get(key)
.map(|s| s.to_owned())
}
#[inline]
pub fn set_option(key: &str, value: &str) {
let mut lock = CONFIG_MANAGER.lock().unwrap();
lock.options.insert(key.to_owned(), value.to_owned());
allow_err!(hbb_common::config::store_path(Self::path(), &*lock));
}
#[inline]
pub fn get_plugin_option(id: &str, key: &str) -> Option<String> {
let lock = CONFIG_MANAGER.lock().unwrap();
match key {
"enabled" => {
let enabled = lock
.plugins
.get(id)
.map(|status| status.enabled.to_owned())
.unwrap_or(true.to_owned())
.to_string();
Some(enabled)
}
_ => None,
}
}
fn set_plugin_option_enabled(id: &str, enabled: bool) -> ResultType<()> {
let mut lock = CONFIG_MANAGER.lock().unwrap();
if let Some(status) = lock.plugins.get_mut(id) {
status.enabled = enabled;
} else {
lock.plugins.insert(id.to_owned(), PluginStatus { enabled });
}
hbb_common::config::store_path(Self::path(), &*lock)
}
pub fn set_plugin_option(id: &str, key: &str, value: &str) {
match key {
"enabled" => {
let enabled = bool::from_str(value).unwrap_or(false);
allow_err!(Self::set_plugin_option_enabled(id, enabled));
if enabled {
allow_err!(super::load_plugin(id));
} else {
super::unload_plugin(id);
}
}
_ => log::error!("No such option {}", key),
}
}
#[inline]
pub fn add_plugin(id: &str) -> ResultType<()> {
let mut lock = CONFIG_MANAGER.lock().unwrap();
lock.plugins
.insert(id.to_owned(), PluginStatus { enabled: true });
hbb_common::config::store_path(Self::path(), &*lock)
}
#[inline]
pub fn remove_plugin(id: &str) -> ResultType<()> {
let mut lock = CONFIG_MANAGER.lock().unwrap();
lock.plugins.remove(id);
hbb_common::config::store_path(Self::path(), &*lock)
}
}
pub(super) extern "C" fn cb_get_local_peer_id() -> *const c_char {
str_to_cstr_ret(&get_id())
}
// Return shared config if peer is nullptr.
pub(super) extern "C" fn cb_get_conf(
peer: *const c_char,
id: *const c_char,
key: *const c_char,
) -> *const c_char {
match (cstr_to_string(id), cstr_to_string(key)) {
(Ok(id), Ok(key)) => {
if peer.is_null() {
SharedConfig::load_if_not_exists(&id);
if let Some(conf) = CONFIG_SHARED.lock().unwrap().get(&id) {
if let Some(value) = conf.get(&key) {
return str_to_cstr_ret(value);
}
}
} else {
match cstr_to_string(peer) {
Ok(peer) => {
PeerConfig::load_if_not_exists(&id, &peer);
if let Some(conf) = CONFIG_PEERS.lock().unwrap().get(&id) {
if let Some(conf) = conf.get(&peer) {
if let Some(value) = conf.get(&key) {
return str_to_cstr_ret(value);
}
}
}
}
Err(_) => {}
}
}
}
_ => {}
}
ptr::null()
}

View File

@@ -1,100 +0,0 @@
use hbb_common::ResultType;
use serde_derive::{Deserialize, Serialize};
use serde_json;
use std::collections::HashMap;
use std::ffi::{c_char, CStr};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UiButton {
key: String,
text: String,
icon: String, // icon can be int in flutter, but string in other ui framework. And it is flexible to use string.
tooltip: String,
action: String, // The action to be triggered when the button is clicked.
}
#[derive(Debug, Clone, Serialize, 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, Clone, Serialize, Deserialize)]
#[serde(tag = "t", content = "c")]
pub enum UiType {
Button(UiButton),
Checkbox(UiCheckbox),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Location {
pub ui: HashMap<String, Vec<UiType>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigItem {
pub key: String,
pub default: String,
pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub shared: Vec<ConfigItem>,
pub peer: Vec<ConfigItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PublishInfo {
pub published: String,
pub last_released: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Meta {
pub id: String,
pub name: String,
pub version: String,
pub description: String,
#[serde(default)]
pub platforms: String,
pub author: String,
pub home: String,
pub license: String,
pub source: String,
pub publish_info: PublishInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Desc {
meta: Meta,
need_reboot: bool,
location: Location,
config: Config,
listen_events: Vec<String>,
}
impl Desc {
pub fn from_cstr(s: *const c_char) -> ResultType<Self> {
let s = unsafe { CStr::from_ptr(s) };
Ok(serde_json::from_str(s.to_str()?)?)
}
pub fn meta(&self) -> &Meta {
&self.meta
}
pub fn location(&self) -> &Location {
&self.location
}
pub fn config(&self) -> &Config {
&self.config
}
pub fn listen_events(&self) -> &Vec<String> {
&self.listen_events
}
}

View File

@@ -1,50 +0,0 @@
#![allow(dead_code)]
pub const ERR_SUCCESS: i32 = 0;
// ======================================================
// Errors from the plugins, must be handled by RustDesk
pub const ERR_RUSTDESK_HANDLE_BASE: i32 = 10000;
// not loaded
pub const ERR_PLUGIN_LOAD: i32 = 10001;
// not initialized
pub const ERR_PLUGIN_MSG_INIT: i32 = 10101;
pub const ERR_PLUGIN_MSG_INIT_INVALID: i32 = 10102;
pub const ERR_PLUGIN_MSG_GET_LOCAL_PEER_ID: i32 = 10103;
pub const ERR_PLUGIN_SIGNATURE_NOT_VERIFIED: i32 = 10104;
pub const ERR_PLUGIN_SIGNATURE_VERIFICATION_FAILED: i32 = 10105;
// invalid
pub const ERR_CALL_UNIMPLEMENTED: i32 = 10201;
pub const ERR_CALL_INVALID_METHOD: i32 = 10202;
pub const ERR_CALL_NOT_SUPPORTED_METHOD: i32 = 10203;
pub const ERR_CALL_INVALID_PEER: i32 = 10204;
// failed on calling
pub const ERR_CALL_INVALID_ARGS: i32 = 10301;
pub const ERR_PEER_ID_MISMATCH: i32 = 10302;
pub const ERR_CALL_CONFIG_VALUE: i32 = 10303;
// no handlers on calling
pub const ERR_NOT_HANDLED: i32 = 10401;
// ======================================================
// Errors from RustDesk callbacks.
pub const ERR_CALLBACK_HANDLE_BASE: i32 = 20000;
pub const ERR_CALLBACK_PLUGIN_ID: i32 = 20001;
pub const ERR_CALLBACK_INVALID_ARGS: i32 = 20002;
pub const ERR_CALLBACK_INVALID_MSG: i32 = 20003;
pub const ERR_CALLBACK_TARGET: i32 = 20004;
pub const ERR_CALLBACK_TARGET_TYPE: i32 = 20005;
pub const ERR_CALLBACK_PEER_NOT_FOUND: i32 = 20006;
pub const ERR_CALLBACK_FAILED: i32 = 21001;
// ======================================================
// Errors from the plugins, should be handled by the plugins.
pub const ERR_PLUGIN_HANDLE_BASE: i32 = 30000;
pub const EER_CALL_FAILED: i32 = 30021;
pub const ERR_PEER_ON_FAILED: i32 = 40012;
pub const ERR_PEER_OFF_FAILED: i32 = 40012;

View File

@@ -1,230 +0,0 @@
// to-do: Interdependence(This mod and crate::ipc) is not good practice here.
use crate::ipc::{connect, Connection, Data};
use hbb_common::{allow_err, log, tokio, ResultType};
use serde_derive::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum InstallStatus {
Downloading(u8),
Installing,
Finished,
FailedCreating,
FailedDownloading,
FailedInstalling,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "t", content = "c")]
pub enum Plugin {
Config(String, String, Option<String>),
ManagerConfig(String, Option<String>),
ManagerPluginConfig(String, String, Option<String>),
Load(String),
Reload(String),
InstallStatus((String, InstallStatus)),
Uninstall(String),
}
#[tokio::main(flavor = "current_thread")]
pub async fn get_config(id: &str, name: &str) -> ResultType<Option<String>> {
get_config_async(id, name, 1_000).await
}
#[tokio::main(flavor = "current_thread")]
pub async fn set_config(id: &str, name: &str, value: String) -> ResultType<()> {
set_config_async(id, name, value).await
}
#[tokio::main(flavor = "current_thread")]
pub async fn get_manager_config(name: &str) -> ResultType<Option<String>> {
get_manager_config_async(name, 1_000).await
}
#[tokio::main(flavor = "current_thread")]
pub async fn set_manager_config(name: &str, value: String) -> ResultType<()> {
set_manager_config_async(name, value).await
}
#[tokio::main(flavor = "current_thread")]
pub async fn get_manager_plugin_config(id: &str, name: &str) -> ResultType<Option<String>> {
get_manager_plugin_config_async(id, name, 1_000).await
}
#[tokio::main(flavor = "current_thread")]
pub async fn set_manager_plugin_config(id: &str, name: &str, value: String) -> ResultType<()> {
set_manager_plugin_config_async(id, name, value).await
}
#[tokio::main(flavor = "current_thread")]
pub async fn load_plugin(id: &str) -> ResultType<()> {
load_plugin_async(id).await
}
#[tokio::main(flavor = "current_thread")]
pub async fn reload_plugin(id: &str) -> ResultType<()> {
reload_plugin_async(id).await
}
#[tokio::main(flavor = "current_thread")]
pub async fn uninstall_plugin(id: &str) -> ResultType<()> {
uninstall_plugin_async(id).await
}
async fn get_config_async(id: &str, name: &str, ms_timeout: u64) -> ResultType<Option<String>> {
let mut c = connect(ms_timeout, "").await?;
c.send(&Data::Plugin(Plugin::Config(
id.to_owned(),
name.to_owned(),
None,
)))
.await?;
if let Some(Data::Plugin(Plugin::Config(id2, name2, value))) =
c.next_timeout(ms_timeout).await?
{
if id == id2 && name == name2 {
return Ok(value);
}
}
return Ok(None);
}
async fn set_config_async(id: &str, name: &str, value: String) -> ResultType<()> {
let mut c = connect(1000, "").await?;
c.send(&Data::Plugin(Plugin::Config(
id.to_owned(),
name.to_owned(),
Some(value),
)))
.await?;
Ok(())
}
async fn get_manager_config_async(name: &str, ms_timeout: u64) -> ResultType<Option<String>> {
let mut c = connect(ms_timeout, "").await?;
c.send(&Data::Plugin(Plugin::ManagerConfig(name.to_owned(), None)))
.await?;
if let Some(Data::Plugin(Plugin::ManagerConfig(name2, value))) =
c.next_timeout(ms_timeout).await?
{
if name == name2 {
return Ok(value);
}
}
return Ok(None);
}
async fn set_manager_config_async(name: &str, value: String) -> ResultType<()> {
let mut c = connect(1000, "").await?;
c.send(&Data::Plugin(Plugin::ManagerConfig(
name.to_owned(),
Some(value),
)))
.await?;
Ok(())
}
async fn get_manager_plugin_config_async(
id: &str,
name: &str,
ms_timeout: u64,
) -> ResultType<Option<String>> {
let mut c = connect(ms_timeout, "").await?;
c.send(&Data::Plugin(Plugin::ManagerPluginConfig(
id.to_owned(),
name.to_owned(),
None,
)))
.await?;
if let Some(Data::Plugin(Plugin::ManagerPluginConfig(id2, name2, value))) =
c.next_timeout(ms_timeout).await?
{
if id == id2 && name == name2 {
return Ok(value);
}
}
return Ok(None);
}
async fn set_manager_plugin_config_async(id: &str, name: &str, value: String) -> ResultType<()> {
let mut c = connect(1000, "").await?;
c.send(&Data::Plugin(Plugin::ManagerPluginConfig(
id.to_owned(),
name.to_owned(),
Some(value),
)))
.await?;
Ok(())
}
pub async fn load_plugin_async(id: &str) -> ResultType<()> {
let mut c = connect(1000, "").await?;
c.send(&Data::Plugin(Plugin::Load(id.to_owned()))).await?;
Ok(())
}
async fn reload_plugin_async(id: &str) -> ResultType<()> {
let mut c = connect(1000, "").await?;
c.send(&Data::Plugin(Plugin::Reload(id.to_owned()))).await?;
Ok(())
}
async fn uninstall_plugin_async(id: &str) -> ResultType<()> {
let mut c = connect(1000, "").await?;
c.send(&Data::Plugin(Plugin::Uninstall(id.to_owned())))
.await?;
Ok(())
}
pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) {
match plugin {
Plugin::Config(id, name, value) => match value {
None => {
let value = super::SharedConfig::get(&id, &name);
allow_err!(
stream
.send(&Data::Plugin(Plugin::Config(id, name, value)))
.await
);
}
Some(value) => {
allow_err!(super::SharedConfig::set(&id, &name, &value));
}
},
Plugin::ManagerConfig(name, value) => match value {
None => {
let value = super::ManagerConfig::get_option(&name);
allow_err!(
stream
.send(&Data::Plugin(Plugin::ManagerConfig(name, value)))
.await
);
}
Some(value) => {
super::ManagerConfig::set_option(&name, &value);
}
},
Plugin::ManagerPluginConfig(id, name, value) => match value {
None => {
let value = super::ManagerConfig::get_plugin_option(&id, &name);
allow_err!(
stream
.send(&Data::Plugin(Plugin::ManagerPluginConfig(id, name, value)))
.await
);
}
Some(value) => {
super::ManagerConfig::set_plugin_option(&id, &name, &value);
}
},
Plugin::Load(id) => {
allow_err!(super::load_plugin(&id));
}
Plugin::Reload(id) => {
allow_err!(super::reload_plugin(&id));
}
Plugin::Uninstall(id) => {
super::manager::uninstall_plugin(&id, false);
}
_ => {}
}
}

View File

@@ -1,600 +0,0 @@
// 1. Check update.
// 2. Install or uninstall.
use super::{desc::Meta as PluginMeta, ipc::InstallStatus, *};
use crate::flutter;
use crate::hbbs_http::create_http_client;
use hbb_common::{allow_err, bail, log, tokio, toml};
use serde_derive::{Deserialize, Serialize};
use serde_json;
use std::{
collections::{HashMap, HashSet},
fs::{read_to_string, remove_dir_all, OpenOptions},
io::Write,
sync::{Arc, Mutex},
};
const MSG_TO_UI_PLUGIN_MANAGER_LIST: &str = "plugin_list";
const MSG_TO_UI_PLUGIN_MANAGER_INSTALL: &str = "plugin_install";
const MSG_TO_UI_PLUGIN_MANAGER_UNINSTALL: &str = "plugin_uninstall";
const IPC_PLUGIN_POSTFIX: &str = "_plugin";
#[cfg(target_os = "windows")]
const PLUGIN_PLATFORM: &str = "windows";
#[cfg(target_os = "linux")]
const PLUGIN_PLATFORM: &str = "linux";
#[cfg(target_os = "macos")]
const PLUGIN_PLATFORM: &str = "macos";
lazy_static::lazy_static! {
static ref PLUGIN_INFO: Arc<Mutex<HashMap<String, PluginInfo>>> = Arc::new(Mutex::new(HashMap::new()));
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct ManagerMeta {
pub version: String,
pub description: String,
pub plugins: Vec<PluginMeta>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginSource {
pub name: String,
pub url: String,
pub description: String,
}
#[derive(Debug, Serialize)]
pub struct PluginInfo {
pub source: PluginSource,
pub meta: PluginMeta,
pub installed_version: String,
pub invalid_reason: String,
}
static PLUGIN_SOURCE_LOCAL: &str = "local";
fn get_plugin_source_list() -> Vec<PluginSource> {
// Only one source for now.
// vec![PluginSource {
// name: "rustdesk".to_string(),
// url: "https://raw.githubusercontent.com/fufesou/rustdesk-plugins/main".to_string(),
// description: "".to_string(),
// }]
vec![]
}
fn get_source_plugins() -> HashMap<String, PluginInfo> {
let mut plugins = HashMap::new();
for source in get_plugin_source_list().into_iter() {
let url = format!("{}/meta.toml", source.url);
match create_http_client().get(&url).send() {
Ok(resp) => {
if !resp.status().is_success() {
log::error!(
"Failed to get plugin list from '{}', status code: {}",
url,
resp.status()
);
}
if let Ok(text) = resp.text() {
match toml::from_str::<ManagerMeta>(&text) {
Ok(manager_meta) => {
for meta in manager_meta.plugins.iter() {
if !meta
.platforms
.to_uppercase()
.contains(&PLUGIN_PLATFORM.to_uppercase())
{
continue;
}
plugins.insert(
meta.id.clone(),
PluginInfo {
source: source.clone(),
meta: meta.clone(),
installed_version: "".to_string(),
invalid_reason: "".to_string(),
},
);
}
}
Err(e) => log::error!("Failed to parse plugin list from '{}', {}", url, e),
}
}
}
Err(e) => log::error!("Failed to get plugin list from '{}', {}", url, e),
}
}
plugins
}
fn send_plugin_list_event(plugins: &HashMap<String, PluginInfo>) {
let mut plugin_list = plugins.values().collect::<Vec<_>>();
plugin_list.sort_by(|a, b| a.meta.name.cmp(&b.meta.name));
if let Ok(plugin_list) = serde_json::to_string(&plugin_list) {
let mut m = HashMap::new();
m.insert("name", MSG_TO_UI_TYPE_PLUGIN_MANAGER);
m.insert(MSG_TO_UI_PLUGIN_MANAGER_LIST, &plugin_list);
if let Ok(event) = serde_json::to_string(&m) {
let _res = flutter::push_global_event(flutter::APP_TYPE_MAIN, event.clone());
}
}
}
pub fn load_plugin_list() {
let mut plugin_info_lock = PLUGIN_INFO.lock().unwrap();
let mut plugins = get_source_plugins();
// A big read lock is needed to prevent race conditions.
// Loading plugin list may be slow.
// Users may call uninstall plugin in the middle.
let plugin_infos = super::plugins::get_plugin_infos();
let plugin_infos_read_lock = plugin_infos.read().unwrap();
for (id, info) in plugin_infos_read_lock.iter() {
if info.uninstalled {
continue;
}
if let Some(p) = plugins.get_mut(id) {
p.installed_version = info.desc.meta().version.clone();
p.invalid_reason = "".to_string();
} else {
plugins.insert(
id.to_string(),
PluginInfo {
source: PluginSource {
name: PLUGIN_SOURCE_LOCAL.to_string(),
url: PLUGIN_SOURCE_LOCAL_DIR.to_string(),
description: "".to_string(),
},
meta: info.desc.meta().clone(),
installed_version: info.desc.meta().version.clone(),
invalid_reason: "".to_string(),
},
);
}
}
send_plugin_list_event(&plugins);
*plugin_info_lock = plugins;
}
#[cfg(target_os = "windows")]
fn elevate_install(
plugin_id: &str,
plugin_url: &str,
same_plugin_exists: bool,
) -> ResultType<bool> {
// to-do: Support args with space in quotes. 'arg 1' and "arg 2"
let args = if same_plugin_exists {
format!("--plugin-install {}", plugin_id)
} else {
format!("--plugin-install {} {}", plugin_id, plugin_url)
};
crate::platform::elevate(&args)
}
#[cfg(target_os = "linux")]
fn elevate_install(
plugin_id: &str,
plugin_url: &str,
same_plugin_exists: bool,
) -> ResultType<bool> {
let mut args = vec!["--plugin-install", plugin_id];
if !same_plugin_exists {
args.push(&plugin_url);
}
crate::platform::elevate(args)
}
#[cfg(target_os = "macos")]
fn elevate_install(
plugin_id: &str,
plugin_url: &str,
same_plugin_exists: bool,
) -> ResultType<bool> {
let mut args = vec!["--plugin-install", plugin_id];
if !same_plugin_exists {
args.push(&plugin_url);
}
crate::platform::elevate(args, "RustDesk wants to install then plugin")
}
#[inline]
#[cfg(target_os = "windows")]
fn elevate_uninstall(plugin_id: &str) -> ResultType<bool> {
crate::platform::elevate(&format!("--plugin-uninstall {}", plugin_id))
}
#[inline]
#[cfg(target_os = "linux")]
fn elevate_uninstall(plugin_id: &str) -> ResultType<bool> {
crate::platform::elevate(vec!["--plugin-uninstall", plugin_id])
}
#[inline]
#[cfg(target_os = "macos")]
fn elevate_uninstall(plugin_id: &str) -> ResultType<bool> {
crate::platform::elevate(
vec!["--plugin-uninstall", plugin_id],
"RustDesk wants to uninstall the plugin",
)
}
pub fn install_plugin(id: &str) -> ResultType<()> {
match PLUGIN_INFO.lock().unwrap().get(id) {
Some(plugin) => {
let mut same_plugin_exists = false;
if let Some(version) = super::plugins::get_version(id) {
if version == plugin.meta.version {
same_plugin_exists = true;
}
}
let plugin_url = format!(
"{}/plugins/{}/{}/{}_{}.zip",
plugin.source.url,
plugin.meta.id,
PLUGIN_PLATFORM,
plugin.meta.id,
plugin.meta.version
);
let allowed_install = elevate_install(id, &plugin_url, same_plugin_exists)?;
if allowed_install && same_plugin_exists {
super::ipc::load_plugin(id)?;
super::plugins::load_plugin(id)?;
super::plugins::mark_uninstalled(id, false);
push_install_event(id, "finished");
}
Ok(())
}
None => {
bail!("Plugin not found: {}", id);
}
}
}
fn get_uninstalled_plugins(uninstalled_plugin_set: &HashSet<String>) -> ResultType<Vec<String>> {
let plugins_dir = super::get_plugins_dir()?;
let mut plugins = Vec::new();
if plugins_dir.exists() {
for entry in std::fs::read_dir(plugins_dir)? {
match entry {
Ok(entry) => {
let plugin_dir = entry.path();
if plugin_dir.is_dir() {
if let Some(id) = plugin_dir.file_name().and_then(|n| n.to_str()) {
if uninstalled_plugin_set.contains(id) {
plugins.push(id.to_string());
}
}
}
}
Err(e) => {
log::error!("Failed to read plugins dir entry, {}", e);
}
}
}
}
Ok(plugins)
}
pub fn remove_uninstalled() -> ResultType<()> {
let mut uninstalled_plugin_set = get_uninstall_id_set()?;
for id in get_uninstalled_plugins(&uninstalled_plugin_set)?.iter() {
super::config::remove(id as _);
if let Ok(dir) = super::get_plugin_dir(id as _) {
allow_err!(remove_dir_all(dir.clone()));
if !dir.exists() {
uninstalled_plugin_set.remove(id);
}
}
}
allow_err!(update_uninstall_id_set(uninstalled_plugin_set));
Ok(())
}
pub fn uninstall_plugin(id: &str, called_by_ui: bool) {
if called_by_ui {
match elevate_uninstall(id) {
Ok(true) => {
if let Err(e) = super::ipc::uninstall_plugin(id) {
log::error!("Failed to uninstall plugin '{}': {}", id, e);
push_uninstall_event(id, "failed");
return;
}
super::plugins::unload_plugin(id);
super::plugins::mark_uninstalled(id, true);
super::config::remove(id);
push_uninstall_event(id, "");
}
Ok(false) => {
return;
}
Err(e) => {
log::error!(
"Failed to uninstall plugin '{}', check permission error: {}",
id,
e
);
push_uninstall_event(id, "failed");
return;
}
}
}
if super::is_server_running() {
super::plugins::unload_plugin(&id);
}
}
fn push_event(id: &str, r#type: &str, msg: &str) {
let mut m = HashMap::new();
m.insert("name", MSG_TO_UI_TYPE_PLUGIN_MANAGER);
m.insert("id", id);
m.insert(r#type, msg);
if let Ok(event) = serde_json::to_string(&m) {
let _res = flutter::push_global_event(flutter::APP_TYPE_MAIN, event.clone());
}
}
#[inline]
fn push_uninstall_event(id: &str, msg: &str) {
push_event(id, MSG_TO_UI_PLUGIN_MANAGER_UNINSTALL, msg);
}
#[inline]
fn push_install_event(id: &str, msg: &str) {
push_event(id, MSG_TO_UI_PLUGIN_MANAGER_INSTALL, msg);
}
async fn handle_conn(mut stream: crate::ipc::Connection) {
loop {
tokio::select! {
res = stream.next() => {
match res {
Err(err) => {
log::trace!("plugin ipc connection closed: {}", err);
break;
}
Ok(Some(data)) => {
match &data {
crate::ipc::Data::Plugin(super::ipc::Plugin::InstallStatus((id, status))) => {
match status {
InstallStatus::Downloading(n) => {
push_install_event(&id, &format!("downloading-{}", n));
},
InstallStatus::Installing => {
push_install_event(&id, "installing");
}
InstallStatus::Finished => {
allow_err!(super::plugins::load_plugin(&id));
allow_err!(super::ipc::load_plugin_async(id).await);
std::thread::spawn(load_plugin_list);
push_install_event(&id, "finished");
}
InstallStatus::FailedCreating => {
push_install_event(&id, "failed-creating");
}
InstallStatus::FailedDownloading => {
push_install_event(&id, "failed-downloading");
}
InstallStatus::FailedInstalling => {
push_install_event(&id, "failed-installing");
}
}
}
_ => {}
}
}
_ => {
}
}
}
}
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[tokio::main]
pub async fn start_ipc() {
match crate::ipc::new_listener(IPC_PLUGIN_POSTFIX).await {
Ok(mut incoming) => {
while let Some(result) = incoming.next().await {
match result {
Ok(stream) => {
log::debug!("Got new connection");
tokio::spawn(handle_conn(crate::ipc::Connection::new(stream)));
}
Err(err) => {
log::error!("Couldn't get plugin client: {:?}", err);
}
}
}
}
Err(err) => {
log::error!("Failed to start plugin ipc server: {}", err);
}
}
}
pub(super) fn get_uninstall_id_set() -> ResultType<HashSet<String>> {
let uninstall_file_path = super::get_uninstall_file_path()?;
if !uninstall_file_path.exists() {
std::fs::create_dir_all(&super::get_plugins_dir()?)?;
return Ok(HashSet::new());
}
let s = read_to_string(uninstall_file_path)?;
Ok(serde_json::from_str::<HashSet<String>>(&s)?)
}
fn update_uninstall_id_set(set: HashSet<String>) -> ResultType<()> {
let content = serde_json::to_string(&set)?;
let file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(super::get_uninstall_file_path()?)?;
let mut writer = std::io::BufWriter::new(file);
writer.write_all(content.as_bytes())?;
Ok(())
}
// install process
pub(super) mod install {
use super::IPC_PLUGIN_POSTFIX;
use crate::hbbs_http::create_http_client;
use crate::{
ipc::{connect, Data},
plugin::ipc::{InstallStatus, Plugin},
};
use hbb_common::{allow_err, bail, log, tokio, ResultType};
use std::{
fs::File,
io::{BufReader, BufWriter, Write},
path::Path,
};
use zip::ZipArchive;
#[tokio::main(flavor = "current_thread")]
async fn send_install_status(id: &str, status: InstallStatus) {
allow_err!(_send_install_status(id, status).await);
}
async fn _send_install_status(id: &str, status: InstallStatus) -> ResultType<()> {
let mut c = connect(1_000, IPC_PLUGIN_POSTFIX).await?;
c.send(&Data::Plugin(Plugin::InstallStatus((
id.to_string(),
status,
))))
.await?;
Ok(())
}
fn download_to_file(url: &str, file: File) -> ResultType<()> {
let resp = match create_http_client().get(url).send() {
Ok(resp) => resp,
Err(e) => {
bail!("get plugin from '{}', {}", url, e);
}
};
if !resp.status().is_success() {
bail!("get plugin from '{}', status code: {}", url, resp.status());
}
let mut writer = BufWriter::new(file);
writer.write_all(resp.bytes()?.as_ref())?;
Ok(())
}
fn download_file(id: &str, url: &str, filename: &Path) -> bool {
let file = match File::create(filename) {
Ok(f) => f,
Err(e) => {
log::error!("Failed to create plugin file: {}", e);
send_install_status(id, InstallStatus::FailedCreating);
return false;
}
};
if let Err(e) = download_to_file(url, file) {
log::error!("Failed to download plugin '{}', {}", id, e);
send_install_status(id, InstallStatus::FailedDownloading);
return false;
}
true
}
fn do_install_file(filename: &Path, target_dir: &Path) -> ResultType<()> {
let mut zip = ZipArchive::new(BufReader::new(File::open(filename)?))?;
for i in 0..zip.len() {
let mut file = zip.by_index(i)?;
let file_path = target_dir.join(file.name());
if file.name().ends_with("/") {
std::fs::create_dir_all(&file_path)?;
} else {
if let Some(p) = file_path.parent() {
if !p.exists() {
std::fs::create_dir_all(&p)?;
}
}
let mut outfile = File::create(&file_path)?;
std::io::copy(&mut file, &mut outfile)?;
}
}
Ok(())
}
pub fn change_uninstall_plugin(id: &str, add: bool) {
match super::get_uninstall_id_set() {
Ok(mut set) => {
if add {
set.insert(id.to_string());
} else {
set.remove(id);
}
if let Err(e) = super::update_uninstall_id_set(set) {
log::error!("Failed to write uninstall list, {}", e);
}
}
Err(e) => log::error!(
"Failed to get plugins dir, unable to read uninstall list, {}",
e
),
}
}
pub fn install_plugin_with_url(id: &str, url: &str) {
log::info!("Installing plugin '{}', url: {}", id, url);
let plugin_dir = match super::super::get_plugin_dir(id) {
Ok(d) => d,
Err(e) => {
send_install_status(id, InstallStatus::FailedCreating);
log::error!("Failed to get plugin dir: {}", e);
return;
}
};
if !plugin_dir.exists() {
if let Err(e) = std::fs::create_dir_all(&plugin_dir) {
send_install_status(id, InstallStatus::FailedCreating);
log::error!("Failed to create plugin dir: {}", e);
return;
}
}
let filename = match url.rsplit('/').next() {
Some(filename) => plugin_dir.join(filename),
None => {
send_install_status(id, InstallStatus::FailedDownloading);
log::error!("Failed to download plugin file, invalid url: {}", url);
return;
}
};
let filename_to_remove = filename.clone();
let _call_on_ret = crate::common::SimpleCallOnReturn {
b: true,
f: Box::new(move || {
if let Err(e) = std::fs::remove_file(&filename_to_remove) {
log::error!("Failed to remove plugin file: {}", e);
}
}),
};
// download
if !download_file(id, url, &filename) {
return;
}
// install
send_install_status(id, InstallStatus::Installing);
if let Err(e) = do_install_file(&filename, &plugin_dir) {
log::error!("Failed to install plugin: {}", e);
send_install_status(id, InstallStatus::FailedInstalling);
return;
}
// finished
send_install_status(id, InstallStatus::Finished);
}
}

View File

@@ -1,188 +0,0 @@
use hbb_common::{bail, libc, log, ResultType};
#[cfg(target_os = "windows")]
use std::env;
use std::{
ffi::{c_char, c_int, c_void, CStr},
path::PathBuf,
ptr::null,
};
mod callback_ext;
mod callback_msg;
mod config;
pub mod desc;
mod errno;
pub mod ipc;
mod manager;
pub mod native;
pub mod native_handlers;
mod plog;
mod plugins;
pub use manager::{
install::{change_uninstall_plugin, install_plugin_with_url},
install_plugin, load_plugin_list, remove_uninstalled, uninstall_plugin,
};
pub use plugins::{
handle_client_event, handle_listen_event, handle_server_event, handle_ui_event, load_plugin,
reload_plugin, sync_ui, unload_plugin,
};
const MSG_TO_UI_TYPE_PLUGIN_EVENT: &str = "plugin_event";
const MSG_TO_UI_TYPE_PLUGIN_RELOAD: &str = "plugin_reload";
const MSG_TO_UI_TYPE_PLUGIN_OPTION: &str = "plugin_option";
const MSG_TO_UI_TYPE_PLUGIN_MANAGER: &str = "plugin_manager";
pub const EVENT_ON_CONN_CLIENT: &str = "on_conn_client";
pub const EVENT_ON_CONN_SERVER: &str = "on_conn_server";
pub const EVENT_ON_CONN_CLOSE_CLIENT: &str = "on_conn_close_client";
pub const EVENT_ON_CONN_CLOSE_SERVER: &str = "on_conn_close_server";
static PLUGIN_SOURCE_LOCAL_DIR: &str = "plugins";
pub use config::{ManagerConfig, PeerConfig, SharedConfig};
/// Common plugin return.
///
/// [Note]
/// The msg must be nullptr if code is errno::ERR_SUCCESS.
/// The msg must be freed by caller if code is not errno::ERR_SUCCESS.
#[repr(C)]
#[derive(Debug)]
pub struct PluginReturn {
pub code: c_int,
pub msg: *const c_char,
}
impl PluginReturn {
pub fn success() -> Self {
Self {
code: errno::ERR_SUCCESS,
msg: null(),
}
}
#[inline]
pub fn is_success(&self) -> bool {
self.code == errno::ERR_SUCCESS
}
pub fn new(code: c_int, msg: &str) -> Self {
Self {
code,
msg: str_to_cstr_ret(msg),
}
}
pub fn get_code_msg(&mut self, id: &str) -> (i32, String) {
if self.is_success() {
(self.code, "".to_owned())
} else {
if self.msg.is_null() {
log::warn!(
"The message pointer from the plugin '{}' is null, but the error code is {}",
id,
self.code
);
return (self.code, "".to_owned());
}
let msg = cstr_to_string(self.msg).unwrap_or_default();
free_c_ptr(self.msg as _);
self.msg = null();
(self.code as _, msg)
}
}
}
fn is_server_running() -> bool {
crate::common::is_server() || crate::common::is_server_running()
}
pub fn init() {
if !is_server_running() {
std::thread::spawn(move || manager::start_ipc());
} else {
if let Err(e) = remove_uninstalled() {
log::error!("Failed to remove plugins: {}", e);
}
}
match manager::get_uninstall_id_set() {
Ok(ids) => {
if let Err(e) = plugins::load_plugins(&ids) {
log::error!("Failed to load plugins: {}", e);
}
}
Err(e) => {
log::error!("Failed to load plugins: {}", e);
}
}
}
#[inline]
#[cfg(target_os = "windows")]
fn get_share_dir() -> ResultType<PathBuf> {
Ok(PathBuf::from(env::var("ProgramData")?))
}
#[inline]
#[cfg(target_os = "linux")]
fn get_share_dir() -> ResultType<PathBuf> {
Ok(PathBuf::from("/usr/share"))
}
#[inline]
#[cfg(target_os = "macos")]
fn get_share_dir() -> ResultType<PathBuf> {
Ok(PathBuf::from("/Library/Application Support"))
}
#[inline]
fn get_plugins_dir() -> ResultType<PathBuf> {
Ok(get_share_dir()?
.join("RustDesk")
.join(PLUGIN_SOURCE_LOCAL_DIR))
}
#[inline]
fn get_plugin_dir(id: &str) -> ResultType<PathBuf> {
Ok(get_plugins_dir()?.join(id))
}
#[inline]
fn get_uninstall_file_path() -> ResultType<PathBuf> {
Ok(get_plugins_dir()?.join("uninstall_list"))
}
#[inline]
fn cstr_to_string(cstr: *const c_char) -> ResultType<String> {
if cstr.is_null() {
bail!("failed to convert string, the pointer is null");
}
Ok(String::from_utf8(unsafe {
CStr::from_ptr(cstr).to_bytes().to_vec()
})?)
}
#[inline]
fn str_to_cstr_ret(s: &str) -> *const c_char {
let mut s = s.as_bytes().to_vec();
s.push(0);
unsafe {
let r = libc::malloc(s.len()) as *mut c_char;
libc::memcpy(
r as *mut libc::c_void,
s.as_ptr() as *const libc::c_void,
s.len(),
);
r
}
}
#[inline]
fn free_c_ptr(p: *mut c_void) {
if !p.is_null() {
unsafe {
libc::free(p);
}
}
}

View File

@@ -1,40 +0,0 @@
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(),
});
}

View File

@@ -1,27 +0,0 @@
#[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
}
};
}

View File

@@ -1,126 +0,0 @@
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, ui::PluginNativeUIHandler};
use super::cstr_to_string;
mod macros;
pub mod session;
pub mod ui;
pub type NR = super::native::NativeReturnValue;
pub type PluginNativeHandlerRegistrar = NativeHandlerRegistrar<Box<dyn Callable + Send + Sync>>;
lazy_static! {
pub static ref NATIVE_HANDLERS_REGISTRAR: Arc<PluginNativeHandlerRegistrar> =
Arc::new(PluginNativeHandlerRegistrar::default());
}
#[derive(Clone)]
pub struct NativeHandlerRegistrar<H> {
handlers: Arc<RwLock<Vec<H>>>,
}
impl Default for PluginNativeHandlerRegistrar {
fn default() -> Self {
Self {
handlers: Arc::new(RwLock::new(vec![
// Add prebuilt native handlers here.
Box::new(PluginNativeSessionHandler::default()),
Box::new(PluginNativeUIHandler::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<String, serde_json::Value>) -> Option<NR>;
/// 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<String, serde_json::Value>,
raw: *const c_void,
raw_len: usize,
) -> Option<NR>;
}
pub trait Callable {
fn call(
&self,
method: &String,
json: *const c_char,
raw: *const c_void,
raw_len: usize,
) -> Option<NR> {
None
}
}
impl<T> 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<NR> {
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<NR> {
for handler in self.handlers.read().unwrap().iter() {
let ret = handler.call(method, json, raw, raw_len);
if ret.is_some() {
return ret;
}
}
None
}
}

View File

@@ -1,219 +0,0 @@
use std::{
collections::HashMap,
ffi::{c_char, c_void},
ptr::addr_of_mut,
sync::{Arc, RwLock},
};
use flutter_rust_bridge::StreamSink;
use crate::{define_method_prefix, flutter_ffi::EventToUI};
const MSG_TO_UI_TYPE_SESSION_CREATED: &str = "session_created";
use super::PluginNativeHandler;
pub type OnSessionRgbaCallback = unsafe extern "C" fn(
*const c_char, // Session ID
*mut c_void, // raw data
*mut usize, // width
*mut usize, // height,
*mut usize, // stride,
*mut scrap::ImageFormat, // ImageFormat
);
#[derive(Default)]
/// Session related handler for librustdesk core.
pub struct PluginNativeSessionHandler {
sessions: Arc<RwLock<Vec<crate::flutter::FlutterSession>>>,
cbs: Arc<RwLock<HashMap<String, OnSessionRgbaCallback>>>,
}
lazy_static::lazy_static! {
pub static ref SESSION_HANDLER: Arc<PluginNativeSessionHandler> = Arc::new(PluginNativeSessionHandler::default());
}
impl PluginNativeHandler for PluginNativeSessionHandler {
define_method_prefix!("session_");
fn on_message(
&self,
method: &str,
data: &serde_json::Map<String, serde_json::Value>,
) -> Option<super::NR> {
match method {
"create_session" => {
if let Some(id) = data.get("id") {
if let Some(id) = id.as_str() {
return Some(super::NR {
return_type: 1,
data: SESSION_HANDLER.create_session(id.to_string()).as_ptr() as _,
});
}
}
}
"start_session" => {
if let Some(id) = data.get("id") {
if let Some(id) = id.as_str() {
let sessions = SESSION_HANDLER.sessions.read().unwrap();
for session in sessions.iter() {
if session.id == id {
let round =
session.connection_round_state.lock().unwrap().new_round();
crate::ui_session_interface::io_loop(session.clone(), round);
}
}
}
}
}
"remove_session_hook" => {
if let Some(id) = data.get("id") {
if let Some(id) = id.as_str() {
SESSION_HANDLER.remove_session_hook(id.to_string());
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_str() {
SESSION_HANDLER.remove_session(id.to_owned());
return Some(super::NR {
return_type: 0,
data: std::ptr::null(),
});
}
}
}
_ => {}
}
None
}
fn on_message_raw(
&self,
method: &str,
data: &serde_json::Map<String, serde_json::Value>,
raw: *const std::ffi::c_void,
_raw_len: usize,
) -> Option<super::NR> {
match method {
"add_session_hook" => {
if let Some(id) = data.get("id") {
if let Some(id) = id.as_str() {
let cb: OnSessionRgbaCallback = unsafe { std::mem::transmute(raw) };
SESSION_HANDLER.add_session_hook(id.to_string(), cb);
return Some(super::NR {
return_type: 0,
data: std::ptr::null(),
});
}
}
}
_ => {}
}
None
}
}
impl PluginNativeSessionHandler {
fn create_session(&self, session_id: String) -> String {
let session =
crate::flutter::session_add(&session_id, false, false, false, "", false, "".to_owned());
if let Ok(session) = session {
let mut sessions = self.sessions.write().unwrap();
sessions.push(session);
// push a event to notify flutter to bind a event stream for this session.
let mut m = HashMap::new();
m.insert("name", MSG_TO_UI_TYPE_SESSION_CREATED);
m.insert("session_id", &session_id);
// todo: APP_TYPE_DESKTOP_REMOTE is not used anymore.
// crate::flutter::APP_TYPE_DESKTOP_REMOTE + window id, is used for multi-window support.
crate::flutter::push_global_event(
crate::flutter::APP_TYPE_DESKTOP_REMOTE,
serde_json::to_string(&m).unwrap_or("".to_string()),
);
return session_id;
} else {
return "".to_string();
}
}
fn add_session_hook(&self, session_id: String, cb: OnSessionRgbaCallback) {
let sessions = self.sessions.read().unwrap();
for session in sessions.iter() {
if session.id == session_id {
self.cbs.write().unwrap().insert(session_id.to_owned(), cb);
session.ui_handler.add_session_hook(
session_id,
crate::flutter::SessionHook::OnSessionRgba(session_rgba_cb),
);
break;
}
}
}
fn remove_session_hook(&self, session_id: String) {
let sessions = self.sessions.read().unwrap();
for session in sessions.iter() {
if session.id == session_id {
session.ui_handler.remove_session_hook(&session_id);
}
}
}
fn remove_session(&self, session_id: String) {
let _ = self.cbs.write().unwrap().remove(&session_id);
let mut sessions = self.sessions.write().unwrap();
for i in 0..sessions.len() {
if sessions[i].id == session_id {
sessions[i].close_event_stream();
sessions[i].close();
sessions.remove(i);
}
}
}
#[inline]
// The callback function for rgba data
fn session_rgba_cb(&self, session_id: String, rgb: &mut scrap::ImageRgb) {
let cbs = self.cbs.read().unwrap();
if let Some(cb) = cbs.get(&session_id) {
unsafe {
cb(
session_id.as_ptr() as _,
rgb.raw.as_mut_ptr() as _,
addr_of_mut!(rgb.w),
addr_of_mut!(rgb.h),
addr_of_mut!(rgb.stride),
addr_of_mut!(rgb.fmt),
);
}
}
}
#[inline]
// The callback function for rgba data
fn session_register_event_stream(&self, session_id: String, stream: StreamSink<EventToUI>) {
let sessions = self.sessions.read().unwrap();
for session in sessions.iter() {
if session.id == session_id {
*session.event_stream.write().unwrap() = Some(stream);
break;
}
}
}
}
#[inline]
fn session_rgba_cb(id: String, rgb: &mut scrap::ImageRgb) {
SESSION_HANDLER.session_rgba_cb(id, rgb);
}
#[inline]
pub fn session_register_event_stream(id: String, stream: StreamSink<EventToUI>) {
SESSION_HANDLER.session_register_event_stream(id, stream);
}

View File

@@ -1,143 +0,0 @@
use std::{collections::HashMap, ffi::c_void, os::raw::c_int};
use serde_json::json;
use crate::{define_method_prefix, flutter::APP_TYPE_MAIN};
use super::PluginNativeHandler;
#[derive(Default)]
pub struct PluginNativeUIHandler;
/// Callback for UI interface.
///
/// [Note]
/// We will transfer the native callback to u64 and post it to flutter.
/// The flutter thread will directly call this method.
///
/// an example of `data` is:
/// ```
/// {
/// "cb": 0x1234567890
/// }
/// ```
/// [Safety]
/// Please make sure the callback u provided is VALID, or memory or calling issues may occur to cause the program crash!
pub type OnUIReturnCallback =
extern "C" fn(return_code: c_int, data: *const c_void, data_len: u64, user_data: *const c_void);
impl PluginNativeHandler for PluginNativeUIHandler {
define_method_prefix!("ui_");
fn on_message(
&self,
method: &str,
data: &serde_json::Map<String, serde_json::Value>,
) -> Option<super::NR> {
match method {
"select_peers_async" => {
if let Some(cb) = data.get("cb") {
if let Some(cb) = cb.as_u64() {
let user_data = match data.get("user_data") {
Some(user_data) => user_data.as_u64().unwrap_or(0),
None => 0,
};
self.select_peers_async(cb, user_data);
return Some(super::NR {
return_type: 0,
data: std::ptr::null(),
});
}
}
return Some(super::NR {
return_type: -1,
data: "missing cb field message".as_ptr() as _,
});
}
"register_ui_entry" => {
let title;
if let Some(v) = data.get("title") {
title = v.as_str().unwrap_or("");
} else {
title = "";
}
if let Some(on_tap_cb) = data.get("on_tap_cb") {
if let Some(on_tap_cb) = on_tap_cb.as_u64() {
let user_data = match data.get("user_data") {
Some(user_data) => user_data.as_u64().unwrap_or(0),
None => 0,
};
self.register_ui_entry(title, on_tap_cb, user_data);
return Some(super::NR {
return_type: 0,
data: std::ptr::null(),
});
}
}
return Some(super::NR {
return_type: -1,
data: "missing cb field message".as_ptr() as _,
});
}
_ => {}
}
None
}
fn on_message_raw(
&self,
method: &str,
data: &serde_json::Map<String, serde_json::Value>,
raw: *const std::ffi::c_void,
_raw_len: usize,
) -> Option<super::NR> {
None
}
}
impl PluginNativeUIHandler {
/// Call with method `select_peers_async` and the following json:
/// ```json
/// {
/// "cb": 0, // The function address
/// "user_data": 0 // An opaque pointer value passed to the callback.
/// }
/// ```
///
/// [Arguments]
/// @param cb: the function address with type [OnUIReturnCallback].
/// @param user_data: the function will be called with this value.
fn select_peers_async(&self, cb: u64, user_data: u64) {
let mut param = HashMap::new();
param.insert("name", json!("native_ui"));
param.insert("action", json!("select_peers"));
param.insert("cb", json!(cb));
param.insert("user_data", json!(user_data));
crate::flutter::push_global_event(
APP_TYPE_MAIN,
serde_json::to_string(&param).unwrap_or("".to_string()),
);
}
/// Call with method `register_ui_entry` and the following json:
/// ```
/// {
///
/// "on_tap_cb": 0, // The function address
/// "user_data": 0, // An opaque pointer value passed to the callback.
/// "title": "entry name"
/// }
/// ```
fn register_ui_entry(&self, title: &str, on_tap_cb: u64, user_data: u64) {
let mut param = HashMap::new();
param.insert("name", json!("native_ui"));
param.insert("action", json!("register_ui_entry"));
param.insert("title", json!(title));
param.insert("cb", json!(on_tap_cb));
param.insert("user_data", json!(user_data));
crate::flutter::push_global_event(
APP_TYPE_MAIN,
serde_json::to_string(&param).unwrap_or("".to_string()),
);
}
}

View File

@@ -1,34 +0,0 @@
use hbb_common::log;
use std::ffi::c_char;
const LOG_LEVEL_TRACE: &[u8; 6] = b"trace\0";
const LOG_LEVEL_DEBUG: &[u8; 6] = b"debug\0";
const LOG_LEVEL_INFO: &[u8; 5] = b"info\0";
const LOG_LEVEL_WARN: &[u8; 5] = b"warn\0";
const LOG_LEVEL_ERROR: &[u8; 6] = b"error\0";
#[inline]
fn is_level(level: *const c_char, level_bytes: &[u8]) -> bool {
level_bytes == unsafe { std::slice::from_raw_parts(level as *const u8, level_bytes.len()) }
}
#[no_mangle]
pub(super) extern "C" fn plugin_log(level: *const c_char, msg: *const c_char) {
if level.is_null() || msg.is_null() {
return;
}
if let Ok(msg) = super::cstr_to_string(msg) {
if is_level(level, LOG_LEVEL_TRACE) {
log::trace!("{}", msg);
} else if is_level(level, LOG_LEVEL_DEBUG) {
log::debug!("{}", msg);
} else if is_level(level, LOG_LEVEL_INFO) {
log::info!("{}", msg);
} else if is_level(level, LOG_LEVEL_WARN) {
log::warn!("{}", msg);
} else if is_level(level, LOG_LEVEL_ERROR) {
log::error!("{}", msg);
}
}
}

View File

@@ -1,659 +0,0 @@
use super::{desc::Desc, errno::*, *};
#[cfg(not(debug_assertions))]
use crate::common::is_server;
use crate::flutter;
use hbb_common::{
bail,
dlopen::symbor::Library,
lazy_static, log,
message_proto::{Message, Misc, PluginFailure, PluginRequest},
ResultType,
};
use serde_derive::Serialize;
use std::{
collections::{HashMap, HashSet},
ffi::{c_char, c_void},
path::Path,
sync::{Arc, RwLock},
};
pub const METHOD_HANDLE_STATUS: &[u8; 14] = b"handle_status\0";
pub const METHOD_HANDLE_SIGNATURE_VERIFICATION: &[u8; 30] = b"handle_signature_verification\0";
const METHOD_HANDLE_UI: &[u8; 10] = b"handle_ui\0";
const METHOD_HANDLE_PEER: &[u8; 12] = b"handle_peer\0";
pub const METHOD_HANDLE_LISTEN_EVENT: &[u8; 20] = b"handle_listen_event\0";
lazy_static::lazy_static! {
static ref PLUGIN_INFO: Arc<RwLock<HashMap<String, PluginInfo>>> = Default::default();
static ref PLUGINS: Arc<RwLock<HashMap<String, Plugin>>> = Default::default();
}
pub(super) struct PluginInfo {
pub path: String,
pub uninstalled: bool,
pub desc: Desc,
}
/// Initialize the plugins.
///
/// data: The initialize data.
type PluginFuncInit = extern "C" fn(data: *const InitData) -> PluginReturn;
/// Reset the plugin.
///
/// data: The initialize data.
type PluginFuncReset = extern "C" fn(data: *const InitData) -> PluginReturn;
/// Clear the plugin.
type PluginFuncClear = extern "C" fn() -> PluginReturn;
/// Get the description of the plugin.
/// Return the description. The plugin allocate memory with `libc::malloc` and return the pointer.
type PluginFuncDesc = extern "C" 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 CallbackMsg = extern "C" fn(
peer: *const c_char,
target: *const c_char,
id: *const c_char,
content: *const c_void,
len: usize,
) -> PluginReturn;
/// Callback to get the config.
/// peer, key are utf8 strings(null terminated).
///
/// peer: The peer id.
/// id: The id of this plugin.
/// key: The key of the config.
///
/// The returned string is utf8 string(null terminated) and must be freed by caller.
type CallbackGetConf =
extern "C" fn(peer: *const c_char, id: *const c_char, key: *const c_char) -> *const c_char;
/// Get local peer id.
///
/// The returned string is utf8 string(null terminated) and must be freed by caller.
type CallbackGetId = extern "C" fn() -> *const c_char;
/// Callback to log.
///
/// level, msg are utf8 strings(null terminated).
/// 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.
///
/// method: The method. "handle_ui" or "handle_peer"
/// peer: The peer id.
/// args: The arguments.
/// len: The length of the arguments.
type PluginFuncCall = extern "C" fn(
method: *const c_char,
peer: *const c_char,
args: *const c_void,
len: usize,
) -> PluginReturn;
/// The main function of the plugin.
/// This function is called mainly for handling messages from the peer,
/// and then send messages back to the peer.
///
/// method: The method. "handle_ui" or "handle_peer"
/// peer: The peer id.
/// args: The arguments.
/// len: The length of the arguments.
/// out: The output.
/// The plugin allocate memory with `libc::malloc` and return the pointer.
/// out_len: The length of the output.
type PluginFuncCallWithOutData = extern "C" fn(
method: *const c_char,
peer: *const c_char,
args: *const c_void,
len: usize,
out: *mut *mut c_void,
out_len: *mut usize,
) -> PluginReturn;
/// The plugin callbacks.
/// msg: The callback to send message to peer or ui.
/// get_conf: The callback to get the config.
/// log: The callback to log.
#[repr(C)]
#[derive(Copy, Clone)]
struct Callbacks {
msg: CallbackMsg,
get_conf: CallbackGetConf,
get_id: CallbackGetId,
log: CallbackLog,
native: CallbackNative,
}
#[derive(Serialize)]
#[repr(C)]
struct InitInfo {
is_server: bool,
}
/// The plugin initialize data.
/// version: The version of the plugin, can't be nullptr.
/// local_peer_id: The local peer id, can't be nullptr.
/// cbs: The callbacks.
#[repr(C)]
struct InitData {
version: *const c_char,
info: *const c_char,
cbs: Callbacks,
}
impl Drop for InitData {
fn drop(&mut self) {
free_c_ptr(self.version as _);
free_c_ptr(self.info as _);
}
}
macro_rules! make_plugin {
($($field:ident : $tp:ty),+) => {
#[allow(dead_code)]
pub struct Plugin {
_lib: Library,
id: Option<String>,
path: String,
$($field: $tp),+
}
impl Plugin {
fn new(path: &str) -> ResultType<Self> {
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) => {
*m
},
Err(e) => {
bail!("Failed to load {} func {}, {}", path, stringify!($field), e);
}
}
;)+
Ok(Self {
_lib: lib,
id: None,
path: path.to_string(),
$( $field ),+
})
}
fn desc(&self) -> ResultType<Desc> {
let desc_ret = (self.desc)();
let desc = Desc::from_cstr(desc_ret);
free_c_ptr(desc_ret as _);
desc
}
fn init(&self, data: &InitData, path: &str) -> ResultType<()> {
let mut init_ret = (self.init)(data as _);
if !init_ret.is_success() {
let (code, msg) = init_ret.get_code_msg(path);
bail!(
"Failed to init plugin {}, code: {}, msg: {}",
path,
code,
msg
);
}
Ok(())
}
fn clear(&self, id: &str) {
let mut clear_ret = (self.clear)();
if !clear_ret.is_success() {
let (code, msg) = clear_ret.get_code_msg(id);
log::error!(
"Failed to clear plugin {}, code: {}, msg: {}",
id,
code,
msg
);
}
}
}
impl Drop for Plugin {
fn drop(&mut self) {
let id = self.id.as_ref().unwrap_or(&self.path);
self.clear(id);
}
}
}
}
make_plugin!(
init: PluginFuncInit,
reset: PluginFuncReset,
clear: PluginFuncClear,
desc: PluginFuncDesc,
call: PluginFuncCall,
call_with_out_data: PluginFuncCallWithOutData
);
#[derive(Serialize)]
pub struct MsgListenEvent {
pub event: String,
}
#[cfg(target_os = "windows")]
const DYLIB_SUFFIX: &str = ".dll";
#[cfg(target_os = "linux")]
const DYLIB_SUFFIX: &str = ".so";
#[cfg(target_os = "macos")]
const DYLIB_SUFFIX: &str = ".dylib";
pub(super) fn load_plugins(uninstalled_ids: &HashSet<String>) -> ResultType<()> {
let plugins_dir = super::get_plugins_dir()?;
if !plugins_dir.exists() {
std::fs::create_dir_all(&plugins_dir)?;
} else {
for entry in std::fs::read_dir(plugins_dir)? {
match entry {
Ok(entry) => {
let plugin_dir = entry.path();
if plugin_dir.is_dir() {
if let Some(plugin_id) = plugin_dir.file_name().and_then(|f| f.to_str()) {
if uninstalled_ids.contains(plugin_id) {
log::debug!(
"Ignore loading '{}' as it should be uninstalled",
plugin_id
);
continue;
}
load_plugin_dir(&plugin_dir);
}
}
}
Err(e) => {
log::error!("Failed to read plugins dir entry, {}", e);
}
}
}
}
Ok(())
}
fn load_plugin_dir(dir: &Path) {
log::debug!("Begin load plugin dir: {}", dir.display());
if let Ok(rd) = std::fs::read_dir(dir) {
for entry in rd {
match entry {
Ok(entry) => {
let path = entry.path();
if path.is_file() {
let filename = entry.file_name();
let filename = filename.to_str().unwrap_or("");
if filename.starts_with("plugin_") && filename.ends_with(DYLIB_SUFFIX) {
if let Some(path) = path.to_str() {
if let Err(e) = load_plugin_path(path) {
log::error!("Failed to load plugin {}, {}", filename, e);
}
}
}
}
}
Err(e) => {
log::error!(
"Failed to read '{}' dir entry, {}",
dir.file_name().and_then(|f| f.to_str()).unwrap_or(""),
e
);
}
}
}
}
}
pub fn unload_plugin(id: &str) {
log::info!("Plugin {} unloaded", id);
PLUGINS.write().unwrap().remove(id);
}
pub(super) fn mark_uninstalled(id: &str, uninstalled: bool) {
log::info!("Plugin {} uninstall", id);
PLUGIN_INFO
.write()
.unwrap()
.get_mut(id)
.map(|info| info.uninstalled = uninstalled);
}
pub fn reload_plugin(id: &str) -> ResultType<()> {
let path = match PLUGIN_INFO.read().unwrap().get(id) {
Some(plugin) => plugin.path.clone(),
None => bail!("Plugin {} not found", id),
};
unload_plugin(id);
load_plugin_path(&path)
}
fn load_plugin_path(path: &str) -> ResultType<()> {
log::info!("Begin load plugin {}", path);
let plugin = Plugin::new(path)?;
let desc = plugin.desc()?;
// to-do validate plugin
// to-do check the plugin id (make sure it does not use another plugin's id)
let id = desc.meta().id.clone();
let plugin_info = PluginInfo {
path: path.to_string(),
uninstalled: false,
desc: desc.clone(),
};
PLUGIN_INFO.write().unwrap().insert(id.clone(), plugin_info);
let init_info = serde_json::to_string(&InitInfo {
is_server: super::is_server_running(),
})?;
let init_data = InitData {
version: str_to_cstr_ret(crate::VERSION),
info: str_to_cstr_ret(&init_info) as _,
cbs: Callbacks {
msg: callback_msg::cb_msg,
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,
},
};
// If do not load the plugin when init failed, the ui will not show the installed plugin.
if let Err(e) = plugin.init(&init_data, path) {
log::error!("Failed to init plugin '{}', {}", desc.meta().id, e);
}
if super::is_server_running() {
super::config::ManagerConfig::add_plugin(&desc.meta().id)?;
}
// update ui
// Ui may be not ready now, so we need to update again once ui is ready.
reload_ui(&desc, None);
// add plugins
PLUGINS.write().unwrap().insert(id.clone(), plugin);
log::info!("Plugin {} loaded, {}", id, path);
Ok(())
}
pub fn sync_ui(sync_to: String) {
for plugin in PLUGIN_INFO.read().unwrap().values() {
reload_ui(&plugin.desc, Some(&sync_to));
}
}
#[inline]
pub fn load_plugin(id: &str) -> ResultType<()> {
load_plugin_dir(&super::get_plugin_dir(id)?);
Ok(())
}
#[inline]
fn handle_event(method: &[u8], id: &str, peer: &str, event: &[u8]) -> ResultType<()> {
let mut peer: String = peer.to_owned();
peer.push('\0');
plugin_call(id, method, &peer, event)
}
pub fn plugin_call(id: &str, method: &[u8], peer: &str, event: &[u8]) -> ResultType<()> {
let mut ret = plugin_call_get_return(id, method, peer, event)?;
if ret.is_success() {
Ok(())
} else {
let (code, msg) = ret.get_code_msg(id);
bail!(
"Failed to handle plugin event, id: {}, method: {}, code: {}, msg: {}",
id,
std::string::String::from_utf8(method.to_vec()).unwrap_or_default(),
code,
msg
);
}
}
#[inline]
pub fn plugin_call_get_return(
id: &str,
method: &[u8],
peer: &str,
event: &[u8],
) -> ResultType<PluginReturn> {
match PLUGINS.read().unwrap().get(id) {
Some(plugin) => Ok((plugin.call)(
method.as_ptr() as _,
peer.as_ptr() as _,
event.as_ptr() as _,
event.len(),
)),
None => bail!("Plugin {} not found", id),
}
}
#[inline]
pub fn handle_ui_event(id: &str, peer: &str, event: &[u8]) -> ResultType<()> {
handle_event(METHOD_HANDLE_UI, id, peer, event)
}
#[inline]
pub fn handle_server_event(id: &str, peer: &str, event: &[u8]) -> ResultType<()> {
handle_event(METHOD_HANDLE_PEER, id, peer, event)
}
fn _handle_listen_event(event: String, peer: String) {
let mut plugins = Vec::new();
for info in PLUGIN_INFO.read().unwrap().values() {
if info.desc.listen_events().contains(&event.to_string()) {
plugins.push(info.desc.meta().id.clone());
}
}
if plugins.is_empty() {
return;
}
if let Ok(evt) = serde_json::to_string(&MsgListenEvent {
event: event.clone(),
}) {
let mut evt_bytes = evt.as_bytes().to_vec();
evt_bytes.push(0);
let mut peer: String = peer.to_owned();
peer.push('\0');
for id in plugins {
match PLUGINS.read().unwrap().get(&id) {
Some(plugin) => {
let mut ret = (plugin.call)(
METHOD_HANDLE_LISTEN_EVENT.as_ptr() as _,
peer.as_ptr() as _,
evt_bytes.as_ptr() as _,
evt_bytes.len(),
);
if !ret.is_success() {
let (code, msg) = ret.get_code_msg(&id);
log::error!(
"Failed to handle plugin listen event, id: {}, event: {}, code: {}, msg: {}",
id,
event,
code,
msg
);
}
}
None => {
log::error!("Plugin {} not found when handle_listen_event", id);
}
}
}
}
}
#[inline]
pub fn handle_listen_event(event: String, peer: String) {
std::thread::spawn(|| _handle_listen_event(event, peer));
}
#[inline]
pub fn handle_client_event(id: &str, peer: &str, event: &[u8]) -> Message {
let mut peer: String = peer.to_owned();
peer.push('\0');
match PLUGINS.read().unwrap().get(id) {
Some(plugin) => {
let mut out = std::ptr::null_mut();
let mut out_len: usize = 0;
let mut ret = (plugin.call_with_out_data)(
METHOD_HANDLE_PEER.as_ptr() as _,
peer.as_ptr() as _,
event.as_ptr() as _,
event.len(),
&mut out as _,
&mut out_len as _,
);
if ret.is_success() {
let msg = make_plugin_request(id, out, out_len);
free_c_ptr(out as _);
msg
} else {
let (code, msg) = ret.get_code_msg(id);
if code > ERR_RUSTDESK_HANDLE_BASE && code < ERR_PLUGIN_HANDLE_BASE {
log::debug!(
"Plugin {} failed to handle client event, code: {}, msg: {}",
id,
code,
msg
);
let name = match PLUGIN_INFO.read().unwrap().get(id) {
Some(plugin) => &plugin.desc.meta().name,
None => "???",
}
.to_owned();
match code {
ERR_CALL_NOT_SUPPORTED_METHOD => {
make_plugin_failure(id, &name, "Plugin method is not supported")
}
ERR_CALL_INVALID_ARGS => {
make_plugin_failure(id, &name, "Plugin arguments is invalid")
}
_ => make_plugin_failure(id, &name, &msg),
}
} else {
log::error!(
"Plugin {} failed to handle client event, code: {}, msg: {}",
id,
code,
msg
);
let msg = make_plugin_request(id, out, out_len);
free_c_ptr(out as _);
msg
}
}
}
None => make_plugin_failure(id, "", "Plugin not found"),
}
}
fn make_plugin_request(id: &str, content: *const c_void, len: usize) -> Message {
let mut misc = Misc::new();
misc.set_plugin_request(PluginRequest {
id: id.to_owned(),
content: unsafe { std::slice::from_raw_parts(content as *const u8, len) }
.clone()
.into(),
..Default::default()
});
let mut msg_out = Message::new();
msg_out.set_misc(misc);
msg_out
}
fn make_plugin_failure(id: &str, name: &str, msg: &str) -> Message {
let mut misc = Misc::new();
misc.set_plugin_failure(PluginFailure {
id: id.to_owned(),
name: name.to_owned(),
msg: msg.to_owned(),
..Default::default()
});
let mut msg_out = Message::new();
msg_out.set_misc(misc);
msg_out
}
fn reload_ui(desc: &Desc, sync_to: Option<&str>) {
for (location, ui) in desc.location().ui.iter() {
if let Ok(ui) = serde_json::to_string(&ui) {
let make_event = |ui: &str| {
let mut m = HashMap::new();
m.insert("name", MSG_TO_UI_TYPE_PLUGIN_RELOAD);
m.insert("id", &desc.meta().id);
m.insert("location", &location);
// Do not depend on the "location" and plugin desc on the ui side.
// Send the ui field to ensure the ui is valid.
m.insert("ui", ui);
serde_json::to_string(&m).unwrap_or("".to_owned())
};
match sync_to {
Some(channel) => {
let _res = flutter::push_global_event(channel, make_event(&ui));
}
None => {
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 = flutter::get_global_event_channels();
if available_channels.contains(&v[1]) {
let _res = flutter::push_global_event(v[1], make_event(&ui));
}
}
}
}
}
}
}
pub(super) fn get_plugin_infos() -> Arc<RwLock<HashMap<String, PluginInfo>>> {
PLUGIN_INFO.clone()
}
pub(super) fn get_desc_conf(id: &str) -> Option<super::desc::Config> {
PLUGIN_INFO
.read()
.unwrap()
.get(id)
.map(|info| info.desc.config().clone())
}
pub(super) fn get_version(id: &str) -> Option<String> {
PLUGIN_INFO
.read()
.unwrap()
.get(id)
.map(|info| info.desc.meta().version.clone())
}

View File

@@ -163,43 +163,6 @@ pub static CLICK_TIME: AtomicI64 = AtomicI64::new(0);
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub static MOUSE_MOVE_TIME: AtomicI64 = AtomicI64::new(0);
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
lazy_static::lazy_static! {
static ref PLUGIN_BLOCK_INPUT_TXS: Arc<Mutex<HashMap<String, std_mpsc::Sender<MessageInput>>>> = Default::default();
static ref PLUGIN_BLOCK_INPUT_TX_RX: (Arc<Mutex<std_mpsc::Sender<bool>>>, Arc<Mutex<std_mpsc::Receiver<bool>>>) = {
let (tx, rx) = std_mpsc::channel();
(Arc::new(Mutex::new(tx)), Arc::new(Mutex::new(rx)))
};
}
// Block input is required for some special cases, such as privacy mode.
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn plugin_block_input(peer: &str, block: bool) -> bool {
if let Some(tx) = PLUGIN_BLOCK_INPUT_TXS.lock().unwrap().get(peer) {
let _ = tx.send(if block {
MessageInput::BlockOnPlugin(peer.to_string())
} else {
MessageInput::BlockOffPlugin(peer.to_string())
});
match PLUGIN_BLOCK_INPUT_TX_RX
.1
.lock()
.unwrap()
.recv_timeout(std::time::Duration::from_millis(3_000))
{
Ok(b) => b == block,
Err(..) => {
log::error!("plugin_block_input timeout");
false
}
}
} else {
false
}
}
#[derive(Clone, Default)]
pub struct ConnInner {
id: i32,
@@ -225,12 +188,6 @@ enum MessageInput {
Pointer((PointerDeviceEvent, i32)),
BlockOn,
BlockOff,
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
BlockOnPlugin(String),
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
BlockOffPlugin(String),
}
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
@@ -1176,12 +1133,6 @@ impl Connection {
let _ = Self::turn_off_privacy_to_msg(id, String::new());
}
}
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
crate::plugin::handle_listen_event(
crate::plugin::EVENT_ON_CONN_CLOSE_SERVER.to_owned(),
conn.lr.my_id.clone(),
);
video_service::notify_video_frame_fetched_by_conn_id(id, None);
if conn.authorized {
password::update_temporary_password();
@@ -1266,32 +1217,6 @@ impl Connection {
);
}
}
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
MessageInput::BlockOnPlugin(_peer) => {
let (ok, _msg) = crate::platform::block_input(true);
if ok {
block_input_mode = true;
}
let _r = PLUGIN_BLOCK_INPUT_TX_RX
.0
.lock()
.unwrap()
.send(block_input_mode);
}
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
MessageInput::BlockOffPlugin(_peer) => {
let (ok, _msg) = crate::platform::block_input(false);
if ok {
block_input_mode = false;
}
let _r = PLUGIN_BLOCK_INPUT_TX_RX
.0
.lock()
.unwrap()
.send(block_input_mode);
}
},
Err(err) => {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -2032,13 +1957,6 @@ impl Connection {
username = "".to_owned();
}
}
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
PLUGIN_BLOCK_INPUT_TXS
.lock()
.unwrap()
.insert(self.lr.my_id.clone(), self.tx_input.clone());
// Terminal feature is supported on desktop only
#[allow(unused_mut)]
let mut terminal = cfg!(not(any(target_os = "android", target_os = "ios")));
@@ -3904,13 +3822,6 @@ impl Connection {
self.change_resolution(Some(dr.display as _), &dr.resolution);
}
}
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Some(misc::Union::PluginRequest(p)) => {
let msg =
crate::plugin::handle_client_event(&p.id, &self.lr.my_id, &p.content);
self.send(msg).await;
}
Some(misc::Union::AutoAdjustFps(fps)) => video_service::VIDEO_QOS
.lock()
.unwrap()

View File

@@ -569,16 +569,6 @@ impl<T: InvokeUiSession> Session<T> {
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_request(&self, request: PluginRequest) {
let mut misc = Misc::new();
misc.set_plugin_request(request);
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 LocalConfig::get_option("access_token").is_empty() {
return "".to_owned();