diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index ec83e1123..93fe0fee5 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -15,7 +15,6 @@ import 'package:flutter_hbb/main.dart'; import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:get/get.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:uni_links/uni_links.dart'; import 'package:uni_links_desktop/uni_links_desktop.dart'; import 'package:window_manager/window_manager.dart'; @@ -205,18 +204,17 @@ class MyTheme { ); static ThemeMode getThemeModePreference() { - return themeModeFromString( - Get.find().getString("themeMode") ?? ""); + return themeModeFromString(bind.mainGetLocalOption(key: kCommConfKeyTheme)); } static void changeDarkMode(ThemeMode mode) { final preference = getThemeModePreference(); if (preference != mode) { if (mode == ThemeMode.system) { - Get.find().setString("themeMode", ""); + bind.mainSetLocalOption(key: kCommConfKeyTheme, value: ''); } else { - Get.find() - .setString("themeMode", mode.toShortString()); + bind.mainSetLocalOption( + key: kCommConfKeyTheme, value: mode.toShortString()); } Get.changeThemeMode(mode); if (desktopType == DesktopType.main) { @@ -1026,8 +1024,8 @@ Future saveWindowPosition(WindowType type, {int? windowId}) async { final isMaximized = await windowManager.isMaximized(); final pos = LastWindowPosition( sz.width, sz.height, position.dx, position.dy, isMaximized); - await Get.find() - .setString(kWindowPrefix + type.name, pos.toString()); + await bind.setLocalFlutterConfig( + k: kWindowPrefix + type.name, v: pos.toString()); break; default: final wc = WindowController.fromWindowId(windowId!); @@ -1037,9 +1035,10 @@ Future saveWindowPosition(WindowType type, {int? windowId}) async { final isMaximized = await wc.isMaximized(); final pos = LastWindowPosition( sz.width, sz.height, position.dx, position.dy, isMaximized); - debugPrint("saving frame: ${windowId}: ${pos.width}/${pos.height}, offset:${pos.offsetWidth}/${pos.offsetHeight}"); - await Get.find() - .setString(kWindowPrefix + type.name, pos.toString()); + debugPrint( + "saving frame: $windowId: ${pos.width}/${pos.height}, offset:${pos.offsetWidth}/${pos.offsetHeight}"); + await bind.setLocalFlutterConfig( + k: kWindowPrefix + type.name, v: pos.toString()); break; } } @@ -1109,7 +1108,7 @@ Future _adjustRestoreMainWindowOffset( .toDouble(); 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); frameTop = min(screen.visibleFrame.top, frameTop); frameRight = max(screen.visibleFrame.right, frameRight); @@ -1136,13 +1135,7 @@ Future restoreWindowPosition(WindowType type, {int? windowId}) async { debugPrint( "Error: windowId cannot be null when saving positions for sub window"); } - final pos = - Get.find().getString(kWindowPrefix + type.name); - - if (pos == null) { - debugPrint("no window position saved, ignore restore"); - return false; - } + final pos = bind.getLocalFlutterConfig(k: kWindowPrefix + type.name); var lpos = LastWindowPosition.loadFromString(pos); if (lpos == null) { debugPrint("window position saved, but cannot be parsed"); @@ -1175,7 +1168,8 @@ Future restoreWindowPosition(WindowType type, {int? windowId}) async { await _adjustRestoreMainWindowSize(lpos.width, lpos.height); final offset = await _adjustRestoreMainWindowOffset( 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) { await wc.center(); } else { @@ -1327,8 +1321,7 @@ void connect(BuildContext context, String id, Future> getHttpHeaders() async { return { - 'Authorization': - 'Bearer ${await bind.mainGetLocalOption(key: 'access_token')}' + 'Authorization': 'Bearer ${bind.mainGetLocalOption(key: 'access_token')}' }; } diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index 81559a3d3..9129e4711 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -22,27 +22,26 @@ class _PeerTabPageState extends State @override void initState() { - () async { - 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; - }); - }(); + setPeer(); 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 Future _handleTabSelection(int index) async { _tabIndex.value = index; - await bind.mainSetLocalOption( - key: 'peer-tab-index', value: index.toString()); + await bind.setLocalFlutterConfig(k: 'peer-tab-index', v: index.toString()); switch (index) { case 0: bind.mainLoadRecentPeers(); @@ -148,9 +147,8 @@ class _PeerTabPageState extends State decoration: peerCardUiType.value == type ? activeDeco : null, child: InkWell( onTap: () async { - await bind.mainSetLocalOption( - key: 'peer-card-ui-type', - value: type.index.toString()); + await bind.setLocalFlutterConfig( + k: 'peer-card-ui-type', v: type.index.toString()); peerCardUiType.value = type; }, child: Icon( diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 48e99943f..acba32780 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -56,7 +56,11 @@ var kWindowEdgeSize = Platform.isWindows ? 1.0 : 5.0; const kWindowBorderWidth = 1.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); diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 12bb935e9..ab8f76ca5 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -5,6 +5,7 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.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_tab_page.dart'; import 'package:flutter_hbb/desktop/widgets/login.dart'; @@ -30,8 +31,8 @@ const double _kListViewBottomMargin = 15; const double _kTitleFontSize = 20; const double _kContentFontSize = 15; const Color _accentColor = MyTheme.accent; -const String _kSettingPageControllerTag = "settingPageController"; -const String _kSettingPageIndexTag = "settingPageIndex"; +const String _kSettingPageControllerTag = 'settingPageController'; +const String _kSettingPageIndexTag = 'settingPageIndex'; class _TabInfo { late final String label; @@ -250,19 +251,19 @@ class _GeneralState extends State<_General> { return _Card(title: 'Theme', children: [ _Radio(context, - value: "light", + value: 'light', groupValue: current, - label: "Light", + label: 'Light', onChanged: onChanged), _Radio(context, - value: "dark", + value: 'dark', groupValue: current, - label: "Dark", + label: 'Dark', onChanged: onChanged), _Radio(context, - value: "system", + value: 'system', groupValue: current, - label: "Follow System", + label: 'Follow System', onChanged: onChanged), ]); } @@ -286,8 +287,8 @@ class _GeneralState extends State<_General> { Widget audio(BuildContext context) { String getDefault() { - if (Platform.isWindows) return "System Sound"; - return ""; + if (Platform.isWindows) return 'System Sound'; + return ''; } Future getValue() async { @@ -300,7 +301,7 @@ class _GeneralState extends State<_General> { } setDevice(String device) { - if (device == getDefault()) device = ""; + if (device == getDefault()) device = ''; bind.mainSetOption(key: 'audio-input', value: device); } @@ -353,7 +354,7 @@ class _GeneralState extends State<_General> { 'allow-auto-record-incoming'), Row( children: [ - Text('${translate('Directory')}:'), + Text('${translate("Directory")}:'), Expanded( child: GestureDetector( onTap: canlaunch ? () => launchUrl(Uri.file(dir)) : null, @@ -386,26 +387,26 @@ class _GeneralState extends State<_General> { Widget language() { return _futureBuilder(future: () async { String langs = await bind.mainGetLangs(); - String lang = await bind.mainGetLocalOption(key: "lang"); - return {"langs": langs, "lang": lang}; + String lang = bind.mainGetLocalOption(key: kCommConfKeyLang); + return {'langs': langs, 'lang': lang}; }(), hasData: (res) { Map data = res as Map; - List langsList = jsonDecode(data["langs"]!); + List langsList = jsonDecode(data['langs']!); Map langsMap = {for (var v in langsList) v[0]: v[1]}; List keys = langsMap.keys.toList(); List values = langsMap.values.toList(); - keys.insert(0, ""); - values.insert(0, "Default"); - String currentKey = data["lang"]!; + keys.insert(0, ''); + values.insert(0, 'Default'); + String currentKey = data['lang']!; if (!keys.contains(currentKey)) { - currentKey = ""; + currentKey = ''; } return _ComboBox( keys: keys, values: values, initialKey: currentKey, onChanged: (key) async { - await bind.mainSetLocalOption(key: "lang", value: key); + await bind.mainSetLocalOption(key: kCommConfKeyLang, value: key); reloadAllWindows(); bind.mainChangeLanguage(lang: key); }, @@ -585,9 +586,9 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { kUseBothPasswords, ]; List values = [ - translate("Use temporary password"), - translate("Use permanent password"), - translate("Use both passwords"), + translate('Use temporary password'), + translate('Use permanent password'), + translate('Use both passwords'), ]; bool tmpEnabled = model.verificationMethod != kUsePermanentPassword; 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. Map oldOptions = jsonDecode(data! as String); old(String key) { - return (oldOptions[key] ?? "").trim(); + return (oldOptions[key] ?? '').trim(); } - RxString idErrMsg = "".obs; - RxString relayErrMsg = "".obs; - RxString apiErrMsg = "".obs; + RxString idErrMsg = ''.obs; + RxString relayErrMsg = ''.obs; + RxString apiErrMsg = ''.obs; var idController = TextEditingController(text: old('custom-rendezvous-server')); var relayController = TextEditingController(text: old('relay-server')); @@ -864,9 +865,9 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { } if (apiServer.isNotEmpty) { if (!apiServer.startsWith('http://') || - !apiServer.startsWith("https://")) { + !apiServer.startsWith('https://')) { apiErrMsg.value = - "${translate("API Server")}: ${translate("invalid_http")}"; + '${translate("API Server")}: ${translate("invalid_http")}'; return false; } } @@ -893,7 +894,7 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { import() { Clipboard.getData(Clipboard.kTextPlain).then((value) { TextEditingController mytext = TextEditingController(); - String? aNullableString = ""; + String? aNullableString = ''; aNullableString = value?.text; mytext.text = aNullableString.toString(); if (mytext.text.isNotEmpty) { @@ -918,13 +919,13 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { } }); } else { - showToast(translate("Invalid server configuration")); + showToast(translate('Invalid server configuration')); } } catch (e) { - showToast(translate("Invalid server configuration")); + showToast(translate('Invalid server configuration')); } } 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['Key'] = keyController.text.trim(); Clipboard.setData(ClipboardData(text: jsonEncode(config))); - showToast(translate("Export server configuration successfully")); + showToast(translate('Export server configuration successfully')); } bool secure = !enabled; @@ -962,7 +963,7 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { Obx(() => _LabeledTextField(context, 'API Server', apiController, apiErrMsg.value, enabled, secure)), _LabeledTextField( - context, 'Key', keyController, "", enabled, secure), + context, 'Key', keyController, '', enabled, secure), Row( mainAxisAlignment: MainAxisAlignment.end, children: [_Button('Apply', submit, enabled: enabled)], @@ -1039,28 +1040,28 @@ class _AboutState extends State<_About> { child: SingleChildScrollView( controller: scrollController, physics: NeverScrollableScrollPhysics(), - child: _Card(title: "About RustDesk", children: [ + child: _Card(title: 'About RustDesk', children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox( height: 8.0, ), - Text("Version: $version").marginSymmetric(vertical: 4.0), + Text('Version: $version').marginSymmetric(vertical: 4.0), InkWell( onTap: () { - launchUrlString("https://rustdesk.com/privacy"); + launchUrlString('https://rustdesk.com/privacy'); }, child: const Text( - "Privacy Statement", + 'Privacy Statement', style: linkStyle, ).marginSymmetric(vertical: 4.0)), InkWell( onTap: () { - launchUrlString("https://rustdesk.com"); + launchUrlString('https://rustdesk.com'); }, child: const Text( - "Website", + 'Website', style: linkStyle, ).marginSymmetric(vertical: 4.0)), Container( @@ -1074,11 +1075,11 @@ class _AboutState extends State<_About> { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Copyright © 2022 Purslane Ltd.\n$license", + 'Copyright © 2022 Purslane Ltd.\n$license', style: const TextStyle(color: Colors.white), ), const Text( - "Made with heart in this chaotic world!", + 'Made with heart in this chaotic world!', style: TextStyle( fontWeight: FontWeight.w800, color: Colors.white), @@ -1472,10 +1473,10 @@ class _ComboBox extends StatelessWidget { void changeSocks5Proxy() async { var socks = await bind.mainGetSocks(); - String proxy = ""; - String proxyMsg = ""; - String username = ""; - String password = ""; + String proxy = ''; + String proxyMsg = ''; + String username = ''; + String password = ''; if (socks.length == 3) { proxy = socks[0]; username = socks[1]; @@ -1489,7 +1490,7 @@ void changeSocks5Proxy() async { gFFI.dialogManager.show((setState, close) { submit() async { setState(() { - proxyMsg = ""; + proxyMsg = ''; isInProgress = true; }); cancel() { @@ -1517,7 +1518,7 @@ void changeSocks5Proxy() async { } return CustomAlertDialog( - title: Text(translate("Socks5 Proxy")), + title: Text(translate('Socks5 Proxy')), content: ConstrainedBox( constraints: const BoxConstraints(minWidth: 500), child: Column( @@ -1530,7 +1531,7 @@ void changeSocks5Proxy() async { children: [ ConstrainedBox( constraints: const BoxConstraints(minWidth: 100), - child: Text("${translate('Hostname')}:") + child: Text('${translate("Hostname")}:') .marginOnly(bottom: 16.0)), const SizedBox( width: 24.0, @@ -1553,7 +1554,7 @@ void changeSocks5Proxy() async { children: [ ConstrainedBox( constraints: const BoxConstraints(minWidth: 100), - child: Text("${translate('Username')}:") + child: Text('${translate("Username")}:') .marginOnly(bottom: 16.0)), const SizedBox( width: 24.0, @@ -1575,7 +1576,7 @@ void changeSocks5Proxy() async { children: [ ConstrainedBox( constraints: const BoxConstraints(minWidth: 100), - child: Text("${translate('Password')}:") + child: Text('${translate("Password")}:') .marginOnly(bottom: 16.0)), const SizedBox( width: 24.0, @@ -1599,8 +1600,8 @@ void changeSocks5Proxy() async { ), ), actions: [ - TextButton(onPressed: close, child: Text(translate("Cancel"))), - TextButton(onPressed: submit, child: Text(translate("OK"))), + TextButton(onPressed: close, child: Text(translate('Cancel'))), + TextButton(onPressed: submit, child: Text(translate('OK'))), ], onSubmit: submit, onCancel: close, diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 76395be9d..21cbe45b9 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -28,14 +28,14 @@ class RemotePage extends StatefulWidget { RemotePage({ Key? key, required this.id, + required this.menubarState, }) : super(key: key); final String id; + final MenubarState menubarState; final SimpleWrapper?> _lastState = SimpleWrapper(null); FFI get ffi => (_lastState.value! as _RemotePageState)._ffi; - RxBool get showMenubar => - (_lastState.value! as _RemotePageState)._showMenubar; @override State createState() { @@ -50,7 +50,6 @@ class _RemotePageState extends State Timer? _timer; String keyboardMode = "legacy"; final _cursorOverImage = false.obs; - final _showMenubar = false.obs; late RxBool _showRemoteCursor; late RxBool _remoteCursorMoved; late RxBool _keyboardEnabled; @@ -239,7 +238,7 @@ class _RemotePageState extends State paints.add(RemoteMenubar( id: widget.id, ffi: _ffi, - show: _showMenubar, + state: widget.menubarState, onEnterOrLeaveImageSetter: (func) => _onEnterOrLeaveImage4Menubar = func, onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Menubar = null, )); diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart index 3068f2db7..4f7e9c5aa 100644 --- a/flutter/lib/desktop/pages/remote_tab_page.dart +++ b/flutter/lib/desktop/pages/remote_tab_page.dart @@ -9,6 +9,7 @@ import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/models/state_model.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/material_mod_popup_menu.dart' as mod_menu; @@ -43,9 +44,12 @@ class _ConnectionTabPageState extends State { static const IconData selectedIcon = Icons.desktop_windows_sharp; static const IconData unselectedIcon = Icons.desktop_windows_outlined; + late MenubarState _menubarState; + var connectionMap = RxList.empty(growable: true); _ConnectionTabPageState(Map params) { + _menubarState = MenubarState(); RemoteCountState.init(); final peerId = params['id']; if (peerId != null) { @@ -59,6 +63,7 @@ class _ConnectionTabPageState extends State { page: RemotePage( key: ValueKey(peerId), id: peerId, + menubarState: _menubarState, ), )); _update_remote_count(); @@ -88,7 +93,11 @@ class _ConnectionTabPageState extends State { selectedIcon: selectedIcon, unselectedIcon: unselectedIcon, 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") { tabController.clear(); @@ -99,6 +108,12 @@ class _ConnectionTabPageState extends State { }); } + @override + void dispose() { + super.dispose(); + _menubarState.save(); + } + @override Widget build(BuildContext context) { final tabWidget = Container( @@ -177,7 +192,7 @@ class _ConnectionTabPageState extends State { ); } - // to-do: some dup code to ../widgets/remote_menubar + // Note: Some dup code to ../widgets/remote_menubar Widget _tabMenuBuilder(String key, CancelFunc cancelFunc) { final List> menu = []; const EdgeInsets padding = EdgeInsets.only(left: 8.0, right: 5.0); @@ -187,7 +202,6 @@ class _ConnectionTabPageState extends State { final ffi = remotePage.ffi; final pi = ffi.ffiModel.pi; final perms = ffi.ffiModel.permissions; - final showMenuBar = remotePage.showMenubar; menu.addAll([ MenuEntryButton( childBuilder: (TextStyle? style) => Text( @@ -202,11 +216,12 @@ class _ConnectionTabPageState extends State { ), MenuEntryButton( childBuilder: (TextStyle? style) => Obx(() => Text( - translate(showMenuBar.isTrue ? 'Hide Menubar' : 'Show Menubar'), + translate( + _menubarState.show.isTrue ? 'Hide Menubar' : 'Show Menubar'), style: style, )), proc: () { - showMenuBar.value = !showMenuBar.value; + _menubarState.switchShow(); cancelFunc(); }, padding: padding, diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 26cc26ddd..75311c4c5 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -21,6 +21,68 @@ import '../../common/shared_state.dart'; import './popup_menu.dart'; 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 { static const Color commonColor = MyTheme.accent; // kMinInteractiveDimension @@ -31,7 +93,7 @@ class _MenubarTheme { class RemoteMenubar extends StatefulWidget { final String id; final FFI ffi; - final RxBool show; + final MenubarState state; final Function(Function(bool)) onEnterOrLeaveImageSetter; final Function() onEnterOrLeaveImageCleaner; @@ -39,7 +101,7 @@ class RemoteMenubar extends StatefulWidget { Key? key, required this.id, required this.ffi, - required this.show, + required this.state, required this.onEnterOrLeaveImageSetter, required this.onEnterOrLeaveImageCleaner, }) : super(key: key); @@ -51,7 +113,6 @@ class RemoteMenubar extends StatefulWidget { class _RemoteMenubarState extends State { final Rx _hideColor = Colors.white12.obs; final _rxHideReplay = rxdart.ReplaySubject(); - final _pinMenubar = false.obs; bool _isCursorOverImage = false; window_size.Screen? _screen; @@ -63,7 +124,8 @@ class _RemoteMenubarState extends State { setState(() {}); } - RxBool get show => widget.show; + RxBool get show => widget.state.show; + bool get pin => widget.state.pin; @override initState() { @@ -82,7 +144,7 @@ class _RemoteMenubarState extends State { .throttleTime(const Duration(milliseconds: 5000), trailing: true, leading: false) .listen((int v) { - if (_pinMenubar.isFalse && show.isTrue && _isCursorOverImage) { + if (!pin && show.isTrue && _isCursorOverImage) { show.value = false; } }); @@ -196,18 +258,15 @@ class _RemoteMenubarState extends State { Widget _buildPinMenubar(BuildContext context) { return Obx(() => IconButton( - tooltip: - translate(_pinMenubar.isTrue ? 'Unpin menubar' : 'Pin menubar'), + tooltip: translate(pin ? 'Unpin menubar' : 'Pin menubar'), onPressed: () { - _pinMenubar.value = !_pinMenubar.value; + widget.state.switchPin(); }, icon: Obx(() => Transform.rotate( - angle: _pinMenubar.isTrue ? math.pi / 4 : 0, + angle: pin ? math.pi / 4 : 0, child: Icon( Icons.push_pin, - color: _pinMenubar.isTrue - ? _MenubarTheme.commonColor - : Colors.grey, + color: pin ? _MenubarTheme.commonColor : Colors.grey, ))), )); } diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index ae15b5cf6..989ba12f5 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -15,7 +15,6 @@ import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:window_manager/window_manager.dart'; import 'package:bot_toast/bot_toast.dart'; @@ -97,7 +96,6 @@ Future main(List args) async { Future initEnv(String appType) async { // global shared preference - await Get.putAsync(() => SharedPreferences.getInstance()); await platformFFI.init(appType); // global FFI, use this **ONLY** for global configuration // for convenience, use global FFI on mobile platform diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index be97b41ff..616a1c985 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -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/state_model.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:tuple/tuple.dart'; import 'package:image/image.dart' as img2; import 'package:flutter_custom_cursor/flutter_custom_cursor.dart'; @@ -1219,7 +1218,7 @@ class FFI { Future close() async { chatModel.close(); 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); } bind.sessionClose(id: id); @@ -1267,9 +1266,10 @@ class PeerInfo { List displays = []; } -Future savePreference(String id, double xCursor, double yCursor, +const canvasKey = 'canvas'; + +Future setCanvasConfig(String id, double xCursor, double yCursor, double xCanvas, double yCanvas, double scale, int currentDisplay) async { - SharedPreferences prefs = await SharedPreferences.getInstance(); final p = {}; p['xCursor'] = xCursor; p['yCursor'] = yCursor; @@ -1277,25 +1277,27 @@ Future savePreference(String id, double xCursor, double yCursor, p['yCanvas'] = yCanvas; p['scale'] = scale; p['currentDisplay'] = currentDisplay; - prefs.setString('peer$id', json.encode(p)); + await bind.sessionSetFlutterConfig(id: id, k: canvasKey, v: jsonEncode(p)); } -Future?> getPreference(String id) async { +Future?> getCanvasConfig(String id) async { if (!isWebDesktop) return null; - SharedPreferences prefs = await SharedPreferences.getInstance(); - var p = prefs.getString('peer$id'); - if (p == null) return null; - Map m = json.decode(p); - return m; + var p = await bind.sessionGetFlutterConfig(id: id, k: canvasKey); + if (p == null || p.isEmpty) return null; + try { + Map m = json.decode(p); + return m; + } catch (e) { + return null; + } } void removePreference(String id) async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - prefs.remove('peer$id'); + await bind.sessionSetFlutterConfig(id: id, k: canvasKey, v: ''); } Future initializeCursorAndCanvas(FFI ffi) async { - var p = await getPreference(ffi.id); + var p = await getCanvasConfig(ffi.id); int currentDisplay = 0; if (p != null) { currentDisplay = p['currentDisplay']; diff --git a/flutter/lib/models/state_model.dart b/flutter/lib/models/state_model.dart index f6e3820b9..dab21fcb6 100644 --- a/flutter/lib/models/state_model.dart +++ b/flutter/lib/models/state_model.dart @@ -8,6 +8,7 @@ class StateGlobal { bool _fullscreen = false; final RxBool _showTabBar = true.obs; final RxDouble _resizeEdgeSize = 8.0.obs; + final RxBool showRemoteMenuBar = false.obs; int get windowId => _windowId; bool get fullscreen => _fullscreen; diff --git a/flutter/lib/models/user_model.dart b/flutter/lib/models/user_model.dart index d2e83990b..e6065743c 100644 --- a/flutter/lib/models/user_model.dart +++ b/flutter/lib/models/user_model.dart @@ -19,7 +19,7 @@ class UserModel { void refreshCurrentUser() async { await getUserName(); - final token = await bind.mainGetLocalOption(key: 'access_token'); + final token = bind.mainGetLocalOption(key: 'access_token'); if (token == '') return; final url = await bind.mainGetApiServer(); final body = { @@ -73,7 +73,7 @@ class UserModel { if (userName.isNotEmpty) { return userName.value; } - final userInfo = await bind.mainGetLocalOption(key: 'user_info'); + final userInfo = bind.mainGetLocalOption(key: 'user_info'); if (userInfo.trim().isEmpty) { return ''; } diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 4cb4b40a7..b7ffeec0a 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -40,7 +40,6 @@ dependencies: #firebase_analytics: ^9.1.5 package_info_plus: ^1.4.2 url_launcher: ^6.0.9 - shared_preferences: ^2.0.6 toggle_switch: ^1.4.0 dash_chat_2: ^0.0.14 draggable_float_widget: ^0.0.2 diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 9eb9cd369..cc9da6420 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -167,9 +167,12 @@ pub struct PeerConfig { #[serde(default)] pub show_quality_monitor: bool, - // the other scalar value must before this + // The other scalar value must before this #[serde(default)] pub options: HashMap, + // Various data for flutter ui + #[serde(default)] + pub ui_flutter: HashMap, #[serde(default)] pub info: PeerInfoSerde, #[serde(default)] @@ -897,6 +900,9 @@ pub struct LocalConfig { pub fav: Vec, #[serde(default)] options: HashMap, + // Various data for flutter ui + #[serde(default)] + ui_flutter: HashMap, } impl LocalConfig { @@ -968,6 +974,27 @@ impl LocalConfig { 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)] diff --git a/src/client.rs b/src/client.rs index d00df1c48..a938702b3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -6,6 +6,7 @@ use cpal::{ }; use magnum_opus::{Channels::*, Decoder as AudioDecoder}; use sha2::{Digest, Sha256}; +#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] use std::sync::atomic::Ordering; use std::{ collections::HashMap, @@ -984,6 +985,32 @@ impl LoginConfigHandler { 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. /// /// # 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() { crate::ui_session_interface::KEYBOARD_HOOKED.store(true, Ordering::SeqCst); } diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 52042da06..04d1d4d29 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -23,9 +23,10 @@ use hbb_common::rendezvous_proto::ConnType; use hbb_common::tokio::{ self, sync::mpsc, - sync::Mutex as TokioMutex, time::{self, Duration, Instant, Interval}, }; +#[cfg(windows)] +use hbb_common::tokio::sync::Mutex as TokioMutex; use hbb_common::{ allow_err, message_proto::*, diff --git a/src/core_main.rs b/src/core_main.rs index 86f77946f..d22d4e71b 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -122,7 +122,7 @@ pub fn core_main() -> Option> { hbb_common::allow_err!(crate::rc::extract_resources(&args[1])); return None; } else if args[0] == "--tray" { - crate::tray::start_tray(crate::ui_interface::OPTIONS.clone()); + crate::tray::start_tray(); return None; } } @@ -149,12 +149,12 @@ pub fn core_main() -> Option> { 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. } - #[cfg(all(target_os = "linux"))] + #[cfg(target_os = "linux")] { 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 - handler.join(); + hbb_common::allow_err!(handler.join()); } } else if args[0] == "--import-config" { if args.len() == 2 { @@ -249,5 +249,6 @@ fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option *mut *mut c_char { } // https://gist.github.com/iskakaushik/1c5b8aa75c77479c33c4320913eebef6 +#[cfg(windows)] fn rust_args_to_c_args(args: Vec, outlen: *mut c_int) -> *mut *mut c_char { let mut v = vec![]; @@ -227,6 +228,7 @@ impl InvokeUiSession for FlutterHandler { id: i32, entries: &Vec, path: String, + #[allow(unused_variables)] is_local: bool, only_count: bool, ) { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 9552bd36e..21603227f 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -19,12 +19,12 @@ use crate::flutter::{self, SESSIONS}; #[cfg(target_os = "android")] use crate::start_server; use crate::ui_interface::{self, *}; +#[cfg(not(any(target_os = "android", target_os = "ios")))] +use crate::ui_session_interface::CUR_SESSION; use crate::{ client::file_trait::FileManager, 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) { *config::APP_DIR.write().unwrap() = app_dir.to_owned(); #[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 { + 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 { + 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 { if let Some(session) = SESSIONS.read().unwrap().get(&id) { 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 { #[cfg(not(any(target_os = "android", target_os = "ios")))] return get_sound_inputs(); - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(any(target_os = "android", target_os = "ios"))] vec![String::from("")] } @@ -537,8 +559,8 @@ pub fn main_post_request(url: String, body: String, header: String) { post_request(url, body, header) } -pub fn main_get_local_option(key: String) -> String { - get_local_option(key) +pub fn main_get_local_option(key: String) -> SyncReturn { + SyncReturn(get_local_option(key)) } pub fn main_set_local_option(key: String, value: String) { @@ -1021,7 +1043,7 @@ pub fn main_is_installed() -> SyncReturn { SyncReturn(is_installed()) } -pub fn main_start_grab_keyboard(){ +pub fn main_start_grab_keyboard() { #[cfg(not(any(target_os = "android", target_os = "ios")))] crate::ui_session_interface::global_grab_keyboard(); } diff --git a/src/hbbs_http/account.rs b/src/hbbs_http/account.rs index cdf724971..de0c82bb9 100644 --- a/src/hbbs_http/account.rs +++ b/src/hbbs_http/account.rs @@ -1,7 +1,7 @@ use super::HbbHttpResponse; use hbb_common::{ config::{Config, LocalConfig}, - log, sleep, tokio, ResultType, + log, ResultType, }; use reqwest::blocking::Client; use serde_derive::{Deserialize, Serialize}; diff --git a/src/server/dbus.rs b/src/server/dbus.rs index a7b21f0dc..5a38fe7cb 100644 --- a/src/server/dbus.rs +++ b/src/server/dbus.rs @@ -6,7 +6,9 @@ use dbus::blocking::Connection; use dbus_crossroads::{Crossroads, IfaceBuilder}; 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_PREFIX: &str = "/dbus"; @@ -65,7 +67,7 @@ fn handle_client_message(builder: &mut IfaceBuilder<()>) { DBUS_METHOD_NEW_CONNECTION, (DBUS_METHOD_NEW_CONNECTION_ID,), (DBUS_METHOD_RETURN,), - move |_, _, (peer_id,): (String,)| { + move |_, _, (_peer_id,): (String,)| { #[cfg(feature = "flutter")] { use crate::flutter::{self, APP_TYPE_MAIN}; @@ -77,7 +79,7 @@ fn handle_client_message(builder: &mut IfaceBuilder<()>) { { let data = HashMap::from([ ("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())) { log::error!("failed to add dbus message to flutter global dbus stream."); diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 170d672c9..44ccece8e 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -373,7 +373,7 @@ fn fix_modifiers(modifiers: &[EnumOrUnknown], en: &mut Enigo, ck: i3 } } -fn is_mouse_active_by_conn(conn: i32) -> bool { +fn active_mouse_(conn: i32) -> bool { // out of time protection if LATEST_CURSOR_POS.lock().unwrap().0.elapsed() > MOUSE_MOVE_PROTECTION_TIMEOUT { return true; @@ -388,13 +388,13 @@ fn is_mouse_active_by_conn(conn: i32) -> bool { // check if input is in valid range match crate::get_cursor_pos() { 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; - if !is_same_input { + if !can_active { last_input.x = -MOUSE_ACTIVE_DISTANCE * 2; last_input.y = -MOUSE_ACTIVE_DISTANCE * 2; } - is_same_input + can_active } None => true, } @@ -405,7 +405,7 @@ fn handle_mouse_(evt: &MouseEvent, conn: i32) { return; } - if !is_mouse_active_by_conn(conn) { + if !active_mouse_(conn) { return; } diff --git a/src/server/video_qos.rs b/src/server/video_qos.rs index 7aaf12d92..ba67d3fc4 100644 --- a/src/server/video_qos.rs +++ b/src/server/video_qos.rs @@ -225,11 +225,7 @@ impl VideoQoS { } pub fn check_abr_config(&mut self) -> bool { - self.enable_abr = if let Some(v) = Config2::get().options.get("enable-abr") { - v != "N" - } else { - true // default is true - }; + self.enable_abr = "N" != Config::get_option("enable-abr"); self.enable_abr } diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 5927bddec..9696ef08d 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -33,10 +33,11 @@ use std::{ collections::HashSet, io::ErrorKind::WouldBlock, ops::{Deref, DerefMut}, - sync::Once, time::{self, Duration, Instant}, }; #[cfg(windows)] +use std::sync::Once; +#[cfg(windows)] use virtual_display; pub const SCRAP_UBUNTU_HIGHER_REQUIRED: &str = "Wayland requires Ubuntu 21.04 or higher version."; diff --git a/src/tray.rs b/src/tray.rs index 80647fa17..30bdc5a59 100644 --- a/src/tray.rs +++ b/src/tray.rs @@ -1,14 +1,12 @@ -use hbb_common::log::debug; +use super::ui_interface::get_option_opt; #[cfg(target_os = "linux")] -use hbb_common::log::{error, info}; +use hbb_common::log::{debug, error, info}; #[cfg(target_os = "linux")] use libappindicator::AppIndicator; #[cfg(target_os = "linux")] use std::env::temp_dir; -use std::{ - collections::HashMap, - sync::{Arc, Mutex}, -}; +#[cfg(target_os = "windows")] +use std::sync::{Arc, Mutex}; #[cfg(target_os = "windows")] use trayicon::{MenuBuilder, TrayIconBuilder}; #[cfg(target_os = "windows")] @@ -17,6 +15,7 @@ use winit::{ event_loop::{ControlFlow, EventLoop}, }; +#[cfg(target_os = "windows")] #[derive(Clone, Eq, PartialEq, Debug)] enum Events { DoubleClickTrayIcon, @@ -25,7 +24,7 @@ enum Events { } #[cfg(target_os = "windows")] -pub fn start_tray(options: Arc>>) { +pub fn start_tray() { let event_loop = EventLoop::::with_user_event(); let proxy = event_loop.create_proxy(); let icon = include_bytes!("../res/tray-icon.ico"); @@ -39,23 +38,19 @@ pub fn start_tray(options: Arc>>) { let old_state = Arc::new(Mutex::new(0)); let _sender = crate::ui_interface::SENDER.lock().unwrap(); 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; return; } else { *control_flow = ControlFlow::Wait; } - let stopped = if let Some(v) = options.lock().unwrap().get("stop-service") { - !v.is_empty() - } else { - false - }; - let stopped = if stopped { 2 } else { 1 }; + let stopped = is_service_stoped(); + let state = if stopped { 2 } else { 1 }; let old = *old_state.lock().unwrap(); - if stopped != old { + if state != old { hbb_common::log::info!("State changed"); let mut m = MenuBuilder::new(); - if stopped == 2 { + if state == 2 { m = m.item( &crate::client::translate("Start Service".to_owned()), Events::StartService, @@ -67,7 +62,7 @@ pub fn start_tray(options: Arc>>) { ); } tray_icon.set_menu(&m).ok(); - *old_state.lock().unwrap() = stopped; + *old_state.lock().unwrap() = state; } match event { @@ -92,10 +87,9 @@ pub fn start_tray(options: Arc>>) { /// [Block] /// This function will block current execution, show the tray icon and handle events. #[cfg(target_os = "linux")] -pub fn start_tray(options: Arc>>) { - use std::time::Duration; - +pub fn start_tray() { use gtk::traits::{GtkMenuItemExt, MenuShellExt, WidgetExt}; + info!("configuring tray"); // init gtk context if let Err(err) = gtk::init() { @@ -104,17 +98,17 @@ pub fn start_tray(options: Arc>>) { } if let Some(mut appindicator) = get_default_app_indicator() { let mut menu = gtk::Menu::new(); - let running = get_service_status(options.clone()); + let stoped = is_service_stoped(); // start/stop service - let label = if !running { + let label = if stoped { crate::client::translate("Start Service".to_owned()) } else { crate::client::translate("Stop service".to_owned()) }; let menu_item_service = gtk::MenuItem::with_label(label.as_str()); menu_item_service.connect_activate(move |item| { - let lock = crate::ui_interface::SENDER.lock().unwrap(); - update_tray_service_item(options.clone(), item); + let _lock = crate::ui_interface::SENDER.lock().unwrap(); + update_tray_service_item(item); }); menu.append(&menu_item_service); // show tray item @@ -129,19 +123,17 @@ pub fn start_tray(options: Arc>>) { } #[cfg(target_os = "linux")] -fn update_tray_service_item(options: Arc>>, item: >k::MenuItem) { - use gtk::{ - traits::{GtkMenuItemExt, ListBoxRowExt}, - MenuItem, - }; - 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 { +fn update_tray_service_item(item: >k::MenuItem) { + use gtk::traits::GtkMenuItemExt; + + if is_service_stoped() { debug!("Now try to start service"); item.set_label(&crate::client::translate("Stop service".to_owned())); 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 { Some(appindicator) } -/// Get service status -/// Return [`true`] if service is running, [`false`] otherwise. +/// Check if service is stoped. +/// Return [`true`] if service is stoped, [`false`] otherwise. #[inline] -fn get_service_status(options: Arc>>) -> bool { - if let Some(v) = options.lock().unwrap().get("stop-service") { - debug!("service stopped: {}", v); - v.is_empty() +fn is_service_stoped() -> bool { + if let Some(v) = get_option_opt("stop-service") { + v == "Y" } else { - true + false } } diff --git a/src/ui.rs b/src/ui.rs index b11e574ba..2d7f8d70a 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -33,7 +33,8 @@ pub mod win_privacy; type Message = RendezvousMessage; -pub type Childs = Arc)>>; +pub type Children = Arc)>>; +#[allow(dead_code)] type Status = (i32, bool, i64, String); lazy_static::lazy_static! { @@ -43,6 +44,7 @@ lazy_static::lazy_static! { struct UIHostHandler; +// to-do: dead code? fn check_connect_status( reconnect: bool, ) -> ( @@ -111,8 +113,8 @@ pub fn start(args: &mut [String]) { args[1] = id; } if args.is_empty() { - let child: Childs = Default::default(); - std::thread::spawn(move || check_zombie(child)); + let children: Children = Default::default(); + std::thread::spawn(move || check_zombie(children)); crate::common::check_software_update(); frame.event_handler(UI {}); 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(); loop { - let mut lock = childs.lock().unwrap(); + let mut lock = children.lock().unwrap(); let mut n = 0; for (id, c) in lock.1.iter_mut() { if let Ok(Some(_)) = c.try_wait() { diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index 61366594c..09e259fa2 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -2,13 +2,14 @@ use std::sync::Arc; use std::{ collections::HashMap, - iter::FromIterator, ops::{Deref, DerefMut}, sync::{ atomic::{AtomicI64, Ordering}, RwLock, }, }; +#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] +use std::iter::FromIterator; #[cfg(windows)] use clipboard::{cliprdr::CliprdrClientContext, empty_clipboard, set_conn_enabled, ContextSend}; diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 29b09addc..7d520662d 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -9,32 +9,37 @@ use std::{ use hbb_common::password_security; use hbb_common::{ allow_err, - config::{self, Config, LocalConfig, PeerConfig, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT}, - directories_next, - futures::future::join_all, - log, - protobuf::Message as _, - rendezvous_proto::*, + config::{self, Config, LocalConfig, PeerConfig}, + directories_next, log, sleep, - tcp::FramedStream, 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")] 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; -pub type Childs = Arc)>>; +pub type Children = Arc)>>; type Status = (i32, bool, i64, String); // (status_num, key_confirmed, mouse_time, id) lazy_static::lazy_static! { - pub static ref CHILDS : Childs = Default::default(); - pub static ref UI_STATUS : Arc> = Arc::new(Mutex::new((0, false, 0, "".to_owned()))); - pub static ref OPTIONS : Arc>> = Arc::new(Mutex::new(Config::get_options())); - pub static ref ASYNC_JOB_STATUS : Arc> = Default::default(); - pub static ref TEMPORARY_PASSWD : Arc> = Arc::new(Mutex::new("".to_owned())); + static ref CHILDREN : Children = Default::default(); + static ref UI_STATUS : Arc> = Arc::new(Mutex::new((0, false, 0, "".to_owned()))); + static ref OPTIONS : Arc>> = Arc::new(Mutex::new(Config::get_options())); + static ref ASYNC_JOB_STATUS : Arc> = Default::default(); + static ref TEMPORARY_PASSWD : Arc> = Arc::new(Mutex::new("".to_owned())); } #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -44,15 +49,16 @@ lazy_static::lazy_static! { #[inline] pub fn recent_sessions_updated() -> bool { - let mut childs = CHILDS.lock().unwrap(); - if childs.0 { - childs.0 = false; + let mut children = CHILDREN.lock().unwrap(); + if children.0 { + children.0 = false; true } else { false } } +#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] #[inline] pub fn get_id() -> String { #[cfg(any(target_os = "android", target_os = "ios"))] @@ -143,10 +149,9 @@ pub fn get_license() -> String { #[cfg(windows)] if let Some(lic) = crate::platform::windows::get_license() { #[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) + #[cfg(not(feature = "flutter"))] return format!( "
Key: {}
Host: {} Api: {}", lic.key, lic.host, lic.api @@ -155,6 +160,11 @@ pub fn get_license() -> String { Default::default() } +#[inline] +pub fn get_option_opt(key: &str) -> Option { + OPTIONS.lock().unwrap().get(key).map(|x| x.clone()) +} + #[inline] pub fn get_option(key: String) -> String { get_option_(&key) @@ -180,6 +190,18 @@ pub fn set_local_option(key: String, value: String) { 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] pub fn peer_has_password(id: String) -> bool { !PeerConfig::load(&id).password.is_empty() @@ -230,7 +252,7 @@ pub fn test_if_valid_server(host: String) -> String { } #[inline] -#[cfg(not(any(target_os = "android", target_os = "ios")))] +#[cfg(feature = "flutter")] pub fn get_sound_inputs() -> Vec { let mut a = Vec::new(); #[cfg(not(target_os = "linux"))] @@ -345,12 +367,15 @@ pub fn set_socks(proxy: String, username: String, password: String) { .ok(); } +#[cfg(not(any(target_os = "android", target_os = "ios")))] #[inline] pub fn is_installed() -> bool { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - return crate::platform::is_installed(); - } + crate::platform::is_installed() +} + +#[cfg(any(target_os = "android", target_os = "ios"))] +#[inline] +pub fn is_installed() -> bool { false } @@ -495,7 +520,7 @@ pub fn remove_peer(id: String) { #[inline] 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 key = (id.clone(), remote_type.clone()); if let Some(c) = lock.1.get_mut(&key) { @@ -612,6 +637,7 @@ pub fn get_version() -> String { crate::VERSION.to_owned() } +#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] #[inline] pub fn get_app_name() -> String { crate::get_app_name() @@ -650,6 +676,7 @@ pub fn create_shortcut(_id: String) { crate::platform::windows::create_shortcut(&_id).ok(); } +#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] #[inline] pub fn discover() { std::thread::spawn(move || { @@ -694,6 +721,7 @@ pub fn open_url(url: String) { allow_err!(std::process::Command::new(p).arg(url).spawn()); } +#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] #[inline] pub fn change_id(id: String) { *ASYNC_JOB_STATUS.lock().unwrap() = " ".to_owned(); @@ -800,13 +828,19 @@ pub fn is_release() -> bool { 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] pub fn is_root() -> bool { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - return crate::platform::is_root(); false } +#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] #[inline] pub fn check_super_user_permission() -> bool { #[cfg(feature = "flatpak")] @@ -818,10 +852,10 @@ pub fn check_super_user_permission() -> bool { } #[allow(dead_code)] -pub fn check_zombie(childs: Childs) { +pub fn check_zombie(children: Children) { let mut deads = Vec::new(); loop { - let mut lock = childs.lock().unwrap(); + let mut lock = children.lock().unwrap(); let mut n = 0; for (id, c) in lock.1.iter_mut() { 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")] pub(crate) async fn send_to_cm(data: &ipc::Data) { 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"; +#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] const UNKNOWN_ERROR: &'static str = "Unknown error"; +#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] #[tokio::main(flavor = "current_thread")] async fn change_id_(id: String, old_id: String) -> &'static str { if !hbb_common::is_valid_custom_id(&id) { @@ -987,6 +1025,7 @@ async fn change_id_(id: String, old_id: String) -> &'static str { err } +#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] async fn check_id( rendezvous_server: String, old_id: String, diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index bf03ed2d3..ead60299a 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -98,6 +98,14 @@ impl Session { 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) { let msg = self.lc.write().unwrap().toggle_option(name.clone()); if name == "enable-file-transfer" { @@ -1590,7 +1598,7 @@ pub fn global_grab_keyboard() { #[cfg(any(target_os = "windows", target_os = "macos"))] std::thread::spawn(move || { let func = move |event: Event| match event.event_type { - EventType::KeyPress(key) | EventType::KeyRelease(key) => { + EventType::KeyPress(..) | EventType::KeyRelease(..) => { // grab all keys if !IS_IN.load(Ordering::SeqCst) { return Some(event);