Merge pull request #4337 from fufesou/feat/plugin_framework_2

Feat/plugin framework
This commit is contained in:
RustDesk 2023-05-12 10:38:38 +08:00 committed by GitHub
commit d29031804c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 1993 additions and 683 deletions

158
Cargo.lock generated
View File

@ -17,6 +17,18 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 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]] [[package]]
name = "ahash" name = "ahash"
version = "0.7.6" version = "0.7.6"
@ -404,6 +416,12 @@ version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]] [[package]]
name = "bindgen" name = "bindgen"
version = "0.59.2" version = "0.59.2"
@ -578,6 +596,27 @@ dependencies = [
"serde 1.0.163", "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]] [[package]]
name = "cairo-rs" name = "cairo-rs"
version = "0.16.7" version = "0.16.7"
@ -737,6 +776,15 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "cipher"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "clang-sys" name = "clang-sys"
version = "1.6.1" version = "1.6.1"
@ -974,6 +1022,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.5.0" version = "0.5.0"
@ -1525,6 +1579,7 @@ checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [ dependencies = [
"block-buffer", "block-buffer",
"crypto-common", "crypto-common",
"subtle",
] ]
[[package]] [[package]]
@ -2814,7 +2869,7 @@ dependencies = [
"tokio-util", "tokio-util",
"toml 0.7.3", "toml 0.7.3",
"winapi 0.3.9", "winapi 0.3.9",
"zstd", "zstd 0.12.3+zstd.1.5.2",
] ]
[[package]] [[package]]
@ -2862,6 +2917,15 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]] [[package]]
name = "hound" name = "hound"
version = "3.5.0" version = "3.5.0"
@ -4074,6 +4138,12 @@ version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]] [[package]]
name = "openssl-probe" name = "openssl-probe"
version = "0.1.5" version = "0.1.5"
@ -4267,6 +4337,17 @@ dependencies = [
"windows-sys 0.45.0", "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]] [[package]]
name = "paste" name = "paste"
version = "1.0.12" version = "1.0.12"
@ -4279,6 +4360,18 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" 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]] [[package]]
name = "peeking_take_while" name = "peeking_take_while"
version = "0.1.2" version = "0.1.2"
@ -5175,6 +5268,7 @@ dependencies = [
"winreg 0.10.1", "winreg 0.10.1",
"winres", "winres",
"wol-rs", "wol-rs",
"zip",
] ]
[[package]] [[package]]
@ -5721,6 +5815,12 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]] [[package]]
name = "syn" name = "syn"
version = "0.15.44" version = "0.15.44"
@ -7291,19 +7391,58 @@ dependencies = [
] ]
[[package]] [[package]]
name = "zstd" name = "zip"
version = "0.9.2+zstd.1.5.1" version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54" checksum = "7e92305c174683d78035cbf1b70e18db6329cc0f1b9cae0a52ca90bf5bfe7125"
dependencies = [ 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]] [[package]]
name = "zstd-safe" 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" 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 = [ dependencies = [
"libc", "libc",
"zstd-sys", "zstd-sys",
@ -7311,12 +7450,13 @@ dependencies = [
[[package]] [[package]]
name = "zstd-sys" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f" checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
"pkg-config",
] ]
[[package]] [[package]]

View File

@ -72,6 +72,7 @@ chrono = "0.4"
cidr-utils = "0.5" cidr-utils = "0.5"
libloading = "0.8" libloading = "0.8"
fon = "0.6" fon = "0.6"
zip = "0.6.5"
[target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies] [target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies]
cpal = "0.15" cpal = "0.15"

View File

@ -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/desktop/pages/desktop_tab_page.dart';
import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/plugin/desc.dart'; import 'package:flutter_hbb/plugin/manager.dart';
import 'package:flutter_hbb/plugin/model.dart'; import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart';
import 'package:flutter_hbb/plugin/common.dart';
import 'package:flutter_hbb/plugin/widget.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -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 { class _Plugin extends StatefulWidget {
const _Plugin({Key? key}) : super(key: key); const _Plugin({Key? key}) : super(key: key);
@ -1507,47 +1454,34 @@ class _Plugin extends StatefulWidget {
} }
class _PluginState extends State<_Plugin> { 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bind.pluginListReload();
final scrollController = ScrollController(); final scrollController = ScrollController();
return DesktopScrollWrapper( return DesktopScrollWrapper(
scrollController: scrollController, scrollController: scrollController,
child: ChangeNotifierProvider.value( child: ChangeNotifierProvider.value(
value: DescModel.instance, value: pluginManager,
child: Consumer<DescModel>(builder: (context, model, child) { child: Consumer<PluginManager>(builder: (context, model, child) {
return ListView( return ListView(
physics: DraggableNeverScrollableScrollPhysics(), physics: DraggableNeverScrollableScrollPhysics(),
controller: scrollController, controller: scrollController,
children: _buildCards(model), children: model.plugins.map((entry) => pluginCard(entry)).toList(),
).marginOnly(bottom: _kListViewBottomMargin); ).marginOnly(bottom: _kListViewBottomMargin);
}), }),
), ),
); );
} }
Widget pluginCard(PluginInfo plugin) {
return ChangeNotifierProvider.value(
value: plugin,
child: Consumer<PluginInfo>(
builder: (context, model, child) => DesktopSettingsCard(plugin: model),
),
);
}
Widget accountAction() { Widget accountAction() {
return Obx(() => _Button( return Obx(() => _Button(
gFFI.userModel.userName.value.isEmpty ? 'Login' : 'Logout', gFFI.userModel.userName.value.isEmpty ? 'Login' : 'Logout',

View File

@ -8,7 +8,7 @@ import 'package:flutter_hbb/models/chat_model.dart';
import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:flutter_hbb/plugin/widget.dart'; import 'package:flutter_hbb/plugin/widgets/desc_ui.dart';
import 'package:flutter_hbb/plugin/common.dart'; import 'package:flutter_hbb/plugin/common.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';

View File

@ -126,6 +126,7 @@ void runMainApp(bool startService) async {
// await windowManager.ensureInitialized(); // await windowManager.ensureInitialized();
gFFI.serverModel.startService(); gFFI.serverModel.startService();
bind.pluginSyncUi(syncTo: kAppTypeMain); bind.pluginSyncUi(syncTo: kAppTypeMain);
bind.pluginListReload();
} }
gFFI.userModel.refreshCurrentUser(); gFFI.userModel.refreshCurrentUser();
runApp(App()); runApp(App());

View File

@ -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/user_model.dart';
import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/plugin/event.dart'; import 'package:flutter_hbb/plugin/event.dart';
import 'package:flutter_hbb/plugin/desc.dart'; import 'package:flutter_hbb/plugin/manager.dart';
import 'package:flutter_hbb/plugin/widget.dart'; import 'package:flutter_hbb/plugin/widgets/desc_ui.dart';
import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
@ -230,8 +230,8 @@ class FfiModel with ChangeNotifier {
parent.target?.serverModel.updateVoiceCallState(evt); parent.target?.serverModel.updateVoiceCallState(evt);
} else if (name == 'fingerprint') { } else if (name == 'fingerprint') {
FingerprintState.find(peerId).value = evt['fingerprint'] ?? ''; FingerprintState.find(peerId).value = evt['fingerprint'] ?? '';
} else if (name == 'plugin_desc') { } else if (name == 'plugin_manager') {
updateDesc(evt); pluginManager.handleEvent(evt);
} else if (name == 'plugin_event') { } else if (name == 'plugin_event') {
handlePluginEvent( handlePluginEvent(
evt, peerId, (Map<String, dynamic> e) => handleMsgBox(e, peerId)); evt, peerId, (Map<String, dynamic> e) => handleMsgBox(e, peerId));

View File

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

View 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;

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import './common.dart'; import './common.dart';
import './desc.dart'; import './manager.dart';
final Map<String, LocationModel> _locationModels = {}; final Map<String, LocationModel> _locationModels = {};
final Map<String, OptionModel> _optionModels = {}; final Map<String, OptionModel> _optionModels = {};
@ -22,16 +22,18 @@ class PluginModel with ChangeNotifier {
final List<UiType> uiList = []; final List<UiType> uiList = [];
final Map<String, String> opts = {}; final Map<String, String> opts = {};
void add(UiType ui) { void add(List<UiType> uiList) {
bool found = false; bool found = false;
for (int i = 0; i < uiList.length; i++) { for (var ui in uiList) {
if (uiList[i].key == ui.key) { for (int i = 0; i < this.uiList.length; i++) {
uiList[i] = ui; if (this.uiList[i].key == ui.key) {
this.uiList[i] = ui;
found = true; found = true;
} }
} }
if (!found) { if (!found) {
uiList.add(ui); this.uiList.add(ui);
}
} }
notifyListeners(); notifyListeners();
} }
@ -44,12 +46,12 @@ class PluginModel with ChangeNotifier {
class LocationModel with ChangeNotifier { class LocationModel with ChangeNotifier {
final Map<PluginId, PluginModel> pluginModels = {}; final Map<PluginId, PluginModel> pluginModels = {};
void add(PluginId id, UiType ui) { void add(PluginId id, List<UiType> uiList) {
if (pluginModels[id] != null) { if (pluginModels[id] != null) {
pluginModels[id]!.add(ui); pluginModels[id]!.add(uiList);
} else { } else {
var model = PluginModel(); var model = PluginModel();
model.add(ui); model.add(uiList);
pluginModels[id] = model; pluginModels[id] = model;
notifyListeners(); notifyListeners();
} }
@ -68,11 +70,11 @@ class LocationModel with ChangeNotifier {
bool get isEmpty => pluginModels.isEmpty; 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) { if (_locationModels[location] == null) {
_locationModels[location] = LocationModel(); _locationModels[location] = LocationModel();
} }
_locationModels[location]?.add(id, ui); _locationModels[location]?.add(id, uiList);
} }
LocationModel? getLocationModel(String location) => _locationModels[location]; LocationModel? getLocationModel(String location) => _locationModels[location];

View File

@ -10,9 +10,9 @@ import 'package:get/get.dart';
import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart'; import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart';
import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/platform_model.dart';
import './desc.dart'; import '../manager.dart';
import './model.dart'; import '../model.dart';
import './common.dart'; import '../common.dart';
// dup to flutter\lib\desktop\pages\desktop_setting_page.dart // dup to flutter\lib\desktop\pages\desktop_setting_page.dart
const double _kCheckBoxLeftMargin = 10; const double _kCheckBoxLeftMargin = 10;
@ -247,7 +247,7 @@ class PluginItem extends StatelessWidget {
}) { }) {
final event = MsgFromUi( final event = MsgFromUi(
id: pluginId, id: pluginId,
name: getDesc(pluginId)?.name ?? '', name: pluginManager.getPlugin(pluginId)?.meta.name ?? '',
location: location, location: location,
key: key, key: key,
value: value:
@ -280,9 +280,15 @@ void handleReloading(Map<String, dynamic> evt, String peer) {
return; return;
} }
try { try {
final ui = UiType.create(json.decode(evt['ui'] as String)); final uiList = <UiType>[];
for (var e in json.decode(evt['ui'] as String)) {
final ui = UiType.create(e);
if (ui != null) { if (ui != null) {
addLocationUi(evt['location']!, evt['id']!, ui); uiList.add(ui);
}
}
if (uiList.isNotEmpty) {
addLocationUi(evt['location']!, evt['id']!, uiList);
} }
} catch (e) { } catch (e) {
debugPrint('Failed handleReloading, json decode of ui, $e '); debugPrint('Failed handleReloading, json decode of ui, $e ');

View 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,
);
}
}

View File

@ -16,7 +16,7 @@ bytes = { version = "1.4", features = ["serde"] }
log = "0.4" log = "0.4"
env_logger = "0.10" env_logger = "0.10"
socket2 = { version = "0.3", features = ["reuseport"] } socket2 = { version = "0.3", features = ["reuseport"] }
zstd = "0.9" zstd = "0.12"
quinn = {version = "0.9", optional = true } quinn = {version = "0.9", optional = true }
anyhow = "1.0" anyhow = "1.0"
futures-util = "0.3" futures-util = "0.3"
@ -35,6 +35,7 @@ chrono = "0.4"
backtrace = "0.3" backtrace = "0.3"
libc = "0.2" libc = "0.2"
dlopen = "0.1" dlopen = "0.1"
toml = "0.7"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
mac_address = "1.1" mac_address = "1.1"
@ -55,5 +56,4 @@ winapi = { version = "0.3", features = ["winuser"] }
osascript = "0.3" osascript = "0.3"
[dev-dependencies] [dev-dependencies]
toml = "0.7"
serde_json = "1.0" serde_json = "1.0"

View File

@ -1,24 +1,29 @@
use std::cell::RefCell; use std::{cell::RefCell, io};
use zstd::block::{Compressor, Decompressor}; 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! { thread_local! {
static COMPRESSOR: RefCell<Compressor> = RefCell::new(Compressor::new()); static COMPRESSOR: RefCell<io::Result<Compressor<'static>>> = RefCell::new(Compressor::new(crate::config::COMPRESS_LEVEL));
static DECOMPRESSOR: RefCell<Decompressor> = RefCell::new(Decompressor::new()); static DECOMPRESSOR: RefCell<io::Result<Decompressor<'static>>> = RefCell::new(Decompressor::new());
} }
/// The library supports regular compression levels from 1 up to ZSTD_maxCLevel(), pub fn compress(data: &[u8]) -> Vec<u8> {
/// 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> {
let mut out = Vec::new(); let mut out = Vec::new();
COMPRESSOR.with(|c| { COMPRESSOR.with(|c| {
if let Ok(mut c) = c.try_borrow_mut() { if let Ok(mut c) = c.try_borrow_mut() {
match c.compress(data, level) { match &mut *c {
Ok(c) => match c.compress(data) {
Ok(res) => out = res, Ok(res) => out = res,
Err(err) => { Err(err) => {
crate::log::debug!("Failed to compress: {}", err); crate::log::debug!("Failed to compress: {}", err);
} }
},
Err(err) => {
crate::log::debug!("Failed to get compressor: {}", err);
}
} }
} }
}); });
@ -29,6 +34,8 @@ pub fn decompress(data: &[u8]) -> Vec<u8> {
let mut out = Vec::new(); let mut out = Vec::new();
DECOMPRESSOR.with(|d| { DECOMPRESSOR.with(|d| {
if let Ok(mut d) = d.try_borrow_mut() { if let Ok(mut d) = d.try_borrow_mut() {
match &mut *d {
Ok(d) => {
const MAX: usize = 1024 * 1024 * 64; const MAX: usize = 1024 * 1024 * 64;
const MIN: usize = 1024 * 1024; const MIN: usize = 1024 * 1024;
let mut n = 30 * data.len(); let mut n = 30 * data.len();
@ -40,6 +47,11 @@ pub fn decompress(data: &[u8]) -> Vec<u8> {
} }
} }
} }
Err(err) => {
crate::log::debug!("Failed to get decompressor: {}", err);
}
}
}
}); });
out out
} }

View File

@ -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 // https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html
use crate::{ use crate::{
compress::{compress, decompress}, compress::{compress, decompress},
config::{Config, COMPRESS_LEVEL}, config::Config,
}; };
pub fn read_dir(path: &Path, include_hidden: bool) -> ResultType<FileDirectory> { pub fn read_dir(path: &Path, include_hidden: bool) -> ResultType<FileDirectory> {
@ -481,7 +481,7 @@ impl TransferJob {
} else { } else {
self.finished_size += offset as u64; self.finished_size += offset as u64;
if !is_compressed_file(name) { if !is_compressed_file(name) {
let tmp = compress(&buf, COMPRESS_LEVEL); let tmp = compress(&buf);
if tmp.len() < buf.len() { if tmp.len() < buf.len() {
buf = tmp; buf = tmp;
compressed = true; compressed = true;

View File

@ -46,6 +46,7 @@ pub mod keyboard;
pub use sysinfo; pub use sysinfo;
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
pub use dlopen; pub use dlopen;
pub use toml;
#[cfg(feature = "quic")] #[cfg(feature = "quic")]
pub type Stream = quic::Connection; pub type Stream = quic::Connection;

View File

@ -82,7 +82,7 @@ BOOL InstallUpdate(LPCWSTR fullInfPath, PBOOL rebootRequired)
DWORD error = GetLastError(); DWORD error = GetLastError();
if (error != 0) 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) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -108,7 +108,7 @@ BOOL Uninstall(LPCWSTR fullInfPath, PBOOL rebootRequired)
DWORD error = GetLastError(); DWORD error = GetLastError();
if (error != 0) 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) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -132,7 +132,7 @@ BOOL IsDeviceCreated(PBOOL created)
DIGCF_DEVICEINTERFACE)); // Function class devices. DIGCF_DEVICEINTERFACE)); // Function class devices.
if (INVALID_HANDLE_VALUE == hardwareDeviceInfo) 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) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -165,7 +165,7 @@ BOOL IsDeviceCreated(PBOOL created)
break; 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) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -209,7 +209,7 @@ BOOL DeviceCreate(PHSWDEVICE hSwDevice)
if (hEvent == INVALID_HANDLE_VALUE || hEvent == NULL) if (hEvent == INVALID_HANDLE_VALUE || hEvent == NULL)
{ {
DWORD error = GetLastError(); DWORD error = GetLastError();
SetLastMsg("CreateEvent failed 0x%lx\n", error); SetLastMsg("Failed DeviceCreate CreateEvent 0x%lx\n", error);
if (g_printMsg) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -247,7 +247,7 @@ BOOL DeviceCreate(PHSWDEVICE hSwDevice)
hSwDevice); hSwDevice);
if (FAILED(hr)) if (FAILED(hr))
{ {
SetLastMsg("SwDeviceCreate failed with 0x%lx\n", hr); SetLastMsg("Failed DeviceCreate SwDeviceCreate 0x%lx\n", hr);
if (g_printMsg) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -261,7 +261,7 @@ BOOL DeviceCreate(PHSWDEVICE hSwDevice)
DWORD waitResult = WaitForSingleObject(hEvent, 10 * 1000); DWORD waitResult = WaitForSingleObject(hEvent, 10 * 1000);
if (waitResult != WAIT_OBJECT_0) 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) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -288,7 +288,7 @@ BOOL MonitorPlugIn(UINT index, UINT edid, INT retries)
if (retries < 0) if (retries < 0)
{ {
SetLastMsg("Invalid tries %d\n", retries); SetLastMsg("Failed MonitorPlugIn invalid tries %d\n", retries);
if (g_printMsg) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -319,7 +319,7 @@ BOOL MonitorPlugIn(UINT index, UINT edid, INT retries)
HRESULT hr = CoCreateGuid(&plugIn.ContainerId); HRESULT hr = CoCreateGuid(&plugIn.ContainerId);
if (!SUCCEEDED(hr)) if (!SUCCEEDED(hr))
{ {
SetLastMsg("CoCreateGuid failed %d\n", hr); SetLastMsg("Failed MonitorPlugIn CoCreateGuid %d\n", hr);
if (g_printMsg) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -348,7 +348,7 @@ BOOL MonitorPlugIn(UINT index, UINT edid, INT retries)
if (ret == FALSE) if (ret == FALSE)
{ {
DWORD error = GetLastError(); DWORD error = GetLastError();
SetLastMsg("DeviceIoControl failed 0x%lx\n", error); SetLastMsg("Failed MonitorPlugIn DeviceIoControl 0x%lx\n", error);
printf(g_lastMsg); printf(g_lastMsg);
} }
} }
@ -382,7 +382,7 @@ BOOL MonitorPlugOut(UINT index)
0)) // Ptr to Overlapped structure 0)) // Ptr to Overlapped structure
{ {
DWORD error = GetLastError(); DWORD error = GetLastError();
SetLastMsg("DeviceIoControl failed 0x%lx\n", error); SetLastMsg("Failed MonitorPlugOut DeviceIoControl 0x%lx\n", error);
if (g_printMsg) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -414,7 +414,7 @@ BOOL MonitorModesUpdate(UINT index, UINT modeCount, PMonitorMode modes)
PCtlMonitorModes pMonitorModes = (PCtlMonitorModes)malloc(buflen); PCtlMonitorModes pMonitorModes = (PCtlMonitorModes)malloc(buflen);
if (pMonitorModes == NULL) if (pMonitorModes == NULL)
{ {
SetLastMsg("CtlMonitorModes malloc failed 0x%lx\n"); SetLastMsg("Failed MonitorModesUpdate CtlMonitorModes malloc 0x%lx\n");
if (g_printMsg) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -441,7 +441,7 @@ BOOL MonitorModesUpdate(UINT index, UINT modeCount, PMonitorMode modes)
0)) // Ptr to Overlapped structure 0)) // Ptr to Overlapped structure
{ {
DWORD error = GetLastError(); DWORD error = GetLastError();
SetLastMsg("DeviceIoControl failed 0x%lx\n", error); SetLastMsg("Failed MonitorModesUpdate DeviceIoControl 0x%lx\n", error);
if (g_printMsg) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -495,7 +495,7 @@ GetDevicePath(
CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES); CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES);
if (cr != CR_SUCCESS) 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) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -611,7 +611,7 @@ BOOLEAN GetDevicePath2(
DIGCF_DEVICEINTERFACE)); // Function class devices. DIGCF_DEVICEINTERFACE)); // Function class devices.
if (INVALID_HANDLE_VALUE == hardwareDeviceInfo) 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) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -627,7 +627,7 @@ BOOLEAN GetDevicePath2(
0, // 0, //
&deviceInterfaceData)) &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) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -649,7 +649,7 @@ BOOLEAN GetDevicePath2(
if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) 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) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -671,7 +671,7 @@ BOOLEAN GetDevicePath2(
} }
else 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) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -687,7 +687,7 @@ BOOLEAN GetDevicePath2(
&requiredLength, &requiredLength,
NULL)) 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) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -698,7 +698,7 @@ BOOLEAN GetDevicePath2(
hr = StringCchCopy(DevicePath, BufLen, deviceInterfaceDetailData->DevicePath); hr = StringCchCopy(DevicePath, BufLen, deviceInterfaceDetailData->DevicePath);
if (FAILED(hr)) if (FAILED(hr))
{ {
SetLastMsg("Error: StringCchCopy failed with HRESULT 0x%x", hr); SetLastMsg("Error: Failed GetDevicePath2 StringCchCopy HRESULT 0x%x", hr);
if (g_printMsg) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -737,7 +737,7 @@ HANDLE DeviceOpenHandle()
} }
if (_tcslen(devicePath) == 0) if (_tcslen(devicePath) == 0)
{ {
SetLastMsg("GetDevicePath got empty device path\n"); SetLastMsg("DeviceOpenHandle GetDevicePath got empty device path\n");
if (g_printMsg) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);
@ -759,7 +759,7 @@ HANDLE DeviceOpenHandle()
if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL) if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
{ {
DWORD error = GetLastError(); DWORD error = GetLastError();
SetLastMsg("CreateFile failed 0x%lx\n", error); SetLastMsg("Failed DeviceOpenHandle CreateFile 0x%lx\n", error);
if (g_printMsg) if (g_printMsg)
{ {
printf(g_lastMsg); printf(g_lastMsg);

View File

@ -1,6 +1,6 @@
use std::{ use std::{
future::Future, future::Future,
sync::{Arc, Mutex}, sync::{Arc, Mutex, RwLock},
}; };
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
@ -19,7 +19,7 @@ use hbb_common::compress::decompress;
use hbb_common::{ use hbb_common::{
allow_err, allow_err,
compress::compress as compress_func, compress::compress as compress_func,
config::{self, Config, COMPRESS_LEVEL, RENDEZVOUS_TIMEOUT}, config::{self, Config, RENDEZVOUS_TIMEOUT},
get_version_number, log, get_version_number, log,
message_proto::*, message_proto::*,
protobuf::Enum, protobuf::Enum,
@ -61,6 +61,7 @@ lazy_static::lazy_static! {
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref IS_SERVER: bool = std::env::args().nth(1) == Some("--server".to_owned()); 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")))] #[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(())); 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 { pub fn global_init() -> bool {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
@ -80,9 +94,14 @@ pub fn global_init() -> bool {
pub fn global_clean() {} pub fn global_clean() {}
#[inline]
pub fn set_server_running(b: bool) {
*SERVER_RUNNING.write().unwrap() = b;
}
#[inline] #[inline]
pub fn is_server() -> bool { pub fn is_server() -> bool {
*IS_SERVER *IS_SERVER || *SERVER_RUNNING.read().unwrap()
} }
#[inline] #[inline]
@ -98,7 +117,7 @@ pub fn valid_for_numlock(evt: &KeyEvent) -> bool {
pub fn create_clipboard_msg(content: String) -> Message { pub fn create_clipboard_msg(content: String) -> Message {
let bytes = content.into_bytes(); let bytes = content.into_bytes();
let compressed = compress_func(&bytes, COMPRESS_LEVEL); let compressed = compress_func(&bytes);
let compress = compressed.len() < bytes.len(); let compress = compressed.len() < bytes.len();
let content = if compress { compressed } else { bytes }; let content = if compress { compressed } else { bytes };
let mut msg = Message::new(); let mut msg = Message::new();

View File

@ -112,7 +112,7 @@ pub fn core_main() -> Option<Vec<String>> {
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
let load_plugins = crate::platform::is_installed(); let load_plugins = crate::platform::is_installed();
if load_plugins { if load_plugins {
hbb_common::allow_err!(crate::plugin::load_plugins()); crate::plugin::init();
} }
} }
if args.is_empty() { if args.is_empty() {
@ -240,6 +240,15 @@ pub fn core_main() -> Option<Vec<String>> {
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
crate::flutter::connection_manager::start_cm_no_ui(); crate::flutter::connection_manager::start_cm_no_ui();
return None; return None;
} else {
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if args[0] == "--plugin-install" {
if args.len() == 3 {
crate::plugin::install_plugin_with_url(&args[1], &args[2]);
}
return None;
}
} }
} }
//_async_logger_holder.map(|x| x.flush()); //_async_logger_holder.map(|x| x.flush());

View File

@ -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] #[inline]
pub fn plugin_id_enable(_id: String, _v: bool) { pub fn plugin_enable(_id: String, _v: bool) -> SyncReturn<()> {
#[cfg(feature = "plugin_framework")] #[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[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() _v.to_string()
)); ));
if _v { if _v {
allow_err!(crate::plugin::load_plugin(None, Some(&_id))); allow_err!(crate::plugin::load_plugin(&_id));
} else { } else {
crate::plugin::unload_plugin(&_id); 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(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[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> { pub fn plugin_feature_is_enabled() -> SyncReturn<bool> {
#[cfg(feature = "plugin_framework")] #[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[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")] #[cfg(target_os = "android")]
pub mod server_side { pub mod server_side {
use hbb_common::{config, log}; use hbb_common::{config, log};

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", "没有指纹"), ("no fingerprints", "没有指纹"),
("Select a peer", "选择一个被控端"), ("Select a peer", "选择一个被控端"),
("Select peers", "选择被控"), ("Select peers", "选择被控"),
("Plugins", "插件") ("Plugins", "插件"),
("Uninstall", "卸载"),
("Update", "更新"),
("Enable", "启用"),
("Disable", "禁用"),
("Options", "选项"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", "Keine Fingerabdrücke"), ("no fingerprints", "Keine Fingerabdrücke"),
("Select a peer", "Gegenstelle auswählen"), ("Select a peer", "Gegenstelle auswählen"),
("Select peers", "Gegenstellen auswählen"), ("Select peers", "Gegenstellen auswählen"),
("Plugins", "Plugins") ("Plugins", "Plugins"),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", "sin huellas digitales"), ("no fingerprints", "sin huellas digitales"),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", "بدون اثر انگشت"), ("no fingerprints", "بدون اثر انگشت"),
("Select a peer", "یک همتا را انتخاب کنید"), ("Select a peer", "یک همتا را انتخاب کنید"),
("Select peers", "همتایان را انتخاب کنید"), ("Select peers", "همتایان را انتخاب کنید"),
("Plugins", "پلاگین ها") ("Plugins", "پلاگین ها"),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", "Nessuna firma digitale"), ("no fingerprints", "Nessuna firma digitale"),
("Select a peer", "Seleziona un peer"), ("Select a peer", "Seleziona un peer"),
("Select peers", "Seelziona peer"), ("Select peers", "Seelziona peer"),
("Plugins", "Plugin") ("Plugins", "Plugin"),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", "geen vingerafdrukken"), ("no fingerprints", "geen vingerafdrukken"),
("Select a peer", "Selecteer een peer"), ("Select a peer", "Selecteer een peer"),
("Select peers", "Selecteer peers"), ("Select peers", "Selecteer peers"),
("Plugins", "Plugins") ("Plugins", "Plugins"),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", "brak sygnatur"), ("no fingerprints", "brak sygnatur"),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", "sem Impressões Digitais"), ("no fingerprints", "sem Impressões Digitais"),
("Select a peer", "Selecione um parceiro"), ("Select a peer", "Selecione um parceiro"),
("Select peers", "Selecione parceiros"), ("Select peers", "Selecione parceiros"),
("Plugins", "Plugins") ("Plugins", "Plugins"),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", "отпечатки отсутствуют"), ("no fingerprints", "отпечатки отсутствуют"),
("Select a peer", "Выберите удалённый узел"), ("Select a peer", "Выберите удалённый узел"),
("Select peers", "Выберите удалённые узлы"), ("Select peers", "Выберите удалённые узлы"),
("Plugins", "Плагины") ("Plugins", "Плагины"),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", "沒有指紋"), ("no fingerprints", "沒有指紋"),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("no fingerprints", ""), ("no fingerprints", ""),
("Select a peer", ""), ("Select a peer", ""),
("Select peers", ""), ("Select peers", ""),
("Plugins", "") ("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -9,10 +9,10 @@ use hbb_common::{
regex::{Captures, Regex}, regex::{Captures, Regex},
}; };
use std::{ use std::{
string::String,
cell::RefCell, cell::RefCell,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::{Child, Command}, process::{Child, Command},
string::String,
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
Arc, Arc,
@ -453,9 +453,7 @@ pub fn get_active_username() -> String {
pub fn get_user_home_by_name(username: &str) -> Option<PathBuf> { pub fn get_user_home_by_name(username: &str) -> Option<PathBuf> {
return match get_user_by_name(username) { return match get_user_by_name(username) {
None => { None => None,
None
}
Some(user) => { Some(user) => {
let home = user.home_dir(); let home = user.home_dir();
if Path::is_dir(home) { if Path::is_dir(home) {
@ -464,7 +462,7 @@ pub fn get_user_home_by_name(username: &str) -> Option<PathBuf> {
None None
} }
} }
} };
} }
pub fn get_active_user_home() -> Option<PathBuf> { 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)) 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 GtkSettingsPtr = *mut c_void;
type GObjectPtr = *mut c_void; type GObjectPtr = *mut c_void;
#[link(name = "gtk-3")] #[link(name = "gtk-3")]
@ -835,12 +852,7 @@ mod desktop {
} }
fn get_display(&mut self) { fn get_display(&mut self) {
let display_envs = vec![ let display_envs = vec![GNOME_SESSION_BINARY, XFCE4_PANEL, SDDM_GREETER, PLASMA_X11];
GNOME_SESSION_BINARY,
XFCE4_PANEL,
SDDM_GREETER,
PLASMA_X11,
];
for diplay_env in display_envs { for diplay_env in display_envs {
self.display = get_env_tries("DISPLAY", &self.uid, diplay_env, 10); self.display = get_env_tries("DISPLAY", &self.uid, diplay_env, 10);
if !self.display.is_empty() { if !self.display.is_empty() {
@ -873,8 +885,8 @@ mod desktop {
auth_found = true; auth_found = true;
} else if auth_found { } else if auth_found {
if std::path::Path::new(v).is_absolute() 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(); self.xauth = v.to_string();
} else { } else {
if let Some(pid) = line.split_whitespace().nth(1) { if let Some(pid) = line.split_whitespace().nth(1) {
@ -903,12 +915,7 @@ mod desktop {
fn get_xauth(&mut self) { fn get_xauth(&mut self) {
// try by direct access to window manager process by name // try by direct access to window manager process by name
let display_envs = vec![ let display_envs = vec![GNOME_SESSION_BINARY, XFCE4_PANEL, SDDM_GREETER, PLASMA_X11];
GNOME_SESSION_BINARY,
XFCE4_PANEL,
SDDM_GREETER,
PLASMA_X11,
];
for diplay_env in display_envs { for diplay_env in display_envs {
self.xauth = get_env_tries("XAUTHORITY", &self.uid, diplay_env, 10); self.xauth = get_env_tries("XAUTHORITY", &self.uid, diplay_env, 10);
if !self.xauth.is_empty() { if !self.xauth.is_empty() {
@ -942,7 +949,10 @@ mod desktop {
} }
} }
Some(home) => { Some(home) => {
format!("{}/.Xauthority", home.as_path().to_string_lossy().to_string()) format!(
"{}/.Xauthority",
home.as_path().to_string_lossy().to_string()
)
} }
} }
}; };

View File

@ -42,7 +42,7 @@ extern "C" bool InputMonitoringAuthStatus(bool prompt) {
#endif #endif
} }
extern "C" bool MacCheckAdminAuthorization() { extern "C" bool Elevate(char* process, char** args) {
AuthorizationRef authRef; AuthorizationRef authRef;
OSStatus status; OSStatus status;
@ -65,10 +65,24 @@ extern "C" bool MacCheckAdminAuthorization() {
return false; 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); AuthorizationFree(authRef, kAuthorizationFlagDefaults);
return true; return true;
} }
extern "C" bool MacCheckAdminAuthorization() {
return Elevate(NULL, NULL);
}
extern "C" float BackingScaleFactor() { extern "C" float BackingScaleFactor() {
NSScreen* s = [NSScreen mainScreen]; NSScreen* s = [NSScreen mainScreen];
if (s) return [s backingScaleFactor]; if (s) return [s backingScaleFactor];

View File

@ -17,11 +17,15 @@ use core_graphics::{
display::{kCGNullWindowID, kCGWindowListOptionOnScreenOnly, CGWindowListCopyWindowInfo}, display::{kCGNullWindowID, kCGWindowListOptionOnScreenOnly, CGWindowListCopyWindowInfo},
window::{kCGWindowName, kCGWindowOwnerPID}, 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 include_dir::{include_dir, Dir};
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use scrap::{libc::c_void, quartz::ffi::*}; 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 = static PRIVILEGES_SCRIPTS_DIR: Dir =
include_dir!("$CARGO_MANIFEST_DIR/src/platform/privileges_scripts"); include_dir!("$CARGO_MANIFEST_DIR/src/platform/privileges_scripts");
@ -35,6 +39,7 @@ extern "C" {
fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> BOOL; fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> BOOL;
fn InputMonitoringAuthStatus(_: BOOL) -> BOOL; fn InputMonitoringAuthStatus(_: BOOL) -> BOOL;
fn MacCheckAdminAuthorization() -> 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 MacGetModeNum(display: u32, numModes: *mut u32) -> BOOL;
fn MacGetModes( fn MacGetModes(
display: u32, 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> { pub fn check_super_user_permission() -> ResultType<bool> {
unsafe { Ok(MacCheckAdminAuthorization() == YES) } 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");
}
}
}

View File

@ -36,6 +36,16 @@ fn path_plugins(id: &str) -> PathBuf {
HbbConfig::path("plugins").join(id) 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 { impl Deref for SharedConfig {
type Target = HashMap<String, String>; type Target = HashMap<String, String>;
@ -207,6 +217,7 @@ impl PeerConfig {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct PluginStatus { pub struct PluginStatus {
pub enabled: bool, pub enabled: bool,
pub uninstalled: bool,
} }
const MANAGER_VERSION: &str = "0.1.0"; const MANAGER_VERSION: &str = "0.1.0";
@ -214,7 +225,6 @@ const MANAGER_VERSION: &str = "0.1.0";
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct ManagerConfig { pub struct ManagerConfig {
pub version: String, pub version: String,
pub enabled: bool,
#[serde(default)] #[serde(default)]
pub options: HashMap<String, String>, pub options: HashMap<String, String>,
#[serde(default)] #[serde(default)]
@ -225,7 +235,6 @@ impl Default for ManagerConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
version: MANAGER_VERSION.to_owned(), version: MANAGER_VERSION.to_owned(),
enabled: true,
options: HashMap::new(), options: HashMap::new(),
plugins: HashMap::new(), plugins: HashMap::new(),
} }
@ -241,9 +250,6 @@ impl ManagerConfig {
#[inline] #[inline]
pub fn get_option(key: &str) -> Option<String> { pub fn get_option(key: &str) -> Option<String> {
if key == "enabled" {
Some(CONFIG_MANAGER.lock().unwrap().enabled.to_string())
} else {
CONFIG_MANAGER CONFIG_MANAGER
.lock() .lock()
.unwrap() .unwrap()
@ -251,33 +257,12 @@ impl ManagerConfig {
.get(key) .get(key)
.map(|s| s.to_owned()) .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)
}
#[inline] #[inline]
pub fn set_option(key: &str, value: &str) { pub fn set_option(key: &str, value: &str) {
if key == "enabled" { let mut lock = CONFIG_MANAGER.lock().unwrap();
let enabled = bool::from_str(value).unwrap_or(false); lock.options.insert(key.to_owned(), value.to_owned());
allow_err!(Self::set_option_enabled(enabled)); allow_err!(hbb_common::config::store_path(Self::path(), &*lock));
if enabled {
allow_err!(super::load_plugins());
} else {
super::unload_plugins();
}
} else {
allow_err!(Self::set_option_not_enabled(key, value));
}
} }
#[inline] #[inline]
@ -295,7 +280,13 @@ impl ManagerConfig {
if let Some(status) = lock.plugins.get_mut(id) { if let Some(status) = lock.plugins.get_mut(id) {
status.enabled = enabled; status.enabled = enabled;
} else { } 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) hbb_common::config::store_path(Self::path(), &*lock)
} }
@ -306,7 +297,7 @@ impl ManagerConfig {
let enabled = bool::from_str(value).unwrap_or(false); let enabled = bool::from_str(value).unwrap_or(false);
allow_err!(Self::set_plugin_option_enabled(id, enabled)); allow_err!(Self::set_plugin_option_enabled(id, enabled));
if enabled { if enabled {
allow_err!(super::load_plugin(None, Some(id))); allow_err!(super::load_plugin(id));
} else { } else {
super::unload_plugin(id); super::unload_plugin(id);
} }
@ -318,29 +309,50 @@ impl ManagerConfig {
#[inline] #[inline]
pub fn add_plugin(id: &str) -> ResultType<()> { pub fn add_plugin(id: &str) -> ResultType<()> {
let mut lock = CONFIG_MANAGER.lock().unwrap(); let mut lock = CONFIG_MANAGER.lock().unwrap();
lock.plugins lock.plugins.insert(
.insert(id.to_owned(), PluginStatus { enabled: true }); id.to_owned(),
PluginStatus {
enabled: true,
uninstalled: false,
},
);
hbb_common::config::store_path(Self::path(), &*lock) hbb_common::config::store_path(Self::path(), &*lock)
} }
#[inline] #[inline]
pub fn remove_plugin(id: &str, uninstall: bool) -> ResultType<()> { pub fn remove_plugin(id: &str) -> ResultType<()> {
let mut lock = CONFIG_MANAGER.lock().unwrap(); let mut lock = CONFIG_MANAGER.lock().unwrap();
lock.plugins.remove(id); lock.plugins.remove(id);
hbb_common::config::store_path(Self::path(), &*lock)?; hbb_common::config::store_path(Self::path(), &*lock)
if uninstall {
allow_err!(fs::remove_dir_all(path_plugins(id)));
}
Ok(())
} }
pub fn remove_plugins(uninstall: bool) { #[inline]
let mut lock = CONFIG_MANAGER.lock().unwrap(); pub fn is_uninstalled(id: &str) -> bool {
lock.plugins.clear(); CONFIG_MANAGER
allow_err!(hbb_common::config::store_path(Self::path(), &*lock)); .lock()
if uninstall { .unwrap()
allow_err!(fs::remove_dir_all(HbbConfig::path("plugins"))); .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();
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(())
} }
} }

View File

@ -30,7 +30,7 @@ pub enum UiType {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Location { pub struct Location {
pub ui: HashMap<String, UiType>, pub ui: HashMap<String, Vec<UiType>>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -46,18 +46,31 @@ pub struct Config {
pub peer: Vec<ConfigItem>, pub peer: Vec<ConfigItem>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PublishInfo {
pub published: String,
pub last_released: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Meta {
pub id: String,
pub name: String,
pub version: String,
pub description: String,
#[serde(default)]
pub platforms: String,
pub author: String,
pub home: String,
pub license: String,
pub source: String,
pub publish_info: PublishInfo,
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Desc { pub struct Desc {
id: String, meta: Meta,
name: String, need_reboot: bool,
version: String,
description: String,
author: String,
home: String,
license: String,
published: String,
released: String,
github: String,
location: Location, location: Location,
config: Config, config: Config,
listen_events: Vec<String>, listen_events: Vec<String>,
@ -69,44 +82,8 @@ impl Desc {
Ok(serde_json::from_str(s.to_str()?)?) Ok(serde_json::from_str(s.to_str()?)?)
} }
pub fn id(&self) -> &str { pub fn meta(&self) -> &Meta {
&self.id &self.meta
}
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 location(&self) -> &Location { pub fn location(&self) -> &Location {

View File

@ -2,8 +2,16 @@
use crate::ipc::{connect, Connection, Data}; use crate::ipc::{connect, Connection, Data};
use hbb_common::{allow_err, log, tokio, ResultType}; use hbb_common::{allow_err, log, tokio, ResultType};
use serde_derive::{Deserialize, Serialize}; 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)] #[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "t", content = "c")] #[serde(tag = "t", content = "c")]
@ -11,7 +19,9 @@ pub enum Plugin {
Config(String, String, Option<String>), Config(String, String, Option<String>),
ManagerConfig(String, Option<String>), ManagerConfig(String, Option<String>),
ManagerPluginConfig(String, String, Option<String>), ManagerPluginConfig(String, String, Option<String>),
Load(String),
Reload(String), Reload(String),
InstallStatus((String, InstallStatus)),
Uninstall(String), 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 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")] #[tokio::main(flavor = "current_thread")]
pub async fn reload_plugin(id: &str) -> ResultType<()> { pub async fn reload_plugin(id: &str) -> ResultType<()> {
reload_plugin_async(id).await reload_plugin_async(id).await
@ -141,6 +156,12 @@ async fn set_manager_plugin_config_async(id: &str, name: &str, value: String) ->
Ok(()) 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<()> { async fn reload_plugin_async(id: &str) -> ResultType<()> {
let mut c = connect(1000, "").await?; let mut c = connect(1000, "").await?;
c.send(&Data::Plugin(Plugin::Reload(id.to_owned()))).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 { match plugin {
Plugin::Config(id, name, value) => match value { Plugin::Config(id, name, value) => match value {
None => { None => {
let value = crate::plugin::SharedConfig::get(&id, &name); let value = super::SharedConfig::get(&id, &name);
allow_err!( allow_err!(
stream stream
.send(&Data::Plugin(Plugin::Config(id, name, value))) .send(&Data::Plugin(Plugin::Config(id, name, value)))
@ -166,12 +187,12 @@ pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) {
); );
} }
Some(value) => { 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 { Plugin::ManagerConfig(name, value) => match value {
None => { None => {
let value = crate::plugin::ManagerConfig::get_option(&name); let value = super::ManagerConfig::get_option(&name);
allow_err!( allow_err!(
stream stream
.send(&Data::Plugin(Plugin::ManagerConfig(name, value))) .send(&Data::Plugin(Plugin::ManagerConfig(name, value)))
@ -179,12 +200,12 @@ pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) {
); );
} }
Some(value) => { Some(value) => {
crate::plugin::ManagerConfig::set_option(&name, &value); super::ManagerConfig::set_option(&name, &value);
} }
}, },
Plugin::ManagerPluginConfig(id, name, value) => match value { Plugin::ManagerPluginConfig(id, name, value) => match value {
None => { None => {
let value = crate::plugin::ManagerConfig::get_plugin_option(&id, &name); let value = super::ManagerConfig::get_plugin_option(&id, &name);
allow_err!( allow_err!(
stream stream
.send(&Data::Plugin(Plugin::ManagerPluginConfig(id, name, value))) .send(&Data::Plugin(Plugin::ManagerPluginConfig(id, name, value)))
@ -192,16 +213,19 @@ pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) {
); );
} }
Some(value) => { 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) => { Plugin::Reload(id) => {
allow_err!(crate::plugin::reload_plugin(&id)); allow_err!(super::reload_plugin(&id));
} }
Plugin::Uninstall(_id) => { Plugin::Uninstall(id) => {
// to-do: uninstall plugin super::manager::uninstall_plugin(&id, false);
// 1. unload 2. remove configs 3. remove config files
// allow_err!(crate::plugin::unload_plugin(&id));
} }
_ => {}
} }
} }

544
src/plugin/manager.rs Normal file
View 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);
}
}

View File

@ -1,6 +1,9 @@
use hbb_common::{libc, ResultType}; use hbb_common::{libc, log, ResultType};
#[cfg(target_os = "windows")]
use std::env;
use std::{ use std::{
ffi::{c_char, c_int, c_void, CStr}, ffi::{c_char, c_int, c_void, CStr},
path::PathBuf,
ptr::null, ptr::null,
}; };
@ -10,28 +13,36 @@ mod config;
pub mod desc; pub mod desc;
mod errno; mod errno;
pub mod ipc; pub mod ipc;
mod manager;
pub mod native; pub mod native;
pub mod native_handlers; pub mod native_handlers;
mod plog; mod plog;
mod plugins; mod plugins;
pub use manager::{
install::install_plugin_with_url, install_plugin, load_plugin_list, uninstall_plugin,
};
pub use plugins::{ pub use plugins::{
handle_client_event, handle_listen_event, handle_server_event, handle_ui_event, load_plugin, 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_EVENT: &str = "plugin_event";
const MSG_TO_UI_TYPE_PLUGIN_RELOAD: &str = "plugin_reload"; 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_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_CLIENT: &str = "on_conn_client";
pub const EVENT_ON_CONN_SERVER: &str = "on_conn_server"; 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_CLIENT: &str = "on_conn_close_client";
pub const EVENT_ON_CONN_CLOSE_SERVER: &str = "on_conn_close_server"; 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}; pub use config::{ManagerConfig, PeerConfig, SharedConfig};
use crate::common::is_server;
/// Common plugin return. /// Common plugin return.
/// ///
/// [Note] /// [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] #[inline]
fn cstr_to_string(cstr: *const c_char) -> ResultType<String> { fn cstr_to_string(cstr: *const c_char) -> ResultType<String> {
Ok(String::from_utf8(unsafe { Ok(String::from_utf8(unsafe {
@ -100,10 +154,10 @@ fn str_to_cstr_ret(s: &str) -> *const c_char {
} }
#[inline] #[inline]
fn free_c_ptr(ret: *mut c_void) { fn free_c_ptr(p: *mut c_void) {
if !ret.is_null() { if !p.is_null() {
unsafe { unsafe {
libc::free(ret); libc::free(p);
} }
} }
} }

View File

@ -3,7 +3,7 @@ use super::{desc::Desc, errno::*, *};
use crate::common::is_server; use crate::common::is_server;
use crate::flutter; use crate::flutter;
use hbb_common::{ use hbb_common::{
allow_err, bail, bail,
dlopen::symbor::Library, dlopen::symbor::Library,
lazy_static, log, lazy_static, log,
message_proto::{Message, Misc, PluginFailure, PluginRequest}, message_proto::{Message, Misc, PluginFailure, PluginRequest},
@ -26,9 +26,10 @@ lazy_static::lazy_static! {
static ref PLUGINS: Arc<RwLock<HashMap<String, Plugin>>> = Default::default(); static ref PLUGINS: Arc<RwLock<HashMap<String, Plugin>>> = Default::default();
} }
struct PluginInfo { pub(super) struct PluginInfo {
path: String, pub path: String,
desc: Desc, pub uninstalled: bool,
pub desc: Desc,
} }
/// Initialize the plugins. /// Initialize the plugins.
@ -136,6 +137,11 @@ struct Callbacks {
native: CallbackNative, native: CallbackNative,
} }
#[derive(Serialize)]
struct InitInfo {
is_server: bool,
}
/// The plugin initialize data. /// The plugin initialize data.
/// version: The version of the plugin, can't be nullptr. /// version: The version of the plugin, can't be nullptr.
/// local_peer_id: The local peer id, can't be nullptr. /// local_peer_id: The local peer id, can't be nullptr.
@ -143,12 +149,14 @@ struct Callbacks {
#[repr(C)] #[repr(C)]
struct InitData { struct InitData {
version: *const c_char, version: *const c_char,
info: *const c_char,
cbs: Callbacks, cbs: Callbacks,
} }
impl Drop for InitData { impl Drop for InitData {
fn drop(&mut self) { fn drop(&mut self) {
free_c_ptr(self.version as _); free_c_ptr(self.version as _);
free_c_ptr(self.info as _);
} }
} }
@ -255,11 +263,31 @@ const DYLIB_SUFFIX: &str = ".so";
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
const DYLIB_SUFFIX: &str = ".dylib"; const DYLIB_SUFFIX: &str = ".dylib";
pub fn load_plugins() -> ResultType<()> { pub(super) fn load_plugins() -> ResultType<()> {
let exe = std::env::current_exe()?.to_string_lossy().to_string(); let plugins_dir = super::get_plugins_dir()?;
match PathBuf::from(&exe).parent() { if !plugins_dir.exists() {
Some(dir) => { std::fs::create_dir_all(&plugins_dir)?;
for entry in std::fs::read_dir(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 { match entry {
Ok(entry) => { Ok(entry) => {
let path = entry.path(); let path = entry.path();
@ -267,40 +295,38 @@ pub fn load_plugins() -> ResultType<()> {
let filename = entry.file_name(); let filename = entry.file_name();
let filename = filename.to_str().unwrap_or(""); let filename = filename.to_str().unwrap_or("");
if filename.starts_with("plugin_") && filename.ends_with(DYLIB_SUFFIX) { if filename.starts_with("plugin_") && filename.ends_with(DYLIB_SUFFIX) {
if let Err(e) = load_plugin(Some(path.to_str().unwrap_or("")), None) if let Some(path) = path.to_str() {
{ if let Err(e) = load_plugin_path(path) {
log::error!("Failed to load plugin {}, {}", filename, e); log::error!("Failed to load plugin {}, {}", filename, e);
} }
} }
} }
} }
}
Err(e) => { Err(e) => {
log::error!("Failed to read dir entry, {}", 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) { pub fn unload_plugin(id: &str) {
log::info!("Plugin {} unloaded", id); log::info!("Plugin {} unloaded", id);
PLUGINS.write().unwrap().remove(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<()> { pub fn reload_plugin(id: &str) -> ResultType<()> {
@ -309,7 +335,7 @@ pub fn reload_plugin(id: &str) -> ResultType<()> {
None => bail!("Plugin {} not found", id), None => bail!("Plugin {} not found", id),
}; };
unload_plugin(id); unload_plugin(id);
load_plugin(Some(&path), Some(id)) load_plugin_path(&path)
} }
fn load_plugin_path(path: &str) -> ResultType<()> { fn load_plugin_path(path: &str) -> ResultType<()> {
@ -319,8 +345,12 @@ fn load_plugin_path(path: &str) -> ResultType<()> {
// to-do validate plugin // to-do validate plugin
// to-do check the plugin id (make sure it does not use another plugin's id) // 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 { let init_data = InitData {
version: str_to_cstr_ret(crate::VERSION), version: str_to_cstr_ret(crate::VERSION),
info: str_to_cstr_ret(&init_info) as _,
cbs: Callbacks { cbs: Callbacks {
msg: callback_msg::cb_msg, msg: callback_msg::cb_msg,
get_conf: config::cb_get_conf, get_conf: config::cb_get_conf,
@ -331,19 +361,19 @@ fn load_plugin_path(path: &str) -> ResultType<()> {
}; };
plugin.init(&init_data, path)?; plugin.init(&init_data, path)?;
if change_manager() { if is_server() {
super::config::ManagerConfig::add_plugin(desc.id())?; super::config::ManagerConfig::add_plugin(&desc.meta().id)?;
} }
// update ui // update ui
// Ui may be not ready now, so we need to update again once ui is ready. // 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); reload_ui(&desc, None);
// add plugins // add plugins
let id = desc.id().to_string(); let id = desc.meta().id.clone();
let plugin_info = PluginInfo { let plugin_info = PluginInfo {
path: path.to_string(), path: path.to_string(),
uninstalled: false,
desc, desc,
}; };
PLUGIN_INFO.write().unwrap().insert(id.clone(), plugin_info); 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) { pub fn sync_ui(sync_to: String) {
for plugin in PLUGIN_INFO.read().unwrap().values() { for plugin in PLUGIN_INFO.read().unwrap().values() {
update_ui_plugin_desc(&plugin.desc, Some(&sync_to));
reload_ui(&plugin.desc, Some(&sync_to)); reload_ui(&plugin.desc, Some(&sync_to));
} }
} }
pub fn load_plugin(path: Option<&str>, id: Option<&str>) -> ResultType<()> { #[inline]
match (path, id) { pub fn load_plugin(id: &str) -> ResultType<()> {
(Some(path), _) => load_plugin_path(path), load_plugin_dir(&super::get_plugin_dir(id)?);
(None, Some(id)) => { Ok(())
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");
}
}
} }
fn handle_event(method: &[u8], id: &str, peer: &str, event: &[u8]) -> ResultType<()> { 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(); let mut plugins = Vec::new();
for info in PLUGIN_INFO.read().unwrap().values() { for info in PLUGIN_INFO.read().unwrap().values() {
if info.desc.listen_events().contains(&event.to_string()) { 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 msg
); );
let name = match PLUGIN_INFO.read().unwrap().get(id) { let name = match PLUGIN_INFO.read().unwrap().get(id) {
Some(plugin) => plugin.desc.name(), Some(plugin) => &plugin.desc.meta().name,
None => "???", None => "???",
} }
.to_owned(); .to_owned();
@ -553,22 +572,13 @@ fn make_plugin_failure(id: &str, name: &str, msg: &str) -> Message {
msg_out 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>) { fn reload_ui(desc: &Desc, sync_to: Option<&str>) {
for (location, ui) in desc.location().ui.iter() { for (location, ui) in desc.location().ui.iter() {
if let Ok(ui) = serde_json::to_string(&ui) { if let Ok(ui) = serde_json::to_string(&ui) {
let make_event = |ui: &str| { let make_event = |ui: &str| {
let mut m = HashMap::new(); let mut m = HashMap::new();
m.insert("name", MSG_TO_UI_TYPE_PLUGIN_RELOAD); m.insert("name", MSG_TO_UI_TYPE_PLUGIN_RELOAD);
m.insert("id", desc.id()); m.insert("id", &desc.meta().id);
m.insert("location", &location); m.insert("location", &location);
// Do not depend on the "location" and plugin desc on the ui side. // Do not depend on the "location" and plugin desc on the ui side.
// Send the ui field to ensure the ui is valid. // 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>) { pub(super) fn get_plugin_infos() -> Arc<RwLock<HashMap<String, PluginInfo>>> {
// This function is rarely used. There's no need to care about serialization efficiency here. PLUGIN_INFO.clone()
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_desc_conf(id: &str) -> Option<super::desc::Config> { 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) .get(id)
.map(|info| info.desc.config().clone()) .map(|info| info.desc.config().clone())
} }
pub(super) fn get_version(id: &str) -> Option<String> {
PLUGIN_INFO
.read()
.unwrap()
.get(id)
.map(|info| info.desc.meta().version.clone())
}

View File

@ -394,6 +394,7 @@ pub async fn start_server(is_server: bool) {
} }
if is_server { if is_server {
crate::common::set_server_running(true);
std::thread::spawn(move || { std::thread::spawn(move || {
if let Err(err) = crate::ipc::start("") { if let Err(err) = crate::ipc::start("") {
log::error!("Failed to start ipc: {}", err); log::error!("Failed to start ipc: {}", err);

View File

@ -6,7 +6,7 @@ use crate::common::IS_X11;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use dispatch::Queue; use dispatch::Queue;
use enigo::{Enigo, Key, KeyboardControllable, MouseButton, MouseControllable}; 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}; use rdev::{self, EventType, Key as RdevKey, KeyCode, RawKey};
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use rdev::{CGEventSourceStateID, CGEventTapLocation, VirtualInput}; use rdev::{CGEventSourceStateID, CGEventTapLocation, VirtualInput};
@ -299,8 +299,7 @@ fn run_cursor(sp: MouseCursorService, state: &mut StateCursor) -> ResultType<()>
msg = cached.clone(); msg = cached.clone();
} else { } else {
let mut data = crate::get_cursor_data(hcursor)?; let mut data = crate::get_cursor_data(hcursor)?;
data.colors = data.colors = hbb_common::compress::compress(&data.colors[..]).into();
hbb_common::compress::compress(&data.colors[..], COMPRESS_LEVEL).into();
let mut tmp = Message::new(); let mut tmp = Message::new();
tmp.set_cursor_data(data); tmp.set_cursor_data(data);
msg = Arc::new(tmp); msg = Arc::new(tmp);