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

100
Cargo.lock generated
View File

@@ -986,27 +986,6 @@ dependencies = [
"serde 1.0.228",
]
[[package]]
name = "bzip2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
dependencies = [
"bzip2-sys",
"libc",
]
[[package]]
name = "bzip2-sys"
version = "0.1.11+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "cacao"
version = "0.4.0-beta2"
@@ -1477,8 +1456,8 @@ dependencies = [
"compression-core",
"flate2",
"memchr",
"zstd 0.13.1",
"zstd-safe 7.1.0",
"zstd",
"zstd-safe",
]
[[package]]
@@ -1549,12 +1528,6 @@ dependencies = [
"unicode-xid 0.2.4",
]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "constant_time_eq"
version = "0.2.6"
@@ -3825,7 +3798,7 @@ dependencies = [
"whoami",
"winapi 0.3.9",
"x11 2.21.0",
"zstd 0.13.1",
"zstd",
]
[[package]]
@@ -6057,35 +6030,12 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "password-hash"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
dependencies = [
"base64ct",
"rand_core 0.6.4",
"subtle",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pbkdf2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
dependencies = [
"digest",
"hmac",
"password-hash",
"sha2",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
@@ -7365,7 +7315,6 @@ dependencies = [
"wol-rs",
"x11-clipboard 0.8.1",
"x11rb 0.12.0",
"zip",
]
[[package]]
@@ -8907,7 +8856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c4ae9724c5888c0417d2396037ed3b60665925624766416e3e342b6ba5dbd3f"
dependencies = [
"base32",
"constant_time_eq 0.2.6",
"constant_time_eq",
"hmac",
"rand 0.8.5",
"sha1",
@@ -11157,52 +11106,13 @@ dependencies = [
"syn 2.0.98",
]
[[package]]
name = "zip"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"aes",
"byteorder",
"bzip2",
"constant_time_eq 0.1.5",
"crc32fast",
"crossbeam-utils",
"flate2",
"hmac",
"pbkdf2",
"sha1",
"time 0.3.36",
"zstd 0.11.2+zstd.1.5.2",
]
[[package]]
name = "zstd"
version = "0.11.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
dependencies = [
"zstd-safe 5.0.2+zstd.1.5.2",
]
[[package]]
name = "zstd"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a"
dependencies = [
"zstd-safe 7.1.0",
]
[[package]]
name = "zstd-safe"
version = "5.0.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
dependencies = [
"libc",
"zstd-sys",
"zstd-safe",
]
[[package]]

View File

@@ -37,7 +37,6 @@ drm = ["scrap/drm"]
# kind of operation and deserves a switch that can remove it from the binary entirely, without
# giving up DRM capture: `--features drm` builds the capture path with no wake code compiled in.
drm-wake = ["drm"]
plugin_framework = []
linux-pkg-config = ["magnum-opus/linux-pkg-config", "scrap/linux-pkg-config"]
unix-file-copy-paste = [
"dep:x11-clipboard",
@@ -81,7 +80,6 @@ hex = "0.4"
chrono = "0.4"
cidr-utils = "0.5"
fon = "0.6"
zip = "0.6"
shutdown_hooks = "0.1"
totp-rs = { version = "5.4", default-features = false, features = ["gen_secret", "otpauth"] }
stunclient = "0.4"
@@ -212,7 +210,7 @@ android-wakelock = { git = "https://github.com/rustdesk-org/android-wakelock" }
[workspace]
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/portable", "libs/remote_printer"]
exclude = ["vdi/host", "examples/custom_plugin"]
exclude = ["vdi/host"]
# Patch libxdo-sys to use a stub implementation that doesn't require libxdo
# This allows building and running on systems without libxdo installed (e.g., Wayland-only)

View File

@@ -16,7 +16,6 @@ import 'package:flutter_hbb/desktop/widgets/update_progress.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/plugin/ui_manager.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:flutter_hbb/utils/platform_channel.dart';
import 'package:get/get.dart';
@@ -111,7 +110,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
}
},
),
buildPluginEntry(),
];
if (isIncomingOnly) {
children.addAll([
@@ -890,21 +888,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
shouldBeBlocked(_block, canBeBlocked);
}
}
Widget buildPluginEntry() {
final entries = PluginUiManager.instance.entries.entries;
return Offstage(
offstage: entries.isEmpty,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...entries.map((entry) {
return entry.value;
})
],
),
);
}
}
void setPasswordDialog({VoidCallback? notEmptyCallback}) async {

View File

@@ -17,8 +17,6 @@ import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/printer_model.dart';
import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/plugin/manager.dart';
import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
@@ -55,7 +53,6 @@ enum SettingsTabKey {
safety,
network,
display,
plugin,
account,
printer,
about,
@@ -74,8 +71,6 @@ class DesktopSettingPage extends StatefulWidget {
bind.mainGetBuildinOption(key: kOptionHideNetworkSetting) != 'Y')
SettingsTabKey.network,
if (!bind.isIncomingOnly()) SettingsTabKey.display,
if (!isWeb && !bind.isIncomingOnly() && bind.pluginFeatureIsEnabled())
SettingsTabKey.plugin,
if (!bind.isDisableAccount()) SettingsTabKey.account,
if (isWindows &&
bind.mainGetBuildinOption(key: kOptionHideRemotePrinterSetting) != 'Y')
@@ -171,7 +166,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
void dispose() {
super.dispose();
Get.delete<PageController>(tag: _kSettingPageControllerTag);
Get.delete<RxInt>(tag: _kSettingPageTabKeyTag);
Get.delete<Rx<SettingsTabKey>>(tag: _kSettingPageTabKeyTag);
WidgetsBinding.instance.removeObserver(this);
_videoConnTimer?.cancel();
}
@@ -196,10 +191,6 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
settingTabs.add(_TabInfo(tab, 'Display',
Icons.desktop_windows_outlined, Icons.desktop_windows));
break;
case SettingsTabKey.plugin:
settingTabs.add(_TabInfo(
tab, 'Plugin', Icons.extension_outlined, Icons.extension));
break;
case SettingsTabKey.account:
settingTabs.add(
_TabInfo(tab, 'Account', Icons.person_outline, Icons.person));
@@ -233,9 +224,6 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
case SettingsTabKey.display:
children.add(const _Display());
break;
case SettingsTabKey.plugin:
children.add(const _Plugin());
break;
case SettingsTabKey.account:
children.add(const _Account());
break;
@@ -2255,51 +2243,6 @@ class _CheckboxState extends State<_Checkbox> {
}
}
class _Plugin extends StatefulWidget {
const _Plugin({Key? key}) : super(key: key);
@override
State<_Plugin> createState() => _PluginState();
}
class _PluginState extends State<_Plugin> {
@override
Widget build(BuildContext context) {
bind.pluginListReload();
final scrollController = ScrollController();
return ChangeNotifierProvider.value(
value: pluginManager,
child: Consumer<PluginManager>(builder: (context, model, child) {
return ListView(
controller: scrollController,
children: model.plugins.map((entry) => pluginCard(entry)).toList(),
).marginOnly(bottom: _kListViewBottomMargin);
}),
);
}
Widget pluginCard(PluginInfo plugin) {
return ChangeNotifierProvider.value(
value: plugin,
child: Consumer<PluginInfo>(
builder: (context, model, child) => DesktopSettingsCard(plugin: model),
),
);
}
Widget accountAction() {
return Obx(() => _Button(
gFFI.userModel.userName.value.isEmpty
? 'Login'
: '${translate('Logout')} (${gFFI.userModel.accountLabelWithHandle})',
() => {
gFFI.userModel.userName.value.isEmpty
? loginDialog()
: logOutConfirmDialog()
}));
}
}
class _Printer extends StatefulWidget {
const _Printer({super.key});

View File

@@ -182,7 +182,6 @@ class _RemotePageState extends State<RemotePage>
WakelockManager.enable(_uniqueKey);
_ffi.ffiModel.updateEventListener(sessionId, widget.id);
if (!isWeb) bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote);
_ffi.qualityMonitorModel.checkShowQualityMonitor(sessionId);
_ffi.dialogManager.loadMobileActionsOverlayVisible();
WidgetsBinding.instance.addPostFrameCallback((_) {

View File

@@ -127,7 +127,6 @@ class _ViewCameraPageState extends State<ViewCameraPage>
WakelockManager.enable(_uniqueKey);
_ffi.ffiModel.updateEventListener(sessionId, widget.id);
if (!isWeb) bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote);
_ffi.qualityMonitorModel.checkShowQualityMonitor(sessionId);
_ffi.dialogManager.loadMobileActionsOverlayVisible();
DesktopMultiWindow.addListener(this);

View File

@@ -10,8 +10,6 @@ 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/plugin/widgets/desc_ui.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';
@@ -1478,20 +1476,13 @@ class _DisplayMenu extends StatefulWidget {
final FFI ffi;
final ToolbarState state;
final Function(bool) setFullscreen;
final Widget pluginItem;
_DisplayMenu(
{Key? key,
required this.id,
required this.ffi,
required this.state,
required this.setFullscreen})
: pluginItem = LocationItem.createLocationItem(
id,
ffi,
kLocationClientRemoteToolbarDisplay,
true,
),
super(key: key);
: super(key: key);
@override
State<_DisplayMenu> createState() => _DisplayMenuState();
@@ -1582,9 +1573,6 @@ class _DisplayMenuState extends State<_DisplayMenu> {
]);
}
}
if (ffi.connType == ConnType.defaultConn) {
menuChildren.add(widget.pluginItem);
}
return menuChildren;
}

View File

@@ -30,9 +30,6 @@ import 'mobile/pages/server_page.dart';
import 'mobile/widgets/deploy_dialog.dart';
import 'models/platform_model.dart';
import 'package:flutter_hbb/plugin/handlers.dart'
if (dart.library.html) 'package:flutter_hbb/web/plugin/handlers.dart';
/// Basic window and launch properties.
int? kWindowId;
WindowType? kWindowType;
@@ -141,8 +138,6 @@ void runMainApp(bool startService) async {
await bind.mainCheckConnectStatus();
if (startService) {
gFFI.serverModel.startService();
bind.pluginSyncUi(syncTo: kAppTypeMain);
bind.pluginListReload();
}
await Future.wait([gFFI.abModel.loadCache(), gFFI.groupModel.loadCache()]);
gFFI.userModel.refreshCurrentUser();
@@ -570,12 +565,6 @@ _registerEventHandler() {
reloadAllWindows();
});
}
// Register native handlers.
if (isDesktop) {
platformFFI.registerEventHandler('native_ui', 'native_ui', (evt) async {
NativeUiHandler.instance.onEvent(evt);
});
}
if (isAndroid) {
platformFFI.registerEventHandler(
'android_needs_deploy', 'android_needs_deploy', (_) async {

View File

@@ -25,9 +25,6 @@ import 'package:flutter_hbb/models/user_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/models/desktop_render_texture.dart';
import 'package:flutter_hbb/models/terminal_model.dart';
import 'package:flutter_hbb/plugin/event.dart';
import 'package:flutter_hbb/plugin/manager.dart';
import 'package:flutter_hbb/plugin/widgets/desc_ui.dart';
import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:flutter_hbb/utils/http_service.dart' as http;
@@ -437,15 +434,6 @@ class FfiModel with ChangeNotifier {
parent.target?.serverModel.updateVoiceCallState(evt);
} else if (name == 'fingerprint') {
FingerprintState.find(peerId).value = evt['fingerprint'] ?? '';
} else if (name == 'plugin_manager') {
pluginManager.handleEvent(evt);
} else if (name == 'plugin_event') {
handlePluginEvent(evt,
(Map<String, dynamic> e) => handleMsgBox(e, sessionId, peerId));
} else if (name == 'plugin_reload') {
handleReloading(evt);
} else if (name == 'plugin_option') {
handleOption(evt);
} else if (name == "sync_peer_hash_password_to_personal_ab") {
if (desktopType == DesktopType.main || isWeb || isMobile) {
final id = evt['id'];

View File

@@ -1,42 +0,0 @@
import 'dart:convert';
typedef PluginId = String;
// ui location
const String kLocationHostMainPlugin = 'host|main|settings|plugin';
const String kLocationClientRemoteToolbarDisplay =
'client|remote|toolbar|display';
class MsgFromUi {
String id;
String name;
String location;
String key;
String value;
String action;
MsgFromUi({
required this.id,
required this.name,
required this.location,
required this.key,
required this.value,
required this.action,
});
Map<String, dynamic> toJson() {
return <String, dynamic>{
'id': id,
'name': name,
'location': location,
'key': key,
'value': value,
'action': action,
};
}
@override
String toString() {
return jsonEncode(toJson());
}
}

View File

@@ -1,18 +0,0 @@
import 'dart:convert';
import 'package:flutter/material.dart';
void handlePluginEvent(
Map<String, dynamic> evt,
Function(Map<String, dynamic> e) handleMsgBox,
) {
Map<String, dynamic>? content;
try {
content = json.decode(evt['content']);
} catch (e) {
debugPrint(
'Json decode plugin event content failed: $e, ${evt['content']}');
}
if (content?['t'] == 'MsgBox') {
handleMsgBox(content?['c']);
}
}

View File

@@ -1,79 +0,0 @@
import 'dart:convert';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/plugin/ui_manager.dart';
import 'package:flutter_hbb/plugin/utils/dialogs.dart';
abstract class NativeHandler {
bool onEvent(Map<String, dynamic> evt);
}
typedef OnSelectPeersCallback = Bool Function(Int returnCode,
Pointer<Void> data, Uint64 dataLength, Pointer<Void> userData);
typedef OnSelectPeersCallbackDart = bool Function(
int returnCode, Pointer<Void> data, int dataLength, Pointer<Void> userData);
class NativeUiHandler extends NativeHandler {
NativeUiHandler._();
static NativeUiHandler instance = NativeUiHandler._();
@override
bool onEvent(Map<String, dynamic> evt) {
final name = evt['name'];
final action = evt['action'];
if (name != "native_ui") {
return false;
}
switch (action) {
case "select_peers":
int cb = evt['cb'];
int userData = evt['user_data'] ?? 0;
final cbFuncNative = Pointer.fromAddress(cb)
.cast<NativeFunction<OnSelectPeersCallback>>();
final cbFuncDart = cbFuncNative.asFunction<OnSelectPeersCallbackDart>();
onSelectPeers(cbFuncDart, userData);
break;
case "register_ui_entry":
int cb = evt['on_tap_cb'];
int userData = evt['user_data'] ?? 0;
String title = evt['title'] ?? "";
final cbFuncNative = Pointer.fromAddress(cb)
.cast<NativeFunction<OnSelectPeersCallback>>();
final cbFuncDart = cbFuncNative.asFunction<OnSelectPeersCallbackDart>();
onRegisterUiEntry(title, cbFuncDart, userData);
break;
default:
return false;
}
return true;
}
void onSelectPeers(OnSelectPeersCallbackDart cb, int userData) async {
showPeerSelectionDialog(onPeersCallback: (peers) {
String json = jsonEncode(<String, dynamic> {
"peers": peers
});
final native = json.toNativeUtf8();
cb(0, native.cast(), native.length, Pointer.fromAddress(userData));
malloc.free(native);
});
}
void onRegisterUiEntry(String title, OnSelectPeersCallbackDart cbFuncDart, int userData) {
Widget widget = InkWell(
child: Container(
height: 25.0,
child: Row(
children: [
Expanded(child: Text(title)),
Icon(Icons.chevron_right_rounded, size: 12.0,)
],
),
),
);
PluginUiManager.instance.registerEntry(title, widget);
}
}

View File

@@ -1,319 +0,0 @@
// The plugin manager is a singleton class that manages the plugins.
// 1. It merge metadata and the desc of plugins.
import 'dart:convert';
import 'dart:collection';
import 'package:flutter/material.dart';
const String kValueTrue = '1';
const String kValueFalse = '0';
class ConfigItem {
String key;
String description;
String defaultValue;
ConfigItem(this.key, this.defaultValue, this.description);
ConfigItem.fromJson(Map<String, dynamic> json)
: key = json['key'] ?? '',
description = json['description'] ?? '',
defaultValue = json['default'] ?? '';
static String get trueValue => kValueTrue;
static String get falseValue => kValueFalse;
static bool isTrue(String value) => value == kValueTrue;
static bool isFalse(String value) => value == kValueFalse;
}
class UiType {
String key;
String text;
String tooltip;
String action;
UiType(this.key, this.text, this.tooltip, this.action);
UiType.fromJson(Map<String, dynamic> json)
: key = json['key'] ?? '',
text = json['text'] ?? '',
tooltip = json['tooltip'] ?? '',
action = json['action'] ?? '';
static UiType? create(Map<String, dynamic> json) {
if (json['t'] == 'Button') {
return UiButton.fromJson(json['c']);
} else if (json['t'] == 'Checkbox') {
return UiCheckbox.fromJson(json['c']);
} else {
return null;
}
}
}
class UiButton extends UiType {
String icon;
UiButton(
{required String key,
required String text,
required this.icon,
required String tooltip,
required String action})
: super(key, text, tooltip, action);
UiButton.fromJson(Map<String, dynamic> json)
: icon = json['icon'] ?? '',
super.fromJson(json);
}
class UiCheckbox extends UiType {
UiCheckbox(
{required String key,
required String text,
required String tooltip,
required String action})
: super(key, text, tooltip, action);
UiCheckbox.fromJson(Map<String, dynamic> json) : super.fromJson(json);
}
class Location {
// location key:
// host|main|settings|plugin
// client|remote|toolbar|display
HashMap<String, UiType> ui;
Location(this.ui);
Location.fromJson(Map<String, dynamic> json) : ui = HashMap() {
(json['ui'] as Map<String, dynamic>).forEach((key, value) {
var ui = UiType.create(value);
if (ui != null) {
this.ui[ui.key] = ui;
}
});
}
}
class PublishInfo {
PublishInfo({
required this.lastReleased,
required this.published,
});
final DateTime lastReleased;
final DateTime published;
}
class Meta {
Meta({
required this.id,
required this.name,
required this.version,
required this.description,
required this.author,
required this.home,
required this.license,
required this.publishInfo,
required this.source,
});
final String id;
final String name;
final String version;
final String description;
final String author;
final String home;
final String license;
final PublishInfo publishInfo;
final String source;
}
class SourceInfo {
String name; // 1. RustDesk github 2. Local
String url;
String description;
SourceInfo({
required this.name,
required this.url,
required this.description,
});
}
class PluginInfo with ChangeNotifier {
SourceInfo sourceInfo;
Meta meta;
String installedVersion; // It is empty if not installed.
String failedMsg;
String invalidReason; // It is empty if valid.
PluginInfo({
required this.sourceInfo,
required this.meta,
required this.installedVersion,
required this.invalidReason,
this.failedMsg = '',
});
bool get installed => installedVersion.isNotEmpty;
bool get needUpdate => installed && installedVersion != meta.version;
void setInstall(String msg) {
if (msg == "finished") {
msg = '';
}
failedMsg = msg;
if (msg.isEmpty) {
installedVersion = meta.version;
}
notifyListeners();
}
void setUninstall(String msg) {
failedMsg = msg;
if (msg.isEmpty) {
installedVersion = '';
}
notifyListeners();
}
}
class PluginManager with ChangeNotifier {
String failedReason = ''; // The reason of failed to load plugins.
final List<PluginInfo> _plugins = [];
PluginManager._();
static final PluginManager _instance = PluginManager._();
static PluginManager get instance => _instance;
List<PluginInfo> get plugins => _plugins;
PluginInfo? getPlugin(String id) {
for (var p in _plugins) {
if (p.meta.id == id) {
return p;
}
}
return null;
}
void handleEvent(Map<String, dynamic> evt) {
if (evt['plugin_list'] != null) {
_handlePluginList(evt['plugin_list']);
} else if (evt['plugin_install'] != null && evt['id'] != null) {
_handlePluginInstall(evt['id'], evt['plugin_install']);
} else if (evt['plugin_uninstall'] != null && evt['id'] != null) {
_handlePluginUninstall(evt['id'], evt['plugin_uninstall']);
} else {
debugPrint('Failed to handle manager event: $evt');
}
}
void _sortPlugins() {
plugins.sort((a, b) {
if (a.installed) {
return -1;
} else if (b.installed) {
return 1;
} else {
return 0;
}
});
}
void _handlePluginList(String pluginList) {
_plugins.clear();
try {
for (var p in json.decode(pluginList) as List<dynamic>) {
final plugin = _getPluginFromEvent(p);
if (plugin == null) {
continue;
}
_plugins.add(plugin);
}
} catch (e) {
debugPrint('Failed to decode $e, plugin list \'$pluginList\'');
}
_sortPlugins();
notifyListeners();
}
void _handlePluginInstall(String id, String msg) {
debugPrint('Plugin \'$id\' install msg $msg');
for (var i = 0; i < _plugins.length; i++) {
if (_plugins[i].meta.id == id) {
_plugins[i].setInstall(msg);
_sortPlugins();
notifyListeners();
return;
}
}
}
void _handlePluginUninstall(String id, String msg) {
debugPrint('Plugin \'$id\' uninstall msg $msg');
for (var i = 0; i < _plugins.length; i++) {
if (_plugins[i].meta.id == id) {
_plugins[i].setUninstall(msg);
_sortPlugins();
notifyListeners();
return;
}
}
}
PluginInfo? _getPluginFromEvent(Map<String, dynamic> evt) {
final s = evt['source'];
assert(s != null, 'Source is null');
if (s == null) {
return null;
}
final source = SourceInfo(
name: s['name'],
url: s['url'] ?? '',
description: s['description'] ?? '',
);
final m = evt['meta'];
assert(m != null, 'Meta is null');
if (m == null) {
return null;
}
late DateTime lastReleased;
late DateTime published;
try {
lastReleased = DateTime.parse(
m['publish_info']?['last_released'] ?? '1970-01-01T00+00:00');
} catch (e) {
lastReleased = DateTime.utc(1970);
}
try {
published = DateTime.parse(
m['publish_info']?['published'] ?? '1970-01-01T00+00:00');
} catch (e) {
published = DateTime.utc(1970);
}
final meta = Meta(
id: m['id'],
name: m['name'],
version: m['version'],
description: m['description'] ?? '',
author: m['author'],
home: m['home'] ?? '',
license: m['license'] ?? '',
source: m['source'] ?? '',
publishInfo:
PublishInfo(lastReleased: lastReleased, published: published),
);
return PluginInfo(
sourceInfo: source,
meta: meta,
installedVersion: evt['installed_version'],
invalidReason: evt['invalid_reason'] ?? '',
);
}
}
PluginManager get pluginManager => PluginManager.instance;

View File

@@ -1,110 +0,0 @@
import 'package:flutter/material.dart';
import './common.dart';
import './manager.dart';
final Map<String, LocationModel> _locationModels = {};
final Map<String, OptionModel> _optionModels = {};
class OptionModel with ChangeNotifier {
String? v;
String? get value => v;
set value(String? v) {
this.v = v;
notifyListeners();
}
static String key(String location, PluginId id, String peer, String k) =>
'$location|$id|$peer|$k';
}
class PluginModel with ChangeNotifier {
final List<UiType> uiList = [];
final Map<String, String> opts = {};
void add(List<UiType> uiList) {
bool found = false;
for (var ui in uiList) {
for (int i = 0; i < this.uiList.length; i++) {
if (this.uiList[i].key == ui.key) {
this.uiList[i] = ui;
found = true;
}
}
if (!found) {
this.uiList.add(ui);
}
}
notifyListeners();
}
String? getOpt(String key) => opts.remove(key);
bool get isEmpty => uiList.isEmpty;
}
class LocationModel with ChangeNotifier {
final Map<PluginId, PluginModel> pluginModels = {};
void add(PluginId id, List<UiType> uiList) {
if (pluginModels[id] != null) {
pluginModels[id]!.add(uiList);
} else {
var model = PluginModel();
model.add(uiList);
pluginModels[id] = model;
notifyListeners();
}
}
void clear() {
pluginModels.clear();
notifyListeners();
}
void remove(PluginId id) {
pluginModels.remove(id);
notifyListeners();
}
bool get isEmpty => pluginModels.isEmpty;
}
void addLocationUi(String location, PluginId id, List<UiType> uiList) {
if (_locationModels[location] == null) {
_locationModels[location] = LocationModel();
}
_locationModels[location]?.add(id, uiList);
}
LocationModel? getLocationModel(String location) => _locationModels[location];
PluginModel? getPluginModel(String location, PluginId id) =>
_locationModels[location]?.pluginModels[id];
void clearPlugin(PluginId pluginId) {
for (var element in _locationModels.values) {
element.remove(pluginId);
}
}
void clearLocations() {
for (var element in _locationModels.values) {
element.clear();
}
}
OptionModel getOptionModel(
String location, PluginId pluginId, String peer, String key) {
final k = OptionModel.key(location, pluginId, peer, key);
if (_optionModels[k] == null) {
_optionModels[k] = OptionModel();
}
return _optionModels[k]!;
}
void updateOption(
String location, PluginId id, String peer, String key, String value) {
final k = OptionModel.key(location, id, peer, key);
_optionModels[k]?.value = value;
}

View File

@@ -1,17 +0,0 @@
import 'package:flutter/material.dart';
class PluginUiManager {
PluginUiManager._();
static PluginUiManager instance = PluginUiManager._();
Map<String, Widget> entries = <String, Widget>{};
void registerEntry(String key, Widget widget) {
entries[key] = widget;
}
void unregisterEntry(String key) {
entries.remove(key);
}
}

View File

@@ -1,86 +0,0 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
void showPeerSelectionDialog(
{bool singleSelection = false,
required Function(List<String>) onPeersCallback}) async {
// load recent peers, we can directly use the peers in `gFFI.recentPeersModel`.
// The plugin is not used for now, so just left it empty here.
final peers = '';
if (peers.isEmpty) {
// debugPrint("load recent peers failed.");
return;
}
Map<String, dynamic> map = jsonDecode(peers);
List<dynamic> peersList = map['peers'] ?? [];
final selected = List<String>.empty(growable: true);
submit() async {
onPeersCallback.call(selected);
}
gFFI.dialogManager.show((setState, close, context) {
return CustomAlertDialog(
title:
Text(translate(singleSelection ? "Select peers" : "Select a peer")),
content: SizedBox(
height: 300.0,
child: ListView.builder(
itemBuilder: (context, index) {
final Map<String, dynamic> peer = peersList[index];
final String platform = peer['platform'] ?? "";
final String id = peer['id'] ?? "";
final String alias = peer['alias'] ?? "";
return GestureDetector(
onTap: () {
setState(() {
if (selected.contains(id)) {
selected.remove(id);
} else {
selected.add(id);
}
});
},
child: Container(
key: ValueKey(index),
height: 50.0,
decoration: BoxDecoration(
color: Theme.of(context).highlightColor,
borderRadius: BorderRadius.circular(12.0)),
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0),
margin: EdgeInsets.symmetric(vertical: 4.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
// platform
SizedBox(
width: 8.0,
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
getPlatformImage(platform, size: 34.0),
],
),
SizedBox(
width: 8.0,
),
// id/alias
Expanded(child: Text(alias.isEmpty ? id : alias)),
],
),
),
);
},
itemCount: peersList.length,
itemExtent: 50.0,
),
),
onSubmit: submit,
);
});
}

View File

@@ -1,301 +0,0 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/model.dart';
import 'package:provider/provider.dart';
import 'package:get/get.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';
import '../manager.dart';
import '../model.dart';
import '../common.dart';
// dup to flutter\lib\desktop\pages\desktop_setting_page.dart
const double _kCheckBoxLeftMargin = 10;
class LocationItem extends StatelessWidget {
final String peerId;
final FFI ffi;
final String location;
final LocationModel locationModel;
final bool isMenu;
LocationItem({
Key? key,
required this.peerId,
required this.ffi,
required this.location,
required this.locationModel,
required this.isMenu,
}) : super(key: key);
bool get isEmpty => locationModel.isEmpty;
static Widget createLocationItem(
String peerId, FFI ffi, String location, bool isMenu) {
final model = getLocationModel(location);
return model == null
? Container()
: LocationItem(
peerId: peerId,
ffi: ffi,
location: location,
locationModel: model,
isMenu: isMenu,
);
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: locationModel,
child: Consumer<LocationModel>(builder: (context, model, child) {
return Column(
children: model.pluginModels.entries
.map((entry) => _buildPluginItem(entry.key, entry.value))
.toList(),
);
}),
);
}
Widget _buildPluginItem(PluginId id, PluginModel model) => PluginItem(
pluginId: id,
peerId: peerId,
ffi: ffi,
location: location,
pluginModel: model,
isMenu: isMenu,
);
}
class PluginItem extends StatelessWidget {
final PluginId pluginId;
final String peerId;
final FFI? ffi;
final String location;
final PluginModel pluginModel;
final bool isMenu;
PluginItem({
Key? key,
required this.pluginId,
required this.peerId,
this.ffi,
required this.location,
required this.pluginModel,
required this.isMenu,
}) : super(key: key);
bool get isEmpty => pluginModel.isEmpty;
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: pluginModel,
child: Consumer<PluginModel>(
builder: (context, pluginModel, child) {
return Column(
children: pluginModel.uiList.map((ui) => _buildItem(ui)).toList(),
);
},
),
);
}
Widget _buildItem(UiType ui) {
Widget? child;
switch (ui.runtimeType) {
case UiButton:
if (isMenu) {
if (ffi != null) {
child = _buildMenuButton(ui as UiButton, ffi!);
}
} else {
child = _buildButton(ui as UiButton);
}
break;
case UiCheckbox:
if (isMenu) {
if (ffi != null) {
child = _buildCheckboxMenuButton(ui as UiCheckbox, ffi!);
}
} else {
child = _buildCheckbox(ui as UiCheckbox);
}
break;
default:
break;
}
// to-do: add plugin icon and tooltip
return child ?? Container();
}
Widget _buildButton(UiButton ui) {
return TextButton(
onPressed: () => bind.pluginEvent(
id: pluginId,
peer: peerId,
event: _makeEvent(ui.key),
),
child: Text(ui.text),
);
}
Widget _buildCheckbox(UiCheckbox ui) {
getChild(OptionModel model) {
final v = _getOption(model, ui.key);
if (v == null) {
// session or plugin not found
return Container();
}
onChanged(bool value) {
bind.pluginEvent(
id: pluginId,
peer: peerId,
event: _makeEvent(ui.key, v: value),
);
}
final value = ConfigItem.isTrue(v);
return GestureDetector(
child: Row(
children: [
Checkbox(
value: value,
onChanged: (_) => onChanged(!value),
).marginOnly(right: 5),
Expanded(
child: Text(translate(ui.text)),
)
],
).marginOnly(left: _kCheckBoxLeftMargin),
onTap: () => onChanged(!value),
);
}
return ChangeNotifierProvider.value(
value: getOptionModel(location, pluginId, peerId, ui.key),
child: Consumer<OptionModel>(
builder: (context, model, child) => getChild(model),
),
);
}
Widget _buildCheckboxMenuButton(UiCheckbox ui, FFI ffi) {
getChild(OptionModel model) {
final v = _getOption(model, ui.key);
if (v == null) {
// session or plugin not found
return Container();
}
return CkbMenuButton(
value: ConfigItem.isTrue(v),
onChanged: (v) {
if (v != null) {
bind.pluginEvent(
id: pluginId,
peer: peerId,
event: _makeEvent(ui.key, v: v),
);
}
},
// to-do: RustDesk translate or plugin translate ?
child: Text(ui.text),
ffi: ffi,
);
}
return ChangeNotifierProvider.value(
value: getOptionModel(location, pluginId, peerId, ui.key),
child: Consumer<OptionModel>(
builder: (context, model, child) => getChild(model),
),
);
}
Widget _buildMenuButton(UiButton ui, FFI ffi) {
return MenuButton(
onPressed: () => bind.pluginEvent(
id: pluginId,
peer: peerId,
event: _makeEvent(ui.key),
),
// to-do: support trailing icon, but it will cause tree shake error.
// ```
// This application cannot tree shake icons fonts. It has non-constant instances of IconData at the following locations:
// Target release_macos_bundle_flutter_assets failed: Exception: Avoid non-constant invocations of IconData or try to build again with --no-tree-shake-icons.
// ```
//
// trailingIcon: Icon(
// IconData(int.parse(ui.icon, radix: 16), fontFamily: 'MaterialIcons')),
//
// to-do: RustDesk translate or plugin translate ?
child: Text(ui.text),
ffi: ffi,
);
}
Uint8List _makeEvent(
String key, {
bool? v,
}) {
final event = MsgFromUi(
id: pluginId,
name: pluginManager.getPlugin(pluginId)?.meta.name ?? '',
location: location,
key: key,
value:
v != null ? (v ? ConfigItem.trueValue : ConfigItem.falseValue) : '',
action: '',
);
return Uint8List.fromList(event.toString().codeUnits);
}
String? _getOption(OptionModel model, String key) {
var v = model.value;
if (v == null) {
try {
if (peerId.isEmpty) {
v = bind.pluginGetSharedOption(id: pluginId, key: key);
} else {
v = bind.pluginGetSessionOption(id: pluginId, peer: peerId, key: key);
}
} catch (e) {
debugPrint('Failed to get option "$key", $e');
v = null;
}
}
return v;
}
}
void handleReloading(Map<String, dynamic> evt) {
if (evt['id'] == null || evt['location'] == null) {
return;
}
try {
final uiList = <UiType>[];
for (var e in json.decode(evt['ui'] as String)) {
final ui = UiType.create(e);
if (ui != null) {
uiList.add(ui);
}
}
if (uiList.isNotEmpty) {
addLocationUi(evt['location']!, evt['id']!, uiList);
}
} catch (e) {
debugPrint('Failed handleReloading, json decode of ui, $e ');
}
}
void handleOption(Map<String, dynamic> evt) {
updateOption(
evt['location'], evt['id'], evt['peer'] ?? '', evt['key'], evt['value']);
}

View File

@@ -1,202 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/plugin/model.dart';
import 'package:flutter_hbb/plugin/common.dart';
import 'package:get/get.dart';
import '../manager.dart';
import './desc_ui.dart';
// to-do: use settings from desktop_setting_page.dart
const double _kCardFixedWidth = 540;
const double _kCardLeftMargin = 15;
const double _kContentHMargin = 15;
const double _kTitleFontSize = 20;
const double _kVersionFontSize = 12;
class DesktopSettingsCard extends StatefulWidget {
final PluginInfo plugin;
DesktopSettingsCard({
Key? key,
required this.plugin,
}) : super(key: key);
@override
State<DesktopSettingsCard> createState() => _DesktopSettingsCardState();
}
class _DesktopSettingsCardState extends State<DesktopSettingsCard> {
PluginInfo get plugin => widget.plugin;
bool get installed => plugin.installed;
bool isEnabled = false;
@override
Widget build(BuildContext context) {
isEnabled = bind.pluginIsEnabled(id: plugin.meta.id);
return Row(
children: [
Flexible(
child: SizedBox(
width: _kCardFixedWidth,
child: Card(
child: Column(
children: [
header(),
body(),
],
).marginOnly(bottom: 10),
).marginOnly(left: _kCardLeftMargin, top: 15),
),
),
],
);
}
Widget header() {
return Row(
children: [
headerNameVersion(),
headerInstallEnable(),
],
).marginOnly(
left: _kContentHMargin,
top: 10,
bottom: 10,
right: _kContentHMargin,
);
}
Widget headerNameVersion() {
return Expanded(
child: Row(
children: [
Text(
widget.plugin.meta.name,
textAlign: TextAlign.start,
style: const TextStyle(
fontSize: _kTitleFontSize,
),
),
SizedBox(
width: 5,
),
Text(
plugin.meta.version,
textAlign: TextAlign.start,
style: const TextStyle(
fontSize: _kVersionFontSize,
),
)
],
),
);
}
Widget headerButton(String label, VoidCallback onPressed) {
return Container(
child: ElevatedButton(
onPressed: onPressed,
child: Text(translate(label)),
),
);
}
Widget headerInstallEnable() {
final installButton = headerButton(
installed ? 'Uninstall' : 'Install',
() {
bind.pluginInstall(
id: plugin.meta.id,
b: !installed,
);
},
);
if (installed) {
final updateButton = plugin.needUpdate
? headerButton('Update', () {
bind.pluginInstall(
id: plugin.meta.id,
b: !installed,
);
})
: Container();
final enableButton = !installed
? Container()
: headerButton(isEnabled ? 'Disable' : 'Enable', () {
if (isEnabled) {
clearPlugin(plugin.meta.id);
}
bind.pluginEnable(id: plugin.meta.id, v: !isEnabled);
setState(() {});
});
return Row(
children: [
updateButton,
SizedBox(
width: 10,
),
installButton,
SizedBox(
width: 10,
),
enableButton,
],
);
} else {
return installButton;
}
}
Widget body() {
return Column(children: [
author(),
description(),
more(),
]).marginOnly(
left: _kCardLeftMargin,
top: 4,
right: _kContentHMargin,
);
}
Widget author() {
return Align(
alignment: Alignment.centerLeft,
child: Text(plugin.meta.author),
);
}
Widget description() {
return Align(
alignment: Alignment.centerLeft,
child: Text(plugin.meta.description),
);
}
Widget more() {
if (!(installed && isEnabled)) {
return Container();
}
final List<Widget> children = [];
final model = getPluginModel(kLocationHostMainPlugin, plugin.meta.id);
if (model != null) {
children.add(PluginItem(
pluginId: plugin.meta.id,
peerId: '',
location: kLocationHostMainPlugin,
pluginModel: model,
isMenu: false,
));
}
return ExpansionTile(
title: Text('Options'),
controlAffinity: ListTileControlAffinity.leading,
children: children,
);
}
}

View File

@@ -1644,78 +1644,6 @@ class RustdeskImpl {
throw UnimplementedError("sendUrlScheme");
}
Future<void> pluginEvent(
{required String id,
required String peer,
required Uint8List event,
dynamic hint}) {
throw UnimplementedError("pluginEvent");
}
Stream<EventToUI> pluginRegisterEventStream(
{required String id, dynamic hint}) {
throw UnimplementedError("pluginRegisterEventStream");
}
String? pluginGetSessionOption(
{required String id,
required String peer,
required String key,
dynamic hint}) {
throw UnimplementedError("pluginGetSessionOption");
}
Future<void> pluginSetSessionOption(
{required String id,
required String peer,
required String key,
required String value,
dynamic hint}) {
throw UnimplementedError("pluginSetSessionOption");
}
String? pluginGetSharedOption(
{required String id, required String key, dynamic hint}) {
throw UnimplementedError("pluginGetSharedOption");
}
Future<void> pluginSetSharedOption(
{required String id,
required String key,
required String value,
dynamic hint}) {
throw UnimplementedError("pluginSetSharedOption");
}
Future<void> pluginReload({required String id, dynamic hint}) {
throw UnimplementedError("pluginReload");
}
void pluginEnable({required String id, required bool v, dynamic hint}) {
throw UnimplementedError("pluginEnable");
}
bool pluginIsEnabled({required String id, dynamic hint}) {
throw UnimplementedError("pluginIsEnabled");
}
bool pluginFeatureIsEnabled({dynamic hint}) {
throw UnimplementedError("pluginFeatureIsEnabled");
}
Future<void> pluginSyncUi({required String syncTo, dynamic hint}) {
throw UnimplementedError("pluginSyncUi");
}
Future<void> pluginListReload({dynamic hint}) {
throw UnimplementedError("pluginListReload");
}
Future<void> pluginInstall(
{required String id, required bool b, dynamic hint}) {
throw UnimplementedError("pluginInstall");
}
bool isSupportMultiUiSession({required String version, dynamic hint}) {
return versionToNumber(v: version) > versionToNumber(v: '1.2.4');
}

View File

@@ -1,14 +0,0 @@
abstract class NativeHandler {
bool onEvent(Map<String, dynamic> evt);
}
class NativeUiHandler extends NativeHandler {
NativeUiHandler._();
static NativeUiHandler instance = NativeUiHandler._();
@override
bool onEvent(Map<String, dynamic> evt) {
throw UnimplementedError();
}
}

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();