From 67413b7419d42435a27a1ba4341aa2e9330366a2 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 21 Apr 2023 21:40:34 +0800 Subject: [PATCH] plugin_framework, flutter event handlers Signed-off-by: fufesou --- flutter/lib/desktop/plugin/event.dart | 60 --------- .../lib/desktop/widgets/remote_toolbar.dart | 4 +- flutter/lib/models/model.dart | 34 ++--- flutter/lib/{desktop => }/plugin/common.dart | 6 - flutter/lib/{desktop => }/plugin/desc.dart | 0 flutter/lib/plugin/event.dart | 11 ++ flutter/lib/{desktop => }/plugin/model.dart | 32 +++++ flutter/lib/{desktop => }/plugin/widget.dart | 86 +++++++------ src/client/io_loop.rs | 2 +- src/flutter_ffi.rs | 14 +- src/plugin/callback_msg.rs | 121 +++++++++++++++--- src/plugin/config.rs | 22 +--- src/plugin/mod.rs | 5 + src/plugin/plugins.rs | 92 +++++++++---- src/server/connection.rs | 4 +- 15 files changed, 301 insertions(+), 192 deletions(-) delete mode 100644 flutter/lib/desktop/plugin/event.dart rename flutter/lib/{desktop => }/plugin/common.dart (81%) rename flutter/lib/{desktop => }/plugin/desc.dart (100%) create mode 100644 flutter/lib/plugin/event.dart rename flutter/lib/{desktop => }/plugin/model.dart (53%) rename flutter/lib/{desktop => }/plugin/widget.dart (68%) diff --git a/flutter/lib/desktop/plugin/event.dart b/flutter/lib/desktop/plugin/event.dart deleted file mode 100644 index 04d0ddf45..000000000 --- a/flutter/lib/desktop/plugin/event.dart +++ /dev/null @@ -1,60 +0,0 @@ -void handlePluginEvent( - Map evt, - String peer, - Function(Map e) handleMsgBox, -) { - // content - // - // { - // "t": "Option", - // "c": { - // "id": "id from RustDesk platform", - // "name": "Privacy Mode", - // "version": "v0.1.0", - // "location": "client|remote|toolbar|display", - // "key": "privacy-mode", - // "value": "1" - // } - // } - // - // { - // "t": "MsgBox", - // "c": { - // "type": "custom-nocancel", - // "title": "Privacy Mode", - // "text": "Failed unknown", - // "link": "" - // } - // } - // - if (evt['content']?['c'] == null) return; - final t = evt['content']?['t']; - if (t == 'Option') { - handleOptionEvent(evt['content']?['c'], peer); - } else if (t == 'MsgBox') { - handleMsgBox(evt['content']?['c']); - } -} - -void handleOptionEvent(Map evt, String peer) { - // content - // - // { - // "id": "id from RustDesk platform", - // "name": "Privacy Mode", - // "version": "v0.1.0", - // "location": "client|remote|toolbar|display", - // "key": "privacy-mode", - // "value": "1" - // } - // - final key = evt['key']; - final value = evt['value']; - if (key == 'privacy-mode') { - if (value == '1') { - // enable privacy mode - } else { - // disable privacy mode - } - } -} diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index f4895c785..fa263e8d0 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -8,8 +8,8 @@ import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; -import 'package:flutter_hbb/desktop/plugin/widget.dart'; -import 'package:flutter_hbb/desktop/plugin/common.dart'; +import 'package:flutter_hbb/plugin/widget.dart'; +import 'package:flutter_hbb/plugin/common.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 406783c2e..089a070d6 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -16,9 +16,9 @@ import 'package:flutter_hbb/models/peer_tab_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/models/user_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; -import 'package:flutter_hbb/desktop/plugin/event.dart'; -import 'package:flutter_hbb/desktop/plugin/desc.dart'; -import 'package:flutter_hbb/desktop/plugin/widget.dart'; +import 'package:flutter_hbb/plugin/event.dart'; +import 'package:flutter_hbb/plugin/desc.dart'; +import 'package:flutter_hbb/plugin/widget.dart'; import 'package:flutter_hbb/common/shared_state.dart'; import 'package:tuple/tuple.dart'; import 'package:image/image.dart' as img2; @@ -209,35 +209,37 @@ class FfiModel with ChangeNotifier { closeConnection(id: peer_id); } else if (name == 'portable_service_running') { parent.target?.elevationModel.onPortableServiceRunning(evt); - } else if (name == "on_url_scheme_received") { + } else if (name == 'on_url_scheme_received') { final url = evt['url'].toString(); parseRustdeskUri(url); - } else if (name == "on_voice_call_waiting") { + } else if (name == 'on_voice_call_waiting') { // Waiting for the response from the peer. parent.target?.chatModel.onVoiceCallWaiting(); - } else if (name == "on_voice_call_started") { + } else if (name == 'on_voice_call_started') { // Voice call is connected. parent.target?.chatModel.onVoiceCallStarted(); - } else if (name == "on_voice_call_closed") { + } else if (name == 'on_voice_call_closed') { // Voice call is closed with reason. final reason = evt['reason'].toString(); parent.target?.chatModel.onVoiceCallClosed(reason); - } else if (name == "on_voice_call_incoming") { + } else if (name == 'on_voice_call_incoming') { // Voice call is requested by the peer. parent.target?.chatModel.onVoiceCallIncoming(); - } else if (name == "update_voice_call_state") { + } else if (name == 'update_voice_call_state') { parent.target?.serverModel.updateVoiceCallState(evt); - } else if (name == "fingerprint") { + } else if (name == 'fingerprint') { FingerprintState.find(peerId).value = evt['fingerprint'] ?? ''; - } else if (name == "plugin_desc") { + } else if (name == 'plugin_desc') { updateDesc(evt); - } else if (name == "plugin_event") { + } else if (name == 'plugin_event') { handlePluginEvent( evt, peerId, (Map e) => handleMsgBox(e, peerId)); - } else if (name == "plugin_reload") { + } else if (name == 'plugin_reload') { handleReloading(evt, peerId); + } else if (name == 'plugin_option') { + handleOption(evt, peerId); } else { - debugPrint("Unknown event name: $name"); + debugPrint('Unknown event name: $name'); } }; } @@ -282,7 +284,7 @@ class FfiModel with ChangeNotifier { // } parent.target?.recordingModel.onSwitchDisplay(); - handleResolutions(peerId, evt["resolutions"]); + handleResolutions(peerId, evt['resolutions']); notifyListeners(); } @@ -321,7 +323,7 @@ class FfiModel with ChangeNotifier { showWaitUacDialog(id, dialogManager, type); } else if (type == 'elevation-error') { showElevationError(id, type, title, text, dialogManager); - } else if (type == "relay-hint") { + } else if (type == 'relay-hint') { showRelayHintDialog(id, type, title, text, dialogManager); } else { var hasRetry = evt['hasRetry'] == 'true'; diff --git a/flutter/lib/desktop/plugin/common.dart b/flutter/lib/plugin/common.dart similarity index 81% rename from flutter/lib/desktop/plugin/common.dart rename to flutter/lib/plugin/common.dart index b1b3dbfa0..7f92ea5a8 100644 --- a/flutter/lib/desktop/plugin/common.dart +++ b/flutter/lib/plugin/common.dart @@ -9,8 +9,6 @@ const String kLocationClientRemoteToolbarDisplay = 'client|remote|toolbar|display'; class MsgFromUi { - String remotePeerId; - String localPeerId; String id; String name; String location; @@ -19,8 +17,6 @@ class MsgFromUi { String action; MsgFromUi({ - required this.remotePeerId, - required this.localPeerId, required this.id, required this.name, required this.location, @@ -31,8 +27,6 @@ class MsgFromUi { Map toJson() { return { - 'remote_peer_id': remotePeerId, - 'local_peer_id': localPeerId, 'id': id, 'name': name, 'location': location, diff --git a/flutter/lib/desktop/plugin/desc.dart b/flutter/lib/plugin/desc.dart similarity index 100% rename from flutter/lib/desktop/plugin/desc.dart rename to flutter/lib/plugin/desc.dart diff --git a/flutter/lib/plugin/event.dart b/flutter/lib/plugin/event.dart new file mode 100644 index 000000000..024a6cf40 --- /dev/null +++ b/flutter/lib/plugin/event.dart @@ -0,0 +1,11 @@ +void handlePluginEvent( + Map evt, + String peer, + Function(Map e) handleMsgBox, +) { + if (evt['content']?['c'] == null) return; + final t = evt['content']?['t']; + if (t == 'MsgBox') { + handleMsgBox(evt['content']?['c']); + } +} diff --git a/flutter/lib/desktop/plugin/model.dart b/flutter/lib/plugin/model.dart similarity index 53% rename from flutter/lib/desktop/plugin/model.dart rename to flutter/lib/plugin/model.dart index a823844aa..5fdc426c7 100644 --- a/flutter/lib/desktop/plugin/model.dart +++ b/flutter/lib/plugin/model.dart @@ -3,15 +3,30 @@ import './common.dart'; import './desc.dart'; final Map locationModels = {}; +final Map kvModels = {}; + +class KvModel with ChangeNotifier { + final Map kv = {}; + + String? get(String key) => kv.remove(key); + + void set(String key, String value) { + kv[key] = value; + notifyListeners(); + } +} class PluginModel with ChangeNotifier { final List uiList = []; + final Map opts = {}; void add(UiType ui) { uiList.add(ui); notifyListeners(); } + String? getOpt(String key) => opts.remove(key); + bool get isEmpty => uiList.isEmpty; } @@ -42,3 +57,20 @@ LocationModel addLocation(String location) { } return locationModels[location]!; } + +String makeKvModelInstance(String location, PluginId id, String peer) => + '$location|$id|$peer'; + +KvModel addKvModel(String location, PluginId pluginId, String peer) { + final instance = makeKvModelInstance(location, pluginId, peer); + if (kvModels[instance] == null) { + kvModels[instance] = KvModel(); + } + return kvModels[instance]!; +} + +void updateOption( + String location, PluginId id, String peer, String key, String value) { + final instance = makeKvModelInstance(location, id, peer); + kvModels[instance]?.set(key, value); +} diff --git a/flutter/lib/desktop/plugin/widget.dart b/flutter/lib/plugin/widget.dart similarity index 68% rename from flutter/lib/desktop/plugin/widget.dart rename to flutter/lib/plugin/widget.dart index 3f7b413ea..ebd3d082e 100644 --- a/flutter/lib/desktop/plugin/widget.dart +++ b/flutter/lib/plugin/widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hbb/models/model.dart'; import 'package:provider/provider.dart'; +// to-do: do not depend on desktop import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart'; import 'package:flutter_hbb/models/platform_model.dart'; @@ -65,6 +66,7 @@ class PluginItem extends StatelessWidget { final FFI ffi; final String location; final PluginModel pluginModel; + final KvModel kvModel; PluginItem({ Key? key, @@ -73,42 +75,49 @@ class PluginItem extends StatelessWidget { required this.ffi, required this.location, required this.pluginModel, - }) : super(key: key); + }) : kvModel = addKvModel(location, pluginId, peerId), + super(key: key); bool get isEmpty => pluginModel.isEmpty; @override Widget build(BuildContext context) { - return ChangeNotifierProvider.value( - value: pluginModel, - child: Consumer(builder: (context, model, child) { - return Column( - children: model.uiList.map((ui) => _buildItem(ui)).toList(), - ); - }), + return MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: pluginModel), + ChangeNotifierProvider.value(value: kvModel), + ], + child: Consumer2( + builder: (context, pluginModel, kvModel, child) { + return Column( + children: pluginModel.uiList.map((ui) => _buildItem(ui)).toList(), + ); + }, + ), ); } - // to-do: add plugin icon and tooltip Widget _buildItem(UiType ui) { + late Widget child; switch (ui.runtimeType) { case UiButton: - return _buildMenuButton(ui as UiButton); + child = _buildMenuButton(ui as UiButton); + break; case UiCheckbox: - return _buildCheckboxMenuButton(ui as UiCheckbox); + child = _buildCheckboxMenuButton(ui as UiCheckbox); + break; default: - return Container(); + child = Container(); } + // to-do: add plugin icon and tooltip + return child; } Uint8List _makeEvent( - String localPeerId, String key, { bool? v, }) { final event = MsgFromUi( - remotePeerId: peerId, - localPeerId: localPeerId, id: pluginId, name: getDesc(pluginId)?.name ?? '', location: location, @@ -122,15 +131,11 @@ class PluginItem extends StatelessWidget { Widget _buildMenuButton(UiButton ui) { return MenuButton( - onPressed: () { - () async { - final localPeerId = await bind.mainGetMyId(); - bind.pluginEvent( - id: pluginId, - event: _makeEvent(localPeerId, ui.key), - ); - }(); - }, + onPressed: () => bind.pluginEvent( + id: pluginId, + peer: peerId, + event: _makeEvent(ui.key), + ), trailingIcon: Icon( IconData(int.parse(ui.icon, radix: 16), fontFamily: 'MaterialIcons')), // to-do: RustDesk translate or plugin translate ? @@ -140,8 +145,15 @@ class PluginItem extends StatelessWidget { } Widget _buildCheckboxMenuButton(UiCheckbox ui) { - final v = - bind.pluginGetSessionOption(id: pluginId, peer: peerId, key: ui.key); + var v = kvModel.get(ui.key); + if (v == null) { + if (peerId.isEmpty) { + v = bind.pluginGetLocalOption(id: pluginId, key: ui.key); + } else { + v = bind.pluginGetSessionOption( + id: pluginId, peer: peerId, key: ui.key); + } + } if (v == null) { // session or plugin not found return Container(); @@ -150,16 +162,14 @@ class PluginItem extends StatelessWidget { value: ConfigItem.isTrue(v), onChanged: (v) { if (v != null) { - () async { - final localPeerId = await bind.mainGetMyId(); - bind.pluginEvent( - id: pluginId, - event: _makeEvent(localPeerId, ui.key, v: v), - ); - }(); + bind.pluginEvent( + id: pluginId, + peer: peerId, + event: _makeEvent(ui.key, v: v), + ); } }, - // to-do: rustdesk translate or plugin translate ? + // to-do: RustDesk translate or plugin translate ? child: Text(ui.text), ffi: ffi, ); @@ -170,6 +180,10 @@ void handleReloading(Map evt, String peer) { if (evt['id'] == null || evt['location'] == null) { return; } - final ui = UiType.fromJson(evt); - addLocationUi(evt['location']!, evt['id']!, ui); + addLocationUi(evt['location']!, evt['id']!, UiType.fromJson(evt)); +} + +void handleOption(Map evt, String peer) { + updateOption( + evt['location'], evt['id'], evt['peer'] ?? '', evt['key'], evt['value']); } diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index bbcd74477..856c20c3f 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1301,7 +1301,7 @@ impl Remote { #[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, &p.content)); + allow_err!(crate::plugin::handle_server_event(&p.id, &self.handler.id, &p.content)); // to-do: show message box on UI when error occurs? } #[cfg(all(feature = "flutter", feature = "plugin_framework"))] diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index b5cc669ab..1a025fbaa 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1397,16 +1397,20 @@ pub fn send_url_scheme(_url: String) { } #[inline] -pub fn plugin_event(_id: String, _event: Vec) { +pub fn plugin_event(_id: String, _peer: String, _event: Vec) { #[cfg(feature = "plugin_framework")] #[cfg(not(any(target_os = "android", target_os = "ios")))] { - allow_err!(crate::plugin::handle_ui_event(&_id, &_event)); + allow_err!(crate::plugin::handle_ui_event(&_id, &_peer, &_event)); } } #[inline] -pub fn plugin_get_session_option(_id: String, _peer: String, _key: String) -> SyncReturn> { +pub fn plugin_get_session_option( + _id: String, + _peer: String, + _key: String, +) -> SyncReturn> { #[cfg(feature = "plugin_framework")] #[cfg(not(any(target_os = "android", target_os = "ios")))] { @@ -1427,7 +1431,7 @@ pub fn plugin_set_session_option(_id: String, _peer: String, _key: String, _valu #[cfg(feature = "plugin_framework")] #[cfg(not(any(target_os = "android", target_os = "ios")))] { - crate::plugin::PeerConfig::set(&_id, &_peer, &_key, &_value); + let _res = crate::plugin::PeerConfig::set(&_id, &_peer, &_key, &_value); } } @@ -1453,7 +1457,7 @@ pub fn plugin_set_local_option(_id: String, _key: String, _value: String) { #[cfg(feature = "plugin_framework")] #[cfg(not(any(target_os = "android", target_os = "ios")))] { - crate::plugin::LocalConfig::set(&_id, &_key, &_value); + let _res = crate::plugin::LocalConfig::set(&_id, &_key, &_value); } } diff --git a/src/plugin/callback_msg.rs b/src/plugin/callback_msg.rs index 4d02db287..23d7392e9 100644 --- a/src/plugin/callback_msg.rs +++ b/src/plugin/callback_msg.rs @@ -1,6 +1,7 @@ -use super::cstr_to_string; +use super::*; use crate::flutter::{self, APP_TYPE_CM, APP_TYPE_MAIN, SESSIONS}; use hbb_common::{lazy_static, log, message_proto::PluginRequest}; +use serde_derive::Deserialize; use serde_json; use std::{ collections::HashMap, @@ -10,6 +11,7 @@ use std::{ const MSG_TO_PEER_TARGET: &str = "peer"; const MSG_TO_UI_TARGET: &str = "ui"; +const MSG_TO_CONFIG_TARGET: &str = "config"; #[allow(dead_code)] const MSG_TO_UI_FLUTTER_CHANNEL_MAIN: u16 = 0x01 << 0; @@ -34,6 +36,22 @@ lazy_static::lazy_static! { }; } +#[derive(Deserialize)] +struct ConfigToUi { + channel: u16, + location: String, +} + +#[derive(Deserialize)] +struct MsgToConfig { + id: String, + r#type: String, + key: String, + value: String, + #[serde(skip_serializing_if = "Option::is_none")] + ui: Option, // If not None, send msg to ui. +} + /// Callback to send message to peer or ui. /// peer, target, id are utf8 strings(null terminated). /// @@ -83,25 +101,42 @@ pub fn callback_msg( let channel = u16::from_be_bytes([content_slice[0], content_slice[1]]); let content = std::string::String::from_utf8(content_slice[2..].to_vec()) .unwrap_or("".to_string()); - let mut m = HashMap::new(); - m.insert("name", "plugin_event"); - m.insert("peer", &peer); - m.insert("content", &content); - let event = serde_json::to_string(&m).unwrap_or("".to_string()); - for (k, v) in MSG_TO_UI_FLUTTER_CHANNELS.iter() { - if channel & k != 0 { - let _res = flutter::push_global_event(v as _, event.clone()); - } - } - if channel & MSG_TO_UI_FLUTTER_CHANNEL_REMOTE != 0 - || channel & MSG_TO_UI_FLUTTER_CHANNEL_TRANSFER != 0 - || channel & MSG_TO_UI_FLUTTER_CHANNEL_FORWARD != 0 + push_event_to_ui(channel, &peer, &content); + } + MSG_TO_CONFIG_TARGET => { + if let Ok(s) = + std::str::from_utf8(unsafe { std::slice::from_raw_parts(content as _, len) }) { - let _res = flutter::push_session_event( - &peer, - "plugin_event", - vec![("peer", &peer), ("content", &content)], - ); + if let Ok(msg) = serde_json::from_str::(s) { + match &msg.r#type as _ { + config::CONFIG_TYPE_LOCAL => { + match config::LocalConfig::set(&msg.id, &msg.key, &msg.value) { + Ok(_) => { + if let Some(ui) = &msg.ui { + // No need to set the peer id for location config. + push_option_to_ui(ui.channel, "", &msg, ui); + } + } + Err(e) => { + log::error!("Failed to set local config, {}", e); + } + } + } + config::CONFIG_TYPE_PEER => { + match config::PeerConfig::set(&msg.id, &peer, &msg.key, &msg.value) { + Ok(_) => { + if let Some(ui) = &msg.ui { + push_option_to_ui(ui.channel, &peer, &msg, ui); + } + } + Err(e) => { + log::error!("Failed to set peer config, {}", e); + } + } + } + _ => {} + } + } } } _ => { @@ -109,3 +144,51 @@ pub fn callback_msg( } } } + +#[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 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()); + 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 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, peer: &str, msg: &MsgToConfig, ui: &ConfigToUi) { + let v = [ + ("name", MSG_TO_UI_TYPE_PLUGIN_OPTION), + ("id", &msg.id), + ("location", &ui.location), + ("key", &msg.key), + ("value", &msg.value), + ]; + let event = serde_json::to_string(&HashMap::from(v)).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()); + } + } + let mut v = v.to_vec(); + v.push(("peer", &peer)); + if is_peer_channel(channel) { + let _res = flutter::push_session_event(&peer, MSG_TO_UI_TYPE_PLUGIN_OPTION, v.to_vec()); + } +} diff --git a/src/plugin/config.rs b/src/plugin/config.rs index d86053649..928cad68b 100644 --- a/src/plugin/config.rs +++ b/src/plugin/config.rs @@ -14,6 +14,9 @@ lazy_static::lazy_static! { static ref CONFIG_PEER_ITEMS: Arc>>> = Default::default(); } +pub(super) const CONFIG_TYPE_LOCAL: &str = "local"; +pub(super) const CONFIG_TYPE_PEER: &str = "peer"; + #[derive(Debug, Default, Serialize, Deserialize)] pub struct LocalConfig(HashMap); #[derive(Debug, Default, Serialize, Deserialize)] @@ -72,14 +75,6 @@ impl LocalConfig { CONFIG_LOCAL.lock().unwrap().insert(id.to_owned(), conf); } - #[inline] - fn save(id: &str) -> ResultType<()> { - match CONFIG_LOCAL.lock().unwrap().get(id) { - Some(config) => hbb_common::config::store_path(Self::path(id), config), - None => bail!("No such plugin {}", id), - } - } - #[inline] pub fn get(id: &str, key: &str) -> Option { if let Some(conf) = CONFIG_LOCAL.lock().unwrap().get(id) { @@ -136,17 +131,6 @@ impl PeerConfig { } } - #[inline] - fn save(id: &str, peer: &str) -> ResultType<()> { - match CONFIG_PEERS.lock().unwrap().get(id) { - Some(peers) => match peers.get(peer) { - Some(config) => hbb_common::config::store_path(Self::path(id, peer), config), - None => bail!("No such peer {}", peer), - }, - None => bail!("No such plugin {}", id), - } - } - #[inline] pub fn get(id: &str, peer: &str, key: &str) -> Option { if let Some(peers) = CONFIG_PEERS.lock().unwrap().get(id) { diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index c7ff2c4af..e0de6386e 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -12,6 +12,11 @@ pub use plugins::{ reload_plugin, unload_plugin, }; +const MSG_TO_UI_TYPE_PLUGIN_DESC: &str = "plugin_desc"; +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"; + pub use config::{LocalConfig, PeerConfig}; #[inline] diff --git a/src/plugin/plugins.rs b/src/plugin/plugins.rs index e05940cd1..9948136a7 100644 --- a/src/plugin/plugins.rs +++ b/src/plugin/plugins.rs @@ -5,8 +5,8 @@ use std::{ sync::{Arc, RwLock}, }; -use super::{callback_msg, desc::Desc, errno::*, get_code_msg_from_ret}; -use crate::flutter; +use super::{desc::Desc, errno::*, *}; +use crate::{flutter, ui_interface::get_id}; use hbb_common::{ bail, dlopen::symbor::Library, @@ -20,6 +20,7 @@ const METHOD_HANDLE_PEER: &[u8; 12] = b"handle_peer\0"; lazy_static::lazy_static! { pub static ref PLUGINS: Arc>> = Default::default(); + pub static ref LOCAL_PEER_ID: Arc> = Default::default(); } /// Initialize the plugins. @@ -59,22 +60,32 @@ type PluginFuncCallbackMsg = fn( len: usize, ); pub type PluginFuncSetCallbackMsg = fn(PluginFuncCallbackMsg); +/// Callback to get the id of local peer id. +/// The returned string is utf8 string(null terminated). +/// Don't free the returned ptr. +type GetIdFuncCallback = fn() -> *const c_char; +pub type PluginFuncGetIdCallback = fn(GetIdFuncCallback); /// The main function of the plugin. /// method: The method. "handle_ui" or "handle_peer" +/// peer: The peer id. /// args: The arguments. /// /// 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. -pub type PluginFuncCall = - fn(method: *const c_char, args: *const c_void, len: usize) -> *const c_void; +pub type PluginFuncCall = fn( + method: *const c_char, + peer: *const c_char, + args: *const c_void, + len: usize, +) -> *const c_void; macro_rules! make_plugin { ($($field:ident : $tp:ty),+) => { pub struct Plugin { _lib: Library, path: String, - desc: Option, + desc_v: Option, $($field: $tp),+ } @@ -101,7 +112,7 @@ macro_rules! make_plugin { Ok(Self { _lib: lib, path: path.to_string(), - desc: None, + desc_v: None, $( $field ),+ }) } @@ -110,12 +121,13 @@ macro_rules! make_plugin { } make_plugin!( - fn_init: PluginFuncInit, - fn_reset: PluginFuncReset, - fn_clear: PluginFuncClear, - fn_desc: PluginFuncDesc, - fn_set_cb_msg: PluginFuncSetCallbackMsg, - fn_call: PluginFuncCall + init: PluginFuncInit, + reset: PluginFuncReset, + clear: PluginFuncClear, + desc: PluginFuncDesc, + call: PluginFuncCall, + set_cb_msg: PluginFuncSetCallbackMsg, + set_cb_get_id: PluginFuncGetIdCallback ); pub fn load_plugins>(dir: P) -> ResultType<()> { @@ -142,7 +154,7 @@ pub fn load_plugins>(dir: P) -> ResultType<()> { pub fn unload_plugin(id: &str) { if let Some(plugin) = PLUGINS.write().unwrap().remove(id) { - let _ret = (plugin.fn_clear)(); + let _ret = (plugin.clear)(); } } @@ -155,9 +167,24 @@ pub fn reload_plugin(id: &str) -> ResultType<()> { load_plugin(&path) } +#[no_mangle] +fn get_local_peer_id() -> *const c_char { + let mut id = (*LOCAL_PEER_ID.read().unwrap()).clone(); + if id.is_empty() { + let mut lock = LOCAL_PEER_ID.write().unwrap(); + id = (*lock).clone(); + if id.is_empty() { + id = get_id(); + id.push('\0'); + *lock = id.clone(); + } + } + id.as_ptr() as _ +} + pub fn load_plugin(path: &str) -> ResultType<()> { let mut plugin = Plugin::new(path)?; - let desc = (plugin.fn_desc)(); + let desc = (plugin.desc)(); let desc_res = Desc::from_cstr(desc); unsafe { libc::free(desc as _); @@ -166,19 +193,27 @@ pub fn load_plugin(path: &str) -> ResultType<()> { let id = desc.id().to_string(); // to-do validate plugin // to-do check the plugin id (make sure it does not use another plugin's id) - (plugin.fn_set_cb_msg)(callback_msg::callback_msg); + (plugin.set_cb_msg)(callback_msg::callback_msg); + (plugin.set_cb_get_id)(get_local_peer_id as _); update_ui_plugin_desc(&desc); update_config(&desc); reload_ui(&desc); - plugin.desc = Some(desc); + plugin.desc_v = Some(desc); PLUGINS.write().unwrap().insert(id, plugin); Ok(()) } -fn handle_event(method: &[u8], id: &str, event: &[u8]) -> ResultType<()> { +fn handle_event(method: &[u8], id: &str, peer: &str, event: &[u8]) -> ResultType<()> { + let mut peer: String = peer.to_owned(); + peer.push('\0'); match PLUGINS.read().unwrap().get(id) { Some(plugin) => { - let ret = (plugin.fn_call)(method.as_ptr() as _, event.as_ptr() as _, event.len()); + let ret = (plugin.call)( + method.as_ptr() as _, + peer.as_ptr() as _, + event.as_ptr() as _, + event.len(), + ); if ret.is_null() { Ok(()) } else { @@ -200,21 +235,24 @@ fn handle_event(method: &[u8], id: &str, event: &[u8]) -> ResultType<()> { } #[inline] -pub fn handle_ui_event(id: &str, event: &[u8]) -> ResultType<()> { - handle_event(METHOD_HANDLE_UI, id, event) +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, event: &[u8]) -> ResultType<()> { - handle_event(METHOD_HANDLE_PEER, id, event) +pub fn handle_server_event(id: &str, peer: &str, event: &[u8]) -> ResultType<()> { + handle_event(METHOD_HANDLE_PEER, id, peer, event) } #[inline] -pub fn handle_client_event(id: &str, event: &[u8]) -> Option { +pub fn handle_client_event(id: &str, peer: &str, event: &[u8]) -> Option { + let mut peer: String = peer.to_owned(); + peer.push('\0'); match PLUGINS.read().unwrap().get(id) { Some(plugin) => { - let ret = (plugin.fn_call)( + let ret = (plugin.call)( METHOD_HANDLE_PEER.as_ptr() as _, + peer.as_ptr() as _, event.as_ptr() as _, event.len(), ); @@ -226,7 +264,7 @@ pub fn handle_client_event(id: &str, event: &[u8]) -> Option { libc::free(ret as _); } if code > ERR_RUSTDESK_HANDLE_BASE && code < ERR_PLUGIN_HANDLE_BASE { - let name = plugin.desc.as_ref().unwrap().name(); + let name = plugin.desc_v.as_ref().unwrap().name(); match code { ERR_CALL_NOT_SUPPORTED_METHOD => Some(make_plugin_response( id, @@ -288,7 +326,7 @@ fn reload_ui(desc: &Desc) { if available_channels.contains(&v[1]) { if let Ok(ui) = serde_json::to_string(&ui) { let mut m = HashMap::new(); - m.insert("name", "plugin_reload"); + m.insert("name", MSG_TO_UI_TYPE_PLUGIN_RELOAD); m.insert("id", desc.id()); m.insert("location", &location); m.insert("ui", &ui); @@ -303,7 +341,7 @@ fn update_ui_plugin_desc(desc: &Desc) { // This function is rarely used. There's no need to care about serialization efficiency here. if let Ok(desc_str) = serde_json::to_string(desc) { let mut m = HashMap::new(); - m.insert("name", "plugin_desc"); + m.insert("name", MSG_TO_UI_TYPE_PLUGIN_DESC); m.insert("desc", &desc_str); flutter::push_global_event(flutter::APP_TYPE_MAIN, serde_json::to_string(&m).unwrap()); flutter::push_global_event( diff --git a/src/server/connection.rs b/src/server/connection.rs index 44e995a3a..e128f511a 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1813,7 +1813,9 @@ impl Connection { #[cfg(all(feature = "flutter", feature = "plugin_framework"))] #[cfg(not(any(target_os = "android", target_os = "ios")))] Some(misc::Union::PluginRequest(p)) => { - if let Some(msg) = crate::plugin::handle_client_event(&p.id, &p.content) { + if let Some(msg) = + crate::plugin::handle_client_event(&p.id, &self.lr.my_id, &p.content) + { self.send(msg).await; } }