diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 388f5bd59..b9a2a7598 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -16,6 +16,8 @@ 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/plugin/event.dart'; +import 'package:flutter_hbb/plugin/reloader.dart'; import 'package:flutter_hbb/common/shared_state.dart'; import 'package:tuple/tuple.dart'; import 'package:image/image.dart' as img2; @@ -226,6 +228,13 @@ class FfiModel with ChangeNotifier { parent.target?.serverModel.updateVoiceCallState(evt); } else if (name == "fingerprint") { FingerprintState.find(peerId).value = evt['fingerprint'] ?? ''; + } else if (name == "plugin_desc") { + handleReloading(evt, peerId); + } else if (name == "plugin_event") { + handlePluginEvent( + evt, peerId, (Map e) => handleMsgBox(e, peerId)); + } else if (name == "plugin_reload") { + handleReloading(evt, peerId); } else { debugPrint("Unknown event name: $name"); } diff --git a/flutter/lib/plugin/desc.dart b/flutter/lib/plugin/desc.dart new file mode 100644 index 000000000..c6b04d11c --- /dev/null +++ b/flutter/lib/plugin/desc.dart @@ -0,0 +1,135 @@ +import 'dart:collection'; + +class UiButton { + String key; + String text; + String icon; + String tooltip; + String action; + + UiButton(this.key, this.text, this.icon, this.tooltip, this.action); + UiButton.fromJson(Map json) + : key = json['key'] ?? '', + text = json['text'] ?? '', + icon = json['icon'] ?? '', + tooltip = json['tooltip'] ?? '', + action = json['action'] ?? ''; +} + +class UiCheckbox { + String key; + String text; + String tooltip; + String action; + + UiCheckbox(this.key, this.text, this.tooltip, this.action); + UiCheckbox.fromJson(Map json) + : key = json['key'] ?? '', + text = json['text'] ?? '', + tooltip = json['tooltip'] ?? '', + action = json['action'] ?? ''; +} + +class UiType { + UiButton? button; + UiCheckbox? checkbox; + + UiType.fromJson(Map json) + : button = json['t'] == 'Button' ? UiButton.fromJson(json['c']) : null, + checkbox = + json['t'] != 'Checkbox' ? UiCheckbox.fromJson(json['c']) : null; +} + +class Location { + HashMap ui; + + Location(this.ui); +} + +class ConfigItem { + String key; + String value; + String description; + String defaultValue; + + ConfigItem(this.key, this.value, this.defaultValue, this.description); + ConfigItem.fromJson(Map json) + : key = json['key'] ?? '', + value = json['value'] ?? '', + description = json['description'] ?? '', + defaultValue = json['default'] ?? ''; +} + +class Config { + List local; + List peer; + + Config(this.local, this.peer); + Config.fromJson(Map json) + : local = (json['local'] as List) + .map((e) => ConfigItem.fromJson(e)) + .toList(), + peer = (json['peer'] as List) + .map((e) => ConfigItem.fromJson(e)) + .toList(); +} + +class Desc { + String id; + String name; + String version; + String description; + String author; + String home; + String license; + String published; + String released; + String github; + Location location; + Config config; + + Desc( + this.id, + this.name, + this.version, + this.description, + this.author, + this.home, + this.license, + this.published, + this.released, + this.github, + this.location, + this.config); + + Desc.fromJson(Map json) + : id = json['id'] ?? '', + name = json['name'] ?? '', + version = json['version'] ?? '', + description = json['description'] ?? '', + author = json['author'] ?? '', + home = json['home'] ?? '', + license = json['license'] ?? '', + published = json['published'] ?? '', + released = json['released'] ?? '', + github = json['github'] ?? '', + location = Location(HashMap.from(json['location'])), + config = Config( + (json['config'] as List) + .map((e) => ConfigItem.fromJson(e)) + .toList(), + (json['config'] as List) + .map((e) => ConfigItem.fromJson(e)) + .toList()); +} + +final mapPluginDesc = {}; + +void updateDesc(Map desc) { + Desc d = Desc.fromJson(desc); + mapPluginDesc[d.id] = d; +} + +Desc? getDesc(String id) { + return mapPluginDesc[id]; +} diff --git a/flutter/lib/plugin/event.dart b/flutter/lib/plugin/event.dart new file mode 100644 index 000000000..04d0ddf45 --- /dev/null +++ b/flutter/lib/plugin/event.dart @@ -0,0 +1,60 @@ +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/plugin/reloader.dart b/flutter/lib/plugin/reloader.dart new file mode 100644 index 000000000..1b1641f87 --- /dev/null +++ b/flutter/lib/plugin/reloader.dart @@ -0,0 +1,29 @@ +void handleReloading(Map evt, String peer) { + // location + // host|main|settings|display|others + // client|remote|toolbar|display + // + // ui + // { + // "t": "Button", + // "c": { + // "key": "key", + // "text": "text", + // "icon": "icon", + // "tooltip": "tooltip", + // "action": "action" + // } + // } + // + // { + // "t": "Checkbox", + // "c": { + // "key": "key", + // "text": "text", + // "tooltip": "tooltip", + // "action": "action" + // } + // } + // + +} diff --git a/flutter/lib/plugin/widget.dart b/flutter/lib/plugin/widget.dart new file mode 100644 index 000000000..dbdfbbadd --- /dev/null +++ b/flutter/lib/plugin/widget.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +final Map pluginWidgets = {}; + +class PluginWidget { + final String id; + final String name; + final String location; + final Widget widget; + + PluginWidget({ + required this.id, + required this.name, + required this.location, + required this.widget, + }); +} diff --git a/src/plugin/callback_msg.rs b/src/plugin/callback_msg.rs index 2ceffca9e..4d02db287 100644 --- a/src/plugin/callback_msg.rs +++ b/src/plugin/callback_msg.rs @@ -99,7 +99,7 @@ pub fn callback_msg( { let _res = flutter::push_session_event( &peer, - "plugin", + "plugin_event", vec![("peer", &peer), ("content", &content)], ); } diff --git a/src/plugin/desc.rs b/src/plugin/desc.rs index 26eda187a..bc094abf9 100644 --- a/src/plugin/desc.rs +++ b/src/plugin/desc.rs @@ -28,12 +28,12 @@ pub enum UiType { Checkbox(UiCheckbox), } -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct Location { pub ui: HashMap, } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConfigItem { pub key: String, pub value: String, @@ -41,13 +41,13 @@ pub struct ConfigItem { pub description: String, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct Config { pub local: Vec, pub peer: Vec, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct Desc { id: String, name: String, diff --git a/src/plugin/plugins.rs b/src/plugin/plugins.rs index b7de6e0be..e05940cd1 100644 --- a/src/plugin/plugins.rs +++ b/src/plugin/plugins.rs @@ -165,7 +165,9 @@ pub fn load_plugin(path: &str) -> ResultType<()> { let desc = desc_res?; 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); + update_ui_plugin_desc(&desc); update_config(&desc); reload_ui(&desc); plugin.desc = Some(desc); @@ -287,6 +289,8 @@ fn reload_ui(desc: &Desc) { if let Ok(ui) = serde_json::to_string(&ui) { let mut m = HashMap::new(); m.insert("name", "plugin_reload"); + m.insert("id", desc.id()); + m.insert("location", &location); m.insert("ui", &ui); flutter::push_global_event(v[1], serde_json::to_string(&m).unwrap()); } @@ -294,3 +298,18 @@ fn reload_ui(desc: &Desc) { } } } + +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("desc", &desc_str); + flutter::push_global_event(flutter::APP_TYPE_MAIN, serde_json::to_string(&m).unwrap()); + flutter::push_global_event( + flutter::APP_TYPE_DESKTOP_REMOTE, + serde_json::to_string(&m).unwrap(), + ); + flutter::push_global_event(flutter::APP_TYPE_CM, serde_json::to_string(&m).unwrap()); + } +}