mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-08 05:20:59 +03:00
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:
100
Cargo.lock
generated
100
Cargo.lock
generated
@@ -986,27 +986,6 @@ dependencies = [
|
||||
"serde 1.0.228",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
|
||||
dependencies = [
|
||||
"bzip2-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.11+1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cacao"
|
||||
version = "0.4.0-beta2"
|
||||
@@ -1477,8 +1456,8 @@ dependencies = [
|
||||
"compression-core",
|
||||
"flate2",
|
||||
"memchr",
|
||||
"zstd 0.13.1",
|
||||
"zstd-safe 7.1.0",
|
||||
"zstd",
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1549,12 +1528,6 @@ dependencies = [
|
||||
"unicode-xid 0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.2.6"
|
||||
@@ -3825,7 +3798,7 @@ dependencies = [
|
||||
"whoami",
|
||||
"winapi 0.3.9",
|
||||
"x11 2.21.0",
|
||||
"zstd 0.13.1",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6057,35 +6030,12 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"hmac",
|
||||
"password-hash",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
@@ -7365,7 +7315,6 @@ dependencies = [
|
||||
"wol-rs",
|
||||
"x11-clipboard 0.8.1",
|
||||
"x11rb 0.12.0",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8907,7 +8856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c4ae9724c5888c0417d2396037ed3b60665925624766416e3e342b6ba5dbd3f"
|
||||
dependencies = [
|
||||
"base32",
|
||||
"constant_time_eq 0.2.6",
|
||||
"constant_time_eq",
|
||||
"hmac",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
@@ -11157,52 +11106,13 @@ dependencies = [
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"byteorder",
|
||||
"bzip2",
|
||||
"constant_time_eq 0.1.5",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"flate2",
|
||||
"hmac",
|
||||
"pbkdf2",
|
||||
"sha1",
|
||||
"time 0.3.36",
|
||||
"zstd 0.11.2+zstd.1.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.11.2+zstd.1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
|
||||
dependencies = [
|
||||
"zstd-safe 5.0.2+zstd.1.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a"
|
||||
dependencies = [
|
||||
"zstd-safe 7.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "5.0.2+zstd.1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"zstd-sys",
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -37,7 +37,6 @@ drm = ["scrap/drm"]
|
||||
# kind of operation and deserves a switch that can remove it from the binary entirely, without
|
||||
# giving up DRM capture: `--features drm` builds the capture path with no wake code compiled in.
|
||||
drm-wake = ["drm"]
|
||||
plugin_framework = []
|
||||
linux-pkg-config = ["magnum-opus/linux-pkg-config", "scrap/linux-pkg-config"]
|
||||
unix-file-copy-paste = [
|
||||
"dep:x11-clipboard",
|
||||
@@ -81,7 +80,6 @@ hex = "0.4"
|
||||
chrono = "0.4"
|
||||
cidr-utils = "0.5"
|
||||
fon = "0.6"
|
||||
zip = "0.6"
|
||||
shutdown_hooks = "0.1"
|
||||
totp-rs = { version = "5.4", default-features = false, features = ["gen_secret", "otpauth"] }
|
||||
stunclient = "0.4"
|
||||
@@ -212,7 +210,7 @@ android-wakelock = { git = "https://github.com/rustdesk-org/android-wakelock" }
|
||||
|
||||
[workspace]
|
||||
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/portable", "libs/remote_printer"]
|
||||
exclude = ["vdi/host", "examples/custom_plugin"]
|
||||
exclude = ["vdi/host"]
|
||||
|
||||
# Patch libxdo-sys to use a stub implementation that doesn't require libxdo
|
||||
# This allows building and running on systems without libxdo installed (e.g., Wayland-only)
|
||||
|
||||
@@ -16,7 +16,6 @@ import 'package:flutter_hbb/desktop/widgets/update_progress.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/models/server_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_hbb/plugin/ui_manager.dart';
|
||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||
import 'package:flutter_hbb/utils/platform_channel.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -111,7 +110,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
}
|
||||
},
|
||||
),
|
||||
buildPluginEntry(),
|
||||
];
|
||||
if (isIncomingOnly) {
|
||||
children.addAll([
|
||||
@@ -890,21 +888,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
shouldBeBlocked(_block, canBeBlocked);
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildPluginEntry() {
|
||||
final entries = PluginUiManager.instance.entries.entries;
|
||||
return Offstage(
|
||||
offstage: entries.isEmpty,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
...entries.map((entry) {
|
||||
return entry.value;
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
|
||||
|
||||
@@ -17,8 +17,6 @@ import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/models/printer_model.dart';
|
||||
import 'package:flutter_hbb/models/server_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_hbb/plugin/manager.dart';
|
||||
import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
@@ -55,7 +53,6 @@ enum SettingsTabKey {
|
||||
safety,
|
||||
network,
|
||||
display,
|
||||
plugin,
|
||||
account,
|
||||
printer,
|
||||
about,
|
||||
@@ -74,8 +71,6 @@ class DesktopSettingPage extends StatefulWidget {
|
||||
bind.mainGetBuildinOption(key: kOptionHideNetworkSetting) != 'Y')
|
||||
SettingsTabKey.network,
|
||||
if (!bind.isIncomingOnly()) SettingsTabKey.display,
|
||||
if (!isWeb && !bind.isIncomingOnly() && bind.pluginFeatureIsEnabled())
|
||||
SettingsTabKey.plugin,
|
||||
if (!bind.isDisableAccount()) SettingsTabKey.account,
|
||||
if (isWindows &&
|
||||
bind.mainGetBuildinOption(key: kOptionHideRemotePrinterSetting) != 'Y')
|
||||
@@ -171,7 +166,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
Get.delete<PageController>(tag: _kSettingPageControllerTag);
|
||||
Get.delete<RxInt>(tag: _kSettingPageTabKeyTag);
|
||||
Get.delete<Rx<SettingsTabKey>>(tag: _kSettingPageTabKeyTag);
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_videoConnTimer?.cancel();
|
||||
}
|
||||
@@ -196,10 +191,6 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
settingTabs.add(_TabInfo(tab, 'Display',
|
||||
Icons.desktop_windows_outlined, Icons.desktop_windows));
|
||||
break;
|
||||
case SettingsTabKey.plugin:
|
||||
settingTabs.add(_TabInfo(
|
||||
tab, 'Plugin', Icons.extension_outlined, Icons.extension));
|
||||
break;
|
||||
case SettingsTabKey.account:
|
||||
settingTabs.add(
|
||||
_TabInfo(tab, 'Account', Icons.person_outline, Icons.person));
|
||||
@@ -233,9 +224,6 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
case SettingsTabKey.display:
|
||||
children.add(const _Display());
|
||||
break;
|
||||
case SettingsTabKey.plugin:
|
||||
children.add(const _Plugin());
|
||||
break;
|
||||
case SettingsTabKey.account:
|
||||
children.add(const _Account());
|
||||
break;
|
||||
@@ -2255,51 +2243,6 @@ class _CheckboxState extends State<_Checkbox> {
|
||||
}
|
||||
}
|
||||
|
||||
class _Plugin extends StatefulWidget {
|
||||
const _Plugin({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<_Plugin> createState() => _PluginState();
|
||||
}
|
||||
|
||||
class _PluginState extends State<_Plugin> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bind.pluginListReload();
|
||||
final scrollController = ScrollController();
|
||||
return ChangeNotifierProvider.value(
|
||||
value: pluginManager,
|
||||
child: Consumer<PluginManager>(builder: (context, model, child) {
|
||||
return ListView(
|
||||
controller: scrollController,
|
||||
children: model.plugins.map((entry) => pluginCard(entry)).toList(),
|
||||
).marginOnly(bottom: _kListViewBottomMargin);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget pluginCard(PluginInfo plugin) {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: plugin,
|
||||
child: Consumer<PluginInfo>(
|
||||
builder: (context, model, child) => DesktopSettingsCard(plugin: model),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget accountAction() {
|
||||
return Obx(() => _Button(
|
||||
gFFI.userModel.userName.value.isEmpty
|
||||
? 'Login'
|
||||
: '${translate('Logout')} (${gFFI.userModel.accountLabelWithHandle})',
|
||||
() => {
|
||||
gFFI.userModel.userName.value.isEmpty
|
||||
? loginDialog()
|
||||
: logOutConfirmDialog()
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class _Printer extends StatefulWidget {
|
||||
const _Printer({super.key});
|
||||
|
||||
|
||||
@@ -182,7 +182,6 @@ class _RemotePageState extends State<RemotePage>
|
||||
WakelockManager.enable(_uniqueKey);
|
||||
|
||||
_ffi.ffiModel.updateEventListener(sessionId, widget.id);
|
||||
if (!isWeb) bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote);
|
||||
_ffi.qualityMonitorModel.checkShowQualityMonitor(sessionId);
|
||||
_ffi.dialogManager.loadMobileActionsOverlayVisible();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
|
||||
@@ -127,7 +127,6 @@ class _ViewCameraPageState extends State<ViewCameraPage>
|
||||
WakelockManager.enable(_uniqueKey);
|
||||
|
||||
_ffi.ffiModel.updateEventListener(sessionId, widget.id);
|
||||
if (!isWeb) bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote);
|
||||
_ffi.qualityMonitorModel.checkShowQualityMonitor(sessionId);
|
||||
_ffi.dialogManager.loadMobileActionsOverlayVisible();
|
||||
DesktopMultiWindow.addListener(this);
|
||||
|
||||
@@ -10,8 +10,6 @@ import 'package:flutter_hbb/models/chat_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||
import 'package:flutter_hbb/plugin/widgets/desc_ui.dart';
|
||||
import 'package:flutter_hbb/plugin/common.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -1478,20 +1476,13 @@ class _DisplayMenu extends StatefulWidget {
|
||||
final FFI ffi;
|
||||
final ToolbarState state;
|
||||
final Function(bool) setFullscreen;
|
||||
final Widget pluginItem;
|
||||
_DisplayMenu(
|
||||
{Key? key,
|
||||
required this.id,
|
||||
required this.ffi,
|
||||
required this.state,
|
||||
required this.setFullscreen})
|
||||
: pluginItem = LocationItem.createLocationItem(
|
||||
id,
|
||||
ffi,
|
||||
kLocationClientRemoteToolbarDisplay,
|
||||
true,
|
||||
),
|
||||
super(key: key);
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<_DisplayMenu> createState() => _DisplayMenuState();
|
||||
@@ -1582,9 +1573,6 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (ffi.connType == ConnType.defaultConn) {
|
||||
menuChildren.add(widget.pluginItem);
|
||||
}
|
||||
return menuChildren;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,6 @@ import 'mobile/pages/server_page.dart';
|
||||
import 'mobile/widgets/deploy_dialog.dart';
|
||||
import 'models/platform_model.dart';
|
||||
|
||||
import 'package:flutter_hbb/plugin/handlers.dart'
|
||||
if (dart.library.html) 'package:flutter_hbb/web/plugin/handlers.dart';
|
||||
|
||||
/// Basic window and launch properties.
|
||||
int? kWindowId;
|
||||
WindowType? kWindowType;
|
||||
@@ -141,8 +138,6 @@ void runMainApp(bool startService) async {
|
||||
await bind.mainCheckConnectStatus();
|
||||
if (startService) {
|
||||
gFFI.serverModel.startService();
|
||||
bind.pluginSyncUi(syncTo: kAppTypeMain);
|
||||
bind.pluginListReload();
|
||||
}
|
||||
await Future.wait([gFFI.abModel.loadCache(), gFFI.groupModel.loadCache()]);
|
||||
gFFI.userModel.refreshCurrentUser();
|
||||
@@ -570,12 +565,6 @@ _registerEventHandler() {
|
||||
reloadAllWindows();
|
||||
});
|
||||
}
|
||||
// Register native handlers.
|
||||
if (isDesktop) {
|
||||
platformFFI.registerEventHandler('native_ui', 'native_ui', (evt) async {
|
||||
NativeUiHandler.instance.onEvent(evt);
|
||||
});
|
||||
}
|
||||
if (isAndroid) {
|
||||
platformFFI.registerEventHandler(
|
||||
'android_needs_deploy', 'android_needs_deploy', (_) async {
|
||||
|
||||
@@ -25,9 +25,6 @@ import 'package:flutter_hbb/models/user_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_hbb/models/desktop_render_texture.dart';
|
||||
import 'package:flutter_hbb/models/terminal_model.dart';
|
||||
import 'package:flutter_hbb/plugin/event.dart';
|
||||
import 'package:flutter_hbb/plugin/manager.dart';
|
||||
import 'package:flutter_hbb/plugin/widgets/desc_ui.dart';
|
||||
import 'package:flutter_hbb/common/shared_state.dart';
|
||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||
import 'package:flutter_hbb/utils/http_service.dart' as http;
|
||||
@@ -437,15 +434,6 @@ class FfiModel with ChangeNotifier {
|
||||
parent.target?.serverModel.updateVoiceCallState(evt);
|
||||
} else if (name == 'fingerprint') {
|
||||
FingerprintState.find(peerId).value = evt['fingerprint'] ?? '';
|
||||
} else if (name == 'plugin_manager') {
|
||||
pluginManager.handleEvent(evt);
|
||||
} else if (name == 'plugin_event') {
|
||||
handlePluginEvent(evt,
|
||||
(Map<String, dynamic> e) => handleMsgBox(e, sessionId, peerId));
|
||||
} else if (name == 'plugin_reload') {
|
||||
handleReloading(evt);
|
||||
} else if (name == 'plugin_option') {
|
||||
handleOption(evt);
|
||||
} else if (name == "sync_peer_hash_password_to_personal_ab") {
|
||||
if (desktopType == DesktopType.main || isWeb || isMobile) {
|
||||
final id = evt['id'];
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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']);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -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']);
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1644,78 +1644,6 @@ class RustdeskImpl {
|
||||
throw UnimplementedError("sendUrlScheme");
|
||||
}
|
||||
|
||||
Future<void> pluginEvent(
|
||||
{required String id,
|
||||
required String peer,
|
||||
required Uint8List event,
|
||||
dynamic hint}) {
|
||||
throw UnimplementedError("pluginEvent");
|
||||
}
|
||||
|
||||
Stream<EventToUI> pluginRegisterEventStream(
|
||||
{required String id, dynamic hint}) {
|
||||
throw UnimplementedError("pluginRegisterEventStream");
|
||||
}
|
||||
|
||||
String? pluginGetSessionOption(
|
||||
{required String id,
|
||||
required String peer,
|
||||
required String key,
|
||||
dynamic hint}) {
|
||||
throw UnimplementedError("pluginGetSessionOption");
|
||||
}
|
||||
|
||||
Future<void> pluginSetSessionOption(
|
||||
{required String id,
|
||||
required String peer,
|
||||
required String key,
|
||||
required String value,
|
||||
dynamic hint}) {
|
||||
throw UnimplementedError("pluginSetSessionOption");
|
||||
}
|
||||
|
||||
String? pluginGetSharedOption(
|
||||
{required String id, required String key, dynamic hint}) {
|
||||
throw UnimplementedError("pluginGetSharedOption");
|
||||
}
|
||||
|
||||
Future<void> pluginSetSharedOption(
|
||||
{required String id,
|
||||
required String key,
|
||||
required String value,
|
||||
dynamic hint}) {
|
||||
throw UnimplementedError("pluginSetSharedOption");
|
||||
}
|
||||
|
||||
Future<void> pluginReload({required String id, dynamic hint}) {
|
||||
throw UnimplementedError("pluginReload");
|
||||
}
|
||||
|
||||
void pluginEnable({required String id, required bool v, dynamic hint}) {
|
||||
throw UnimplementedError("pluginEnable");
|
||||
}
|
||||
|
||||
bool pluginIsEnabled({required String id, dynamic hint}) {
|
||||
throw UnimplementedError("pluginIsEnabled");
|
||||
}
|
||||
|
||||
bool pluginFeatureIsEnabled({dynamic hint}) {
|
||||
throw UnimplementedError("pluginFeatureIsEnabled");
|
||||
}
|
||||
|
||||
Future<void> pluginSyncUi({required String syncTo, dynamic hint}) {
|
||||
throw UnimplementedError("pluginSyncUi");
|
||||
}
|
||||
|
||||
Future<void> pluginListReload({dynamic hint}) {
|
||||
throw UnimplementedError("pluginListReload");
|
||||
}
|
||||
|
||||
Future<void> pluginInstall(
|
||||
{required String id, required bool b, dynamic hint}) {
|
||||
throw UnimplementedError("pluginInstall");
|
||||
}
|
||||
|
||||
bool isSupportMultiUiSession({required String version, dynamic hint}) {
|
||||
return versionToNumber(v: version) > versionToNumber(v: '1.2.4');
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1437,14 +1437,6 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
|
||||
#[cfg(all(feature = "flutter", feature = "unix-file-copy-paste"))]
|
||||
crate::flutter::update_file_clipboard_required();
|
||||
|
||||
// on connection established client
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
crate::plugin::handle_listen_event(
|
||||
crate::plugin::EVENT_ON_CONN_CLIENT.to_owned(),
|
||||
self.handler.get_id(),
|
||||
);
|
||||
}
|
||||
|
||||
if self.handler.is_file_transfer() {
|
||||
@@ -1988,26 +1980,6 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Some(misc::Union::PluginRequest(p)) => {
|
||||
allow_err!(crate::plugin::handle_server_event(
|
||||
&p.id,
|
||||
&self.handler.get_id(),
|
||||
&p.content
|
||||
));
|
||||
// to-do: show message box on UI when error occurs?
|
||||
}
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Some(misc::Union::PluginFailure(p)) => {
|
||||
let name = if p.name.is_empty() {
|
||||
"plugin".to_string()
|
||||
} else {
|
||||
p.name
|
||||
};
|
||||
self.handler.msgbox("custom-nocancel", &name, &p.msg, "");
|
||||
}
|
||||
Some(misc::Union::SupportedEncoding(e)) => {
|
||||
log::info!("update supported encoding:{:?}", e);
|
||||
self.handler.lc.write().unwrap().supported_encoding = e;
|
||||
|
||||
@@ -190,9 +190,6 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
crate::platform::elevate_or_run_as_system(click_setup, _is_elevate, _is_run_as_system);
|
||||
return None;
|
||||
}
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
init_plugins(&args);
|
||||
if args.is_empty() || crate::common::is_empty_uni_link(&args[0]) {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
@@ -737,22 +734,6 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
crate::platform::gtk_sudo::exec();
|
||||
}
|
||||
return None;
|
||||
} else {
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if args[0] == "--plugin-install" {
|
||||
if args.len() == 2 {
|
||||
crate::plugin::change_uninstall_plugin(&args[1], false);
|
||||
} else if args.len() == 3 {
|
||||
crate::plugin::install_plugin_with_url(&args[1], &args[2]);
|
||||
}
|
||||
return None;
|
||||
} else if args[0] == "--plugin-uninstall" {
|
||||
if args.len() == 2 {
|
||||
crate::plugin::change_uninstall_plugin(&args[1], true);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
//_async_logger_holder.map(|x| x.flush());
|
||||
@@ -762,23 +743,6 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
return Some(args);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn init_plugins(args: &Vec<String>) {
|
||||
if args.is_empty() || "--server" == (&args[0] as &str) {
|
||||
#[cfg(debug_assertions)]
|
||||
let load_plugins = true;
|
||||
#[cfg(not(debug_assertions))]
|
||||
let load_plugins = crate::platform::is_installed();
|
||||
if load_plugins {
|
||||
crate::plugin::init();
|
||||
}
|
||||
} else if "--service" == (&args[0] as &str) {
|
||||
hbb_common::allow_err!(crate::plugin::remove_uninstalled());
|
||||
}
|
||||
}
|
||||
|
||||
fn import_config(path: &str) {
|
||||
use hbb_common::{config::*, get_exe_time, get_modified_time};
|
||||
let path2 = path.replace(".toml", "2.toml");
|
||||
|
||||
@@ -225,8 +225,6 @@ pub struct FlutterHandler {
|
||||
session_handlers: Arc<RwLock<HashMap<SessionID, SessionHandler>>>,
|
||||
display_rgbas: Arc<RwLock<HashMap<usize, RgbaData>>>,
|
||||
peer_info: Arc<RwLock<PeerInfo>>,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
hooks: Arc<RwLock<HashMap<String, SessionHook>>>,
|
||||
use_texture_render: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
@@ -236,8 +234,6 @@ impl Default for FlutterHandler {
|
||||
session_handlers: Default::default(),
|
||||
display_rgbas: Default::default(),
|
||||
peer_info: Default::default(),
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
hooks: Default::default(),
|
||||
use_texture_render: Arc::new(
|
||||
AtomicBool::new(crate::ui_interface::use_texture_render()),
|
||||
),
|
||||
@@ -636,30 +632,6 @@ impl FlutterHandler {
|
||||
serde_json::ser::to_string(&msg_vec).unwrap_or("".to_owned())
|
||||
}
|
||||
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub(crate) fn add_session_hook(&self, key: String, hook: SessionHook) -> bool {
|
||||
let mut hooks = self.hooks.write().unwrap();
|
||||
if hooks.contains_key(&key) {
|
||||
// Already has the hook with this key.
|
||||
return false;
|
||||
}
|
||||
let _ = hooks.insert(key, hook);
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub(crate) fn remove_session_hook(&self, key: &String) -> bool {
|
||||
let mut hooks = self.hooks.write().unwrap();
|
||||
if !hooks.contains_key(key) {
|
||||
// The hook with this key does not found.
|
||||
return false;
|
||||
}
|
||||
let _ = hooks.remove(key);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn update_use_texture_render(&self) {
|
||||
self.use_texture_render
|
||||
.store(crate::ui_interface::use_texture_render(), Ordering::Relaxed);
|
||||
@@ -1194,15 +1166,6 @@ impl InvokeUiSession for FlutterHandler {
|
||||
impl FlutterHandler {
|
||||
#[inline]
|
||||
fn on_rgba_soft_render(&self, display: usize, rgba: &mut scrap::ImageRgb) {
|
||||
// Give a chance for plugins or etc to hook a rgba data.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
for (key, hook) in self.hooks.read().unwrap().iter() {
|
||||
match hook {
|
||||
SessionHook::OnSessionRgba(cb) => {
|
||||
cb(key.to_owned(), rgba);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the current rgba is not fetched by flutter, i.e., is valid.
|
||||
// We give up sending a new event to flutter.
|
||||
let mut rgba_write_lock = self.display_rgbas.write().unwrap();
|
||||
@@ -1963,12 +1926,6 @@ pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Hooks for session.
|
||||
#[derive(Clone)]
|
||||
pub enum SessionHook {
|
||||
OnSessionRgba(fn(String, &mut scrap::ImageRgb)),
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_cur_session() -> Option<FlutterSession> {
|
||||
sessions::get_session_by_session_id(&*CUR_SESSION_ID.read().unwrap())
|
||||
|
||||
@@ -12,9 +12,6 @@ use crate::{
|
||||
ui_interface::{self, *},
|
||||
};
|
||||
use flutter_rust_bridge::{StreamSink, SyncReturn};
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::allow_err;
|
||||
use hbb_common::{
|
||||
config::{self, LocalConfig, PeerConfig, PeerInfoSerde},
|
||||
fs, lazy_static, log,
|
||||
@@ -2522,180 +2519,6 @@ pub fn send_url_scheme(_url: String) {
|
||||
std::thread::spawn(move || crate::handle_url_scheme(_url));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn plugin_event(_id: String, _peer: String, _event: Vec<u8>) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
allow_err!(crate::plugin::handle_ui_event(&_id, &_peer, &_event));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_register_event_stream(_id: String, _event2ui: StreamSink<EventToUI>) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
{
|
||||
crate::plugin::native_handlers::session::session_register_event_stream(_id, _event2ui);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn plugin_get_session_option(
|
||||
_id: String,
|
||||
_peer: String,
|
||||
_key: String,
|
||||
) -> SyncReturn<Option<String>> {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
SyncReturn(crate::plugin::PeerConfig::get(&_id, &_peer, &_key))
|
||||
}
|
||||
#[cfg(any(
|
||||
not(feature = "plugin_framework"),
|
||||
target_os = "android",
|
||||
target_os = "ios"
|
||||
))]
|
||||
{
|
||||
SyncReturn(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn plugin_set_session_option(_id: String, _peer: String, _key: String, _value: String) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
let _res = crate::plugin::PeerConfig::set(&_id, &_peer, &_key, &_value);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn plugin_get_shared_option(_id: String, _key: String) -> SyncReturn<Option<String>> {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
SyncReturn(crate::plugin::ipc::get_config(&_id, &_key).unwrap_or(None))
|
||||
}
|
||||
#[cfg(any(
|
||||
not(feature = "plugin_framework"),
|
||||
target_os = "android",
|
||||
target_os = "ios"
|
||||
))]
|
||||
{
|
||||
SyncReturn(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn plugin_set_shared_option(_id: String, _key: String, _value: String) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
allow_err!(crate::plugin::ipc::set_config(&_id, &_key, _value));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn plugin_reload(_id: String) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
allow_err!(crate::plugin::ipc::reload_plugin(&_id,));
|
||||
allow_err!(crate::plugin::reload_plugin(&_id));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn plugin_enable(_id: String, _v: bool) -> SyncReturn<()> {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
allow_err!(crate::plugin::ipc::set_manager_plugin_config(
|
||||
&_id,
|
||||
"enabled",
|
||||
_v.to_string()
|
||||
));
|
||||
if _v {
|
||||
allow_err!(crate::plugin::load_plugin(&_id));
|
||||
} else {
|
||||
crate::plugin::unload_plugin(&_id);
|
||||
}
|
||||
}
|
||||
SyncReturn(())
|
||||
}
|
||||
|
||||
pub fn plugin_is_enabled(_id: String) -> SyncReturn<bool> {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
SyncReturn(
|
||||
match crate::plugin::ipc::get_manager_plugin_config(&_id, "enabled") {
|
||||
Ok(Some(enabled)) => bool::from_str(&enabled).unwrap_or(false),
|
||||
_ => false,
|
||||
},
|
||||
)
|
||||
}
|
||||
#[cfg(any(
|
||||
not(feature = "plugin_framework"),
|
||||
target_os = "android",
|
||||
target_os = "ios"
|
||||
))]
|
||||
{
|
||||
SyncReturn(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_feature_is_enabled() -> SyncReturn<bool> {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
let enabled = true;
|
||||
#[cfg(not(debug_assertions))]
|
||||
let enabled = is_installed();
|
||||
SyncReturn(enabled)
|
||||
}
|
||||
#[cfg(any(
|
||||
not(feature = "plugin_framework"),
|
||||
target_os = "android",
|
||||
target_os = "ios"
|
||||
))]
|
||||
{
|
||||
SyncReturn(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_sync_ui(_sync_to: String) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
if plugin_feature_is_enabled().0 {
|
||||
crate::plugin::sync_ui(_sync_to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_list_reload() {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
crate::plugin::load_plugin_list();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_install(_id: String, _b: bool) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
if _b {
|
||||
if let Err(e) = crate::plugin::install_plugin(&_id) {
|
||||
log::error!("Failed to install plugin '{}': {}", _id, e);
|
||||
}
|
||||
} else {
|
||||
crate::plugin::uninstall_plugin(&_id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_support_multi_ui_session(version: String) -> SyncReturn<bool> {
|
||||
SyncReturn(crate::common::is_support_multi_ui_session(&version))
|
||||
}
|
||||
|
||||
@@ -19,9 +19,6 @@ pub(crate) use ipc_drm::DrmConn;
|
||||
#[cfg(all(target_os = "linux", feature = "drm"))]
|
||||
pub(crate) use ipc_drm::connect_drm;
|
||||
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::plugin::ipc::Plugin;
|
||||
use crate::{
|
||||
common::{is_server, CheckTestNatType},
|
||||
privacy_mode,
|
||||
@@ -404,9 +401,6 @@ pub enum Data {
|
||||
StartVoiceCall,
|
||||
VoiceCallResponse(bool),
|
||||
CloseVoiceCall(String),
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Plugin(Plugin),
|
||||
#[cfg(windows)]
|
||||
SyncWinCpuUsage(Option<f64>),
|
||||
FileTransferLog((String, String)),
|
||||
@@ -1076,9 +1070,6 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
.await
|
||||
);
|
||||
}
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Data::Plugin(plugin) => crate::plugin::ipc::handle_plugin(plugin, stream).await,
|
||||
#[cfg(windows)]
|
||||
Data::ControlledSessionCount(_) => {
|
||||
allow_err!(
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "البصمة"),
|
||||
("Copy Fingerprint", "نسخ البصمة"),
|
||||
("no fingerprints", "لا توجد بصمات اصابع"),
|
||||
("Select a peer", "اختر قرين"),
|
||||
("Select peers", "اختر الاقران"),
|
||||
("Plugins", "الاضافات"),
|
||||
("Uninstall", "الغاء التثبيت"),
|
||||
("Update", "تحديث"),
|
||||
("Enable", "تفعيل"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Адбітак"),
|
||||
("Copy Fingerprint", "Капіяваць адбітак"),
|
||||
("no fingerprints", "адбіткі адсутнічаюць"),
|
||||
("Select a peer", "Выберыце абанента"),
|
||||
("Select peers", "Выберыце абанентаў"),
|
||||
("Plugins", "Убудовы"),
|
||||
("Uninstall", "Выдаліць"),
|
||||
("Update", "Абнавіць"),
|
||||
("Enable", "Уключыць"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Пръстов отпечатък"),
|
||||
("Copy Fingerprint", "Копиране на пръстов отпечатък"),
|
||||
("no fingerprints", "Няма пръстови отпечатъци"),
|
||||
("Select a peer", "Избери отдалечена страна"),
|
||||
("Select peers", "Избери отдалечени страни"),
|
||||
("Plugins", "Плъгини"),
|
||||
("Uninstall", "Премахни"),
|
||||
("Update", "Обновяване"),
|
||||
("Enable", "Позволяване"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Empremta"),
|
||||
("Copy Fingerprint", "Copia l'empremta"),
|
||||
("no fingerprints", "Cap empremta"),
|
||||
("Select a peer", "Seleccioneu un client"),
|
||||
("Select peers", "Seleccioneu els clients"),
|
||||
("Plugins", "Complements"),
|
||||
("Uninstall", "Desinstal·la"),
|
||||
("Update", "Actualitza"),
|
||||
("Enable", "Activa"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "指纹"),
|
||||
("Copy Fingerprint", "复制指纹"),
|
||||
("no fingerprints", "没有指纹"),
|
||||
("Select a peer", "选择一个被控端"),
|
||||
("Select peers", "选择被控"),
|
||||
("Plugins", "插件"),
|
||||
("Uninstall", "卸载"),
|
||||
("Update", "更新"),
|
||||
("Enable", "启用"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Otisk"),
|
||||
("Copy Fingerprint", "Kopírovat otisk"),
|
||||
("no fingerprints", "žádný otisk"),
|
||||
("Select a peer", "Výběr protistrany"),
|
||||
("Select peers", "Vybrat protistrany"),
|
||||
("Plugins", "Pluginy"),
|
||||
("Uninstall", "Odinstalovat"),
|
||||
("Update", "Aktualizovat"),
|
||||
("Enable", "Povolit"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Fingeraftryk"),
|
||||
("Copy Fingerprint", "Kopiér fingeraftryk"),
|
||||
("no fingerprints", "Ingen fingeraftryk"),
|
||||
("Select a peer", "Vælg en peer"),
|
||||
("Select peers", "Vælg peers"),
|
||||
("Plugins", "Plugins"),
|
||||
("Uninstall", "Afinstallér"),
|
||||
("Update", "Opdatér"),
|
||||
("Enable", "Aktivér"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Fingerabdruck"),
|
||||
("Copy Fingerprint", "Fingerabdruck kopieren"),
|
||||
("no fingerprints", "Keine Fingerabdrücke"),
|
||||
("Select a peer", "Gegenstelle auswählen"),
|
||||
("Select peers", "Gegenstellen auswählen"),
|
||||
("Plugins", "Plugins"),
|
||||
("Uninstall", "Deinstallieren"),
|
||||
("Update", "Update"),
|
||||
("Enable", "Aktivieren"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Δακτυλικό αποτύπωμα"),
|
||||
("Copy Fingerprint", "Αντιγραφή δακτυλικού αποτυπώματος"),
|
||||
("no fingerprints", "χωρίς δακτυλικά αποτυπώματα"),
|
||||
("Select a peer", "Επιλέξτε έναν σταθμό"),
|
||||
("Select peers", "Επιλέξτε σταθμούς"),
|
||||
("Plugins", "Επεκτάσεις"),
|
||||
("Uninstall", "Κατάργηση εγκατάστασης"),
|
||||
("Update", "Ενημέρωση"),
|
||||
("Enable", "Ενεργοποίηση"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Fingrospuro"),
|
||||
("Copy Fingerprint", "Kopii fingrospuron"),
|
||||
("no fingerprints", "Neniuj fingrospuroj"),
|
||||
("Select a peer", "Elekti samulon"),
|
||||
("Select peers", "Elekti samulojn"),
|
||||
("Plugins", "Kromprogramoj"),
|
||||
("Uninstall", "Malinstali"),
|
||||
("Update", "Ĝisdatigi"),
|
||||
("Enable", "Ebligi"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Huella digital"),
|
||||
("Copy Fingerprint", "Copiar huella digital"),
|
||||
("no fingerprints", "sin huellas digitales"),
|
||||
("Select a peer", "Seleccionar un par"),
|
||||
("Select peers", "Seleccionar pares"),
|
||||
("Plugins", "Complementos"),
|
||||
("Uninstall", "Desinstalar"),
|
||||
("Update", "Actualizar"),
|
||||
("Enable", "Habilitar"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Sõrmejälg"),
|
||||
("Copy Fingerprint", "Kopeeri sõrmejälg"),
|
||||
("no fingerprints", "Sõrmejäljed puuduvad"),
|
||||
("Select a peer", "Vali partner"),
|
||||
("Select peers", "Vali partnerid"),
|
||||
("Plugins", "Pluginad"),
|
||||
("Uninstall", "Desinstalli"),
|
||||
("Update", "Uuenda"),
|
||||
("Enable", "Luba"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Hatz-marka"),
|
||||
("Copy Fingerprint", "Kopiatu hatz-marka"),
|
||||
("no fingerprints", "hatz-markarik ez"),
|
||||
("Select a peer", "Hautatu parekidea"),
|
||||
("Select peers", "Hautatu parekideak"),
|
||||
("Plugins", "Pluginak"),
|
||||
("Uninstall", "Desinstalatu"),
|
||||
("Update", "Eguneratu"),
|
||||
("Enable", "Gaitu"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "\n اثر انگشت"),
|
||||
("Copy Fingerprint", "کپی کردن اثر انگشت"),
|
||||
("no fingerprints", "بدون اثر انگشت"),
|
||||
("Select a peer", "یک همتا را انتخاب کنید"),
|
||||
("Select peers", "همتایان را انتخاب کنید"),
|
||||
("Plugins", "پلاگین ها"),
|
||||
("Uninstall", "حذف نصب"),
|
||||
("Update", "به روز رسانی"),
|
||||
("Enable", "فعال کردن"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Sormenjälki"),
|
||||
("Copy Fingerprint", "Kopioi sormenjälki"),
|
||||
("no fingerprints", "Ei sormenjälkiä"),
|
||||
("Select a peer", "Valitse vastapää"),
|
||||
("Select peers", "Valitse useita vastapään laitteita"),
|
||||
("Plugins", "Laajennukset"),
|
||||
("Uninstall", "Poista asennus"),
|
||||
("Update", "Päivitä"),
|
||||
("Enable", "Ota käyttöön"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Empreinte numérique"),
|
||||
("Copy Fingerprint", "Copier l’empreinte numérique"),
|
||||
("no fingerprints", "Aucune empreinte numérique"),
|
||||
("Select a peer", "Sélectionnez l’appareil distant"),
|
||||
("Select peers", "Sélectionnez les appareils distants"),
|
||||
("Plugins", "Plugins"),
|
||||
("Uninstall", "Désinstaller"),
|
||||
("Update", "Mettre à jour"),
|
||||
("Enable", "Activer"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "ანაბეჭდი"),
|
||||
("Copy Fingerprint", "ანაბეჭდის კოპირება"),
|
||||
("no fingerprints", "ანაბეჭდები არ არის"),
|
||||
("Select a peer", "აირჩიეთ დისტანციური კვანძი"),
|
||||
("Select peers", "აირჩიეთ დისტანციური კვანძები"),
|
||||
("Plugins", "დანამატები"),
|
||||
("Uninstall", "წაშლა"),
|
||||
("Update", "განახლება"),
|
||||
("Enable", "ჩართვა"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "ફિંગરપ્રિન્ટ"),
|
||||
("Copy Fingerprint", "ફિંગરપ્રિન્ટ કોપી કરો"),
|
||||
("no fingerprints", "કોઈ ફિંગરપ્રિન્ટ નથી"),
|
||||
("Select a peer", "એક પીઅર પસંદ કરો"),
|
||||
("Select peers", "પીઅર્સ પસંદ કરો"),
|
||||
("Plugins", "પ્લગઇન્સ"),
|
||||
("Uninstall", "અનઇન્સ્ટોલ કરો"),
|
||||
("Update", "અપડેટ કરો"),
|
||||
("Enable", "સક્ષમ કરો"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "טביעת אצבע"),
|
||||
("Copy Fingerprint", "העתק טביעת אצבע"),
|
||||
("no fingerprints", "אין טביעות אצבע"),
|
||||
("Select a peer", "בחר עמית"),
|
||||
("Select peers", "בחר עמיתים"),
|
||||
("Plugins", "תוספים"),
|
||||
("Uninstall", "הסר"),
|
||||
("Update", "עדכן"),
|
||||
("Enable", "פועל"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "फिंगरप्रिंट"),
|
||||
("Copy Fingerprint", "फिंगरप्रिंट कॉपी करें"),
|
||||
("no fingerprints", "कोई फिंगरप्रिंट नहीं"),
|
||||
("Select a peer", "एक पीयर (Peer) चुनें"),
|
||||
("Select peers", "पीयर्स चुनें"),
|
||||
("Plugins", "प्लगइन्स"),
|
||||
("Uninstall", "अनइंस्टॉल करें"),
|
||||
("Update", "अपडेट करें"),
|
||||
("Enable", "सक्षम करें"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Otisak"),
|
||||
("Copy Fingerprint", "Kopirat otisak"),
|
||||
("no fingerprints", "nema otiska"),
|
||||
("Select a peer", "Izbor druge strane"),
|
||||
("Select peers", "Odaberite druge strane"),
|
||||
("Plugins", "Dodaci"),
|
||||
("Uninstall", "Deinstaliraj"),
|
||||
("Update", "Ažuriraj"),
|
||||
("Enable", "Dopustiti"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Ujjlenyomat"),
|
||||
("Copy Fingerprint", "Ujjlenyomat másolása"),
|
||||
("no fingerprints", "nincsenek ujjlenyomatok"),
|
||||
("Select a peer", "Egy távoli állomás kiválasztása"),
|
||||
("Select peers", "Távoli állomások kiválasztása"),
|
||||
("Plugins", "Beépülő modulok"),
|
||||
("Uninstall", "Eltávolítás"),
|
||||
("Update", "Frissítés"),
|
||||
("Enable", "Engedélyezés"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Sidik jari"),
|
||||
("Copy Fingerprint", "Salin sidik jari"),
|
||||
("no fingerprints", "Tidak ada sidik jari"),
|
||||
("Select a peer", "Pilih rekan"),
|
||||
("Select peers", "Pilih rekan-rekan"),
|
||||
("Plugins", "Plugin"),
|
||||
("Uninstall", "Hapus instalasi"),
|
||||
("Update", "Perbarui"),
|
||||
("Enable", "Aktifkan"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Firma digitale"),
|
||||
("Copy Fingerprint", "Copia firma digitale"),
|
||||
("no fingerprints", "Nessuna firma digitale"),
|
||||
("Select a peer", "Seleziona dispositivo remoto"),
|
||||
("Select peers", "Seleziona dispositivi remoti"),
|
||||
("Plugins", "Plugin"),
|
||||
("Uninstall", "Disinstalla"),
|
||||
("Update", "Aggiorna"),
|
||||
("Enable", "Abilita"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "フィンガープリント"),
|
||||
("Copy Fingerprint", "フィンガープリントをコピー"),
|
||||
("no fingerprints", "フィンガープリントがありません"),
|
||||
("Select a peer", "リモートコンピューターを選択"),
|
||||
("Select peers", "複数のリモートコンピューターを選択"),
|
||||
("Plugins", "プラグイン"),
|
||||
("Uninstall", "アンインストール"),
|
||||
("Update", "更新"),
|
||||
("Enable", "有効"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "지문"),
|
||||
("Copy Fingerprint", "지문 복사"),
|
||||
("no fingerprints", "지문이 없습니다"),
|
||||
("Select a peer", "피어 선택"),
|
||||
("Select peers", "피어 선택"),
|
||||
("Plugins", "플러그인"),
|
||||
("Uninstall", "설치 제거"),
|
||||
("Update", "업데이트"),
|
||||
("Enable", "허용"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Саусақ ізі"),
|
||||
("Copy Fingerprint", "Саусақ ізін көшіру"),
|
||||
("no fingerprints", "Саусақ іздері жоқ"),
|
||||
("Select a peer", "Пир таңдау"),
|
||||
("Select peers", "Пирлерді таңдау"),
|
||||
("Plugins", "Плагиндер"),
|
||||
("Uninstall", "Жою"),
|
||||
("Update", "Жаңарту"),
|
||||
("Enable", "Қосу"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Kontrolinis kodas"),
|
||||
("Copy Fingerprint", "Kopijuoti kontrolinį kodą"),
|
||||
("no fingerprints", "Nėra kontrolinių kodų"),
|
||||
("Select a peer", "Pasirinkite įrenginį"),
|
||||
("Select peers", "Pasirinkite įrenginius"),
|
||||
("Plugins", "Papildiniai"),
|
||||
("Uninstall", "Pašalinti"),
|
||||
("Update", "Atnaujinti"),
|
||||
("Enable", "Įgalinti"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Pirkstu nospiedums"),
|
||||
("Copy Fingerprint", "Kopēt pirkstu nospiedumu"),
|
||||
("no fingerprints", "nav pirkstu nospiedumu"),
|
||||
("Select a peer", "Atlasīt līdzīgu"),
|
||||
("Select peers", "Atlasīt līdzīgus"),
|
||||
("Plugins", "Spraudņi"),
|
||||
("Uninstall", "Atinstalēt"),
|
||||
("Update", "Atjaunināt"),
|
||||
("Enable", "Iespējot"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "ഫിംഗർപ്രിന്റ്"),
|
||||
("Copy Fingerprint", "ഫിംഗർപ്രിന്റ് കോപ്പി ചെയ്യുക"),
|
||||
("no fingerprints", "ഫിംഗർപ്രിന്റുകൾ ഇല്ല"),
|
||||
("Select a peer", "ഒരാളെ തിരഞ്ഞെടുക്കുക"),
|
||||
("Select peers", "തിരഞ്ഞെടുക്കുക"),
|
||||
("Plugins", "പ്ലഗിനുകൾ"),
|
||||
("Uninstall", "അൺഇൻസ്റ്റാൾ ചെയ്യുക"),
|
||||
("Update", "അപ്ഡേറ്റ് ചെയ്യുക"),
|
||||
("Enable", "പ്രവർത്തനക്ഷമമാക്കുക"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Fingeravtrykk"),
|
||||
("Copy Fingerprint", "Kopier fingeravtrykk"),
|
||||
("no fingerprints", "Ingen fingeravtrykk"),
|
||||
("Select a peer", "Velg en motpart"),
|
||||
("Select peers", "Velg motparter"),
|
||||
("Plugins", "Programtillegg"),
|
||||
("Uninstall", "Avinstaller"),
|
||||
("Update", "Oppdater"),
|
||||
("Enable", "Aktiver"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Vingerafdruk"),
|
||||
("Copy Fingerprint", "Vingerafdruk kopiëren"),
|
||||
("no fingerprints", "geen vingerafdrukken"),
|
||||
("Select a peer", "Selecteer een peer"),
|
||||
("Select peers", "Selecteer peers"),
|
||||
("Plugins", "Plugins"),
|
||||
("Uninstall", "Verwijderen"),
|
||||
("Update", "Bijwerken"),
|
||||
("Enable", "Activeren"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Sygnatura"),
|
||||
("Copy Fingerprint", "Skopiuj sygnaturę"),
|
||||
("no fingerprints", "brak sygnatur"),
|
||||
("Select a peer", "Wybierz zdalne urządzenie"),
|
||||
("Select peers", "Wybierz zdalne urządzenia"),
|
||||
("Plugins", "Wtyczki"),
|
||||
("Uninstall", "Odinstaluj"),
|
||||
("Update", "Aktualizuj"),
|
||||
("Enable", "Włącz"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Impressão digital"),
|
||||
("Copy Fingerprint", "Copiar impressão digital"),
|
||||
("no fingerprints", "Sem impressões digitais"),
|
||||
("Select a peer", "Selecionar um destino"),
|
||||
("Select peers", "Selecionar destinos"),
|
||||
("Plugins", "Plugins"),
|
||||
("Uninstall", "Desinstalar"),
|
||||
("Update", "Atualizar"),
|
||||
("Enable", "Ativar"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Impressão Digital"),
|
||||
("Copy Fingerprint", "Copiar Impressão Digital"),
|
||||
("no fingerprints", "sem Impressões Digitais"),
|
||||
("Select a peer", "Selecione um parceiro"),
|
||||
("Select peers", "Selecione parceiros"),
|
||||
("Plugins", "Plugins"),
|
||||
("Uninstall", "Desinstalar"),
|
||||
("Update", "Atualizar"),
|
||||
("Enable", "Habilitar"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Amprentă digitală"),
|
||||
("Copy Fingerprint", "Copiază amprenta digitală"),
|
||||
("no fingerprints", "Nicio amprentă digitală"),
|
||||
("Select a peer", "Selectează un dispozitiv pereche"),
|
||||
("Select peers", "Selectează dispozitive pereche"),
|
||||
("Plugins", "Pluginuri"),
|
||||
("Uninstall", "Dezinstalează"),
|
||||
("Update", "Actualizează"),
|
||||
("Enable", "Activează"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Отпечаток"),
|
||||
("Copy Fingerprint", "Копировать отпечаток"),
|
||||
("no fingerprints", "отпечатки отсутствуют"),
|
||||
("Select a peer", "Выберите удалённый узел"),
|
||||
("Select peers", "Выберите удалённые узлы"),
|
||||
("Plugins", "Плагины"),
|
||||
("Uninstall", "Удалить"),
|
||||
("Update", "Обновить"),
|
||||
("Enable", "Включить"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Firma digitale"),
|
||||
("Copy Fingerprint", "Còpia firma digitale"),
|
||||
("no fingerprints", "Peruna firma digitale"),
|
||||
("Select a peer", "Seletziona su dispositivu remotu"),
|
||||
("Select peers", "Seletziona sos dispositivos remotos"),
|
||||
("Plugins", "Cumplementos"),
|
||||
("Uninstall", "Disinstalla"),
|
||||
("Update", "Atualiza"),
|
||||
("Enable", "Abìlita"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Odtlačok prsta"),
|
||||
("Copy Fingerprint", "Kopírovať odtlačok prsta"),
|
||||
("no fingerprints", "žiadne odtlačky prstov"),
|
||||
("Select a peer", "Výber partnera"),
|
||||
("Select peers", "Výber partnerov"),
|
||||
("Plugins", "Pluginy"),
|
||||
("Uninstall", "Odinštalovať"),
|
||||
("Update", "Aktualizovať"),
|
||||
("Enable", "Povoliť"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Prstni odtis"),
|
||||
("Copy Fingerprint", "Kopiraj prstni odtis"),
|
||||
("no fingerprints", "ni prstnega odtisa"),
|
||||
("Select a peer", "Izberite partnerja"),
|
||||
("Select peers", "Izberite partnerje"),
|
||||
("Plugins", "Vključki"),
|
||||
("Uninstall", "Odstrani"),
|
||||
("Update", "Posodobi"),
|
||||
("Enable", "Omogoči"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Gjurma e gishtit"),
|
||||
("Copy Fingerprint", "Kopjo gjurmën e gishtit"),
|
||||
("no fingerprints", "Nuk ka gjurmë gishtash"),
|
||||
("Select a peer", "Zgjidh një peer"),
|
||||
("Select peers", "Zgjidh peer-at"),
|
||||
("Plugins", "Shtojcat"),
|
||||
("Uninstall", "Çinstalo"),
|
||||
("Update", "Përditëso"),
|
||||
("Enable", "Aktivizo"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Otisak"),
|
||||
("Copy Fingerprint", "Kopiraj otisak"),
|
||||
("no fingerprints", "Nema otisaka"),
|
||||
("Select a peer", "Izaberi klijenta"),
|
||||
("Select peers", "Izaberi klijente"),
|
||||
("Plugins", "Dodaci"),
|
||||
("Uninstall", "Deinstaliraj"),
|
||||
("Update", "Ažuriraj"),
|
||||
("Enable", "Omogući"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Fingeravtryck"),
|
||||
("Copy Fingerprint", "Kopiera fingeravtryck"),
|
||||
("no fingerprints", "inga fingeravtryck"),
|
||||
("Select a peer", "Välj en klient"),
|
||||
("Select peers", "Välj klienter"),
|
||||
("Plugins", "Plugin"),
|
||||
("Uninstall", "Avinstallera"),
|
||||
("Update", "Uppdatera"),
|
||||
("Enable", "Aktivera"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "கைரேகை"),
|
||||
("Copy Fingerprint", "கைரேகை நகல்"),
|
||||
("no fingerprints", "கைரேகைகள் இல்லை"),
|
||||
("Select a peer", "பியர் தேர்வு"),
|
||||
("Select peers", "பியர்கள் தேர்வு"),
|
||||
("Plugins", "இணைப்புகள்"),
|
||||
("Uninstall", "நிறுவல் நீக்கு"),
|
||||
("Update", "புதுப்பி"),
|
||||
("Enable", "இயக்கு"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", ""),
|
||||
("Copy Fingerprint", ""),
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "ลายนิ้วมือ"),
|
||||
("Copy Fingerprint", "คัดลอกลายนิ้วมือ"),
|
||||
("no fingerprints", "ไม่มีลายนิ้วมือ"),
|
||||
("Select a peer", "เลือกผู้ใช้งาน"),
|
||||
("Select peers", "เลือกผู้ใช้งาน"),
|
||||
("Plugins", "ปลั๊กอิน"),
|
||||
("Uninstall", "ถอนการติดตั้ง"),
|
||||
("Update", "อัปเดต"),
|
||||
("Enable", "เปิดใช้งาน"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Parmak İzi"),
|
||||
("Copy Fingerprint", "Parmak İzini Kopyala"),
|
||||
("no fingerprints", "parmak izi yok"),
|
||||
("Select a peer", "Bir cihaz seçin"),
|
||||
("Select peers", "Cihazları seçin"),
|
||||
("Plugins", "Eklentiler"),
|
||||
("Uninstall", "Kaldır"),
|
||||
("Update", "Güncelle"),
|
||||
("Enable", "Etkinleştir"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "指紋"),
|
||||
("Copy Fingerprint", "複製指紋"),
|
||||
("no fingerprints", "沒有指紋"),
|
||||
("Select a peer", "選擇夥伴"),
|
||||
("Select peers", "選擇夥伴"),
|
||||
("Plugins", "外掛程式"),
|
||||
("Uninstall", "解除安裝"),
|
||||
("Update", "更新"),
|
||||
("Enable", "啟用"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Відбитки пальців"),
|
||||
("Copy Fingerprint", "Копіювати відбитки пальців"),
|
||||
("no fingerprints", "немає відбитків пальців"),
|
||||
("Select a peer", "Оберіть віддалений пристрій"),
|
||||
("Select peers", "Оберіть віддалені пристрої"),
|
||||
("Plugins", "Плагіни"),
|
||||
("Uninstall", "Видалити"),
|
||||
("Update", "Оновити"),
|
||||
("Enable", "Увімкнути"),
|
||||
|
||||
@@ -483,9 +483,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Fingerprint", "Dấu vân tay"),
|
||||
("Copy Fingerprint", "Sao chép fingerprint"),
|
||||
("no fingerprints", "không có fingerprint"),
|
||||
("Select a peer", "Chọn một đối tác"),
|
||||
("Select peers", "Chọn các đối tác"),
|
||||
("Plugins", "Plugin"),
|
||||
("Uninstall", "Gỡ cài đặt"),
|
||||
("Update", "Cập nhật"),
|
||||
("Enable", "Bật"),
|
||||
|
||||
@@ -46,10 +46,6 @@ mod lang;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
mod port_forward;
|
||||
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub mod plugin;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
mod tray;
|
||||
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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(¶m).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(¶m).unwrap_or("".to_string()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -163,43 +163,6 @@ pub static CLICK_TIME: AtomicI64 = AtomicI64::new(0);
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub static MOUSE_MOVE_TIME: AtomicI64 = AtomicI64::new(0);
|
||||
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
lazy_static::lazy_static! {
|
||||
static ref PLUGIN_BLOCK_INPUT_TXS: Arc<Mutex<HashMap<String, std_mpsc::Sender<MessageInput>>>> = Default::default();
|
||||
static ref PLUGIN_BLOCK_INPUT_TX_RX: (Arc<Mutex<std_mpsc::Sender<bool>>>, Arc<Mutex<std_mpsc::Receiver<bool>>>) = {
|
||||
let (tx, rx) = std_mpsc::channel();
|
||||
(Arc::new(Mutex::new(tx)), Arc::new(Mutex::new(rx)))
|
||||
};
|
||||
}
|
||||
|
||||
// Block input is required for some special cases, such as privacy mode.
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn plugin_block_input(peer: &str, block: bool) -> bool {
|
||||
if let Some(tx) = PLUGIN_BLOCK_INPUT_TXS.lock().unwrap().get(peer) {
|
||||
let _ = tx.send(if block {
|
||||
MessageInput::BlockOnPlugin(peer.to_string())
|
||||
} else {
|
||||
MessageInput::BlockOffPlugin(peer.to_string())
|
||||
});
|
||||
match PLUGIN_BLOCK_INPUT_TX_RX
|
||||
.1
|
||||
.lock()
|
||||
.unwrap()
|
||||
.recv_timeout(std::time::Duration::from_millis(3_000))
|
||||
{
|
||||
Ok(b) => b == block,
|
||||
Err(..) => {
|
||||
log::error!("plugin_block_input timeout");
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ConnInner {
|
||||
id: i32,
|
||||
@@ -225,12 +188,6 @@ enum MessageInput {
|
||||
Pointer((PointerDeviceEvent, i32)),
|
||||
BlockOn,
|
||||
BlockOff,
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
BlockOnPlugin(String),
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
BlockOffPlugin(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
@@ -1176,12 +1133,6 @@ impl Connection {
|
||||
let _ = Self::turn_off_privacy_to_msg(id, String::new());
|
||||
}
|
||||
}
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
crate::plugin::handle_listen_event(
|
||||
crate::plugin::EVENT_ON_CONN_CLOSE_SERVER.to_owned(),
|
||||
conn.lr.my_id.clone(),
|
||||
);
|
||||
video_service::notify_video_frame_fetched_by_conn_id(id, None);
|
||||
if conn.authorized {
|
||||
password::update_temporary_password();
|
||||
@@ -1266,32 +1217,6 @@ impl Connection {
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
MessageInput::BlockOnPlugin(_peer) => {
|
||||
let (ok, _msg) = crate::platform::block_input(true);
|
||||
if ok {
|
||||
block_input_mode = true;
|
||||
}
|
||||
let _r = PLUGIN_BLOCK_INPUT_TX_RX
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send(block_input_mode);
|
||||
}
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
MessageInput::BlockOffPlugin(_peer) => {
|
||||
let (ok, _msg) = crate::platform::block_input(false);
|
||||
if ok {
|
||||
block_input_mode = false;
|
||||
}
|
||||
let _r = PLUGIN_BLOCK_INPUT_TX_RX
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send(block_input_mode);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@@ -2032,13 +1957,6 @@ impl Connection {
|
||||
username = "".to_owned();
|
||||
}
|
||||
}
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
PLUGIN_BLOCK_INPUT_TXS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(self.lr.my_id.clone(), self.tx_input.clone());
|
||||
|
||||
// Terminal feature is supported on desktop only
|
||||
#[allow(unused_mut)]
|
||||
let mut terminal = cfg!(not(any(target_os = "android", target_os = "ios")));
|
||||
@@ -3904,13 +3822,6 @@ impl Connection {
|
||||
self.change_resolution(Some(dr.display as _), &dr.resolution);
|
||||
}
|
||||
}
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Some(misc::Union::PluginRequest(p)) => {
|
||||
let msg =
|
||||
crate::plugin::handle_client_event(&p.id, &self.lr.my_id, &p.content);
|
||||
self.send(msg).await;
|
||||
}
|
||||
Some(misc::Union::AutoAdjustFps(fps)) => video_service::VIDEO_QOS
|
||||
.lock()
|
||||
.unwrap()
|
||||
|
||||
@@ -569,16 +569,6 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
self.send(Data::Message(msg));
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn send_plugin_request(&self, request: PluginRequest) {
|
||||
let mut misc = Misc::new();
|
||||
misc.set_plugin_request(request);
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_misc(misc);
|
||||
self.send(Data::Message(msg_out));
|
||||
}
|
||||
|
||||
pub fn get_audit_server(&self, typ: String) -> String {
|
||||
if LocalConfig::get_option("access_token").is_empty() {
|
||||
return "".to_owned();
|
||||
|
||||
Reference in New Issue
Block a user