Merge pull request #2051 from fufesou/save_remote_menubar_pin_2

Save remote menubar pin 2
This commit is contained in:
RustDesk 2022-11-11 07:59:54 +08:00 committed by GitHub
commit 03e7b48d6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 430 additions and 240 deletions

View File

@ -15,7 +15,6 @@ import 'package:flutter_hbb/main.dart';
import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/models/peer_model.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uni_links/uni_links.dart'; import 'package:uni_links/uni_links.dart';
import 'package:uni_links_desktop/uni_links_desktop.dart'; import 'package:uni_links_desktop/uni_links_desktop.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
@ -205,18 +204,17 @@ class MyTheme {
); );
static ThemeMode getThemeModePreference() { static ThemeMode getThemeModePreference() {
return themeModeFromString( return themeModeFromString(bind.mainGetLocalOption(key: kCommConfKeyTheme));
Get.find<SharedPreferences>().getString("themeMode") ?? "");
} }
static void changeDarkMode(ThemeMode mode) { static void changeDarkMode(ThemeMode mode) {
final preference = getThemeModePreference(); final preference = getThemeModePreference();
if (preference != mode) { if (preference != mode) {
if (mode == ThemeMode.system) { if (mode == ThemeMode.system) {
Get.find<SharedPreferences>().setString("themeMode", ""); bind.mainSetLocalOption(key: kCommConfKeyTheme, value: '');
} else { } else {
Get.find<SharedPreferences>() bind.mainSetLocalOption(
.setString("themeMode", mode.toShortString()); key: kCommConfKeyTheme, value: mode.toShortString());
} }
Get.changeThemeMode(mode); Get.changeThemeMode(mode);
if (desktopType == DesktopType.main) { if (desktopType == DesktopType.main) {
@ -1026,8 +1024,8 @@ Future<void> saveWindowPosition(WindowType type, {int? windowId}) async {
final isMaximized = await windowManager.isMaximized(); final isMaximized = await windowManager.isMaximized();
final pos = LastWindowPosition( final pos = LastWindowPosition(
sz.width, sz.height, position.dx, position.dy, isMaximized); sz.width, sz.height, position.dx, position.dy, isMaximized);
await Get.find<SharedPreferences>() await bind.setLocalFlutterConfig(
.setString(kWindowPrefix + type.name, pos.toString()); k: kWindowPrefix + type.name, v: pos.toString());
break; break;
default: default:
final wc = WindowController.fromWindowId(windowId!); final wc = WindowController.fromWindowId(windowId!);
@ -1037,9 +1035,10 @@ Future<void> saveWindowPosition(WindowType type, {int? windowId}) async {
final isMaximized = await wc.isMaximized(); final isMaximized = await wc.isMaximized();
final pos = LastWindowPosition( final pos = LastWindowPosition(
sz.width, sz.height, position.dx, position.dy, isMaximized); sz.width, sz.height, position.dx, position.dy, isMaximized);
debugPrint("saving frame: ${windowId}: ${pos.width}/${pos.height}, offset:${pos.offsetWidth}/${pos.offsetHeight}"); debugPrint(
await Get.find<SharedPreferences>() "saving frame: $windowId: ${pos.width}/${pos.height}, offset:${pos.offsetWidth}/${pos.offsetHeight}");
.setString(kWindowPrefix + type.name, pos.toString()); await bind.setLocalFlutterConfig(
k: kWindowPrefix + type.name, v: pos.toString());
break; break;
} }
} }
@ -1109,7 +1108,7 @@ Future<Offset?> _adjustRestoreMainWindowOffset(
.toDouble(); .toDouble();
if (isDesktop || isWebDesktop) { if (isDesktop || isWebDesktop) {
for(final screen in await window_size.getScreenList()) { for (final screen in await window_size.getScreenList()) {
frameLeft = min(screen.visibleFrame.left, frameLeft); frameLeft = min(screen.visibleFrame.left, frameLeft);
frameTop = min(screen.visibleFrame.top, frameTop); frameTop = min(screen.visibleFrame.top, frameTop);
frameRight = max(screen.visibleFrame.right, frameRight); frameRight = max(screen.visibleFrame.right, frameRight);
@ -1136,13 +1135,7 @@ Future<bool> restoreWindowPosition(WindowType type, {int? windowId}) async {
debugPrint( debugPrint(
"Error: windowId cannot be null when saving positions for sub window"); "Error: windowId cannot be null when saving positions for sub window");
} }
final pos = final pos = bind.getLocalFlutterConfig(k: kWindowPrefix + type.name);
Get.find<SharedPreferences>().getString(kWindowPrefix + type.name);
if (pos == null) {
debugPrint("no window position saved, ignore restore");
return false;
}
var lpos = LastWindowPosition.loadFromString(pos); var lpos = LastWindowPosition.loadFromString(pos);
if (lpos == null) { if (lpos == null) {
debugPrint("window position saved, but cannot be parsed"); debugPrint("window position saved, but cannot be parsed");
@ -1175,7 +1168,8 @@ Future<bool> restoreWindowPosition(WindowType type, {int? windowId}) async {
await _adjustRestoreMainWindowSize(lpos.width, lpos.height); await _adjustRestoreMainWindowSize(lpos.width, lpos.height);
final offset = await _adjustRestoreMainWindowOffset( final offset = await _adjustRestoreMainWindowOffset(
lpos.offsetWidth, lpos.offsetHeight); lpos.offsetWidth, lpos.offsetHeight);
debugPrint("restore lpos: ${size.width}/${size.height}, offset:${offset?.dx}/${offset?.dy}"); debugPrint(
"restore lpos: ${size.width}/${size.height}, offset:${offset?.dx}/${offset?.dy}");
if (offset == null) { if (offset == null) {
await wc.center(); await wc.center();
} else { } else {
@ -1327,8 +1321,7 @@ void connect(BuildContext context, String id,
Future<Map<String, String>> getHttpHeaders() async { Future<Map<String, String>> getHttpHeaders() async {
return { return {
'Authorization': 'Authorization': 'Bearer ${bind.mainGetLocalOption(key: 'access_token')}'
'Bearer ${await bind.mainGetLocalOption(key: 'access_token')}'
}; };
} }

View File

@ -22,27 +22,26 @@ class _PeerTabPageState extends State<PeerTabPage>
@override @override
void initState() { void initState() {
() async { setPeer();
await bind.mainGetLocalOption(key: 'peer-tab-index').then((value) {
if (value == '') return;
final tab = int.parse(value);
_tabIndex.value = tab;
});
await bind.mainGetLocalOption(key: 'peer-card-ui-type').then((value) {
if (value == '') return;
final tab = int.parse(value);
peerCardUiType.value =
tab == PeerUiType.list.index ? PeerUiType.list : PeerUiType.grid;
});
}();
super.initState(); super.initState();
} }
setPeer() {
final index = bind.getLocalFlutterConfig(k: 'peer-tab-index');
if (index == '') return;
_tabIndex.value = int.parse(index);
final uiType = bind.getLocalFlutterConfig(k: 'peer-card-ui-type');
if (uiType == '') return;
peerCardUiType.value = int.parse(uiType) == PeerUiType.list.index
? PeerUiType.list
: PeerUiType.grid;
}
// hard code for now // hard code for now
Future<void> _handleTabSelection(int index) async { Future<void> _handleTabSelection(int index) async {
_tabIndex.value = index; _tabIndex.value = index;
await bind.mainSetLocalOption( await bind.setLocalFlutterConfig(k: 'peer-tab-index', v: index.toString());
key: 'peer-tab-index', value: index.toString());
switch (index) { switch (index) {
case 0: case 0:
bind.mainLoadRecentPeers(); bind.mainLoadRecentPeers();
@ -148,9 +147,8 @@ class _PeerTabPageState extends State<PeerTabPage>
decoration: peerCardUiType.value == type ? activeDeco : null, decoration: peerCardUiType.value == type ? activeDeco : null,
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
await bind.mainSetLocalOption( await bind.setLocalFlutterConfig(
key: 'peer-card-ui-type', k: 'peer-card-ui-type', v: type.index.toString());
value: type.index.toString());
peerCardUiType.value = type; peerCardUiType.value = type;
}, },
child: Icon( child: Icon(

View File

@ -56,7 +56,11 @@ var kWindowEdgeSize = Platform.isWindows ? 1.0 : 5.0;
const kWindowBorderWidth = 1.0; const kWindowBorderWidth = 1.0;
const kDesktopMenuPadding = EdgeInsets.only(left: 12.0, right: 3.0); const kDesktopMenuPadding = EdgeInsets.only(left: 12.0, right: 3.0);
const kInvalidValueStr = "InvalidValueStr"; const kInvalidValueStr = 'InvalidValueStr';
// Config key shared by flutter and other ui.
const kCommConfKeyTheme = 'theme';
const kCommConfKeyLang = 'lang';
const kMobilePageConstraints = BoxConstraints(maxWidth: 600); const kMobilePageConstraints = BoxConstraints(maxWidth: 600);

View File

@ -5,6 +5,7 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; 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/desktop/widgets/login.dart'; import 'package:flutter_hbb/desktop/widgets/login.dart';
@ -30,8 +31,8 @@ const double _kListViewBottomMargin = 15;
const double _kTitleFontSize = 20; const double _kTitleFontSize = 20;
const double _kContentFontSize = 15; const double _kContentFontSize = 15;
const Color _accentColor = MyTheme.accent; const Color _accentColor = MyTheme.accent;
const String _kSettingPageControllerTag = "settingPageController"; const String _kSettingPageControllerTag = 'settingPageController';
const String _kSettingPageIndexTag = "settingPageIndex"; const String _kSettingPageIndexTag = 'settingPageIndex';
class _TabInfo { class _TabInfo {
late final String label; late final String label;
@ -250,19 +251,19 @@ class _GeneralState extends State<_General> {
return _Card(title: 'Theme', children: [ return _Card(title: 'Theme', children: [
_Radio<String>(context, _Radio<String>(context,
value: "light", value: 'light',
groupValue: current, groupValue: current,
label: "Light", label: 'Light',
onChanged: onChanged), onChanged: onChanged),
_Radio<String>(context, _Radio<String>(context,
value: "dark", value: 'dark',
groupValue: current, groupValue: current,
label: "Dark", label: 'Dark',
onChanged: onChanged), onChanged: onChanged),
_Radio<String>(context, _Radio<String>(context,
value: "system", value: 'system',
groupValue: current, groupValue: current,
label: "Follow System", label: 'Follow System',
onChanged: onChanged), onChanged: onChanged),
]); ]);
} }
@ -286,8 +287,8 @@ class _GeneralState extends State<_General> {
Widget audio(BuildContext context) { Widget audio(BuildContext context) {
String getDefault() { String getDefault() {
if (Platform.isWindows) return "System Sound"; if (Platform.isWindows) return 'System Sound';
return ""; return '';
} }
Future<String> getValue() async { Future<String> getValue() async {
@ -300,7 +301,7 @@ class _GeneralState extends State<_General> {
} }
setDevice(String device) { setDevice(String device) {
if (device == getDefault()) device = ""; if (device == getDefault()) device = '';
bind.mainSetOption(key: 'audio-input', value: device); bind.mainSetOption(key: 'audio-input', value: device);
} }
@ -353,7 +354,7 @@ class _GeneralState extends State<_General> {
'allow-auto-record-incoming'), 'allow-auto-record-incoming'),
Row( Row(
children: [ children: [
Text('${translate('Directory')}:'), Text('${translate("Directory")}:'),
Expanded( Expanded(
child: GestureDetector( child: GestureDetector(
onTap: canlaunch ? () => launchUrl(Uri.file(dir)) : null, onTap: canlaunch ? () => launchUrl(Uri.file(dir)) : null,
@ -386,26 +387,26 @@ class _GeneralState extends State<_General> {
Widget language() { Widget language() {
return _futureBuilder(future: () async { return _futureBuilder(future: () async {
String langs = await bind.mainGetLangs(); String langs = await bind.mainGetLangs();
String lang = await bind.mainGetLocalOption(key: "lang"); String lang = bind.mainGetLocalOption(key: kCommConfKeyLang);
return {"langs": langs, "lang": lang}; return {'langs': langs, 'lang': lang};
}(), hasData: (res) { }(), hasData: (res) {
Map<String, String> data = res as Map<String, String>; Map<String, String> data = res as Map<String, String>;
List<dynamic> langsList = jsonDecode(data["langs"]!); List<dynamic> langsList = jsonDecode(data['langs']!);
Map<String, String> langsMap = {for (var v in langsList) v[0]: v[1]}; Map<String, String> langsMap = {for (var v in langsList) v[0]: v[1]};
List<String> keys = langsMap.keys.toList(); List<String> keys = langsMap.keys.toList();
List<String> values = langsMap.values.toList(); List<String> values = langsMap.values.toList();
keys.insert(0, ""); keys.insert(0, '');
values.insert(0, "Default"); values.insert(0, 'Default');
String currentKey = data["lang"]!; String currentKey = data['lang']!;
if (!keys.contains(currentKey)) { if (!keys.contains(currentKey)) {
currentKey = ""; currentKey = '';
} }
return _ComboBox( return _ComboBox(
keys: keys, keys: keys,
values: values, values: values,
initialKey: currentKey, initialKey: currentKey,
onChanged: (key) async { onChanged: (key) async {
await bind.mainSetLocalOption(key: "lang", value: key); await bind.mainSetLocalOption(key: kCommConfKeyLang, value: key);
reloadAllWindows(); reloadAllWindows();
bind.mainChangeLanguage(lang: key); bind.mainChangeLanguage(lang: key);
}, },
@ -585,9 +586,9 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
kUseBothPasswords, kUseBothPasswords,
]; ];
List<String> values = [ List<String> values = [
translate("Use temporary password"), translate('Use temporary password'),
translate("Use permanent password"), translate('Use permanent password'),
translate("Use both passwords"), translate('Use both passwords'),
]; ];
bool tmpEnabled = model.verificationMethod != kUsePermanentPassword; bool tmpEnabled = model.verificationMethod != kUsePermanentPassword;
bool permEnabled = model.verificationMethod != kUseTemporaryPassword; bool permEnabled = model.verificationMethod != kUseTemporaryPassword;
@ -830,12 +831,12 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
// Setting page is not modal, oldOptions should only be used when getting options, never when setting. // Setting page is not modal, oldOptions should only be used when getting options, never when setting.
Map<String, dynamic> oldOptions = jsonDecode(data! as String); Map<String, dynamic> oldOptions = jsonDecode(data! as String);
old(String key) { old(String key) {
return (oldOptions[key] ?? "").trim(); return (oldOptions[key] ?? '').trim();
} }
RxString idErrMsg = "".obs; RxString idErrMsg = ''.obs;
RxString relayErrMsg = "".obs; RxString relayErrMsg = ''.obs;
RxString apiErrMsg = "".obs; RxString apiErrMsg = ''.obs;
var idController = var idController =
TextEditingController(text: old('custom-rendezvous-server')); TextEditingController(text: old('custom-rendezvous-server'));
var relayController = TextEditingController(text: old('relay-server')); var relayController = TextEditingController(text: old('relay-server'));
@ -864,9 +865,9 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
} }
if (apiServer.isNotEmpty) { if (apiServer.isNotEmpty) {
if (!apiServer.startsWith('http://') || if (!apiServer.startsWith('http://') ||
!apiServer.startsWith("https://")) { !apiServer.startsWith('https://')) {
apiErrMsg.value = apiErrMsg.value =
"${translate("API Server")}: ${translate("invalid_http")}"; '${translate("API Server")}: ${translate("invalid_http")}';
return false; return false;
} }
} }
@ -893,7 +894,7 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
import() { import() {
Clipboard.getData(Clipboard.kTextPlain).then((value) { Clipboard.getData(Clipboard.kTextPlain).then((value) {
TextEditingController mytext = TextEditingController(); TextEditingController mytext = TextEditingController();
String? aNullableString = ""; String? aNullableString = '';
aNullableString = value?.text; aNullableString = value?.text;
mytext.text = aNullableString.toString(); mytext.text = aNullableString.toString();
if (mytext.text.isNotEmpty) { if (mytext.text.isNotEmpty) {
@ -918,13 +919,13 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
} }
}); });
} else { } else {
showToast(translate("Invalid server configuration")); showToast(translate('Invalid server configuration'));
} }
} catch (e) { } catch (e) {
showToast(translate("Invalid server configuration")); showToast(translate('Invalid server configuration'));
} }
} else { } else {
showToast(translate("Clipboard is empty")); showToast(translate('Clipboard is empty'));
} }
}); });
} }
@ -936,7 +937,7 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
config['ApiServer'] = apiController.text.trim(); config['ApiServer'] = apiController.text.trim();
config['Key'] = keyController.text.trim(); config['Key'] = keyController.text.trim();
Clipboard.setData(ClipboardData(text: jsonEncode(config))); Clipboard.setData(ClipboardData(text: jsonEncode(config)));
showToast(translate("Export server configuration successfully")); showToast(translate('Export server configuration successfully'));
} }
bool secure = !enabled; bool secure = !enabled;
@ -962,7 +963,7 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
Obx(() => _LabeledTextField(context, 'API Server', apiController, Obx(() => _LabeledTextField(context, 'API Server', apiController,
apiErrMsg.value, enabled, secure)), apiErrMsg.value, enabled, secure)),
_LabeledTextField( _LabeledTextField(
context, 'Key', keyController, "", enabled, secure), context, 'Key', keyController, '', enabled, secure),
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [_Button('Apply', submit, enabled: enabled)], children: [_Button('Apply', submit, enabled: enabled)],
@ -1039,28 +1040,28 @@ class _AboutState extends State<_About> {
child: SingleChildScrollView( child: SingleChildScrollView(
controller: scrollController, controller: scrollController,
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
child: _Card(title: "About RustDesk", children: [ child: _Card(title: 'About RustDesk', children: [
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Text("Version: $version").marginSymmetric(vertical: 4.0), Text('Version: $version').marginSymmetric(vertical: 4.0),
InkWell( InkWell(
onTap: () { onTap: () {
launchUrlString("https://rustdesk.com/privacy"); launchUrlString('https://rustdesk.com/privacy');
}, },
child: const Text( child: const Text(
"Privacy Statement", 'Privacy Statement',
style: linkStyle, style: linkStyle,
).marginSymmetric(vertical: 4.0)), ).marginSymmetric(vertical: 4.0)),
InkWell( InkWell(
onTap: () { onTap: () {
launchUrlString("https://rustdesk.com"); launchUrlString('https://rustdesk.com');
}, },
child: const Text( child: const Text(
"Website", 'Website',
style: linkStyle, style: linkStyle,
).marginSymmetric(vertical: 4.0)), ).marginSymmetric(vertical: 4.0)),
Container( Container(
@ -1074,11 +1075,11 @@ class _AboutState extends State<_About> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Copyright &copy; 2022 Purslane Ltd.\n$license", 'Copyright &copy; 2022 Purslane Ltd.\n$license',
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
), ),
const Text( const Text(
"Made with heart in this chaotic world!", 'Made with heart in this chaotic world!',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
color: Colors.white), color: Colors.white),
@ -1472,10 +1473,10 @@ class _ComboBox extends StatelessWidget {
void changeSocks5Proxy() async { void changeSocks5Proxy() async {
var socks = await bind.mainGetSocks(); var socks = await bind.mainGetSocks();
String proxy = ""; String proxy = '';
String proxyMsg = ""; String proxyMsg = '';
String username = ""; String username = '';
String password = ""; String password = '';
if (socks.length == 3) { if (socks.length == 3) {
proxy = socks[0]; proxy = socks[0];
username = socks[1]; username = socks[1];
@ -1489,7 +1490,7 @@ void changeSocks5Proxy() async {
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
submit() async { submit() async {
setState(() { setState(() {
proxyMsg = ""; proxyMsg = '';
isInProgress = true; isInProgress = true;
}); });
cancel() { cancel() {
@ -1517,7 +1518,7 @@ void changeSocks5Proxy() async {
} }
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate("Socks5 Proxy")), title: Text(translate('Socks5 Proxy')),
content: ConstrainedBox( content: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 500), constraints: const BoxConstraints(minWidth: 500),
child: Column( child: Column(
@ -1530,7 +1531,7 @@ void changeSocks5Proxy() async {
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Hostname')}:") child: Text('${translate("Hostname")}:')
.marginOnly(bottom: 16.0)), .marginOnly(bottom: 16.0)),
const SizedBox( const SizedBox(
width: 24.0, width: 24.0,
@ -1553,7 +1554,7 @@ void changeSocks5Proxy() async {
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Username')}:") child: Text('${translate("Username")}:')
.marginOnly(bottom: 16.0)), .marginOnly(bottom: 16.0)),
const SizedBox( const SizedBox(
width: 24.0, width: 24.0,
@ -1575,7 +1576,7 @@ void changeSocks5Proxy() async {
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Password')}:") child: Text('${translate("Password")}:')
.marginOnly(bottom: 16.0)), .marginOnly(bottom: 16.0)),
const SizedBox( const SizedBox(
width: 24.0, width: 24.0,
@ -1599,8 +1600,8 @@ void changeSocks5Proxy() async {
), ),
), ),
actions: [ actions: [
TextButton(onPressed: close, child: Text(translate("Cancel"))), TextButton(onPressed: close, child: Text(translate('Cancel'))),
TextButton(onPressed: submit, child: Text(translate("OK"))), TextButton(onPressed: submit, child: Text(translate('OK'))),
], ],
onSubmit: submit, onSubmit: submit,
onCancel: close, onCancel: close,

View File

@ -28,14 +28,14 @@ class RemotePage extends StatefulWidget {
RemotePage({ RemotePage({
Key? key, Key? key,
required this.id, required this.id,
required this.menubarState,
}) : super(key: key); }) : super(key: key);
final String id; final String id;
final MenubarState menubarState;
final SimpleWrapper<State<RemotePage>?> _lastState = SimpleWrapper(null); final SimpleWrapper<State<RemotePage>?> _lastState = SimpleWrapper(null);
FFI get ffi => (_lastState.value! as _RemotePageState)._ffi; FFI get ffi => (_lastState.value! as _RemotePageState)._ffi;
RxBool get showMenubar =>
(_lastState.value! as _RemotePageState)._showMenubar;
@override @override
State<RemotePage> createState() { State<RemotePage> createState() {
@ -50,7 +50,6 @@ class _RemotePageState extends State<RemotePage>
Timer? _timer; Timer? _timer;
String keyboardMode = "legacy"; String keyboardMode = "legacy";
final _cursorOverImage = false.obs; final _cursorOverImage = false.obs;
final _showMenubar = false.obs;
late RxBool _showRemoteCursor; late RxBool _showRemoteCursor;
late RxBool _remoteCursorMoved; late RxBool _remoteCursorMoved;
late RxBool _keyboardEnabled; late RxBool _keyboardEnabled;
@ -239,7 +238,7 @@ class _RemotePageState extends State<RemotePage>
paints.add(RemoteMenubar( paints.add(RemoteMenubar(
id: widget.id, id: widget.id,
ffi: _ffi, ffi: _ffi,
show: _showMenubar, state: widget.menubarState,
onEnterOrLeaveImageSetter: (func) => _onEnterOrLeaveImage4Menubar = func, onEnterOrLeaveImageSetter: (func) => _onEnterOrLeaveImage4Menubar = func,
onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Menubar = null, onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Menubar = null,
)); ));

View File

@ -9,6 +9,7 @@ import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/desktop/pages/remote_page.dart'; import 'package:flutter_hbb/desktop/pages/remote_page.dart';
import 'package:flutter_hbb/desktop/widgets/remote_menubar.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart' import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart'
as mod_menu; as mod_menu;
@ -43,9 +44,12 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
static const IconData selectedIcon = Icons.desktop_windows_sharp; static const IconData selectedIcon = Icons.desktop_windows_sharp;
static const IconData unselectedIcon = Icons.desktop_windows_outlined; static const IconData unselectedIcon = Icons.desktop_windows_outlined;
late MenubarState _menubarState;
var connectionMap = RxList<Widget>.empty(growable: true); var connectionMap = RxList<Widget>.empty(growable: true);
_ConnectionTabPageState(Map<String, dynamic> params) { _ConnectionTabPageState(Map<String, dynamic> params) {
_menubarState = MenubarState();
RemoteCountState.init(); RemoteCountState.init();
final peerId = params['id']; final peerId = params['id'];
if (peerId != null) { if (peerId != null) {
@ -59,6 +63,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
page: RemotePage( page: RemotePage(
key: ValueKey(peerId), key: ValueKey(peerId),
id: peerId, id: peerId,
menubarState: _menubarState,
), ),
)); ));
_update_remote_count(); _update_remote_count();
@ -88,7 +93,11 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
selectedIcon: selectedIcon, selectedIcon: selectedIcon,
unselectedIcon: unselectedIcon, unselectedIcon: unselectedIcon,
onTabCloseButton: () => tabController.closeBy(id), onTabCloseButton: () => tabController.closeBy(id),
page: RemotePage(key: ValueKey(id), id: id), page: RemotePage(
key: ValueKey(id),
id: id,
menubarState: _menubarState,
),
)); ));
} else if (call.method == "onDestroy") { } else if (call.method == "onDestroy") {
tabController.clear(); tabController.clear();
@ -99,6 +108,12 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
}); });
} }
@override
void dispose() {
super.dispose();
_menubarState.save();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final tabWidget = Container( final tabWidget = Container(
@ -177,7 +192,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
); );
} }
// to-do: some dup code to ../widgets/remote_menubar // Note: Some dup code to ../widgets/remote_menubar
Widget _tabMenuBuilder(String key, CancelFunc cancelFunc) { Widget _tabMenuBuilder(String key, CancelFunc cancelFunc) {
final List<MenuEntryBase<String>> menu = []; final List<MenuEntryBase<String>> menu = [];
const EdgeInsets padding = EdgeInsets.only(left: 8.0, right: 5.0); const EdgeInsets padding = EdgeInsets.only(left: 8.0, right: 5.0);
@ -187,7 +202,6 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
final ffi = remotePage.ffi; final ffi = remotePage.ffi;
final pi = ffi.ffiModel.pi; final pi = ffi.ffiModel.pi;
final perms = ffi.ffiModel.permissions; final perms = ffi.ffiModel.permissions;
final showMenuBar = remotePage.showMenubar;
menu.addAll([ menu.addAll([
MenuEntryButton<String>( MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text( childBuilder: (TextStyle? style) => Text(
@ -202,11 +216,12 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
), ),
MenuEntryButton<String>( MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Obx(() => Text( childBuilder: (TextStyle? style) => Obx(() => Text(
translate(showMenuBar.isTrue ? 'Hide Menubar' : 'Show Menubar'), translate(
_menubarState.show.isTrue ? 'Hide Menubar' : 'Show Menubar'),
style: style, style: style,
)), )),
proc: () { proc: () {
showMenuBar.value = !showMenuBar.value; _menubarState.switchShow();
cancelFunc(); cancelFunc();
}, },
padding: padding, padding: padding,

View File

@ -21,6 +21,68 @@ import '../../common/shared_state.dart';
import './popup_menu.dart'; import './popup_menu.dart';
import './material_mod_popup_menu.dart' as mod_menu; import './material_mod_popup_menu.dart' as mod_menu;
class MenubarState {
final kStoreKey = "remoteMenubarState";
late RxBool show;
late RxBool _pin;
MenubarState() {
final s = bind.getLocalFlutterConfig(k: kStoreKey);
if (s.isEmpty) {
_initSet(false, false);
return;
}
try {
final m = jsonDecode(s);
if (m == null) {
_initSet(false, false);
} else {
_initSet(m['pin'] ?? false, m['pin'] ?? false);
}
} catch (e) {
debugPrint('Failed to decode menubar state ${e.toString()}');
_initSet(false, false);
}
}
_initSet(bool s, bool p) {
show = RxBool(s);
_pin = RxBool(p);
}
bool get pin => _pin.value;
switchShow() async {
show.value = !show.value;
}
setShow(bool v) async {
if (show.value != v) {
show.value = v;
}
}
switchPin() async {
_pin.value = !_pin.value;
// Save everytime changed, as this func will not be called frequently
await save();
}
setPin(bool v) async {
if (_pin.value != v) {
_pin.value = v;
// Save everytime changed, as this func will not be called frequently
await save();
}
}
save() async {
bind.setLocalFlutterConfig(
k: kStoreKey, v: jsonEncode({'pin': _pin.value}));
}
}
class _MenubarTheme { class _MenubarTheme {
static const Color commonColor = MyTheme.accent; static const Color commonColor = MyTheme.accent;
// kMinInteractiveDimension // kMinInteractiveDimension
@ -31,7 +93,7 @@ class _MenubarTheme {
class RemoteMenubar extends StatefulWidget { class RemoteMenubar extends StatefulWidget {
final String id; final String id;
final FFI ffi; final FFI ffi;
final RxBool show; final MenubarState state;
final Function(Function(bool)) onEnterOrLeaveImageSetter; final Function(Function(bool)) onEnterOrLeaveImageSetter;
final Function() onEnterOrLeaveImageCleaner; final Function() onEnterOrLeaveImageCleaner;
@ -39,7 +101,7 @@ class RemoteMenubar extends StatefulWidget {
Key? key, Key? key,
required this.id, required this.id,
required this.ffi, required this.ffi,
required this.show, required this.state,
required this.onEnterOrLeaveImageSetter, required this.onEnterOrLeaveImageSetter,
required this.onEnterOrLeaveImageCleaner, required this.onEnterOrLeaveImageCleaner,
}) : super(key: key); }) : super(key: key);
@ -51,7 +113,6 @@ class RemoteMenubar extends StatefulWidget {
class _RemoteMenubarState extends State<RemoteMenubar> { class _RemoteMenubarState extends State<RemoteMenubar> {
final Rx<Color> _hideColor = Colors.white12.obs; final Rx<Color> _hideColor = Colors.white12.obs;
final _rxHideReplay = rxdart.ReplaySubject<int>(); final _rxHideReplay = rxdart.ReplaySubject<int>();
final _pinMenubar = false.obs;
bool _isCursorOverImage = false; bool _isCursorOverImage = false;
window_size.Screen? _screen; window_size.Screen? _screen;
@ -63,7 +124,8 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
setState(() {}); setState(() {});
} }
RxBool get show => widget.show; RxBool get show => widget.state.show;
bool get pin => widget.state.pin;
@override @override
initState() { initState() {
@ -82,7 +144,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
.throttleTime(const Duration(milliseconds: 5000), .throttleTime(const Duration(milliseconds: 5000),
trailing: true, leading: false) trailing: true, leading: false)
.listen((int v) { .listen((int v) {
if (_pinMenubar.isFalse && show.isTrue && _isCursorOverImage) { if (!pin && show.isTrue && _isCursorOverImage) {
show.value = false; show.value = false;
} }
}); });
@ -196,18 +258,15 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
Widget _buildPinMenubar(BuildContext context) { Widget _buildPinMenubar(BuildContext context) {
return Obx(() => IconButton( return Obx(() => IconButton(
tooltip: tooltip: translate(pin ? 'Unpin menubar' : 'Pin menubar'),
translate(_pinMenubar.isTrue ? 'Unpin menubar' : 'Pin menubar'),
onPressed: () { onPressed: () {
_pinMenubar.value = !_pinMenubar.value; widget.state.switchPin();
}, },
icon: Obx(() => Transform.rotate( icon: Obx(() => Transform.rotate(
angle: _pinMenubar.isTrue ? math.pi / 4 : 0, angle: pin ? math.pi / 4 : 0,
child: Icon( child: Icon(
Icons.push_pin, Icons.push_pin,
color: _pinMenubar.isTrue color: pin ? _MenubarTheme.commonColor : Colors.grey,
? _MenubarTheme.commonColor
: Colors.grey,
))), ))),
)); ));
} }

View File

@ -15,7 +15,6 @@ import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:bot_toast/bot_toast.dart'; import 'package:bot_toast/bot_toast.dart';
@ -97,7 +96,6 @@ Future<void> main(List<String> args) async {
Future<void> initEnv(String appType) async { Future<void> initEnv(String appType) async {
// global shared preference // global shared preference
await Get.putAsync(() => SharedPreferences.getInstance());
await platformFFI.init(appType); await platformFFI.init(appType);
// global FFI, use this **ONLY** for global configuration // global FFI, use this **ONLY** for global configuration
// for convenience, use global FFI on mobile platform // for convenience, use global FFI on mobile platform

View File

@ -16,7 +16,6 @@ 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/utils/multi_window_manager.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:image/image.dart' as img2; import 'package:image/image.dart' as img2;
import 'package:flutter_custom_cursor/flutter_custom_cursor.dart'; import 'package:flutter_custom_cursor/flutter_custom_cursor.dart';
@ -1219,7 +1218,7 @@ class FFI {
Future<void> close() async { Future<void> close() async {
chatModel.close(); chatModel.close();
if (imageModel.image != null && !isWebDesktop) { if (imageModel.image != null && !isWebDesktop) {
await savePreference(id, cursorModel.x, cursorModel.y, canvasModel.x, await setCanvasConfig(id, cursorModel.x, cursorModel.y, canvasModel.x,
canvasModel.y, canvasModel.scale, ffiModel.pi.currentDisplay); canvasModel.y, canvasModel.scale, ffiModel.pi.currentDisplay);
} }
bind.sessionClose(id: id); bind.sessionClose(id: id);
@ -1267,9 +1266,10 @@ class PeerInfo {
List<Display> displays = []; List<Display> displays = [];
} }
Future<void> savePreference(String id, double xCursor, double yCursor, const canvasKey = 'canvas';
Future<void> setCanvasConfig(String id, double xCursor, double yCursor,
double xCanvas, double yCanvas, double scale, int currentDisplay) async { double xCanvas, double yCanvas, double scale, int currentDisplay) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final p = <String, dynamic>{}; final p = <String, dynamic>{};
p['xCursor'] = xCursor; p['xCursor'] = xCursor;
p['yCursor'] = yCursor; p['yCursor'] = yCursor;
@ -1277,25 +1277,27 @@ Future<void> savePreference(String id, double xCursor, double yCursor,
p['yCanvas'] = yCanvas; p['yCanvas'] = yCanvas;
p['scale'] = scale; p['scale'] = scale;
p['currentDisplay'] = currentDisplay; p['currentDisplay'] = currentDisplay;
prefs.setString('peer$id', json.encode(p)); await bind.sessionSetFlutterConfig(id: id, k: canvasKey, v: jsonEncode(p));
} }
Future<Map<String, dynamic>?> getPreference(String id) async { Future<Map<String, dynamic>?> getCanvasConfig(String id) async {
if (!isWebDesktop) return null; if (!isWebDesktop) return null;
SharedPreferences prefs = await SharedPreferences.getInstance(); var p = await bind.sessionGetFlutterConfig(id: id, k: canvasKey);
var p = prefs.getString('peer$id'); if (p == null || p.isEmpty) return null;
if (p == null) return null; try {
Map<String, dynamic> m = json.decode(p); Map<String, dynamic> m = json.decode(p);
return m; return m;
} catch (e) {
return null;
}
} }
void removePreference(String id) async { void removePreference(String id) async {
SharedPreferences prefs = await SharedPreferences.getInstance(); await bind.sessionSetFlutterConfig(id: id, k: canvasKey, v: '');
prefs.remove('peer$id');
} }
Future<void> initializeCursorAndCanvas(FFI ffi) async { Future<void> initializeCursorAndCanvas(FFI ffi) async {
var p = await getPreference(ffi.id); var p = await getCanvasConfig(ffi.id);
int currentDisplay = 0; int currentDisplay = 0;
if (p != null) { if (p != null) {
currentDisplay = p['currentDisplay']; currentDisplay = p['currentDisplay'];

View File

@ -8,6 +8,7 @@ class StateGlobal {
bool _fullscreen = false; bool _fullscreen = false;
final RxBool _showTabBar = true.obs; final RxBool _showTabBar = true.obs;
final RxDouble _resizeEdgeSize = 8.0.obs; final RxDouble _resizeEdgeSize = 8.0.obs;
final RxBool showRemoteMenuBar = false.obs;
int get windowId => _windowId; int get windowId => _windowId;
bool get fullscreen => _fullscreen; bool get fullscreen => _fullscreen;

View File

@ -19,7 +19,7 @@ class UserModel {
void refreshCurrentUser() async { void refreshCurrentUser() async {
await getUserName(); await getUserName();
final token = await bind.mainGetLocalOption(key: 'access_token'); final token = bind.mainGetLocalOption(key: 'access_token');
if (token == '') return; if (token == '') return;
final url = await bind.mainGetApiServer(); final url = await bind.mainGetApiServer();
final body = { final body = {
@ -73,7 +73,7 @@ class UserModel {
if (userName.isNotEmpty) { if (userName.isNotEmpty) {
return userName.value; return userName.value;
} }
final userInfo = await bind.mainGetLocalOption(key: 'user_info'); final userInfo = bind.mainGetLocalOption(key: 'user_info');
if (userInfo.trim().isEmpty) { if (userInfo.trim().isEmpty) {
return ''; return '';
} }

View File

@ -40,7 +40,6 @@ dependencies:
#firebase_analytics: ^9.1.5 #firebase_analytics: ^9.1.5
package_info_plus: ^1.4.2 package_info_plus: ^1.4.2
url_launcher: ^6.0.9 url_launcher: ^6.0.9
shared_preferences: ^2.0.6
toggle_switch: ^1.4.0 toggle_switch: ^1.4.0
dash_chat_2: ^0.0.14 dash_chat_2: ^0.0.14
draggable_float_widget: ^0.0.2 draggable_float_widget: ^0.0.2

View File

@ -167,9 +167,12 @@ pub struct PeerConfig {
#[serde(default)] #[serde(default)]
pub show_quality_monitor: bool, pub show_quality_monitor: bool,
// the other scalar value must before this // The other scalar value must before this
#[serde(default)] #[serde(default)]
pub options: HashMap<String, String>, pub options: HashMap<String, String>,
// Various data for flutter ui
#[serde(default)]
pub ui_flutter: HashMap<String, String>,
#[serde(default)] #[serde(default)]
pub info: PeerInfoSerde, pub info: PeerInfoSerde,
#[serde(default)] #[serde(default)]
@ -897,6 +900,9 @@ pub struct LocalConfig {
pub fav: Vec<String>, pub fav: Vec<String>,
#[serde(default)] #[serde(default)]
options: HashMap<String, String>, options: HashMap<String, String>,
// Various data for flutter ui
#[serde(default)]
ui_flutter: HashMap<String, String>,
} }
impl LocalConfig { impl LocalConfig {
@ -968,6 +974,27 @@ impl LocalConfig {
config.store(); config.store();
} }
} }
pub fn get_flutter_config(k: &str) -> String {
if let Some(v) = LOCAL_CONFIG.read().unwrap().ui_flutter.get(k) {
v.clone()
} else {
"".to_owned()
}
}
pub fn set_flutter_config(k: String, v: String) {
let mut config = LOCAL_CONFIG.write().unwrap();
let v2 = if v.is_empty() { None } else { Some(&v) };
if v2 != config.ui_flutter.get(&k) {
if v2.is_none() {
config.ui_flutter.remove(&k);
} else {
config.ui_flutter.insert(k, v);
}
config.store();
}
}
} }
#[derive(Debug, Default, Serialize, Deserialize, Clone)] #[derive(Debug, Default, Serialize, Deserialize, Clone)]

View File

@ -6,6 +6,7 @@ use cpal::{
}; };
use magnum_opus::{Channels::*, Decoder as AudioDecoder}; use magnum_opus::{Channels::*, Decoder as AudioDecoder};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::{ use std::{
collections::HashMap, collections::HashMap,
@ -984,6 +985,32 @@ impl LoginConfigHandler {
self.save_config(config); self.save_config(config);
} }
/// Set a ui config of flutter for handler's [`PeerConfig`].
///
/// # Arguments
///
/// * `k` - key of option
/// * `v` - value of option
pub fn set_ui_flutter(&mut self, k: String, v: String) {
let mut config = self.load_config();
config.ui_flutter.insert(k, v);
self.save_config(config);
}
/// Get a ui config of flutter for handler's [`PeerConfig`].
/// Return String if the option is found, otherwise return "".
///
/// # Arguments
///
/// * `k` - key of option
pub fn get_ui_flutter(&self, k: &str) -> String {
if let Some(v) = self.config.ui_flutter.get(k) {
v.clone()
} else {
"".to_owned()
}
}
/// Toggle an option in the handler. /// Toggle an option in the handler.
/// ///
/// # Arguments /// # Arguments
@ -1947,6 +1974,7 @@ fn decode_id_pk(signed: &[u8], key: &sign::PublicKey) -> ResultType<(String, [u8
} }
} }
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
pub fn disable_keyboard_listening() { pub fn disable_keyboard_listening() {
crate::ui_session_interface::KEYBOARD_HOOKED.store(true, Ordering::SeqCst); crate::ui_session_interface::KEYBOARD_HOOKED.store(true, Ordering::SeqCst);
} }

View File

@ -23,9 +23,10 @@ use hbb_common::rendezvous_proto::ConnType;
use hbb_common::tokio::{ use hbb_common::tokio::{
self, self,
sync::mpsc, sync::mpsc,
sync::Mutex as TokioMutex,
time::{self, Duration, Instant, Interval}, time::{self, Duration, Instant, Interval},
}; };
#[cfg(windows)]
use hbb_common::tokio::sync::Mutex as TokioMutex;
use hbb_common::{ use hbb_common::{
allow_err, allow_err,
message_proto::*, message_proto::*,

View File

@ -122,7 +122,7 @@ pub fn core_main() -> Option<Vec<String>> {
hbb_common::allow_err!(crate::rc::extract_resources(&args[1])); hbb_common::allow_err!(crate::rc::extract_resources(&args[1]));
return None; return None;
} else if args[0] == "--tray" { } else if args[0] == "--tray" {
crate::tray::start_tray(crate::ui_interface::OPTIONS.clone()); crate::tray::start_tray();
return None; return None;
} }
} }
@ -149,12 +149,12 @@ pub fn core_main() -> Option<Vec<String>> {
std::thread::spawn(move || crate::start_server(true)); std::thread::spawn(move || crate::start_server(true));
// to-do: for flutter, starting tray not ready yet, or we can reuse sciter's tray implementation. // to-do: for flutter, starting tray not ready yet, or we can reuse sciter's tray implementation.
} }
#[cfg(all(target_os = "linux"))] #[cfg(target_os = "linux")]
{ {
let handler = std::thread::spawn(move || crate::start_server(true)); let handler = std::thread::spawn(move || crate::start_server(true));
crate::tray::start_tray(crate::ui_interface::OPTIONS.clone()); crate::tray::start_tray();
// revent server exit when encountering errors from tray // revent server exit when encountering errors from tray
handler.join(); hbb_common::allow_err!(handler.join());
} }
} else if args[0] == "--import-config" { } else if args[0] == "--import-config" {
if args.len() == 2 { if args.len() == 2 {
@ -249,5 +249,6 @@ fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<Strin
} }
} }
} }
#[cfg(not(target_os = "linux"))]
return None; return None;
} }

View File

@ -54,6 +54,7 @@ pub extern "C" fn rustdesk_core_main(args_len: *mut c_int) -> *mut *mut c_char {
} }
// https://gist.github.com/iskakaushik/1c5b8aa75c77479c33c4320913eebef6 // https://gist.github.com/iskakaushik/1c5b8aa75c77479c33c4320913eebef6
#[cfg(windows)]
fn rust_args_to_c_args(args: Vec<String>, outlen: *mut c_int) -> *mut *mut c_char { fn rust_args_to_c_args(args: Vec<String>, outlen: *mut c_int) -> *mut *mut c_char {
let mut v = vec![]; let mut v = vec![];
@ -227,6 +228,7 @@ impl InvokeUiSession for FlutterHandler {
id: i32, id: i32,
entries: &Vec<FileEntry>, entries: &Vec<FileEntry>,
path: String, path: String,
#[allow(unused_variables)]
is_local: bool, is_local: bool,
only_count: bool, only_count: bool,
) { ) {

View File

@ -19,12 +19,12 @@ use crate::flutter::{self, SESSIONS};
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
use crate::start_server; use crate::start_server;
use crate::ui_interface::{self, *}; use crate::ui_interface::{self, *};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::ui_session_interface::CUR_SESSION;
use crate::{ use crate::{
client::file_trait::FileManager, client::file_trait::FileManager,
flutter::{make_fd_to_json, session_add, session_start_}, flutter::{make_fd_to_json, session_add, session_start_},
}; };
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::ui_session_interface::CUR_SESSION;
fn initialize(app_dir: &str) { fn initialize(app_dir: &str) {
*config::APP_DIR.write().unwrap() = app_dir.to_owned(); *config::APP_DIR.write().unwrap() = app_dir.to_owned();
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
@ -162,6 +162,28 @@ pub fn session_toggle_option(id: String, value: String) {
} }
} }
pub fn session_get_flutter_config(id: String, k: String) -> Option<String> {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
Some(session.get_flutter_config(k))
} else {
None
}
}
pub fn session_set_flutter_config(id: String, k: String, v: String) {
if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) {
session.set_flutter_config(k, v);
}
}
pub fn get_local_flutter_config(k: String) -> SyncReturn<String> {
SyncReturn(ui_interface::get_local_flutter_config(k))
}
pub fn set_local_flutter_config(k: String, v: String) {
ui_interface::set_local_flutter_config(k, v);
}
pub fn session_get_image_quality(id: String) -> Option<String> { pub fn session_get_image_quality(id: String) -> Option<String> {
if let Some(session) = SESSIONS.read().unwrap().get(&id) { if let Some(session) = SESSIONS.read().unwrap().get(&id) {
Some(session.get_image_quality()) Some(session.get_image_quality())
@ -418,7 +440,7 @@ pub fn session_resume_job(id: String, act_id: i32, is_remote: bool) {
pub fn main_get_sound_inputs() -> Vec<String> { pub fn main_get_sound_inputs() -> Vec<String> {
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
return get_sound_inputs(); return get_sound_inputs();
#[cfg(any(target_os = "android", target_os = "linux"))] #[cfg(any(target_os = "android", target_os = "ios"))]
vec![String::from("")] vec![String::from("")]
} }
@ -537,8 +559,8 @@ pub fn main_post_request(url: String, body: String, header: String) {
post_request(url, body, header) post_request(url, body, header)
} }
pub fn main_get_local_option(key: String) -> String { pub fn main_get_local_option(key: String) -> SyncReturn<String> {
get_local_option(key) SyncReturn(get_local_option(key))
} }
pub fn main_set_local_option(key: String, value: String) { pub fn main_set_local_option(key: String, value: String) {
@ -1021,7 +1043,7 @@ pub fn main_is_installed() -> SyncReturn<bool> {
SyncReturn(is_installed()) SyncReturn(is_installed())
} }
pub fn main_start_grab_keyboard(){ pub fn main_start_grab_keyboard() {
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
crate::ui_session_interface::global_grab_keyboard(); crate::ui_session_interface::global_grab_keyboard();
} }

View File

@ -1,7 +1,7 @@
use super::HbbHttpResponse; use super::HbbHttpResponse;
use hbb_common::{ use hbb_common::{
config::{Config, LocalConfig}, config::{Config, LocalConfig},
log, sleep, tokio, ResultType, log, ResultType,
}; };
use reqwest::blocking::Client; use reqwest::blocking::Client;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};

View File

@ -6,7 +6,9 @@
use dbus::blocking::Connection; use dbus::blocking::Connection;
use dbus_crossroads::{Crossroads, IfaceBuilder}; use dbus_crossroads::{Crossroads, IfaceBuilder};
use hbb_common::{log}; use hbb_common::{log};
use std::{error::Error, fmt, time::Duration, collections::HashMap}; use std::{error::Error, fmt, time::Duration};
#[cfg(feature = "flutter")]
use std::collections::HashMap;
const DBUS_NAME: &str = "org.rustdesk.rustdesk"; const DBUS_NAME: &str = "org.rustdesk.rustdesk";
const DBUS_PREFIX: &str = "/dbus"; const DBUS_PREFIX: &str = "/dbus";
@ -65,7 +67,7 @@ fn handle_client_message(builder: &mut IfaceBuilder<()>) {
DBUS_METHOD_NEW_CONNECTION, DBUS_METHOD_NEW_CONNECTION,
(DBUS_METHOD_NEW_CONNECTION_ID,), (DBUS_METHOD_NEW_CONNECTION_ID,),
(DBUS_METHOD_RETURN,), (DBUS_METHOD_RETURN,),
move |_, _, (peer_id,): (String,)| { move |_, _, (_peer_id,): (String,)| {
#[cfg(feature = "flutter")] #[cfg(feature = "flutter")]
{ {
use crate::flutter::{self, APP_TYPE_MAIN}; use crate::flutter::{self, APP_TYPE_MAIN};
@ -77,7 +79,7 @@ fn handle_client_message(builder: &mut IfaceBuilder<()>) {
{ {
let data = HashMap::from([ let data = HashMap::from([
("name", "new_connection"), ("name", "new_connection"),
("peer_id", peer_id.as_str()) ("peer_id", _peer_id.as_str())
]); ]);
if !stream.add(serde_json::ser::to_string(&data).unwrap_or("".to_string())) { if !stream.add(serde_json::ser::to_string(&data).unwrap_or("".to_string())) {
log::error!("failed to add dbus message to flutter global dbus stream."); log::error!("failed to add dbus message to flutter global dbus stream.");

View File

@ -373,7 +373,7 @@ fn fix_modifiers(modifiers: &[EnumOrUnknown<ControlKey>], en: &mut Enigo, ck: i3
} }
} }
fn is_mouse_active_by_conn(conn: i32) -> bool { fn active_mouse_(conn: i32) -> bool {
// out of time protection // out of time protection
if LATEST_CURSOR_POS.lock().unwrap().0.elapsed() > MOUSE_MOVE_PROTECTION_TIMEOUT { if LATEST_CURSOR_POS.lock().unwrap().0.elapsed() > MOUSE_MOVE_PROTECTION_TIMEOUT {
return true; return true;
@ -388,13 +388,13 @@ fn is_mouse_active_by_conn(conn: i32) -> bool {
// check if input is in valid range // check if input is in valid range
match crate::get_cursor_pos() { match crate::get_cursor_pos() {
Some((x, y)) => { Some((x, y)) => {
let is_same_input = (last_input.x - x).abs() < MOUSE_ACTIVE_DISTANCE let can_active = (last_input.x - x).abs() < MOUSE_ACTIVE_DISTANCE
&& (last_input.y - y).abs() < MOUSE_ACTIVE_DISTANCE; && (last_input.y - y).abs() < MOUSE_ACTIVE_DISTANCE;
if !is_same_input { if !can_active {
last_input.x = -MOUSE_ACTIVE_DISTANCE * 2; last_input.x = -MOUSE_ACTIVE_DISTANCE * 2;
last_input.y = -MOUSE_ACTIVE_DISTANCE * 2; last_input.y = -MOUSE_ACTIVE_DISTANCE * 2;
} }
is_same_input can_active
} }
None => true, None => true,
} }
@ -405,7 +405,7 @@ fn handle_mouse_(evt: &MouseEvent, conn: i32) {
return; return;
} }
if !is_mouse_active_by_conn(conn) { if !active_mouse_(conn) {
return; return;
} }

View File

@ -225,11 +225,7 @@ impl VideoQoS {
} }
pub fn check_abr_config(&mut self) -> bool { pub fn check_abr_config(&mut self) -> bool {
self.enable_abr = if let Some(v) = Config2::get().options.get("enable-abr") { self.enable_abr = "N" != Config::get_option("enable-abr");
v != "N"
} else {
true // default is true
};
self.enable_abr self.enable_abr
} }

View File

@ -33,10 +33,11 @@ use std::{
collections::HashSet, collections::HashSet,
io::ErrorKind::WouldBlock, io::ErrorKind::WouldBlock,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
sync::Once,
time::{self, Duration, Instant}, time::{self, Duration, Instant},
}; };
#[cfg(windows)] #[cfg(windows)]
use std::sync::Once;
#[cfg(windows)]
use virtual_display; use virtual_display;
pub const SCRAP_UBUNTU_HIGHER_REQUIRED: &str = "Wayland requires Ubuntu 21.04 or higher version."; pub const SCRAP_UBUNTU_HIGHER_REQUIRED: &str = "Wayland requires Ubuntu 21.04 or higher version.";

View File

@ -1,14 +1,12 @@
use hbb_common::log::debug; use super::ui_interface::get_option_opt;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use hbb_common::log::{error, info}; use hbb_common::log::{debug, error, info};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use libappindicator::AppIndicator; use libappindicator::AppIndicator;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use std::env::temp_dir; use std::env::temp_dir;
use std::{ #[cfg(target_os = "windows")]
collections::HashMap, use std::sync::{Arc, Mutex};
sync::{Arc, Mutex},
};
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
use trayicon::{MenuBuilder, TrayIconBuilder}; use trayicon::{MenuBuilder, TrayIconBuilder};
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
@ -17,6 +15,7 @@ use winit::{
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoop},
}; };
#[cfg(target_os = "windows")]
#[derive(Clone, Eq, PartialEq, Debug)] #[derive(Clone, Eq, PartialEq, Debug)]
enum Events { enum Events {
DoubleClickTrayIcon, DoubleClickTrayIcon,
@ -25,7 +24,7 @@ enum Events {
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub fn start_tray(options: Arc<Mutex<HashMap<String, String>>>) { pub fn start_tray() {
let event_loop = EventLoop::<Events>::with_user_event(); let event_loop = EventLoop::<Events>::with_user_event();
let proxy = event_loop.create_proxy(); let proxy = event_loop.create_proxy();
let icon = include_bytes!("../res/tray-icon.ico"); let icon = include_bytes!("../res/tray-icon.ico");
@ -39,23 +38,19 @@ pub fn start_tray(options: Arc<Mutex<HashMap<String, String>>>) {
let old_state = Arc::new(Mutex::new(0)); let old_state = Arc::new(Mutex::new(0));
let _sender = crate::ui_interface::SENDER.lock().unwrap(); let _sender = crate::ui_interface::SENDER.lock().unwrap();
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
if options.lock().unwrap().get("ipc-closed").is_some() { if get_option_opt("ipc-closed").is_some() {
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
return; return;
} else { } else {
*control_flow = ControlFlow::Wait; *control_flow = ControlFlow::Wait;
} }
let stopped = if let Some(v) = options.lock().unwrap().get("stop-service") { let stopped = is_service_stoped();
!v.is_empty() let state = if stopped { 2 } else { 1 };
} else {
false
};
let stopped = if stopped { 2 } else { 1 };
let old = *old_state.lock().unwrap(); let old = *old_state.lock().unwrap();
if stopped != old { if state != old {
hbb_common::log::info!("State changed"); hbb_common::log::info!("State changed");
let mut m = MenuBuilder::new(); let mut m = MenuBuilder::new();
if stopped == 2 { if state == 2 {
m = m.item( m = m.item(
&crate::client::translate("Start Service".to_owned()), &crate::client::translate("Start Service".to_owned()),
Events::StartService, Events::StartService,
@ -67,7 +62,7 @@ pub fn start_tray(options: Arc<Mutex<HashMap<String, String>>>) {
); );
} }
tray_icon.set_menu(&m).ok(); tray_icon.set_menu(&m).ok();
*old_state.lock().unwrap() = stopped; *old_state.lock().unwrap() = state;
} }
match event { match event {
@ -92,10 +87,9 @@ pub fn start_tray(options: Arc<Mutex<HashMap<String, String>>>) {
/// [Block] /// [Block]
/// This function will block current execution, show the tray icon and handle events. /// This function will block current execution, show the tray icon and handle events.
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub fn start_tray(options: Arc<Mutex<HashMap<String, String>>>) { pub fn start_tray() {
use std::time::Duration;
use gtk::traits::{GtkMenuItemExt, MenuShellExt, WidgetExt}; use gtk::traits::{GtkMenuItemExt, MenuShellExt, WidgetExt};
info!("configuring tray"); info!("configuring tray");
// init gtk context // init gtk context
if let Err(err) = gtk::init() { if let Err(err) = gtk::init() {
@ -104,17 +98,17 @@ pub fn start_tray(options: Arc<Mutex<HashMap<String, String>>>) {
} }
if let Some(mut appindicator) = get_default_app_indicator() { if let Some(mut appindicator) = get_default_app_indicator() {
let mut menu = gtk::Menu::new(); let mut menu = gtk::Menu::new();
let running = get_service_status(options.clone()); let stoped = is_service_stoped();
// start/stop service // start/stop service
let label = if !running { let label = if stoped {
crate::client::translate("Start Service".to_owned()) crate::client::translate("Start Service".to_owned())
} else { } else {
crate::client::translate("Stop service".to_owned()) crate::client::translate("Stop service".to_owned())
}; };
let menu_item_service = gtk::MenuItem::with_label(label.as_str()); let menu_item_service = gtk::MenuItem::with_label(label.as_str());
menu_item_service.connect_activate(move |item| { menu_item_service.connect_activate(move |item| {
let lock = crate::ui_interface::SENDER.lock().unwrap(); let _lock = crate::ui_interface::SENDER.lock().unwrap();
update_tray_service_item(options.clone(), item); update_tray_service_item(item);
}); });
menu.append(&menu_item_service); menu.append(&menu_item_service);
// show tray item // show tray item
@ -129,19 +123,17 @@ pub fn start_tray(options: Arc<Mutex<HashMap<String, String>>>) {
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn update_tray_service_item(options: Arc<Mutex<HashMap<String, String>>>, item: &gtk::MenuItem) { fn update_tray_service_item(item: &gtk::MenuItem) {
use gtk::{ use gtk::traits::GtkMenuItemExt;
traits::{GtkMenuItemExt, ListBoxRowExt},
MenuItem, if is_service_stoped() {
};
if get_service_status(options.clone()) {
debug!("Now try to stop service");
item.set_label(&crate::client::translate("Start Service".to_owned()));
crate::ipc::set_option("stop-service", "Y");
} else {
debug!("Now try to start service"); debug!("Now try to start service");
item.set_label(&crate::client::translate("Stop service".to_owned())); item.set_label(&crate::client::translate("Stop service".to_owned()));
crate::ipc::set_option("stop-service", ""); crate::ipc::set_option("stop-service", "");
} else {
debug!("Now try to stop service");
item.set_label(&crate::client::translate("Start Service".to_owned()));
crate::ipc::set_option("stop-service", "Y");
} }
} }
@ -171,14 +163,13 @@ fn get_default_app_indicator() -> Option<AppIndicator> {
Some(appindicator) Some(appindicator)
} }
/// Get service status /// Check if service is stoped.
/// Return [`true`] if service is running, [`false`] otherwise. /// Return [`true`] if service is stoped, [`false`] otherwise.
#[inline] #[inline]
fn get_service_status(options: Arc<Mutex<HashMap<String, String>>>) -> bool { fn is_service_stoped() -> bool {
if let Some(v) = options.lock().unwrap().get("stop-service") { if let Some(v) = get_option_opt("stop-service") {
debug!("service stopped: {}", v); v == "Y"
v.is_empty()
} else { } else {
true false
} }
} }

View File

@ -33,7 +33,8 @@ pub mod win_privacy;
type Message = RendezvousMessage; type Message = RendezvousMessage;
pub type Childs = Arc<Mutex<(bool, HashMap<(String, String), Child>)>>; pub type Children = Arc<Mutex<(bool, HashMap<(String, String), Child>)>>;
#[allow(dead_code)]
type Status = (i32, bool, i64, String); type Status = (i32, bool, i64, String);
lazy_static::lazy_static! { lazy_static::lazy_static! {
@ -43,6 +44,7 @@ lazy_static::lazy_static! {
struct UIHostHandler; struct UIHostHandler;
// to-do: dead code?
fn check_connect_status( fn check_connect_status(
reconnect: bool, reconnect: bool,
) -> ( ) -> (
@ -111,8 +113,8 @@ pub fn start(args: &mut [String]) {
args[1] = id; args[1] = id;
} }
if args.is_empty() { if args.is_empty() {
let child: Childs = Default::default(); let children: Children = Default::default();
std::thread::spawn(move || check_zombie(child)); std::thread::spawn(move || check_zombie(children));
crate::common::check_software_update(); crate::common::check_software_update();
frame.event_handler(UI {}); frame.event_handler(UI {});
frame.sciter_handler(UIHostHandler {}); frame.sciter_handler(UIHostHandler {});
@ -662,10 +664,10 @@ impl sciter::host::HostHandler for UIHostHandler {
} }
} }
pub fn check_zombie(childs: Childs) { pub fn check_zombie(children: Children) {
let mut deads = Vec::new(); let mut deads = Vec::new();
loop { loop {
let mut lock = childs.lock().unwrap(); let mut lock = children.lock().unwrap();
let mut n = 0; let mut n = 0;
for (id, c) in lock.1.iter_mut() { for (id, c) in lock.1.iter_mut() {
if let Ok(Some(_)) = c.try_wait() { if let Ok(Some(_)) = c.try_wait() {

View File

@ -2,13 +2,14 @@
use std::sync::Arc; use std::sync::Arc;
use std::{ use std::{
collections::HashMap, collections::HashMap,
iter::FromIterator,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
sync::{ sync::{
atomic::{AtomicI64, Ordering}, atomic::{AtomicI64, Ordering},
RwLock, RwLock,
}, },
}; };
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
use std::iter::FromIterator;
#[cfg(windows)] #[cfg(windows)]
use clipboard::{cliprdr::CliprdrClientContext, empty_clipboard, set_conn_enabled, ContextSend}; use clipboard::{cliprdr::CliprdrClientContext, empty_clipboard, set_conn_enabled, ContextSend};

View File

@ -9,32 +9,37 @@ use std::{
use hbb_common::password_security; use hbb_common::password_security;
use hbb_common::{ use hbb_common::{
allow_err, allow_err,
config::{self, Config, LocalConfig, PeerConfig, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT}, config::{self, Config, LocalConfig, PeerConfig},
directories_next, directories_next, log,
futures::future::join_all,
log,
protobuf::Message as _,
rendezvous_proto::*,
sleep, sleep,
tcp::FramedStream,
tokio::{self, sync::mpsc, time}, tokio::{self, sync::mpsc, time},
}; };
use crate::{common::SOFTWARE_UPDATE_URL, ipc, platform}; #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
use hbb_common::{
config::{RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
futures::future::join_all,
protobuf::Message as _,
rendezvous_proto::*,
tcp::FramedStream,
};
#[cfg(feature = "flutter")] #[cfg(feature = "flutter")]
use crate::hbbs_http::account; use crate::hbbs_http::account;
use crate::{common::SOFTWARE_UPDATE_URL, ipc, platform};
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
type Message = RendezvousMessage; type Message = RendezvousMessage;
pub type Childs = Arc<Mutex<(bool, HashMap<(String, String), Child>)>>; pub type Children = Arc<Mutex<(bool, HashMap<(String, String), Child>)>>;
type Status = (i32, bool, i64, String); // (status_num, key_confirmed, mouse_time, id) type Status = (i32, bool, i64, String); // (status_num, key_confirmed, mouse_time, id)
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub static ref CHILDS : Childs = Default::default(); static ref CHILDREN : Children = Default::default();
pub static ref UI_STATUS : Arc<Mutex<Status>> = Arc::new(Mutex::new((0, false, 0, "".to_owned()))); static ref UI_STATUS : Arc<Mutex<Status>> = Arc::new(Mutex::new((0, false, 0, "".to_owned())));
pub static ref OPTIONS : Arc<Mutex<HashMap<String, String>>> = Arc::new(Mutex::new(Config::get_options())); static ref OPTIONS : Arc<Mutex<HashMap<String, String>>> = Arc::new(Mutex::new(Config::get_options()));
pub static ref ASYNC_JOB_STATUS : Arc<Mutex<String>> = Default::default(); static ref ASYNC_JOB_STATUS : Arc<Mutex<String>> = Default::default();
pub static ref TEMPORARY_PASSWD : Arc<Mutex<String>> = Arc::new(Mutex::new("".to_owned())); static ref TEMPORARY_PASSWD : Arc<Mutex<String>> = Arc::new(Mutex::new("".to_owned()));
} }
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
@ -44,15 +49,16 @@ lazy_static::lazy_static! {
#[inline] #[inline]
pub fn recent_sessions_updated() -> bool { pub fn recent_sessions_updated() -> bool {
let mut childs = CHILDS.lock().unwrap(); let mut children = CHILDREN.lock().unwrap();
if childs.0 { if children.0 {
childs.0 = false; children.0 = false;
true true
} else { } else {
false false
} }
} }
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[inline] #[inline]
pub fn get_id() -> String { pub fn get_id() -> String {
#[cfg(any(target_os = "android", target_os = "ios"))] #[cfg(any(target_os = "android", target_os = "ios"))]
@ -143,10 +149,9 @@ pub fn get_license() -> String {
#[cfg(windows)] #[cfg(windows)]
if let Some(lic) = crate::platform::windows::get_license() { if let Some(lic) = crate::platform::windows::get_license() {
#[cfg(feature = "flutter")] #[cfg(feature = "flutter")]
{ return format!("Key: {}\nHost: {}\nApi: {}", lic.key, lic.host, lic.api);
return format!("Key: {}\nHost: {}\nApi: {}", lic.key, lic.host, lic.api);
}
// default license format is html formed (sciter) // default license format is html formed (sciter)
#[cfg(not(feature = "flutter"))]
return format!( return format!(
"<br /> Key: {} <br /> Host: {} Api: {}", "<br /> Key: {} <br /> Host: {} Api: {}",
lic.key, lic.host, lic.api lic.key, lic.host, lic.api
@ -155,6 +160,11 @@ pub fn get_license() -> String {
Default::default() Default::default()
} }
#[inline]
pub fn get_option_opt(key: &str) -> Option<String> {
OPTIONS.lock().unwrap().get(key).map(|x| x.clone())
}
#[inline] #[inline]
pub fn get_option(key: String) -> String { pub fn get_option(key: String) -> String {
get_option_(&key) get_option_(&key)
@ -180,6 +190,18 @@ pub fn set_local_option(key: String, value: String) {
LocalConfig::set_option(key, value); LocalConfig::set_option(key, value);
} }
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[inline]
pub fn get_local_flutter_config(key: String) -> String {
LocalConfig::get_flutter_config(&key)
}
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[inline]
pub fn set_local_flutter_config(key: String, value: String) {
LocalConfig::set_flutter_config(key, value);
}
#[inline] #[inline]
pub fn peer_has_password(id: String) -> bool { pub fn peer_has_password(id: String) -> bool {
!PeerConfig::load(&id).password.is_empty() !PeerConfig::load(&id).password.is_empty()
@ -230,7 +252,7 @@ pub fn test_if_valid_server(host: String) -> String {
} }
#[inline] #[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(feature = "flutter")]
pub fn get_sound_inputs() -> Vec<String> { pub fn get_sound_inputs() -> Vec<String> {
let mut a = Vec::new(); let mut a = Vec::new();
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
@ -345,12 +367,15 @@ pub fn set_socks(proxy: String, username: String, password: String) {
.ok(); .ok();
} }
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[inline] #[inline]
pub fn is_installed() -> bool { pub fn is_installed() -> bool {
#[cfg(not(any(target_os = "android", target_os = "ios")))] crate::platform::is_installed()
{ }
return crate::platform::is_installed();
} #[cfg(any(target_os = "android", target_os = "ios"))]
#[inline]
pub fn is_installed() -> bool {
false false
} }
@ -495,7 +520,7 @@ pub fn remove_peer(id: String) {
#[inline] #[inline]
pub fn new_remote(id: String, remote_type: String) { pub fn new_remote(id: String, remote_type: String) {
let mut lock = CHILDS.lock().unwrap(); let mut lock = CHILDREN.lock().unwrap();
let args = vec![format!("--{}", remote_type), id.clone()]; let args = vec![format!("--{}", remote_type), id.clone()];
let key = (id.clone(), remote_type.clone()); let key = (id.clone(), remote_type.clone());
if let Some(c) = lock.1.get_mut(&key) { if let Some(c) = lock.1.get_mut(&key) {
@ -612,6 +637,7 @@ pub fn get_version() -> String {
crate::VERSION.to_owned() crate::VERSION.to_owned()
} }
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[inline] #[inline]
pub fn get_app_name() -> String { pub fn get_app_name() -> String {
crate::get_app_name() crate::get_app_name()
@ -650,6 +676,7 @@ pub fn create_shortcut(_id: String) {
crate::platform::windows::create_shortcut(&_id).ok(); crate::platform::windows::create_shortcut(&_id).ok();
} }
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[inline] #[inline]
pub fn discover() { pub fn discover() {
std::thread::spawn(move || { std::thread::spawn(move || {
@ -694,6 +721,7 @@ pub fn open_url(url: String) {
allow_err!(std::process::Command::new(p).arg(url).spawn()); allow_err!(std::process::Command::new(p).arg(url).spawn());
} }
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[inline] #[inline]
pub fn change_id(id: String) { pub fn change_id(id: String) {
*ASYNC_JOB_STATUS.lock().unwrap() = " ".to_owned(); *ASYNC_JOB_STATUS.lock().unwrap() = " ".to_owned();
@ -800,13 +828,19 @@ pub fn is_release() -> bool {
return false; return false;
} }
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[inline]
pub fn is_root() -> bool {
crate::platform::is_root()
}
#[cfg(any(target_os = "android", target_os = "ios"))]
#[inline] #[inline]
pub fn is_root() -> bool { pub fn is_root() -> bool {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
return crate::platform::is_root();
false false
} }
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[inline] #[inline]
pub fn check_super_user_permission() -> bool { pub fn check_super_user_permission() -> bool {
#[cfg(feature = "flatpak")] #[cfg(feature = "flatpak")]
@ -818,10 +852,10 @@ pub fn check_super_user_permission() -> bool {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn check_zombie(childs: Childs) { pub fn check_zombie(children: Children) {
let mut deads = Vec::new(); let mut deads = Vec::new();
loop { loop {
let mut lock = childs.lock().unwrap(); let mut lock = children.lock().unwrap();
let mut n = 0; let mut n = 0;
for (id, c) in lock.1.iter_mut() { for (id, c) in lock.1.iter_mut() {
if let Ok(Some(_)) = c.try_wait() { if let Ok(Some(_)) = c.try_wait() {
@ -928,6 +962,7 @@ pub(crate) async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedRe
} }
} }
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
pub(crate) async fn send_to_cm(data: &ipc::Data) { pub(crate) async fn send_to_cm(data: &ipc::Data) {
if let Ok(mut c) = ipc::connect(1000, "_cm").await { if let Ok(mut c) = ipc::connect(1000, "_cm").await {
@ -935,9 +970,12 @@ pub(crate) async fn send_to_cm(data: &ipc::Data) {
} }
} }
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
const INVALID_FORMAT: &'static str = "Invalid format"; const INVALID_FORMAT: &'static str = "Invalid format";
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
const UNKNOWN_ERROR: &'static str = "Unknown error"; const UNKNOWN_ERROR: &'static str = "Unknown error";
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn change_id_(id: String, old_id: String) -> &'static str { async fn change_id_(id: String, old_id: String) -> &'static str {
if !hbb_common::is_valid_custom_id(&id) { if !hbb_common::is_valid_custom_id(&id) {
@ -987,6 +1025,7 @@ async fn change_id_(id: String, old_id: String) -> &'static str {
err err
} }
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
async fn check_id( async fn check_id(
rendezvous_server: String, rendezvous_server: String,
old_id: String, old_id: String,

View File

@ -98,6 +98,14 @@ impl<T: InvokeUiSession> Session<T> {
self.lc.write().unwrap().save_view_style(value); self.lc.write().unwrap().save_view_style(value);
} }
pub fn set_flutter_config(&mut self, k: String, v: String) {
self.lc.write().unwrap().set_ui_flutter(k, v);
}
pub fn get_flutter_config(&self, k: String) -> String {
self.lc.write().unwrap().get_ui_flutter(&k)
}
pub fn toggle_option(&mut self, name: String) { pub fn toggle_option(&mut self, name: String) {
let msg = self.lc.write().unwrap().toggle_option(name.clone()); let msg = self.lc.write().unwrap().toggle_option(name.clone());
if name == "enable-file-transfer" { if name == "enable-file-transfer" {
@ -1590,7 +1598,7 @@ pub fn global_grab_keyboard() {
#[cfg(any(target_os = "windows", target_os = "macos"))] #[cfg(any(target_os = "windows", target_os = "macos"))]
std::thread::spawn(move || { std::thread::spawn(move || {
let func = move |event: Event| match event.event_type { let func = move |event: Event| match event.event_type {
EventType::KeyPress(key) | EventType::KeyRelease(key) => { EventType::KeyPress(..) | EventType::KeyRelease(..) => {
// grab all keys // grab all keys
if !IS_IN.load(Ordering::SeqCst) { if !IS_IN.load(Ordering::SeqCst) {
return Some(event); return Some(event);