Merge pull request #4337 from fufesou/feat/plugin_framework_2
Feat/plugin framework
This commit is contained in:
commit
d29031804c
158
Cargo.lock
generated
158
Cargo.lock
generated
@ -17,6 +17,18 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
@ -404,6 +416,12 @@ version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.59.2"
|
||||
@ -578,6 +596,27 @@ dependencies = [
|
||||
"serde 1.0.163",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
|
||||
dependencies = [
|
||||
"bzip2-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.11+1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cairo-rs"
|
||||
version = "0.16.7"
|
||||
@ -737,6 +776,15 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.6.1"
|
||||
@ -974,6 +1022,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.5.0"
|
||||
@ -1525,6 +1579,7 @@ checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2814,7 +2869,7 @@ dependencies = [
|
||||
"tokio-util",
|
||||
"toml 0.7.3",
|
||||
"winapi 0.3.9",
|
||||
"zstd",
|
||||
"zstd 0.12.3+zstd.1.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2862,6 +2917,15 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hound"
|
||||
version = "3.5.0"
|
||||
@ -4074,6 +4138,12 @@ version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
@ -4267,6 +4337,17 @@ dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.12"
|
||||
@ -4279,6 +4360,18 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"hmac",
|
||||
"password-hash",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
@ -5175,6 +5268,7 @@ dependencies = [
|
||||
"winreg 0.10.1",
|
||||
"winres",
|
||||
"wol-rs",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5721,6 +5815,12 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.15.44"
|
||||
@ -7291,19 +7391,58 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.9.2+zstd.1.5.1"
|
||||
name = "zip"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54"
|
||||
checksum = "7e92305c174683d78035cbf1b70e18db6329cc0f1b9cae0a52ca90bf5bfe7125"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
"aes",
|
||||
"byteorder",
|
||||
"bzip2",
|
||||
"constant_time_eq",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"flate2",
|
||||
"hmac",
|
||||
"pbkdf2",
|
||||
"sha1",
|
||||
"time 0.3.21",
|
||||
"zstd 0.11.2+zstd.1.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.11.2+zstd.1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
|
||||
dependencies = [
|
||||
"zstd-safe 5.0.2+zstd.1.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.12.3+zstd.1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806"
|
||||
dependencies = [
|
||||
"zstd-safe 6.0.5+zstd.1.5.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "4.1.3+zstd.1.5.1"
|
||||
version = "5.0.2+zstd.1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79"
|
||||
checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "6.0.5+zstd.1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d56d9e60b4b1758206c238a10165fbcae3ca37b01744e394c463463f6529d23b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"zstd-sys",
|
||||
@ -7311,12 +7450,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "1.6.2+zstd.1.5.1"
|
||||
version = "2.0.8+zstd.1.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f"
|
||||
checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -72,6 +72,7 @@ chrono = "0.4"
|
||||
cidr-utils = "0.5"
|
||||
libloading = "0.8"
|
||||
fon = "0.6"
|
||||
zip = "0.6.5"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies]
|
||||
cpal = "0.15"
|
||||
|
@ -10,10 +10,8 @@ import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/models/server_model.dart';
|
||||
import 'package:flutter_hbb/plugin/desc.dart';
|
||||
import 'package:flutter_hbb/plugin/model.dart';
|
||||
import 'package:flutter_hbb/plugin/common.dart';
|
||||
import 'package:flutter_hbb/plugin/widget.dart';
|
||||
import 'package:flutter_hbb/plugin/manager.dart';
|
||||
import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
@ -1448,57 +1446,6 @@ class _CheckboxState extends State<_Checkbox> {
|
||||
}
|
||||
}
|
||||
|
||||
class PluginCard extends StatefulWidget {
|
||||
final PluginId pluginId;
|
||||
final Desc desc;
|
||||
const PluginCard({
|
||||
Key? key,
|
||||
required this.pluginId,
|
||||
required this.desc,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<PluginCard> createState() => PluginCardState();
|
||||
}
|
||||
|
||||
class PluginCardState extends State<PluginCard> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final children = [
|
||||
_Button(
|
||||
'Reload',
|
||||
() async {
|
||||
clearPlugin(widget.pluginId);
|
||||
await bind.pluginReload(id: widget.pluginId);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
_Checkbox(
|
||||
label: 'Enable',
|
||||
getValue: () => bind.pluginIdIsEnabled(id: widget.pluginId),
|
||||
setValue: (bool v) async {
|
||||
if (!v) {
|
||||
clearPlugin(widget.pluginId);
|
||||
}
|
||||
await bind.pluginIdEnable(id: widget.pluginId, v: v);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
];
|
||||
final model = getPluginModel(kLocationHostMainPlugin, widget.pluginId);
|
||||
if (model != null) {
|
||||
children.add(PluginItem(
|
||||
pluginId: widget.pluginId,
|
||||
peerId: '',
|
||||
location: kLocationHostMainPlugin,
|
||||
pluginModel: model,
|
||||
isMenu: false,
|
||||
));
|
||||
}
|
||||
return _Card(title: widget.desc.name, children: children);
|
||||
}
|
||||
}
|
||||
|
||||
class _Plugin extends StatefulWidget {
|
||||
const _Plugin({Key? key}) : super(key: key);
|
||||
|
||||
@ -1507,47 +1454,34 @@ class _Plugin extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PluginState extends State<_Plugin> {
|
||||
// temp checkbox widget
|
||||
|
||||
List<Widget> _buildCards(DescModel model) => [
|
||||
_Card(
|
||||
title: 'Plugin',
|
||||
children: [
|
||||
_Checkbox(
|
||||
label: 'Enable',
|
||||
getValue: () => bind.pluginIsEnabled() ?? false,
|
||||
setValue: (bool v) async {
|
||||
if (!v) {
|
||||
clearLocations();
|
||||
}
|
||||
await bind.pluginEnable(v: v);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
...model.all.entries
|
||||
.map((entry) => PluginCard(pluginId: entry.key, desc: entry.value))
|
||||
.toList(),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bind.pluginListReload();
|
||||
final scrollController = ScrollController();
|
||||
return DesktopScrollWrapper(
|
||||
scrollController: scrollController,
|
||||
child: ChangeNotifierProvider.value(
|
||||
value: DescModel.instance,
|
||||
child: Consumer<DescModel>(builder: (context, model, child) {
|
||||
value: pluginManager,
|
||||
child: Consumer<PluginManager>(builder: (context, model, child) {
|
||||
return ListView(
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
controller: scrollController,
|
||||
children: _buildCards(model),
|
||||
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' : 'Logout',
|
||||
|
@ -8,7 +8,7 @@ import 'package:flutter_hbb/models/chat_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||
import 'package:flutter_hbb/plugin/widget.dart';
|
||||
import 'package:flutter_hbb/plugin/widgets/desc_ui.dart';
|
||||
import 'package:flutter_hbb/plugin/common.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
@ -126,6 +126,7 @@ void runMainApp(bool startService) async {
|
||||
// await windowManager.ensureInitialized();
|
||||
gFFI.serverModel.startService();
|
||||
bind.pluginSyncUi(syncTo: kAppTypeMain);
|
||||
bind.pluginListReload();
|
||||
}
|
||||
gFFI.userModel.refreshCurrentUser();
|
||||
runApp(App());
|
||||
|
@ -17,8 +17,8 @@ import 'package:flutter_hbb/models/server_model.dart';
|
||||
import 'package:flutter_hbb/models/user_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_hbb/plugin/event.dart';
|
||||
import 'package:flutter_hbb/plugin/desc.dart';
|
||||
import 'package:flutter_hbb/plugin/widget.dart';
|
||||
import 'package:flutter_hbb/plugin/manager.dart';
|
||||
import 'package:flutter_hbb/plugin/widgets/desc_ui.dart';
|
||||
import 'package:flutter_hbb/common/shared_state.dart';
|
||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
@ -230,8 +230,8 @@ class FfiModel with ChangeNotifier {
|
||||
parent.target?.serverModel.updateVoiceCallState(evt);
|
||||
} else if (name == 'fingerprint') {
|
||||
FingerprintState.find(peerId).value = evt['fingerprint'] ?? '';
|
||||
} else if (name == 'plugin_desc') {
|
||||
updateDesc(evt);
|
||||
} else if (name == 'plugin_manager') {
|
||||
pluginManager.handleEvent(evt);
|
||||
} else if (name == 'plugin_event') {
|
||||
handlePluginEvent(
|
||||
evt, peerId, (Map<String, dynamic> e) => handleMsgBox(e, peerId));
|
||||
|
@ -1,180 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:collection';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import './common.dart';
|
||||
|
||||
const String kValueTrue = '1';
|
||||
const String kValueFalse = '0';
|
||||
|
||||
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 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 Config {
|
||||
List<ConfigItem> shared;
|
||||
List<ConfigItem> peer;
|
||||
|
||||
Config(this.shared, this.peer);
|
||||
Config.fromJson(Map<String, dynamic> json)
|
||||
: shared = (json['shared'] as List<dynamic>)
|
||||
.map((e) => ConfigItem.fromJson(e))
|
||||
.toList(),
|
||||
peer = (json['peer'] as List<dynamic>)
|
||||
.map((e) => ConfigItem.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
|
||||
class Desc {
|
||||
String id;
|
||||
String name;
|
||||
String version;
|
||||
String description;
|
||||
String author;
|
||||
String home;
|
||||
String license;
|
||||
String published;
|
||||
String released;
|
||||
String github;
|
||||
Location location;
|
||||
Config config;
|
||||
|
||||
Desc(
|
||||
this.id,
|
||||
this.name,
|
||||
this.version,
|
||||
this.description,
|
||||
this.author,
|
||||
this.home,
|
||||
this.license,
|
||||
this.published,
|
||||
this.released,
|
||||
this.github,
|
||||
this.location,
|
||||
this.config);
|
||||
|
||||
Desc.fromJson(Map<String, dynamic> json)
|
||||
: id = json['id'] ?? '',
|
||||
name = json['name'] ?? '',
|
||||
version = json['version'] ?? '',
|
||||
description = json['description'] ?? '',
|
||||
author = json['author'] ?? '',
|
||||
home = json['home'] ?? '',
|
||||
license = json['license'] ?? '',
|
||||
published = json['published'] ?? '',
|
||||
released = json['released'] ?? '',
|
||||
github = json['github'] ?? '',
|
||||
location = Location.fromJson(json['location']),
|
||||
config = Config.fromJson(json['config']);
|
||||
}
|
||||
|
||||
class DescModel with ChangeNotifier {
|
||||
final data = <PluginId, Desc>{};
|
||||
|
||||
DescModel._();
|
||||
|
||||
void _updateDesc(Map<String, dynamic> desc) {
|
||||
try {
|
||||
Desc d = Desc.fromJson(json.decode(desc['desc']));
|
||||
data[d.id] = d;
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('DescModel json.decode fail(): $e');
|
||||
}
|
||||
}
|
||||
|
||||
Desc? _getDesc(String id) {
|
||||
return data[id];
|
||||
}
|
||||
|
||||
Map<PluginId, Desc> get all => data;
|
||||
|
||||
static final DescModel _instance = DescModel._();
|
||||
static DescModel get instance => _instance;
|
||||
}
|
||||
|
||||
void updateDesc(Map<String, dynamic> desc) =>
|
||||
DescModel.instance._updateDesc(desc);
|
||||
Desc? getDesc(String id) => DescModel.instance._getDesc(id);
|
347
flutter/lib/plugin/manager.dart
Normal file
347
flutter/lib/plugin/manager.dart
Normal file
@ -0,0 +1,347 @@
|
||||
// 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 update(PluginInfo plugin) {
|
||||
assert(plugin.meta.id == meta.id, 'Plugin id not match');
|
||||
if (plugin.meta.id != meta.id) {
|
||||
// log error
|
||||
return;
|
||||
}
|
||||
sourceInfo = plugin.sourceInfo;
|
||||
meta = plugin.meta;
|
||||
installedVersion = plugin.installedVersion;
|
||||
invalidReason = plugin.invalidReason;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
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_update'] != null) {
|
||||
_handlePluginUpdate(evt['plugin_update']);
|
||||
} 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 _handlePluginUpdate(Map<String, dynamic> evt) {
|
||||
final plugin = _getPluginFromEvent(evt);
|
||||
if (plugin == null) {
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < _plugins.length; i++) {
|
||||
if (_plugins[i].meta.id == plugin.meta.id) {
|
||||
_plugins[i].update(plugin);
|
||||
_sortPlugins();
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import './common.dart';
|
||||
import './desc.dart';
|
||||
import './manager.dart';
|
||||
|
||||
final Map<String, LocationModel> _locationModels = {};
|
||||
final Map<String, OptionModel> _optionModels = {};
|
||||
@ -22,16 +22,18 @@ class PluginModel with ChangeNotifier {
|
||||
final List<UiType> uiList = [];
|
||||
final Map<String, String> opts = {};
|
||||
|
||||
void add(UiType ui) {
|
||||
void add(List<UiType> uiList) {
|
||||
bool found = false;
|
||||
for (int i = 0; i < uiList.length; i++) {
|
||||
if (uiList[i].key == ui.key) {
|
||||
uiList[i] = ui;
|
||||
found = true;
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
uiList.add(ui);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
@ -44,12 +46,12 @@ class PluginModel with ChangeNotifier {
|
||||
class LocationModel with ChangeNotifier {
|
||||
final Map<PluginId, PluginModel> pluginModels = {};
|
||||
|
||||
void add(PluginId id, UiType ui) {
|
||||
void add(PluginId id, List<UiType> uiList) {
|
||||
if (pluginModels[id] != null) {
|
||||
pluginModels[id]!.add(ui);
|
||||
pluginModels[id]!.add(uiList);
|
||||
} else {
|
||||
var model = PluginModel();
|
||||
model.add(ui);
|
||||
model.add(uiList);
|
||||
pluginModels[id] = model;
|
||||
notifyListeners();
|
||||
}
|
||||
@ -68,11 +70,11 @@ class LocationModel with ChangeNotifier {
|
||||
bool get isEmpty => pluginModels.isEmpty;
|
||||
}
|
||||
|
||||
void addLocationUi(String location, PluginId id, UiType ui) {
|
||||
void addLocationUi(String location, PluginId id, List<UiType> uiList) {
|
||||
if (_locationModels[location] == null) {
|
||||
_locationModels[location] = LocationModel();
|
||||
}
|
||||
_locationModels[location]?.add(id, ui);
|
||||
_locationModels[location]?.add(id, uiList);
|
||||
}
|
||||
|
||||
LocationModel? getLocationModel(String location) => _locationModels[location];
|
||||
|
@ -10,9 +10,9 @@ import 'package:get/get.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
|
||||
import './desc.dart';
|
||||
import './model.dart';
|
||||
import './common.dart';
|
||||
import '../manager.dart';
|
||||
import '../model.dart';
|
||||
import '../common.dart';
|
||||
|
||||
// dup to flutter\lib\desktop\pages\desktop_setting_page.dart
|
||||
const double _kCheckBoxLeftMargin = 10;
|
||||
@ -247,7 +247,7 @@ class PluginItem extends StatelessWidget {
|
||||
}) {
|
||||
final event = MsgFromUi(
|
||||
id: pluginId,
|
||||
name: getDesc(pluginId)?.name ?? '',
|
||||
name: pluginManager.getPlugin(pluginId)?.meta.name ?? '',
|
||||
location: location,
|
||||
key: key,
|
||||
value:
|
||||
@ -280,9 +280,15 @@ void handleReloading(Map<String, dynamic> evt, String peer) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final ui = UiType.create(json.decode(evt['ui'] as String));
|
||||
if (ui != null) {
|
||||
addLocationUi(evt['location']!, evt['id']!, ui);
|
||||
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 ');
|
202
flutter/lib/plugin/widgets/desktop_settings.dart
Normal file
202
flutter/lib/plugin/widgets/desktop_settings.dart
Normal file
@ -0,0 +1,202 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ bytes = { version = "1.4", features = ["serde"] }
|
||||
log = "0.4"
|
||||
env_logger = "0.10"
|
||||
socket2 = { version = "0.3", features = ["reuseport"] }
|
||||
zstd = "0.9"
|
||||
zstd = "0.12"
|
||||
quinn = {version = "0.9", optional = true }
|
||||
anyhow = "1.0"
|
||||
futures-util = "0.3"
|
||||
@ -35,6 +35,7 @@ chrono = "0.4"
|
||||
backtrace = "0.3"
|
||||
libc = "0.2"
|
||||
dlopen = "0.1"
|
||||
toml = "0.7"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
mac_address = "1.1"
|
||||
@ -55,5 +56,4 @@ winapi = { version = "0.3", features = ["winuser"] }
|
||||
osascript = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
toml = "0.7"
|
||||
serde_json = "1.0"
|
||||
|
@ -1,23 +1,28 @@
|
||||
use std::cell::RefCell;
|
||||
use zstd::block::{Compressor, Decompressor};
|
||||
use std::{cell::RefCell, io};
|
||||
use zstd::bulk::{Compressor, Decompressor};
|
||||
|
||||
// The library supports regular compression levels from 1 up to ZSTD_maxCLevel(),
|
||||
// which is currently 22. Levels >= 20
|
||||
// Default level is ZSTD_CLEVEL_DEFAULT==3.
|
||||
// value 0 means default, which is controlled by ZSTD_CLEVEL_DEFAULT
|
||||
thread_local! {
|
||||
static COMPRESSOR: RefCell<Compressor> = RefCell::new(Compressor::new());
|
||||
static DECOMPRESSOR: RefCell<Decompressor> = RefCell::new(Decompressor::new());
|
||||
static COMPRESSOR: RefCell<io::Result<Compressor<'static>>> = RefCell::new(Compressor::new(crate::config::COMPRESS_LEVEL));
|
||||
static DECOMPRESSOR: RefCell<io::Result<Decompressor<'static>>> = RefCell::new(Decompressor::new());
|
||||
}
|
||||
|
||||
/// The library supports regular compression levels from 1 up to ZSTD_maxCLevel(),
|
||||
/// which is currently 22. Levels >= 20
|
||||
/// Default level is ZSTD_CLEVEL_DEFAULT==3.
|
||||
/// value 0 means default, which is controlled by ZSTD_CLEVEL_DEFAULT
|
||||
pub fn compress(data: &[u8], level: i32) -> Vec<u8> {
|
||||
pub fn compress(data: &[u8]) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
COMPRESSOR.with(|c| {
|
||||
if let Ok(mut c) = c.try_borrow_mut() {
|
||||
match c.compress(data, level) {
|
||||
Ok(res) => out = res,
|
||||
match &mut *c {
|
||||
Ok(c) => match c.compress(data) {
|
||||
Ok(res) => out = res,
|
||||
Err(err) => {
|
||||
crate::log::debug!("Failed to compress: {}", err);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
crate::log::debug!("Failed to compress: {}", err);
|
||||
crate::log::debug!("Failed to get compressor: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -29,14 +34,21 @@ pub fn decompress(data: &[u8]) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
DECOMPRESSOR.with(|d| {
|
||||
if let Ok(mut d) = d.try_borrow_mut() {
|
||||
const MAX: usize = 1024 * 1024 * 64;
|
||||
const MIN: usize = 1024 * 1024;
|
||||
let mut n = 30 * data.len();
|
||||
n = n.clamp(MIN, MAX);
|
||||
match d.decompress(data, n) {
|
||||
Ok(res) => out = res,
|
||||
match &mut *d {
|
||||
Ok(d) => {
|
||||
const MAX: usize = 1024 * 1024 * 64;
|
||||
const MIN: usize = 1024 * 1024;
|
||||
let mut n = 30 * data.len();
|
||||
n = n.clamp(MIN, MAX);
|
||||
match d.decompress(data, n) {
|
||||
Ok(res) => out = res,
|
||||
Err(err) => {
|
||||
crate::log::debug!("Failed to decompress: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
crate::log::debug!("Failed to decompress: {}", err);
|
||||
crate::log::debug!("Failed to get decompressor: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use crate::{bail, get_version_number, message_proto::*, ResultType, Stream};
|
||||
// https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html
|
||||
use crate::{
|
||||
compress::{compress, decompress},
|
||||
config::{Config, COMPRESS_LEVEL},
|
||||
config::Config,
|
||||
};
|
||||
|
||||
pub fn read_dir(path: &Path, include_hidden: bool) -> ResultType<FileDirectory> {
|
||||
@ -481,7 +481,7 @@ impl TransferJob {
|
||||
} else {
|
||||
self.finished_size += offset as u64;
|
||||
if !is_compressed_file(name) {
|
||||
let tmp = compress(&buf, COMPRESS_LEVEL);
|
||||
let tmp = compress(&buf);
|
||||
if tmp.len() < buf.len() {
|
||||
buf = tmp;
|
||||
compressed = true;
|
||||
|
@ -46,6 +46,7 @@ pub mod keyboard;
|
||||
pub use sysinfo;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub use dlopen;
|
||||
pub use toml;
|
||||
|
||||
#[cfg(feature = "quic")]
|
||||
pub type Stream = quic::Connection;
|
||||
|
@ -82,7 +82,7 @@ BOOL InstallUpdate(LPCWSTR fullInfPath, PBOOL rebootRequired)
|
||||
DWORD error = GetLastError();
|
||||
if (error != 0)
|
||||
{
|
||||
SetLastMsg("UpdateDriverForPlugAndPlayDevicesW failed, last error 0x%x\n", error);
|
||||
SetLastMsg("Failed InstallUpdate UpdateDriverForPlugAndPlayDevicesW, last error 0x%x\n", error);
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -108,7 +108,7 @@ BOOL Uninstall(LPCWSTR fullInfPath, PBOOL rebootRequired)
|
||||
DWORD error = GetLastError();
|
||||
if (error != 0)
|
||||
{
|
||||
SetLastMsg("DiUninstallDriverW failed, last error 0x%x\n", error);
|
||||
SetLastMsg("Failed Uninstall DiUninstallDriverW, last error 0x%x\n", error);
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -132,7 +132,7 @@ BOOL IsDeviceCreated(PBOOL created)
|
||||
DIGCF_DEVICEINTERFACE)); // Function class devices.
|
||||
if (INVALID_HANDLE_VALUE == hardwareDeviceInfo)
|
||||
{
|
||||
SetLastMsg("Idd device: SetupDiGetClassDevs failed, last error 0x%x\n", GetLastError());
|
||||
SetLastMsg("Idd device: Failed IsDeviceCreated SetupDiGetClassDevs, last error 0x%x\n", GetLastError());
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -165,7 +165,7 @@ BOOL IsDeviceCreated(PBOOL created)
|
||||
break;
|
||||
}
|
||||
|
||||
SetLastMsg("Idd device: SetupDiEnumDeviceInterfaces failed, last error 0x%x\n", error);
|
||||
SetLastMsg("Idd device: Failed IsDeviceCreated SetupDiEnumDeviceInterfaces, last error 0x%x\n", error);
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -209,7 +209,7 @@ BOOL DeviceCreate(PHSWDEVICE hSwDevice)
|
||||
if (hEvent == INVALID_HANDLE_VALUE || hEvent == NULL)
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
SetLastMsg("CreateEvent failed 0x%lx\n", error);
|
||||
SetLastMsg("Failed DeviceCreate CreateEvent 0x%lx\n", error);
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -247,7 +247,7 @@ BOOL DeviceCreate(PHSWDEVICE hSwDevice)
|
||||
hSwDevice);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
SetLastMsg("SwDeviceCreate failed with 0x%lx\n", hr);
|
||||
SetLastMsg("Failed DeviceCreate SwDeviceCreate 0x%lx\n", hr);
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -261,7 +261,7 @@ BOOL DeviceCreate(PHSWDEVICE hSwDevice)
|
||||
DWORD waitResult = WaitForSingleObject(hEvent, 10 * 1000);
|
||||
if (waitResult != WAIT_OBJECT_0)
|
||||
{
|
||||
SetLastMsg("Wait for device creation failed 0x%d\n", waitResult);
|
||||
SetLastMsg("Failed DeviceCreate wait for device creation 0x%d\n", waitResult);
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -288,7 +288,7 @@ BOOL MonitorPlugIn(UINT index, UINT edid, INT retries)
|
||||
|
||||
if (retries < 0)
|
||||
{
|
||||
SetLastMsg("Invalid tries %d\n", retries);
|
||||
SetLastMsg("Failed MonitorPlugIn invalid tries %d\n", retries);
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -319,7 +319,7 @@ BOOL MonitorPlugIn(UINT index, UINT edid, INT retries)
|
||||
HRESULT hr = CoCreateGuid(&plugIn.ContainerId);
|
||||
if (!SUCCEEDED(hr))
|
||||
{
|
||||
SetLastMsg("CoCreateGuid failed %d\n", hr);
|
||||
SetLastMsg("Failed MonitorPlugIn CoCreateGuid %d\n", hr);
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -348,7 +348,7 @@ BOOL MonitorPlugIn(UINT index, UINT edid, INT retries)
|
||||
if (ret == FALSE)
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
SetLastMsg("DeviceIoControl failed 0x%lx\n", error);
|
||||
SetLastMsg("Failed MonitorPlugIn DeviceIoControl 0x%lx\n", error);
|
||||
printf(g_lastMsg);
|
||||
}
|
||||
}
|
||||
@ -382,7 +382,7 @@ BOOL MonitorPlugOut(UINT index)
|
||||
0)) // Ptr to Overlapped structure
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
SetLastMsg("DeviceIoControl failed 0x%lx\n", error);
|
||||
SetLastMsg("Failed MonitorPlugOut DeviceIoControl 0x%lx\n", error);
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -414,7 +414,7 @@ BOOL MonitorModesUpdate(UINT index, UINT modeCount, PMonitorMode modes)
|
||||
PCtlMonitorModes pMonitorModes = (PCtlMonitorModes)malloc(buflen);
|
||||
if (pMonitorModes == NULL)
|
||||
{
|
||||
SetLastMsg("CtlMonitorModes malloc failed 0x%lx\n");
|
||||
SetLastMsg("Failed MonitorModesUpdate CtlMonitorModes malloc 0x%lx\n");
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -441,7 +441,7 @@ BOOL MonitorModesUpdate(UINT index, UINT modeCount, PMonitorMode modes)
|
||||
0)) // Ptr to Overlapped structure
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
SetLastMsg("DeviceIoControl failed 0x%lx\n", error);
|
||||
SetLastMsg("Failed MonitorModesUpdate DeviceIoControl 0x%lx\n", error);
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -495,7 +495,7 @@ GetDevicePath(
|
||||
CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES);
|
||||
if (cr != CR_SUCCESS)
|
||||
{
|
||||
SetLastMsg("Error GetDevicePath 0x%x retrieving device interface list size.\n", cr);
|
||||
SetLastMsg("Failed GetDevicePath 0x%x retrieving device interface list size.\n", cr);
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -611,7 +611,7 @@ BOOLEAN GetDevicePath2(
|
||||
DIGCF_DEVICEINTERFACE)); // Function class devices.
|
||||
if (INVALID_HANDLE_VALUE == hardwareDeviceInfo)
|
||||
{
|
||||
SetLastMsg("Idd device: SetupDiGetClassDevs failed, last error 0x%x\n", GetLastError());
|
||||
SetLastMsg("Idd device: GetDevicePath2 SetupDiGetClassDevs failed, last error 0x%x\n", GetLastError());
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -627,7 +627,7 @@ BOOLEAN GetDevicePath2(
|
||||
0, //
|
||||
&deviceInterfaceData))
|
||||
{
|
||||
SetLastMsg("Idd device: SetupDiEnumDeviceInterfaces failed, last error 0x%x\n", GetLastError());
|
||||
SetLastMsg("Idd device: GetDevicePath2 SetupDiEnumDeviceInterfaces failed, last error 0x%x\n", GetLastError());
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -649,7 +649,7 @@ BOOLEAN GetDevicePath2(
|
||||
|
||||
if (ERROR_INSUFFICIENT_BUFFER != GetLastError())
|
||||
{
|
||||
SetLastMsg("Idd device: SetupDiGetDeviceInterfaceDetail failed, last error 0x%x\n", GetLastError());
|
||||
SetLastMsg("Idd device: GetDevicePath2 SetupDiGetDeviceInterfaceDetail failed, last error 0x%x\n", GetLastError());
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -671,7 +671,7 @@ BOOLEAN GetDevicePath2(
|
||||
}
|
||||
else
|
||||
{
|
||||
SetLastMsg("Idd device: HeapAlloc failed, last error 0x%x\n", GetLastError());
|
||||
SetLastMsg("Idd device: Failed GetDevicePath2 HeapAlloc, last error 0x%x\n", GetLastError());
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -687,7 +687,7 @@ BOOLEAN GetDevicePath2(
|
||||
&requiredLength,
|
||||
NULL))
|
||||
{
|
||||
SetLastMsg("Idd device: SetupDiGetDeviceInterfaceDetail failed, last error 0x%x\n", GetLastError());
|
||||
SetLastMsg("Idd device: Failed GetDevicePath2 SetupDiGetDeviceInterfaceDetail, last error 0x%x\n", GetLastError());
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -698,7 +698,7 @@ BOOLEAN GetDevicePath2(
|
||||
hr = StringCchCopy(DevicePath, BufLen, deviceInterfaceDetailData->DevicePath);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
SetLastMsg("Error: StringCchCopy failed with HRESULT 0x%x", hr);
|
||||
SetLastMsg("Error: Failed GetDevicePath2 StringCchCopy HRESULT 0x%x", hr);
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -737,7 +737,7 @@ HANDLE DeviceOpenHandle()
|
||||
}
|
||||
if (_tcslen(devicePath) == 0)
|
||||
{
|
||||
SetLastMsg("GetDevicePath got empty device path\n");
|
||||
SetLastMsg("DeviceOpenHandle GetDevicePath got empty device path\n");
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
@ -759,7 +759,7 @@ HANDLE DeviceOpenHandle()
|
||||
if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
SetLastMsg("CreateFile failed 0x%lx\n", error);
|
||||
SetLastMsg("Failed DeviceOpenHandle CreateFile 0x%lx\n", error);
|
||||
if (g_printMsg)
|
||||
{
|
||||
printf(g_lastMsg);
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
sync::{Arc, Mutex},
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
@ -19,7 +19,7 @@ use hbb_common::compress::decompress;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
compress::compress as compress_func,
|
||||
config::{self, Config, COMPRESS_LEVEL, RENDEZVOUS_TIMEOUT},
|
||||
config::{self, Config, RENDEZVOUS_TIMEOUT},
|
||||
get_version_number, log,
|
||||
message_proto::*,
|
||||
protobuf::Enum,
|
||||
@ -61,6 +61,7 @@ lazy_static::lazy_static! {
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref IS_SERVER: bool = std::env::args().nth(1) == Some("--server".to_owned());
|
||||
static ref SERVER_RUNNING: Arc<RwLock<bool>> = Default::default();
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@ -68,6 +69,19 @@ lazy_static::lazy_static! {
|
||||
static ref ARBOARD_MTX: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
|
||||
}
|
||||
|
||||
pub struct SimpleCallOnReturn {
|
||||
pub b: bool,
|
||||
pub f: Box<dyn Fn() + 'static>,
|
||||
}
|
||||
|
||||
impl Drop for SimpleCallOnReturn {
|
||||
fn drop(&mut self) {
|
||||
if self.b {
|
||||
(self.f)();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn global_init() -> bool {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
@ -80,9 +94,14 @@ pub fn global_init() -> bool {
|
||||
|
||||
pub fn global_clean() {}
|
||||
|
||||
#[inline]
|
||||
pub fn set_server_running(b: bool) {
|
||||
*SERVER_RUNNING.write().unwrap() = b;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_server() -> bool {
|
||||
*IS_SERVER
|
||||
*IS_SERVER || *SERVER_RUNNING.read().unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -98,7 +117,7 @@ pub fn valid_for_numlock(evt: &KeyEvent) -> bool {
|
||||
|
||||
pub fn create_clipboard_msg(content: String) -> Message {
|
||||
let bytes = content.into_bytes();
|
||||
let compressed = compress_func(&bytes, COMPRESS_LEVEL);
|
||||
let compressed = compress_func(&bytes);
|
||||
let compress = compressed.len() < bytes.len();
|
||||
let content = if compress { compressed } else { bytes };
|
||||
let mut msg = Message::new();
|
||||
|
@ -112,7 +112,7 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
#[cfg(not(debug_assertions))]
|
||||
let load_plugins = crate::platform::is_installed();
|
||||
if load_plugins {
|
||||
hbb_common::allow_err!(crate::plugin::load_plugins());
|
||||
crate::plugin::init();
|
||||
}
|
||||
}
|
||||
if args.is_empty() {
|
||||
@ -240,6 +240,15 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
crate::flutter::connection_manager::start_cm_no_ui();
|
||||
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() == 3 {
|
||||
crate::plugin::install_plugin_with_url(&args[1], &args[2]);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
//_async_logger_holder.map(|x| x.flush());
|
||||
|
@ -1497,17 +1497,8 @@ pub fn plugin_reload(_id: String) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_id_uninstall(_id: String) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
crate::plugin::unload_plugin(&_id);
|
||||
allow_err!(crate::plugin::ipc::uninstall_plugin(&_id));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn plugin_id_enable(_id: String, _v: bool) {
|
||||
pub fn plugin_enable(_id: String, _v: bool) -> SyncReturn<()> {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
@ -1517,14 +1508,15 @@ pub fn plugin_id_enable(_id: String, _v: bool) {
|
||||
_v.to_string()
|
||||
));
|
||||
if _v {
|
||||
allow_err!(crate::plugin::load_plugin(None, Some(&_id)));
|
||||
allow_err!(crate::plugin::load_plugin(&_id));
|
||||
} else {
|
||||
crate::plugin::unload_plugin(&_id);
|
||||
}
|
||||
}
|
||||
SyncReturn(())
|
||||
}
|
||||
|
||||
pub fn plugin_id_is_enabled(_id: String) -> SyncReturn<bool> {
|
||||
pub fn plugin_is_enabled(_id: String) -> SyncReturn<bool> {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
@ -1545,42 +1537,6 @@ pub fn plugin_id_is_enabled(_id: String) -> SyncReturn<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_enable(_v: bool) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
allow_err!(crate::plugin::ipc::set_manager_config(
|
||||
"enabled",
|
||||
_v.to_string()
|
||||
));
|
||||
if _v {
|
||||
allow_err!(crate::plugin::load_plugins());
|
||||
} else {
|
||||
crate::plugin::unload_plugins();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_is_enabled() -> SyncReturn<Option<bool>> {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
let r = match crate::plugin::ipc::get_manager_config("enabled") {
|
||||
Ok(Some(enabled)) => Some(bool::from_str(&enabled).unwrap_or(false)),
|
||||
_ => None,
|
||||
};
|
||||
SyncReturn(r)
|
||||
}
|
||||
#[cfg(any(
|
||||
not(feature = "plugin_framework"),
|
||||
target_os = "android",
|
||||
target_os = "ios"
|
||||
))]
|
||||
{
|
||||
SyncReturn(Some(false))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_feature_is_enabled() -> SyncReturn<bool> {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@ -1611,6 +1567,28 @@ pub fn plugin_sync_ui(_sync_to: String) {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod server_side {
|
||||
use hbb_common::{config, log};
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", "没有指纹"),
|
||||
("Select a peer", "选择一个被控端"),
|
||||
("Select peers", "选择被控"),
|
||||
("Plugins", "插件")
|
||||
("Plugins", "插件"),
|
||||
("Uninstall", "卸载"),
|
||||
("Update", "更新"),
|
||||
("Enable", "启用"),
|
||||
("Disable", "禁用"),
|
||||
("Options", "选项"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", "Keine Fingerabdrücke"),
|
||||
("Select a peer", "Gegenstelle auswählen"),
|
||||
("Select peers", "Gegenstellen auswählen"),
|
||||
("Plugins", "Plugins")
|
||||
("Plugins", "Plugins"),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", "sin huellas digitales"),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", "بدون اثر انگشت"),
|
||||
("Select a peer", "یک همتا را انتخاب کنید"),
|
||||
("Select peers", "همتایان را انتخاب کنید"),
|
||||
("Plugins", "پلاگین ها")
|
||||
("Plugins", "پلاگین ها"),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", "Nessuna firma digitale"),
|
||||
("Select a peer", "Seleziona un peer"),
|
||||
("Select peers", "Seelziona peer"),
|
||||
("Plugins", "Plugin")
|
||||
("Plugins", "Plugin"),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", "geen vingerafdrukken"),
|
||||
("Select a peer", "Selecteer een peer"),
|
||||
("Select peers", "Selecteer peers"),
|
||||
("Plugins", "Plugins")
|
||||
("Plugins", "Plugins"),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", "brak sygnatur"),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", "sem Impressões Digitais"),
|
||||
("Select a peer", "Selecione um parceiro"),
|
||||
("Select peers", "Selecione parceiros"),
|
||||
("Plugins", "Plugins")
|
||||
("Plugins", "Plugins"),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", "отпечатки отсутствуют"),
|
||||
("Select a peer", "Выберите удалённый узел"),
|
||||
("Select peers", "Выберите удалённые узлы"),
|
||||
("Plugins", "Плагины")
|
||||
("Plugins", "Плагины"),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", "沒有指紋"),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("no fingerprints", ""),
|
||||
("Select a peer", ""),
|
||||
("Select peers", ""),
|
||||
("Plugins", "")
|
||||
("Plugins", ""),
|
||||
("Uninstall", ""),
|
||||
("Update", ""),
|
||||
("Enable", ""),
|
||||
("Disable", ""),
|
||||
("Options", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -9,10 +9,10 @@ use hbb_common::{
|
||||
regex::{Captures, Regex},
|
||||
};
|
||||
use std::{
|
||||
string::String,
|
||||
cell::RefCell,
|
||||
path::{Path, PathBuf},
|
||||
process::{Child, Command},
|
||||
string::String,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
@ -453,9 +453,7 @@ pub fn get_active_username() -> String {
|
||||
|
||||
pub fn get_user_home_by_name(username: &str) -> Option<PathBuf> {
|
||||
return match get_user_by_name(username) {
|
||||
None => {
|
||||
None
|
||||
}
|
||||
None => None,
|
||||
Some(user) => {
|
||||
let home = user.home_dir();
|
||||
if Path::is_dir(home) {
|
||||
@ -464,7 +462,7 @@ pub fn get_user_home_by_name(username: &str) -> Option<PathBuf> {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_active_user_home() -> Option<PathBuf> {
|
||||
@ -662,6 +660,25 @@ pub fn check_super_user_permission() -> ResultType<bool> {
|
||||
Ok(status.success() && status.code() == Some(0))
|
||||
}
|
||||
|
||||
pub fn elevate(args: Vec<&str>) -> ResultType<Option<Child>> {
|
||||
let cmd = std::env::current_exe()?;
|
||||
match cmd.to_str() {
|
||||
Some(cmd) => {
|
||||
let mut args_with_exe = vec![cmd];
|
||||
args_with_exe.append(&mut args.clone());
|
||||
// -E required for opensuse
|
||||
if is_opensuse() {
|
||||
args_with_exe.insert(0, "-E");
|
||||
}
|
||||
let task = Command::new("pkexec").args(args_with_exe).spawn()?;
|
||||
Ok(Some(task))
|
||||
}
|
||||
None => {
|
||||
hbb_common::bail!("Failed to get current exe as str");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type GtkSettingsPtr = *mut c_void;
|
||||
type GObjectPtr = *mut c_void;
|
||||
#[link(name = "gtk-3")]
|
||||
@ -835,12 +852,7 @@ mod desktop {
|
||||
}
|
||||
|
||||
fn get_display(&mut self) {
|
||||
let display_envs = vec![
|
||||
GNOME_SESSION_BINARY,
|
||||
XFCE4_PANEL,
|
||||
SDDM_GREETER,
|
||||
PLASMA_X11,
|
||||
];
|
||||
let display_envs = vec![GNOME_SESSION_BINARY, XFCE4_PANEL, SDDM_GREETER, PLASMA_X11];
|
||||
for diplay_env in display_envs {
|
||||
self.display = get_env_tries("DISPLAY", &self.uid, diplay_env, 10);
|
||||
if !self.display.is_empty() {
|
||||
@ -873,8 +885,8 @@ mod desktop {
|
||||
auth_found = true;
|
||||
} else if auth_found {
|
||||
if std::path::Path::new(v).is_absolute()
|
||||
&& std::path::Path::new(v).exists() {
|
||||
|
||||
&& std::path::Path::new(v).exists()
|
||||
{
|
||||
self.xauth = v.to_string();
|
||||
} else {
|
||||
if let Some(pid) = line.split_whitespace().nth(1) {
|
||||
@ -903,12 +915,7 @@ mod desktop {
|
||||
|
||||
fn get_xauth(&mut self) {
|
||||
// try by direct access to window manager process by name
|
||||
let display_envs = vec![
|
||||
GNOME_SESSION_BINARY,
|
||||
XFCE4_PANEL,
|
||||
SDDM_GREETER,
|
||||
PLASMA_X11,
|
||||
];
|
||||
let display_envs = vec![GNOME_SESSION_BINARY, XFCE4_PANEL, SDDM_GREETER, PLASMA_X11];
|
||||
for diplay_env in display_envs {
|
||||
self.xauth = get_env_tries("XAUTHORITY", &self.uid, diplay_env, 10);
|
||||
if !self.xauth.is_empty() {
|
||||
@ -928,7 +935,7 @@ mod desktop {
|
||||
gdm
|
||||
} else {
|
||||
let username = &self.username;
|
||||
match get_user_home_by_name(username) {
|
||||
match get_user_home_by_name(username) {
|
||||
None => {
|
||||
if username == "root" {
|
||||
format!("/{}/.Xauthority", username)
|
||||
@ -942,7 +949,10 @@ mod desktop {
|
||||
}
|
||||
}
|
||||
Some(home) => {
|
||||
format!("{}/.Xauthority", home.as_path().to_string_lossy().to_string())
|
||||
format!(
|
||||
"{}/.Xauthority",
|
||||
home.as_path().to_string_lossy().to_string()
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -42,31 +42,45 @@ extern "C" bool InputMonitoringAuthStatus(bool prompt) {
|
||||
#endif
|
||||
}
|
||||
|
||||
extern "C" bool Elevate(char* process, char** args) {
|
||||
AuthorizationRef authRef;
|
||||
OSStatus status;
|
||||
|
||||
status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
|
||||
kAuthorizationFlagDefaults, &authRef);
|
||||
if (status != errAuthorizationSuccess) {
|
||||
printf("Failed to create AuthorizationRef\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
AuthorizationItem authItem = {kAuthorizationRightExecute, 0, NULL, 0};
|
||||
AuthorizationRights authRights = {1, &authItem};
|
||||
AuthorizationFlags flags = kAuthorizationFlagDefaults |
|
||||
kAuthorizationFlagInteractionAllowed |
|
||||
kAuthorizationFlagPreAuthorize |
|
||||
kAuthorizationFlagExtendRights;
|
||||
status = AuthorizationCopyRights(authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL);
|
||||
if (status != errAuthorizationSuccess) {
|
||||
printf("Failed to authorize\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (process != NULL) {
|
||||
FILE *pipe = NULL;
|
||||
status = AuthorizationExecuteWithPrivileges(authRef, process, kAuthorizationFlagDefaults, args, &pipe);
|
||||
if (status != errAuthorizationSuccess) {
|
||||
printf("Failed to run as root\n");
|
||||
AuthorizationFree(authRef, kAuthorizationFlagDefaults);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AuthorizationFree(authRef, kAuthorizationFlagDefaults);
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" bool MacCheckAdminAuthorization() {
|
||||
AuthorizationRef authRef;
|
||||
OSStatus status;
|
||||
|
||||
status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
|
||||
kAuthorizationFlagDefaults, &authRef);
|
||||
if (status != errAuthorizationSuccess) {
|
||||
printf("Failed to create AuthorizationRef\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
AuthorizationItem authItem = {kAuthorizationRightExecute, 0, NULL, 0};
|
||||
AuthorizationRights authRights = {1, &authItem};
|
||||
AuthorizationFlags flags = kAuthorizationFlagDefaults |
|
||||
kAuthorizationFlagInteractionAllowed |
|
||||
kAuthorizationFlagPreAuthorize |
|
||||
kAuthorizationFlagExtendRights;
|
||||
status = AuthorizationCopyRights(authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL);
|
||||
if (status != errAuthorizationSuccess) {
|
||||
printf("Failed to authorize\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
AuthorizationFree(authRef, kAuthorizationFlagDefaults);
|
||||
return true;
|
||||
return Elevate(NULL, NULL);
|
||||
}
|
||||
|
||||
extern "C" float BackingScaleFactor() {
|
||||
|
@ -17,11 +17,15 @@ use core_graphics::{
|
||||
display::{kCGNullWindowID, kCGWindowListOptionOnScreenOnly, CGWindowListCopyWindowInfo},
|
||||
window::{kCGWindowName, kCGWindowOwnerPID},
|
||||
};
|
||||
use hbb_common::{allow_err, anyhow::anyhow, bail, log, message_proto::Resolution};
|
||||
use hbb_common::{allow_err, anyhow::anyhow, bail, libc, log, message_proto::Resolution};
|
||||
use include_dir::{include_dir, Dir};
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use scrap::{libc::c_void, quartz::ffi::*};
|
||||
use std::path::PathBuf;
|
||||
use std::{
|
||||
ffi::{c_char, CString},
|
||||
mem::size_of,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
static PRIVILEGES_SCRIPTS_DIR: Dir =
|
||||
include_dir!("$CARGO_MANIFEST_DIR/src/platform/privileges_scripts");
|
||||
@ -35,6 +39,7 @@ extern "C" {
|
||||
fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> BOOL;
|
||||
fn InputMonitoringAuthStatus(_: BOOL) -> BOOL;
|
||||
fn MacCheckAdminAuthorization() -> BOOL;
|
||||
fn Elevate(process: *const c_char, args: *const *const c_char) -> BOOL;
|
||||
fn MacGetModeNum(display: u32, numModes: *mut u32) -> BOOL;
|
||||
fn MacGetModes(
|
||||
display: u32,
|
||||
@ -671,3 +676,30 @@ pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<
|
||||
pub fn check_super_user_permission() -> ResultType<bool> {
|
||||
unsafe { Ok(MacCheckAdminAuthorization() == YES) }
|
||||
}
|
||||
|
||||
pub fn elevate(args: Vec<&str>) -> ResultType<bool> {
|
||||
let cmd = std::env::current_exe()?;
|
||||
match cmd.to_str() {
|
||||
Some(cmd) => {
|
||||
let cmd = CString::new(cmd)?;
|
||||
let mut cstring_args = Vec::new();
|
||||
for arg in args.iter() {
|
||||
cstring_args.push(CString::new(*arg)?);
|
||||
}
|
||||
unsafe {
|
||||
let args_ptr: *mut *const c_char =
|
||||
libc::malloc((cstring_args.len() + 1) * size_of::<*const c_char>()) as _;
|
||||
for i in 0..cstring_args.len() {
|
||||
*args_ptr.add(i) = cstring_args[i].as_ptr() as _;
|
||||
}
|
||||
*args_ptr.add(cstring_args.len()) = std::ptr::null() as _;
|
||||
let r = Elevate(cmd.as_ptr() as _, args_ptr as _);
|
||||
libc::free(args_ptr as _);
|
||||
Ok(r == YES)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
bail!("Failed to get current exe str");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,16 @@ 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>;
|
||||
|
||||
@ -207,6 +217,7 @@ impl PeerConfig {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PluginStatus {
|
||||
pub enabled: bool,
|
||||
pub uninstalled: bool,
|
||||
}
|
||||
|
||||
const MANAGER_VERSION: &str = "0.1.0";
|
||||
@ -214,7 +225,6 @@ const MANAGER_VERSION: &str = "0.1.0";
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ManagerConfig {
|
||||
pub version: String,
|
||||
pub enabled: bool,
|
||||
#[serde(default)]
|
||||
pub options: HashMap<String, String>,
|
||||
#[serde(default)]
|
||||
@ -225,7 +235,6 @@ impl Default for ManagerConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
version: MANAGER_VERSION.to_owned(),
|
||||
enabled: true,
|
||||
options: HashMap::new(),
|
||||
plugins: HashMap::new(),
|
||||
}
|
||||
@ -241,43 +250,19 @@ impl ManagerConfig {
|
||||
|
||||
#[inline]
|
||||
pub fn get_option(key: &str) -> Option<String> {
|
||||
if key == "enabled" {
|
||||
Some(CONFIG_MANAGER.lock().unwrap().enabled.to_string())
|
||||
} else {
|
||||
CONFIG_MANAGER
|
||||
.lock()
|
||||
.unwrap()
|
||||
.options
|
||||
.get(key)
|
||||
.map(|s| s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
fn set_option_enabled(enabled: bool) -> ResultType<()> {
|
||||
let mut lock = CONFIG_MANAGER.lock().unwrap();
|
||||
lock.enabled = enabled;
|
||||
hbb_common::config::store_path(Self::path(), &*lock)
|
||||
}
|
||||
|
||||
fn set_option_not_enabled(key: &str, value: &str) -> ResultType<()> {
|
||||
let mut lock = CONFIG_MANAGER.lock().unwrap();
|
||||
lock.options.insert(key.to_owned(), value.to_owned());
|
||||
hbb_common::config::store_path(Self::path(), &*lock)
|
||||
CONFIG_MANAGER
|
||||
.lock()
|
||||
.unwrap()
|
||||
.options
|
||||
.get(key)
|
||||
.map(|s| s.to_owned())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_option(key: &str, value: &str) {
|
||||
if key == "enabled" {
|
||||
let enabled = bool::from_str(value).unwrap_or(false);
|
||||
allow_err!(Self::set_option_enabled(enabled));
|
||||
if enabled {
|
||||
allow_err!(super::load_plugins());
|
||||
} else {
|
||||
super::unload_plugins();
|
||||
}
|
||||
} else {
|
||||
allow_err!(Self::set_option_not_enabled(key, value));
|
||||
}
|
||||
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]
|
||||
@ -295,7 +280,13 @@ impl ManagerConfig {
|
||||
if let Some(status) = lock.plugins.get_mut(id) {
|
||||
status.enabled = enabled;
|
||||
} else {
|
||||
lock.plugins.insert(id.to_owned(), PluginStatus { enabled });
|
||||
lock.plugins.insert(
|
||||
id.to_owned(),
|
||||
PluginStatus {
|
||||
enabled,
|
||||
uninstalled: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
hbb_common::config::store_path(Self::path(), &*lock)
|
||||
}
|
||||
@ -306,7 +297,7 @@ impl ManagerConfig {
|
||||
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(None, Some(id)));
|
||||
allow_err!(super::load_plugin(id));
|
||||
} else {
|
||||
super::unload_plugin(id);
|
||||
}
|
||||
@ -318,29 +309,50 @@ impl ManagerConfig {
|
||||
#[inline]
|
||||
pub fn add_plugin(id: &str) -> ResultType<()> {
|
||||
let mut lock = CONFIG_MANAGER.lock().unwrap();
|
||||
lock.plugins
|
||||
.insert(id.to_owned(), PluginStatus { enabled: true });
|
||||
lock.plugins.insert(
|
||||
id.to_owned(),
|
||||
PluginStatus {
|
||||
enabled: true,
|
||||
uninstalled: false,
|
||||
},
|
||||
);
|
||||
hbb_common::config::store_path(Self::path(), &*lock)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn remove_plugin(id: &str, uninstall: bool) -> ResultType<()> {
|
||||
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)?;
|
||||
if uninstall {
|
||||
allow_err!(fs::remove_dir_all(path_plugins(id)));
|
||||
}
|
||||
Ok(())
|
||||
hbb_common::config::store_path(Self::path(), &*lock)
|
||||
}
|
||||
|
||||
pub fn remove_plugins(uninstall: bool) {
|
||||
#[inline]
|
||||
pub fn is_uninstalled(id: &str) -> bool {
|
||||
CONFIG_MANAGER
|
||||
.lock()
|
||||
.unwrap()
|
||||
.plugins
|
||||
.get(id)
|
||||
.map(|p| p.uninstalled)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_uninstall(id: &str, uninstall: bool) -> ResultType<()> {
|
||||
let mut lock = CONFIG_MANAGER.lock().unwrap();
|
||||
lock.plugins.clear();
|
||||
allow_err!(hbb_common::config::store_path(Self::path(), &*lock));
|
||||
if uninstall {
|
||||
allow_err!(fs::remove_dir_all(HbbConfig::path("plugins")));
|
||||
if let Some(status) = lock.plugins.get_mut(id) {
|
||||
status.uninstalled = uninstall;
|
||||
} else {
|
||||
lock.plugins.insert(
|
||||
id.to_owned(),
|
||||
PluginStatus {
|
||||
enabled: true,
|
||||
uninstalled: uninstall,
|
||||
},
|
||||
);
|
||||
}
|
||||
hbb_common::config::store_path(Self::path(), &*lock)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ pub enum UiType {
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Location {
|
||||
pub ui: HashMap<String, UiType>,
|
||||
pub ui: HashMap<String, Vec<UiType>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@ -46,18 +46,31 @@ pub struct Config {
|
||||
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, Serialize, Deserialize)]
|
||||
pub struct Desc {
|
||||
id: String,
|
||||
name: String,
|
||||
version: String,
|
||||
description: String,
|
||||
author: String,
|
||||
home: String,
|
||||
license: String,
|
||||
published: String,
|
||||
released: String,
|
||||
github: String,
|
||||
meta: Meta,
|
||||
need_reboot: bool,
|
||||
location: Location,
|
||||
config: Config,
|
||||
listen_events: Vec<String>,
|
||||
@ -69,44 +82,8 @@ impl Desc {
|
||||
Ok(serde_json::from_str(s.to_str()?)?)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn version(&self) -> &str {
|
||||
&self.version
|
||||
}
|
||||
|
||||
pub fn description(&self) -> &str {
|
||||
&self.description
|
||||
}
|
||||
|
||||
pub fn author(&self) -> &str {
|
||||
&self.author
|
||||
}
|
||||
|
||||
pub fn home(&self) -> &str {
|
||||
&self.home
|
||||
}
|
||||
|
||||
pub fn license(&self) -> &str {
|
||||
&self.license
|
||||
}
|
||||
|
||||
pub fn published(&self) -> &str {
|
||||
&self.published
|
||||
}
|
||||
|
||||
pub fn released(&self) -> &str {
|
||||
&self.released
|
||||
}
|
||||
|
||||
pub fn github(&self) -> &str {
|
||||
&self.github
|
||||
pub fn meta(&self) -> &Meta {
|
||||
&self.meta
|
||||
}
|
||||
|
||||
pub fn location(&self) -> &Location {
|
||||
|
@ -2,8 +2,16 @@
|
||||
use crate::ipc::{connect, Connection, Data};
|
||||
use hbb_common::{allow_err, log, tokio, ResultType};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
#[cfg(not(windows))]
|
||||
use std::{fs::File, io::prelude::*};
|
||||
|
||||
#[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")]
|
||||
@ -11,7 +19,9 @@ 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),
|
||||
}
|
||||
|
||||
@ -45,6 +55,11 @@ pub async fn set_manager_plugin_config(id: &str, name: &str, value: String) -> R
|
||||
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
|
||||
@ -141,6 +156,12 @@ async fn set_manager_plugin_config_async(id: &str, name: &str, value: String) ->
|
||||
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?;
|
||||
@ -158,7 +179,7 @@ pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) {
|
||||
match plugin {
|
||||
Plugin::Config(id, name, value) => match value {
|
||||
None => {
|
||||
let value = crate::plugin::SharedConfig::get(&id, &name);
|
||||
let value = super::SharedConfig::get(&id, &name);
|
||||
allow_err!(
|
||||
stream
|
||||
.send(&Data::Plugin(Plugin::Config(id, name, value)))
|
||||
@ -166,12 +187,12 @@ pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) {
|
||||
);
|
||||
}
|
||||
Some(value) => {
|
||||
allow_err!(crate::plugin::SharedConfig::set(&id, &name, &value));
|
||||
allow_err!(super::SharedConfig::set(&id, &name, &value));
|
||||
}
|
||||
},
|
||||
Plugin::ManagerConfig(name, value) => match value {
|
||||
None => {
|
||||
let value = crate::plugin::ManagerConfig::get_option(&name);
|
||||
let value = super::ManagerConfig::get_option(&name);
|
||||
allow_err!(
|
||||
stream
|
||||
.send(&Data::Plugin(Plugin::ManagerConfig(name, value)))
|
||||
@ -179,12 +200,12 @@ pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) {
|
||||
);
|
||||
}
|
||||
Some(value) => {
|
||||
crate::plugin::ManagerConfig::set_option(&name, &value);
|
||||
super::ManagerConfig::set_option(&name, &value);
|
||||
}
|
||||
},
|
||||
Plugin::ManagerPluginConfig(id, name, value) => match value {
|
||||
None => {
|
||||
let value = crate::plugin::ManagerConfig::get_plugin_option(&id, &name);
|
||||
let value = super::ManagerConfig::get_plugin_option(&id, &name);
|
||||
allow_err!(
|
||||
stream
|
||||
.send(&Data::Plugin(Plugin::ManagerPluginConfig(id, name, value)))
|
||||
@ -192,16 +213,19 @@ pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) {
|
||||
);
|
||||
}
|
||||
Some(value) => {
|
||||
crate::plugin::ManagerConfig::set_plugin_option(&id, &name, &value);
|
||||
super::ManagerConfig::set_plugin_option(&id, &name, &value);
|
||||
}
|
||||
},
|
||||
Plugin::Load(id) => {
|
||||
allow_err!(super::config::ManagerConfig::set_uninstall(&id, false));
|
||||
allow_err!(super::load_plugin(&id));
|
||||
}
|
||||
Plugin::Reload(id) => {
|
||||
allow_err!(crate::plugin::reload_plugin(&id));
|
||||
allow_err!(super::reload_plugin(&id));
|
||||
}
|
||||
Plugin::Uninstall(_id) => {
|
||||
// to-do: uninstall plugin
|
||||
// 1. unload 2. remove configs 3. remove config files
|
||||
// allow_err!(crate::plugin::unload_plugin(&id));
|
||||
Plugin::Uninstall(id) => {
|
||||
super::manager::uninstall_plugin(&id, false);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
544
src/plugin/manager.rs
Normal file
544
src/plugin/manager.rs
Normal file
@ -0,0 +1,544 @@
|
||||
// 1. Check update.
|
||||
// 2. Install or uninstall.
|
||||
|
||||
use super::{desc::Meta as PluginMeta, ipc::InstallStatus, *};
|
||||
use crate::flutter;
|
||||
use hbb_common::{allow_err, bail, log, tokio, toml};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
const MSG_TO_UI_PLUGIN_MANAGER_LIST: &str = "plugin_list";
|
||||
const MSG_TO_UI_PLUGIN_MANAGER_UPDATE: &str = "plugin_update";
|
||||
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(),
|
||||
}]
|
||||
}
|
||||
|
||||
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 reqwest::blocking::get(&url) {
|
||||
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)
|
||||
};
|
||||
Ok(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);
|
||||
}
|
||||
let allowed_install = match crate::platform::elevate(args) {
|
||||
Ok(Some(mut child)) => match child.wait() {
|
||||
Ok(status) => {
|
||||
if status.success() {
|
||||
true
|
||||
} else {
|
||||
log::error!(
|
||||
"Failed to wait install process, process status: {:?}",
|
||||
status
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to wait install process, error: {}", e);
|
||||
false
|
||||
}
|
||||
},
|
||||
Ok(None) => false,
|
||||
Err(e) => {
|
||||
log::error!("Failed to run install process, error: {}", e);
|
||||
false
|
||||
}
|
||||
};
|
||||
Ok(allowed_install)
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
Ok(crate::platform::elevate(args)?)
|
||||
}
|
||||
|
||||
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() -> 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 super::config::ManagerConfig::is_uninstalled(id) {
|
||||
plugins.push(id.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to read plugins dir entry, {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(plugins)
|
||||
}
|
||||
|
||||
pub(super) fn remove_plugins() -> ResultType<()> {
|
||||
for id in get_uninstalled_plugins()?.iter() {
|
||||
super::config::remove(id as _);
|
||||
if let Ok(dir) = super::get_plugin_dir(id as _) {
|
||||
allow_err!(fs::remove_dir_all(dir));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn uninstall_plugin(id: &str, called_by_ui: bool) {
|
||||
if called_by_ui {
|
||||
match crate::platform::check_super_user_permission() {
|
||||
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 is_server() {
|
||||
super::plugins::unload_plugin(&id);
|
||||
// allow_err is Ok here.
|
||||
allow_err!(super::config::ManagerConfig::set_uninstall(&id, true));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// install process
|
||||
pub(super) mod install {
|
||||
use super::IPC_PLUGIN_POSTFIX;
|
||||
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::PathBuf,
|
||||
};
|
||||
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 reqwest::blocking::get(url) {
|
||||
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: &PathBuf) -> 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: &PathBuf, target_dir: &PathBuf) -> 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 install_plugin_with_url(id: &str, url: &str) {
|
||||
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 = plugin_dir.join(format!("{}.zip", id));
|
||||
|
||||
// download
|
||||
if !download_file(id, url, &filename) {
|
||||
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);
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
// 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,6 +1,9 @@
|
||||
use hbb_common::{libc, ResultType};
|
||||
use hbb_common::{libc, log, ResultType};
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::env;
|
||||
use std::{
|
||||
ffi::{c_char, c_int, c_void, CStr},
|
||||
path::PathBuf,
|
||||
ptr::null,
|
||||
};
|
||||
|
||||
@ -10,28 +13,36 @@ 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::install_plugin_with_url, install_plugin, load_plugin_list, uninstall_plugin,
|
||||
};
|
||||
pub use plugins::{
|
||||
handle_client_event, handle_listen_event, handle_server_event, handle_ui_event, load_plugin,
|
||||
load_plugins, reload_plugin, sync_ui, unload_plugin, unload_plugins,
|
||||
reload_plugin, sync_ui, unload_plugin,
|
||||
};
|
||||
|
||||
const MSG_TO_UI_TYPE_PLUGIN_DESC: &str = "plugin_desc";
|
||||
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};
|
||||
|
||||
use crate::common::is_server;
|
||||
|
||||
/// Common plugin return.
|
||||
///
|
||||
/// [Note]
|
||||
@ -77,6 +88,49 @@ impl PluginReturn {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
if !is_server() {
|
||||
std::thread::spawn(move || manager::start_ipc());
|
||||
} else {
|
||||
if let Err(e) = manager::remove_plugins() {
|
||||
log::error!("Failed to remove plugins: {}", e);
|
||||
}
|
||||
}
|
||||
if let Err(e) = plugins::load_plugins() {
|
||||
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 cstr_to_string(cstr: *const c_char) -> ResultType<String> {
|
||||
Ok(String::from_utf8(unsafe {
|
||||
@ -100,10 +154,10 @@ fn str_to_cstr_ret(s: &str) -> *const c_char {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn free_c_ptr(ret: *mut c_void) {
|
||||
if !ret.is_null() {
|
||||
fn free_c_ptr(p: *mut c_void) {
|
||||
if !p.is_null() {
|
||||
unsafe {
|
||||
libc::free(ret);
|
||||
libc::free(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use super::{desc::Desc, errno::*, *};
|
||||
use crate::common::is_server;
|
||||
use crate::flutter;
|
||||
use hbb_common::{
|
||||
allow_err, bail,
|
||||
bail,
|
||||
dlopen::symbor::Library,
|
||||
lazy_static, log,
|
||||
message_proto::{Message, Misc, PluginFailure, PluginRequest},
|
||||
@ -26,9 +26,10 @@ lazy_static::lazy_static! {
|
||||
static ref PLUGINS: Arc<RwLock<HashMap<String, Plugin>>> = Default::default();
|
||||
}
|
||||
|
||||
struct PluginInfo {
|
||||
path: String,
|
||||
desc: Desc,
|
||||
pub(super) struct PluginInfo {
|
||||
pub path: String,
|
||||
pub uninstalled: bool,
|
||||
pub desc: Desc,
|
||||
}
|
||||
|
||||
/// Initialize the plugins.
|
||||
@ -136,6 +137,11 @@ struct Callbacks {
|
||||
native: CallbackNative,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
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.
|
||||
@ -143,12 +149,14 @@ struct 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 _);
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,52 +263,70 @@ const DYLIB_SUFFIX: &str = ".so";
|
||||
#[cfg(target_os = "macos")]
|
||||
const DYLIB_SUFFIX: &str = ".dylib";
|
||||
|
||||
pub fn load_plugins() -> ResultType<()> {
|
||||
let exe = std::env::current_exe()?.to_string_lossy().to_string();
|
||||
match PathBuf::from(&exe).parent() {
|
||||
Some(dir) => {
|
||||
for entry in std::fs::read_dir(dir)? {
|
||||
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 Err(e) = load_plugin(Some(path.to_str().unwrap_or("")), None)
|
||||
{
|
||||
pub(super) fn load_plugins() -> 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() {
|
||||
load_plugin_dir(&plugin_dir);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to read plugins dir entry, {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_plugin_dir(dir: &PathBuf) {
|
||||
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, {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"Failed to read '{}' dir entry, {}",
|
||||
dir.file_name().and_then(|f| f.to_str()).unwrap_or(""),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
bail!("Failed to get parent dir of {}", exe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unload_plugins() {
|
||||
log::info!("Plugins unloaded");
|
||||
PLUGINS.write().unwrap().clear();
|
||||
if change_manager() {
|
||||
super::config::ManagerConfig::remove_plugins(false);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unload_plugin(id: &str) {
|
||||
log::info!("Plugin {} unloaded", id);
|
||||
PLUGINS.write().unwrap().remove(id);
|
||||
if change_manager() {
|
||||
allow_err!(super::config::ManagerConfig::remove_plugin(id, false));
|
||||
}
|
||||
}
|
||||
|
||||
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<()> {
|
||||
@ -309,7 +335,7 @@ pub fn reload_plugin(id: &str) -> ResultType<()> {
|
||||
None => bail!("Plugin {} not found", id),
|
||||
};
|
||||
unload_plugin(id);
|
||||
load_plugin(Some(&path), Some(id))
|
||||
load_plugin_path(&path)
|
||||
}
|
||||
|
||||
fn load_plugin_path(path: &str) -> ResultType<()> {
|
||||
@ -319,8 +345,12 @@ fn load_plugin_path(path: &str) -> ResultType<()> {
|
||||
// to-do validate plugin
|
||||
// to-do check the plugin id (make sure it does not use another plugin's id)
|
||||
|
||||
let init_info = serde_json::to_string(&InitInfo {
|
||||
is_server: crate::common::is_server(),
|
||||
})?;
|
||||
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,
|
||||
@ -331,19 +361,19 @@ fn load_plugin_path(path: &str) -> ResultType<()> {
|
||||
};
|
||||
plugin.init(&init_data, path)?;
|
||||
|
||||
if change_manager() {
|
||||
super::config::ManagerConfig::add_plugin(desc.id())?;
|
||||
if is_server() {
|
||||
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.
|
||||
update_ui_plugin_desc(&desc, None);
|
||||
reload_ui(&desc, None);
|
||||
|
||||
// add plugins
|
||||
let id = desc.id().to_string();
|
||||
let id = desc.meta().id.clone();
|
||||
let plugin_info = PluginInfo {
|
||||
path: path.to_string(),
|
||||
uninstalled: false,
|
||||
desc,
|
||||
};
|
||||
PLUGIN_INFO.write().unwrap().insert(id.clone(), plugin_info);
|
||||
@ -355,25 +385,14 @@ fn load_plugin_path(path: &str) -> ResultType<()> {
|
||||
|
||||
pub fn sync_ui(sync_to: String) {
|
||||
for plugin in PLUGIN_INFO.read().unwrap().values() {
|
||||
update_ui_plugin_desc(&plugin.desc, Some(&sync_to));
|
||||
reload_ui(&plugin.desc, Some(&sync_to));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_plugin(path: Option<&str>, id: Option<&str>) -> ResultType<()> {
|
||||
match (path, id) {
|
||||
(Some(path), _) => load_plugin_path(path),
|
||||
(None, Some(id)) => {
|
||||
let path = match PLUGIN_INFO.read().unwrap().get(id) {
|
||||
Some(plugin) => plugin.path.clone(),
|
||||
None => bail!("Plugin {} not found", id),
|
||||
};
|
||||
load_plugin_path(&path)
|
||||
}
|
||||
(None, None) => {
|
||||
bail!("path and id are both None");
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn load_plugin(id: &str) -> ResultType<()> {
|
||||
load_plugin_dir(&super::get_plugin_dir(id)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(method: &[u8], id: &str, peer: &str, event: &[u8]) -> ResultType<()> {
|
||||
@ -418,7 +437,7 @@ 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.id().to_string());
|
||||
plugins.push(info.desc.meta().id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@ -496,7 +515,7 @@ pub fn handle_client_event(id: &str, peer: &str, event: &[u8]) -> Message {
|
||||
msg
|
||||
);
|
||||
let name = match PLUGIN_INFO.read().unwrap().get(id) {
|
||||
Some(plugin) => plugin.desc.name(),
|
||||
Some(plugin) => &plugin.desc.meta().name,
|
||||
None => "???",
|
||||
}
|
||||
.to_owned();
|
||||
@ -553,22 +572,13 @@ fn make_plugin_failure(id: &str, name: &str, msg: &str) -> Message {
|
||||
msg_out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn change_manager() -> bool {
|
||||
#[cfg(debug_assertions)]
|
||||
let change_manager = true;
|
||||
#[cfg(not(debug_assertions))]
|
||||
let change_manager = is_server();
|
||||
change_manager
|
||||
}
|
||||
|
||||
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.id());
|
||||
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.
|
||||
@ -601,25 +611,8 @@ fn reload_ui(desc: &Desc, sync_to: Option<&str>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_ui_plugin_desc(desc: &Desc, sync_to: Option<&str>) {
|
||||
// This function is rarely used. There's no need to care about serialization efficiency here.
|
||||
if let Ok(desc_str) = serde_json::to_string(desc) {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("name", MSG_TO_UI_TYPE_PLUGIN_DESC);
|
||||
m.insert("desc", &desc_str);
|
||||
let event = serde_json::to_string(&m).unwrap_or("".to_owned());
|
||||
match sync_to {
|
||||
Some(channel) => {
|
||||
let _res = flutter::push_global_event(channel, event.clone());
|
||||
}
|
||||
None => {
|
||||
let _res = flutter::push_global_event(flutter::APP_TYPE_MAIN, event.clone());
|
||||
let _res =
|
||||
flutter::push_global_event(flutter::APP_TYPE_DESKTOP_REMOTE, event.clone());
|
||||
let _res = flutter::push_global_event(flutter::APP_TYPE_CM, event.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
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> {
|
||||
@ -629,3 +622,11 @@ pub(super) fn get_desc_conf(id: &str) -> Option<super::desc::Config> {
|
||||
.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())
|
||||
}
|
||||
|
@ -394,6 +394,7 @@ pub async fn start_server(is_server: bool) {
|
||||
}
|
||||
|
||||
if is_server {
|
||||
crate::common::set_server_running(true);
|
||||
std::thread::spawn(move || {
|
||||
if let Err(err) = crate::ipc::start("") {
|
||||
log::error!("Failed to start ipc: {}", err);
|
||||
|
@ -6,7 +6,7 @@ use crate::common::IS_X11;
|
||||
#[cfg(target_os = "macos")]
|
||||
use dispatch::Queue;
|
||||
use enigo::{Enigo, Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
use hbb_common::{config::COMPRESS_LEVEL, get_time, protobuf::EnumOrUnknown};
|
||||
use hbb_common::{get_time, protobuf::EnumOrUnknown};
|
||||
use rdev::{self, EventType, Key as RdevKey, KeyCode, RawKey};
|
||||
#[cfg(target_os = "macos")]
|
||||
use rdev::{CGEventSourceStateID, CGEventTapLocation, VirtualInput};
|
||||
@ -299,8 +299,7 @@ fn run_cursor(sp: MouseCursorService, state: &mut StateCursor) -> ResultType<()>
|
||||
msg = cached.clone();
|
||||
} else {
|
||||
let mut data = crate::get_cursor_data(hcursor)?;
|
||||
data.colors =
|
||||
hbb_common::compress::compress(&data.colors[..], COMPRESS_LEVEL).into();
|
||||
data.colors = hbb_common::compress::compress(&data.colors[..]).into();
|
||||
let mut tmp = Message::new();
|
||||
tmp.set_cursor_data(data);
|
||||
msg = Arc::new(tmp);
|
||||
|
Loading…
x
Reference in New Issue
Block a user