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