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", "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]] [[package]]
name = "cacao" name = "cacao"
version = "0.4.0-beta2" version = "0.4.0-beta2"
@@ -1477,8 +1456,8 @@ dependencies = [
"compression-core", "compression-core",
"flate2", "flate2",
"memchr", "memchr",
"zstd 0.13.1", "zstd",
"zstd-safe 7.1.0", "zstd-safe",
] ]
[[package]] [[package]]
@@ -1549,12 +1528,6 @@ dependencies = [
"unicode-xid 0.2.4", "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]] [[package]]
name = "constant_time_eq" name = "constant_time_eq"
version = "0.2.6" version = "0.2.6"
@@ -3825,7 +3798,7 @@ dependencies = [
"whoami", "whoami",
"winapi 0.3.9", "winapi 0.3.9",
"x11 2.21.0", "x11 2.21.0",
"zstd 0.13.1", "zstd",
] ]
[[package]] [[package]]
@@ -6057,35 +6030,12 @@ dependencies = [
"windows-targets 0.52.6", "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]] [[package]]
name = "paste" name = "paste"
version = "1.0.15" version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 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]] [[package]]
name = "peeking_take_while" name = "peeking_take_while"
version = "0.1.2" version = "0.1.2"
@@ -7365,7 +7315,6 @@ dependencies = [
"wol-rs", "wol-rs",
"x11-clipboard 0.8.1", "x11-clipboard 0.8.1",
"x11rb 0.12.0", "x11rb 0.12.0",
"zip",
] ]
[[package]] [[package]]
@@ -8907,7 +8856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c4ae9724c5888c0417d2396037ed3b60665925624766416e3e342b6ba5dbd3f" checksum = "6c4ae9724c5888c0417d2396037ed3b60665925624766416e3e342b6ba5dbd3f"
dependencies = [ dependencies = [
"base32", "base32",
"constant_time_eq 0.2.6", "constant_time_eq",
"hmac", "hmac",
"rand 0.8.5", "rand 0.8.5",
"sha1", "sha1",
@@ -11157,52 +11106,13 @@ dependencies = [
"syn 2.0.98", "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]] [[package]]
name = "zstd" name = "zstd"
version = "0.13.1" version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a"
dependencies = [ dependencies = [
"zstd-safe 7.1.0", "zstd-safe",
]
[[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",
] ]
[[package]] [[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 # 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. # giving up DRM capture: `--features drm` builds the capture path with no wake code compiled in.
drm-wake = ["drm"] drm-wake = ["drm"]
plugin_framework = []
linux-pkg-config = ["magnum-opus/linux-pkg-config", "scrap/linux-pkg-config"] linux-pkg-config = ["magnum-opus/linux-pkg-config", "scrap/linux-pkg-config"]
unix-file-copy-paste = [ unix-file-copy-paste = [
"dep:x11-clipboard", "dep:x11-clipboard",
@@ -81,7 +80,6 @@ hex = "0.4"
chrono = "0.4" chrono = "0.4"
cidr-utils = "0.5" cidr-utils = "0.5"
fon = "0.6" fon = "0.6"
zip = "0.6"
shutdown_hooks = "0.1" shutdown_hooks = "0.1"
totp-rs = { version = "5.4", default-features = false, features = ["gen_secret", "otpauth"] } totp-rs = { version = "5.4", default-features = false, features = ["gen_secret", "otpauth"] }
stunclient = "0.4" stunclient = "0.4"
@@ -212,7 +210,7 @@ android-wakelock = { git = "https://github.com/rustdesk-org/android-wakelock" }
[workspace] [workspace]
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/portable", "libs/remote_printer"] 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 # 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) # 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/platform_model.dart';
import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/models/state_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/multi_window_manager.dart';
import 'package:flutter_hbb/utils/platform_channel.dart'; import 'package:flutter_hbb/utils/platform_channel.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -111,7 +110,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
} }
}, },
), ),
buildPluginEntry(),
]; ];
if (isIncomingOnly) { if (isIncomingOnly) {
children.addAll([ children.addAll([
@@ -890,21 +888,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
shouldBeBlocked(_block, canBeBlocked); 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 { 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/printer_model.dart';
import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/models/state_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:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@@ -55,7 +53,6 @@ enum SettingsTabKey {
safety, safety,
network, network,
display, display,
plugin,
account, account,
printer, printer,
about, about,
@@ -74,8 +71,6 @@ class DesktopSettingPage extends StatefulWidget {
bind.mainGetBuildinOption(key: kOptionHideNetworkSetting) != 'Y') bind.mainGetBuildinOption(key: kOptionHideNetworkSetting) != 'Y')
SettingsTabKey.network, SettingsTabKey.network,
if (!bind.isIncomingOnly()) SettingsTabKey.display, if (!bind.isIncomingOnly()) SettingsTabKey.display,
if (!isWeb && !bind.isIncomingOnly() && bind.pluginFeatureIsEnabled())
SettingsTabKey.plugin,
if (!bind.isDisableAccount()) SettingsTabKey.account, if (!bind.isDisableAccount()) SettingsTabKey.account,
if (isWindows && if (isWindows &&
bind.mainGetBuildinOption(key: kOptionHideRemotePrinterSetting) != 'Y') bind.mainGetBuildinOption(key: kOptionHideRemotePrinterSetting) != 'Y')
@@ -171,7 +166,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
void dispose() { void dispose() {
super.dispose(); super.dispose();
Get.delete<PageController>(tag: _kSettingPageControllerTag); Get.delete<PageController>(tag: _kSettingPageControllerTag);
Get.delete<RxInt>(tag: _kSettingPageTabKeyTag); Get.delete<Rx<SettingsTabKey>>(tag: _kSettingPageTabKeyTag);
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
_videoConnTimer?.cancel(); _videoConnTimer?.cancel();
} }
@@ -196,10 +191,6 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
settingTabs.add(_TabInfo(tab, 'Display', settingTabs.add(_TabInfo(tab, 'Display',
Icons.desktop_windows_outlined, Icons.desktop_windows)); Icons.desktop_windows_outlined, Icons.desktop_windows));
break; break;
case SettingsTabKey.plugin:
settingTabs.add(_TabInfo(
tab, 'Plugin', Icons.extension_outlined, Icons.extension));
break;
case SettingsTabKey.account: case SettingsTabKey.account:
settingTabs.add( settingTabs.add(
_TabInfo(tab, 'Account', Icons.person_outline, Icons.person)); _TabInfo(tab, 'Account', Icons.person_outline, Icons.person));
@@ -233,9 +224,6 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
case SettingsTabKey.display: case SettingsTabKey.display:
children.add(const _Display()); children.add(const _Display());
break; break;
case SettingsTabKey.plugin:
children.add(const _Plugin());
break;
case SettingsTabKey.account: case SettingsTabKey.account:
children.add(const _Account()); children.add(const _Account());
break; 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 { class _Printer extends StatefulWidget {
const _Printer({super.key}); const _Printer({super.key});

View File

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

View File

@@ -127,7 +127,6 @@ class _ViewCameraPageState extends State<ViewCameraPage>
WakelockManager.enable(_uniqueKey); WakelockManager.enable(_uniqueKey);
_ffi.ffiModel.updateEventListener(sessionId, widget.id); _ffi.ffiModel.updateEventListener(sessionId, widget.id);
if (!isWeb) bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote);
_ffi.qualityMonitorModel.checkShowQualityMonitor(sessionId); _ffi.qualityMonitorModel.checkShowQualityMonitor(sessionId);
_ffi.dialogManager.loadMobileActionsOverlayVisible(); _ffi.dialogManager.loadMobileActionsOverlayVisible();
DesktopMultiWindow.addListener(this); 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/models/state_model.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/utils/multi_window_manager.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:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -1478,20 +1476,13 @@ class _DisplayMenu extends StatefulWidget {
final FFI ffi; final FFI ffi;
final ToolbarState state; final ToolbarState state;
final Function(bool) setFullscreen; final Function(bool) setFullscreen;
final Widget pluginItem;
_DisplayMenu( _DisplayMenu(
{Key? key, {Key? key,
required this.id, required this.id,
required this.ffi, required this.ffi,
required this.state, required this.state,
required this.setFullscreen}) required this.setFullscreen})
: pluginItem = LocationItem.createLocationItem( : super(key: key);
id,
ffi,
kLocationClientRemoteToolbarDisplay,
true,
),
super(key: key);
@override @override
State<_DisplayMenu> createState() => _DisplayMenuState(); State<_DisplayMenu> createState() => _DisplayMenuState();
@@ -1582,9 +1573,6 @@ class _DisplayMenuState extends State<_DisplayMenu> {
]); ]);
} }
} }
if (ffi.connType == ConnType.defaultConn) {
menuChildren.add(widget.pluginItem);
}
return menuChildren; return menuChildren;
} }

View File

@@ -30,9 +30,6 @@ import 'mobile/pages/server_page.dart';
import 'mobile/widgets/deploy_dialog.dart'; import 'mobile/widgets/deploy_dialog.dart';
import 'models/platform_model.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. /// Basic window and launch properties.
int? kWindowId; int? kWindowId;
WindowType? kWindowType; WindowType? kWindowType;
@@ -141,8 +138,6 @@ void runMainApp(bool startService) async {
await bind.mainCheckConnectStatus(); await bind.mainCheckConnectStatus();
if (startService) { if (startService) {
gFFI.serverModel.startService(); gFFI.serverModel.startService();
bind.pluginSyncUi(syncTo: kAppTypeMain);
bind.pluginListReload();
} }
await Future.wait([gFFI.abModel.loadCache(), gFFI.groupModel.loadCache()]); await Future.wait([gFFI.abModel.loadCache(), gFFI.groupModel.loadCache()]);
gFFI.userModel.refreshCurrentUser(); gFFI.userModel.refreshCurrentUser();
@@ -570,12 +565,6 @@ _registerEventHandler() {
reloadAllWindows(); reloadAllWindows();
}); });
} }
// Register native handlers.
if (isDesktop) {
platformFFI.registerEventHandler('native_ui', 'native_ui', (evt) async {
NativeUiHandler.instance.onEvent(evt);
});
}
if (isAndroid) { if (isAndroid) {
platformFFI.registerEventHandler( platformFFI.registerEventHandler(
'android_needs_deploy', 'android_needs_deploy', (_) async { '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/state_model.dart';
import 'package:flutter_hbb/models/desktop_render_texture.dart'; import 'package:flutter_hbb/models/desktop_render_texture.dart';
import 'package:flutter_hbb/models/terminal_model.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/common/shared_state.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:flutter_hbb/utils/http_service.dart' as http; import 'package:flutter_hbb/utils/http_service.dart' as http;
@@ -437,15 +434,6 @@ class FfiModel with ChangeNotifier {
parent.target?.serverModel.updateVoiceCallState(evt); parent.target?.serverModel.updateVoiceCallState(evt);
} else if (name == 'fingerprint') { } else if (name == 'fingerprint') {
FingerprintState.find(peerId).value = evt['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") { } else if (name == "sync_peer_hash_password_to_personal_ab") {
if (desktopType == DesktopType.main || isWeb || isMobile) { if (desktopType == DesktopType.main || isWeb || isMobile) {
final id = evt['id']; 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"); 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}) { bool isSupportMultiUiSession({required String version, dynamic hint}) {
return versionToNumber(v: version) > versionToNumber(v: '1.2.4'); 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"))] #[cfg(all(feature = "flutter", feature = "unix-file-copy-paste"))]
crate::flutter::update_file_clipboard_required(); 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() { 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)) => { Some(misc::Union::SupportedEncoding(e)) => {
log::info!("update supported encoding:{:?}", e); log::info!("update supported encoding:{:?}", e);
self.handler.lc.write().unwrap().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); crate::platform::elevate_or_run_as_system(click_setup, _is_elevate, _is_run_as_system);
return None; 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]) { if args.is_empty() || crate::common::is_empty_uni_link(&args[0]) {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
@@ -737,22 +734,6 @@ pub fn core_main() -> Option<Vec<String>> {
crate::platform::gtk_sudo::exec(); crate::platform::gtk_sudo::exec();
} }
return None; 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()); //_async_logger_holder.map(|x| x.flush());
@@ -762,23 +743,6 @@ pub fn core_main() -> Option<Vec<String>> {
return Some(args); 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) { fn import_config(path: &str) {
use hbb_common::{config::*, get_exe_time, get_modified_time}; use hbb_common::{config::*, get_exe_time, get_modified_time};
let path2 = path.replace(".toml", "2.toml"); let path2 = path.replace(".toml", "2.toml");

View File

@@ -225,8 +225,6 @@ pub struct FlutterHandler {
session_handlers: Arc<RwLock<HashMap<SessionID, SessionHandler>>>, session_handlers: Arc<RwLock<HashMap<SessionID, SessionHandler>>>,
display_rgbas: Arc<RwLock<HashMap<usize, RgbaData>>>, display_rgbas: Arc<RwLock<HashMap<usize, RgbaData>>>,
peer_info: Arc<RwLock<PeerInfo>>, 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>, use_texture_render: Arc<AtomicBool>,
} }
@@ -236,8 +234,6 @@ impl Default for FlutterHandler {
session_handlers: Default::default(), session_handlers: Default::default(),
display_rgbas: Default::default(), display_rgbas: Default::default(),
peer_info: Default::default(), peer_info: Default::default(),
#[cfg(not(any(target_os = "android", target_os = "ios")))]
hooks: Default::default(),
use_texture_render: Arc::new( use_texture_render: Arc::new(
AtomicBool::new(crate::ui_interface::use_texture_render()), 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()) 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) { pub fn update_use_texture_render(&self) {
self.use_texture_render self.use_texture_render
.store(crate::ui_interface::use_texture_render(), Ordering::Relaxed); .store(crate::ui_interface::use_texture_render(), Ordering::Relaxed);
@@ -1194,15 +1166,6 @@ impl InvokeUiSession for FlutterHandler {
impl FlutterHandler { impl FlutterHandler {
#[inline] #[inline]
fn on_rgba_soft_render(&self, display: usize, rgba: &mut scrap::ImageRgb) { 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. // If the current rgba is not fetched by flutter, i.e., is valid.
// We give up sending a new event to flutter. // We give up sending a new event to flutter.
let mut rgba_write_lock = self.display_rgbas.write().unwrap(); 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] #[inline]
pub fn get_cur_session() -> Option<FlutterSession> { pub fn get_cur_session() -> Option<FlutterSession> {
sessions::get_session_by_session_id(&*CUR_SESSION_ID.read().unwrap()) sessions::get_session_by_session_id(&*CUR_SESSION_ID.read().unwrap())

View File

@@ -12,9 +12,6 @@ use crate::{
ui_interface::{self, *}, ui_interface::{self, *},
}; };
use flutter_rust_bridge::{StreamSink, SyncReturn}; 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::{ use hbb_common::{
config::{self, LocalConfig, PeerConfig, PeerInfoSerde}, config::{self, LocalConfig, PeerConfig, PeerInfoSerde},
fs, lazy_static, log, fs, lazy_static, log,
@@ -2522,180 +2519,6 @@ pub fn send_url_scheme(_url: String) {
std::thread::spawn(move || crate::handle_url_scheme(_url)); 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> { pub fn is_support_multi_ui_session(version: String) -> SyncReturn<bool> {
SyncReturn(crate::common::is_support_multi_ui_session(&version)) 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"))] #[cfg(all(target_os = "linux", feature = "drm"))]
pub(crate) use ipc_drm::connect_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::{ use crate::{
common::{is_server, CheckTestNatType}, common::{is_server, CheckTestNatType},
privacy_mode, privacy_mode,
@@ -404,9 +401,6 @@ pub enum Data {
StartVoiceCall, StartVoiceCall,
VoiceCallResponse(bool), VoiceCallResponse(bool),
CloseVoiceCall(String), CloseVoiceCall(String),
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Plugin(Plugin),
#[cfg(windows)] #[cfg(windows)]
SyncWinCpuUsage(Option<f64>), SyncWinCpuUsage(Option<f64>),
FileTransferLog((String, String)), FileTransferLog((String, String)),
@@ -1076,9 +1070,6 @@ async fn handle(data: Data, stream: &mut Connection) {
.await .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)] #[cfg(windows)]
Data::ControlledSessionCount(_) => { Data::ControlledSessionCount(_) => {
allow_err!( allow_err!(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Sormenjälki"), ("Fingerprint", "Sormenjälki"),
("Copy Fingerprint", "Kopioi sormenjälki"), ("Copy Fingerprint", "Kopioi sormenjälki"),
("no fingerprints", "Ei sormenjälkiä"), ("no fingerprints", "Ei sormenjälkiä"),
("Select a peer", "Valitse vastapää"),
("Select peers", "Valitse useita vastapään laitteita"),
("Plugins", "Laajennukset"),
("Uninstall", "Poista asennus"), ("Uninstall", "Poista asennus"),
("Update", "Päivitä"), ("Update", "Päivitä"),
("Enable", "Ota käyttöön"), ("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"), ("Fingerprint", "Empreinte numérique"),
("Copy Fingerprint", "Copier lempreinte numérique"), ("Copy Fingerprint", "Copier lempreinte numérique"),
("no fingerprints", "Aucune empreinte 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"), ("Uninstall", "Désinstaller"),
("Update", "Mettre à jour"), ("Update", "Mettre à jour"),
("Enable", "Activer"), ("Enable", "Activer"),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Ujjlenyomat"), ("Fingerprint", "Ujjlenyomat"),
("Copy Fingerprint", "Ujjlenyomat másolása"), ("Copy Fingerprint", "Ujjlenyomat másolása"),
("no fingerprints", "nincsenek ujjlenyomatok"), ("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"), ("Uninstall", "Eltávolítás"),
("Update", "Frissítés"), ("Update", "Frissítés"),
("Enable", "Engedélyezé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"), ("Fingerprint", "Sidik jari"),
("Copy Fingerprint", "Salin sidik jari"), ("Copy Fingerprint", "Salin sidik jari"),
("no fingerprints", "Tidak ada sidik jari"), ("no fingerprints", "Tidak ada sidik jari"),
("Select a peer", "Pilih rekan"),
("Select peers", "Pilih rekan-rekan"),
("Plugins", "Plugin"),
("Uninstall", "Hapus instalasi"), ("Uninstall", "Hapus instalasi"),
("Update", "Perbarui"), ("Update", "Perbarui"),
("Enable", "Aktifkan"), ("Enable", "Aktifkan"),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Pirkstu nospiedums"), ("Fingerprint", "Pirkstu nospiedums"),
("Copy Fingerprint", "Kopēt pirkstu nospiedumu"), ("Copy Fingerprint", "Kopēt pirkstu nospiedumu"),
("no fingerprints", "nav 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"), ("Uninstall", "Atinstalēt"),
("Update", "Atjaunināt"), ("Update", "Atjaunināt"),
("Enable", "Iespējot"), ("Enable", "Iespējot"),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Dấu vân tay"), ("Fingerprint", "Dấu vân tay"),
("Copy Fingerprint", "Sao chép fingerprint"), ("Copy Fingerprint", "Sao chép fingerprint"),
("no fingerprints", "không có 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"), ("Uninstall", "Gỡ cài đặt"),
("Update", "Cập nhật"), ("Update", "Cập nhật"),
("Enable", "Bật"), ("Enable", "Bật"),

View File

@@ -46,10 +46,6 @@ mod lang;
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
mod port_forward; 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")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
mod tray; 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")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
pub static MOUSE_MOVE_TIME: AtomicI64 = AtomicI64::new(0); 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)] #[derive(Clone, Default)]
pub struct ConnInner { pub struct ConnInner {
id: i32, id: i32,
@@ -225,12 +188,6 @@ enum MessageInput {
Pointer((PointerDeviceEvent, i32)), Pointer((PointerDeviceEvent, i32)),
BlockOn, BlockOn,
BlockOff, 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)] #[derive(Clone, Debug, Hash, Eq, PartialEq)]
@@ -1176,12 +1133,6 @@ impl Connection {
let _ = Self::turn_off_privacy_to_msg(id, String::new()); 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); video_service::notify_video_frame_fetched_by_conn_id(id, None);
if conn.authorized { if conn.authorized {
password::update_temporary_password(); 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) => { Err(err) => {
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -2032,13 +1957,6 @@ impl Connection {
username = "".to_owned(); 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 // Terminal feature is supported on desktop only
#[allow(unused_mut)] #[allow(unused_mut)]
let mut terminal = cfg!(not(any(target_os = "android", target_os = "ios"))); 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); 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 Some(misc::Union::AutoAdjustFps(fps)) => video_service::VIDEO_QOS
.lock() .lock()
.unwrap() .unwrap()

View File

@@ -569,16 +569,6 @@ impl<T: InvokeUiSession> Session<T> {
self.send(Data::Message(msg)); 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 { pub fn get_audit_server(&self, typ: String) -> String {
if LocalConfig::get_option("access_token").is_empty() { if LocalConfig::get_option("access_token").is_empty() {
return "".to_owned(); return "".to_owned();