diff --git a/flutter/assets/GitHub.svg b/flutter/assets/GitHub.svg deleted file mode 100644 index ef0bb12a7..000000000 --- a/flutter/assets/GitHub.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/Google.svg b/flutter/assets/Google.svg deleted file mode 100644 index df394a84f..000000000 --- a/flutter/assets/Google.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/auth-apple.svg b/flutter/assets/auth-apple.svg new file mode 100644 index 000000000..6933fbc3b --- /dev/null +++ b/flutter/assets/auth-apple.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/assets/auth-auth0.svg b/flutter/assets/auth-auth0.svg new file mode 100644 index 000000000..dbe3ed236 --- /dev/null +++ b/flutter/assets/auth-auth0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/assets/auth-azure.svg b/flutter/assets/auth-azure.svg new file mode 100644 index 000000000..b7435604d --- /dev/null +++ b/flutter/assets/auth-azure.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/assets/auth-default.svg b/flutter/assets/auth-default.svg new file mode 100644 index 000000000..bf5fa9073 --- /dev/null +++ b/flutter/assets/auth-default.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/assets/auth-facebook.svg b/flutter/assets/auth-facebook.svg new file mode 100644 index 000000000..f58725000 --- /dev/null +++ b/flutter/assets/auth-facebook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/assets/auth-github.svg b/flutter/assets/auth-github.svg new file mode 100644 index 000000000..778b7b341 --- /dev/null +++ b/flutter/assets/auth-github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/assets/auth-google.svg b/flutter/assets/auth-google.svg new file mode 100644 index 000000000..18970f31a --- /dev/null +++ b/flutter/assets/auth-google.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flutter/assets/Okta.svg b/flutter/assets/auth-okta.svg similarity index 100% rename from flutter/assets/Okta.svg rename to flutter/assets/auth-okta.svg diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index e96f02772..e518a56e0 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -11,6 +11,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_hbb/common/formatter/id_formatter.dart'; import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/main.dart'; @@ -1240,7 +1241,7 @@ bool option2bool(String option, String value) { option == "stop-service" || option == "direct-server" || option == "stop-rendezvous-service" || - option == "force-always-relay") { + option == kOptionForceAlwaysRelay) { res = value == "Y"; } else { assert(false); @@ -1257,7 +1258,7 @@ String bool2option(String option, bool b) { option == "stop-service" || option == "direct-server" || option == "stop-rendezvous-service" || - option == "force-always-relay") { + option == kOptionForceAlwaysRelay) { res = b ? 'Y' : ''; } else { assert(false); @@ -1288,6 +1289,14 @@ bool mainGetLocalBoolOptionSync(String key) { return option2bool(key, bind.mainGetLocalOption(key: key)); } +bool mainGetPeerBoolOptionSync(String id, String key) { + return option2bool(key, bind.mainGetPeerOptionSync(id: id, key: key)); +} + +mainSetPeerBoolOptionSync(String id, String key, bool v) { + bind.mainSetPeerOptionSync(id: id, key: key, value: bool2option(key, v)); +} + Future matchPeer(String searchText, Peer peer) async { if (searchText.isEmpty) { return true; @@ -1416,17 +1425,18 @@ Future saveWindowPosition(WindowType type, {int? windowId}) async { k: kWindowPrefix + type.name, v: pos.toString()); if (type == WindowType.RemoteDesktop && windowId != null) { - await _saveSessionWindowPosition(windowId, pos); + await _saveSessionWindowPosition(type, windowId, pos); } } -Future _saveSessionWindowPosition(int windowId, LastWindowPosition pos) async { +Future _saveSessionWindowPosition( + WindowType windowType, int windowId, LastWindowPosition pos) async { final remoteList = await DesktopMultiWindow.invokeMethod( windowId, kWindowEventGetRemoteList, null); if (remoteList != null) { for (final peerId in remoteList.split(',')) { bind.sessionSetFlutterConfigByPeerId( - id: peerId, k: kWindowPrefix, v: pos.toString()); + id: peerId, k: kWindowPrefix + windowType.name, v: pos.toString()); } } } @@ -1533,9 +1543,19 @@ Future restoreWindowPosition(WindowType type, bool isRemotePeerPos = false; String? pos; + // No need to check mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs) + // Though "open in tabs" is true and the new window restore peer position, it's ok. if (type == WindowType.RemoteDesktop && windowId != null && peerId != null) { - pos = await bind.sessionGetFlutterConfigByPeerId( - id: peerId, k: kWindowPrefix); + // If the restore position is called by main window, and the peer id is not null + // then we may need to get the position by reading the peer config. + // Because the session may not be read at this time. + if (desktopType == DesktopType.main) { + pos = bind.mainGetPeerFlutterConfigSync( + id: peerId, k: kWindowPrefix + type.name); + } else { + pos = await bind.sessionGetFlutterConfigByPeerId( + id: peerId, k: kWindowPrefix + type.name); + } isRemotePeerPos = pos != null; } pos ??= bind.getLocalFlutterConfig(k: kWindowPrefix + type.name); @@ -1545,7 +1565,9 @@ Future restoreWindowPosition(WindowType type, debugPrint("no window position saved, ignoring position restoration"); return false; } - if (type == WindowType.RemoteDesktop && !isRemotePeerPos && windowId != null) { + if (type == WindowType.RemoteDesktop && + !isRemotePeerPos && + windowId != null) { if (lpos.offsetWidth != null) { lpos.offsetWidth = lpos.offsetWidth! + windowId * 20; } @@ -1801,14 +1823,13 @@ connectMainDesktop( required bool isTcpTunneling, required bool isRDP, bool? forceRelay, - bool forceSeparateWindow = false, }) async { if (isFileTransfer) { await rustDeskWinManager.newFileTransfer(id, forceRelay: forceRelay); } else if (isTcpTunneling || isRDP) { await rustDeskWinManager.newPortForward(id, isRDP, forceRelay: forceRelay); } else { - await rustDeskWinManager.newRemoteDesktop(id, forceRelay: forceRelay, forceSeparateWindow: forceSeparateWindow); + await rustDeskWinManager.newRemoteDesktop(id, forceRelay: forceRelay); } } @@ -1822,9 +1843,16 @@ connect( bool isFileTransfer = false, bool isTcpTunneling = false, bool isRDP = false, - bool forceSeparateWindow = false, }) async { if (id == '') return; + if (!isDesktop || desktopType == DesktopType.main) { + try { + if (Get.isRegistered()) { + final idController = Get.find(); + idController.text = formatID(id); + } + } catch (_) {} + } id = id.replaceAll(' ', ''); final oldId = id; id = await bind.mainHandleRelayId(id: id); @@ -1840,7 +1868,6 @@ connect( isTcpTunneling: isTcpTunneling, isRDP: isRDP, forceRelay: forceRelay, - forceSeparateWindow: forceSeparateWindow, ); } else { await rustDeskWinManager.call(WindowType.Main, kWindowConnect, { @@ -1849,7 +1876,6 @@ connect( 'isTcpTunneling': isTcpTunneling, 'isRDP': isRDP, 'forceRelay': forceRelay, - 'forceSeparateWindow': forceSeparateWindow, }); } } else { @@ -2314,3 +2340,10 @@ Widget unreadTopRightBuilder(RxInt? count, {Widget? icon}) { ], ); } + +String toCapitalized(String s) { + if (s.isEmpty) { + return s; + } + return s.substring(0, 1).toUpperCase() + s.substring(1); +} diff --git a/flutter/lib/common/formatter/id_formatter.dart b/flutter/lib/common/formatter/id_formatter.dart index bd7f76666..c2329d53f 100644 --- a/flutter/lib/common/formatter/id_formatter.dart +++ b/flutter/lib/common/formatter/id_formatter.dart @@ -35,6 +35,11 @@ class IDTextInputFormatter extends TextInputFormatter { String formatID(String id) { String id2 = id.replaceAll(' ', ''); + String suffix = ''; + if (id2.endsWith(r'\r') || id2.endsWith(r'/r')) { + suffix = id2.substring(id2.length - 2, id2.length); + id2 = id2.substring(0, id2.length - 2); + } if (int.tryParse(id2) == null) return id; String newID = ''; if (id2.length <= 3) { @@ -47,7 +52,7 @@ String formatID(String id) { newID += " ${id2.substring(i, i + 3)}"; } } - return newID; + return newID + suffix; } String trimID(String id) { diff --git a/flutter/lib/common/widgets/dialog.dart b/flutter/lib/common/widgets/dialog.dart index 05a105e68..77307418a 100644 --- a/flutter/lib/common/widgets/dialog.dart +++ b/flutter/lib/common/widgets/dialog.dart @@ -1352,7 +1352,7 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async { msgBoxCommon(ffi.dialogManager, 'Custom Image Quality', content, [btnClose]); } -void deletePeerConfirmDialog(Function onSubmit) async { +void deletePeerConfirmDialog(Function onSubmit, String title) async { gFFI.dialogManager.show( (setState, close, context) { submit() async { @@ -1368,8 +1368,10 @@ void deletePeerConfirmDialog(Function onSubmit) async { Icons.delete_rounded, color: Colors.red, ), - Text(translate('Delete')).paddingOnly( - left: 10, + Expanded( + child: Text(title, overflow: TextOverflow.ellipsis).paddingOnly( + left: 10, + ), ), ], ), diff --git a/flutter/lib/common/widgets/login.dart b/flutter/lib/common/widgets/login.dart index 17cc7090c..d7037e58f 100644 --- a/flutter/lib/common/widgets/login.dart +++ b/flutter/lib/common/widgets/login.dart @@ -12,25 +12,33 @@ import 'package:url_launcher/url_launcher.dart'; import '../../common.dart'; import './dialog.dart'; +const kOpSvgList = ['github', 'google', 'apple', 'okta', 'facebook', 'azure', 'auth0']; + class _IconOP extends StatelessWidget { - final String icon; - final double iconWidth; + final String op; + final String? icon; final EdgeInsets margin; const _IconOP( {Key? key, + required this.op, required this.icon, - required this.iconWidth, this.margin = const EdgeInsets.symmetric(horizontal: 4.0)}) : super(key: key); @override Widget build(BuildContext context) { + final svgFile = kOpSvgList.contains(op.toLowerCase()) ? op.toLowerCase() : 'default'; return Container( margin: margin, - child: SvgPicture.asset( - 'assets/$icon.svg', - width: iconWidth, - ), + child: icon == null + ? SvgPicture.asset( + 'assets/auth-$svgFile.svg', + width: 20, + ) + : SvgPicture.string( + icon!, + width: 20, + ), ); } } @@ -38,7 +46,7 @@ class _IconOP extends StatelessWidget { class ButtonOP extends StatelessWidget { final String op; final RxString curOP; - final double iconWidth; + final String? icon; final Color primaryColor; final double height; final Function() onTap; @@ -47,7 +55,7 @@ class ButtonOP extends StatelessWidget { Key? key, required this.op, required this.curOP, - required this.iconWidth, + required this.icon, required this.primaryColor, required this.height, required this.onTap, @@ -61,7 +69,7 @@ class ButtonOP extends StatelessWidget { width: 200, child: Obx(() => ElevatedButton( style: ElevatedButton.styleFrom( - primary: curOP.value.isEmpty || curOP.value == op + backgroundColor: curOP.value.isEmpty || curOP.value == op ? primaryColor : Colors.grey, ).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)), @@ -69,17 +77,21 @@ class ButtonOP extends StatelessWidget { child: Row( children: [ SizedBox( - width: 30, - child: _IconOP( - icon: op, - iconWidth: iconWidth, - margin: EdgeInsets.only(right: 5), - )), + width: 30, + child: _IconOP( + op: op, + icon: icon, + margin: EdgeInsets.only(right: 5), + ), + ), Expanded( - child: FittedBox( - fit: BoxFit.scaleDown, - child: Center( - child: Text('${translate("Continue with")} $op')))), + child: FittedBox( + fit: BoxFit.scaleDown, + child: Center( + child: Text( + '${translate("Continue with")} ${op.toLowerCase() == "github" ? "GitHub" : toCapitalized(op)}')), + ), + ), ], ))), ), @@ -89,8 +101,8 @@ class ButtonOP extends StatelessWidget { class ConfigOP { final String op; - final double iconWidth; - ConfigOP({required this.op, required this.iconWidth}); + final String? icon; + ConfigOP({required this.op, required this.icon}); } class WidgetOP extends StatefulWidget { @@ -182,7 +194,7 @@ class _WidgetOPState extends State { ButtonOP( op: widget.config.op, curOP: widget.curOP, - iconWidth: widget.config.iconWidth, + icon: widget.config.icon, primaryColor: str2color(widget.config.op, 0x7f), height: 36, onTap: () async { @@ -380,7 +392,7 @@ Future loginDialog() async { final loginOptions = [].obs; Future.delayed(Duration.zero, () async { - loginOptions.value = await UserModel.queryLoginOptions(); + loginOptions.value = await UserModel.queryOidcLoginOptions(); }); final res = await gFFI.dialogManager.show((setState, close, context) { @@ -460,12 +472,8 @@ Future loginDialog() async { } thirdAuthWidget() => Obx(() { - final oidcOptions = loginOptions - .where((opt) => opt.startsWith(kAuthReqTypeOidc)) - .map((opt) => opt.substring(kAuthReqTypeOidc.length)) - .toList(); return Offstage( - offstage: oidcOptions.isEmpty, + offstage: loginOptions.isEmpty, child: Column( children: [ const SizedBox( @@ -480,12 +488,8 @@ Future loginDialog() async { height: 8.0, ), LoginWidgetOP( - ops: [ - ConfigOP(op: 'GitHub', iconWidth: 20), - ConfigOP(op: 'Google', iconWidth: 20), - ConfigOP(op: 'Okta', iconWidth: 38), - ] - .where((op) => oidcOptions.contains(op.op.toLowerCase())) + ops: loginOptions + .map((e) => ConfigOP(op: e['name'], icon: e['icon'])) .toList(), curOP: curOP, cbLogin: (Map authBody) { diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 61c5cb69e..a88bb4930 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -404,7 +404,6 @@ abstract class BasePeerCard extends StatelessWidget { bool isFileTransfer = false, bool isTcpTunneling = false, bool isRDP = false, - bool forceSeparateWindow = false, }) { return MenuEntryButton( childBuilder: (TextStyle? style) => Text( @@ -418,7 +417,6 @@ abstract class BasePeerCard extends StatelessWidget { isFileTransfer: isFileTransfer, isTcpTunneling: isTcpTunneling, isRDP: isRDP, - forceSeparateWindow: forceSeparateWindow, ); }, padding: menuPadding, @@ -427,25 +425,13 @@ abstract class BasePeerCard extends StatelessWidget { } @protected - List> _connectActions(BuildContext context, Peer peer) { - final actions = [_connectAction(context, peer, false)]; - if (!mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow)) { - actions.add(_connectAction(context, peer, true)); - } - return actions; - } - - @protected - MenuEntryBase _connectAction( - BuildContext context, Peer peer, bool forceSeparateWindow) { + MenuEntryBase _connectAction(BuildContext context, Peer peer) { return _connectCommonAction( context, peer.id, (peer.alias.isEmpty - ? translate('Connect') - : '${translate('Connect')} ${peer.id}') + - (forceSeparateWindow ? ' (${translate('separate window')})' : ''), - forceSeparateWindow: forceSeparateWindow, + ? translate('Connect') + : '${translate('Connect')} ${peer.id}'), ); } @@ -538,15 +524,40 @@ abstract class BasePeerCard extends StatelessWidget { ); } + Future> _openNewConnInAction( + String id, String label, String key) async { + return MenuEntrySwitch( + switchType: SwitchType.scheckbox, + text: translate(label), + getter: () async => mainGetPeerBoolOptionSync(id, key), + setter: (bool v) async { + await bind.mainSetPeerOption( + id: id, key: key, value: bool2option(key, v)); + }, + padding: menuPadding, + dismissOnClicked: true, + ); + } + + _openInTabsAction(String id) async => + await _openNewConnInAction(id, 'Open in New Tab', kOptionOpenInTabs); + + _openInWindowsAction(String id) async => await _openNewConnInAction( + id, 'Open in New Window', kOptionOpenInWindows); + + _openNewConnInOptAction(String id) async => + mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs) + ? await _openInWindowsAction(id) + : await _openInTabsAction(id); + @protected Future _isForceAlwaysRelay(String id) async { - return (await bind.mainGetPeerOption(id: id, key: 'force-always-relay')) + return (await bind.mainGetPeerOption(id: id, key: kOptionForceAlwaysRelay)) .isNotEmpty; } @protected Future> _forceAlwaysRelayAction(String id) async { - const option = 'force-always-relay'; return MenuEntrySwitch( switchType: SwitchType.scheckbox, text: translate('Always connect via relay'), @@ -555,7 +566,9 @@ abstract class BasePeerCard extends StatelessWidget { }, setter: (bool v) async { await bind.mainSetPeerOption( - id: id, key: option, value: bool2option(option, v)); + id: id, + key: kOptionForceAlwaysRelay, + value: bool2option(kOptionForceAlwaysRelay, v)); }, padding: menuPadding, dismissOnClicked: true, @@ -623,7 +636,8 @@ abstract class BasePeerCard extends StatelessWidget { } } - deletePeerConfirmDialog(onSubmit); + deletePeerConfirmDialog(onSubmit, + '${translate('Delete')} "${peer.alias.isEmpty ? formatID(peer.id) : peer.alias}"?'); }, padding: menuPadding, dismissOnClicked: true, @@ -813,7 +827,7 @@ class RecentPeerCard extends BasePeerCard { Future>> _buildMenuItems( BuildContext context) async { final List> menuItems = [ - ..._connectActions(context, peer), + _connectAction(context, peer), _transferFileAction(context, peer.id), ]; @@ -822,6 +836,7 @@ class RecentPeerCard extends BasePeerCard { if (isDesktop && peer.platform != 'Android') { menuItems.add(_tcpTunnelingAction(context, peer.id)); } + // menuItems.add(await _openNewConnInOptAction(peer.id)); menuItems.add(await _forceAlwaysRelayAction(peer.id)); if (peer.platform == 'Windows') { menuItems.add(_rdpAction(context, peer.id)); @@ -869,12 +884,13 @@ class FavoritePeerCard extends BasePeerCard { Future>> _buildMenuItems( BuildContext context) async { final List> menuItems = [ - ..._connectActions(context, peer), + _connectAction(context, peer), _transferFileAction(context, peer.id), ]; if (isDesktop && peer.platform != 'Android') { menuItems.add(_tcpTunnelingAction(context, peer.id)); } + // menuItems.add(await _openNewConnInOptAction(peer.id)); menuItems.add(await _forceAlwaysRelayAction(peer.id)); if (peer.platform == 'Windows') { menuItems.add(_rdpAction(context, peer.id)); @@ -919,7 +935,7 @@ class DiscoveredPeerCard extends BasePeerCard { Future>> _buildMenuItems( BuildContext context) async { final List> menuItems = [ - ..._connectActions(context, peer), + _connectAction(context, peer), _transferFileAction(context, peer.id), ]; @@ -928,6 +944,7 @@ class DiscoveredPeerCard extends BasePeerCard { if (isDesktop && peer.platform != 'Android') { menuItems.add(_tcpTunnelingAction(context, peer.id)); } + // menuItems.add(await _openNewConnInOptAction(peer.id)); menuItems.add(await _forceAlwaysRelayAction(peer.id)); if (peer.platform == 'Windows') { menuItems.add(_rdpAction(context, peer.id)); @@ -971,12 +988,13 @@ class AddressBookPeerCard extends BasePeerCard { Future>> _buildMenuItems( BuildContext context) async { final List> menuItems = [ - ..._connectActions(context, peer), + _connectAction(context, peer), _transferFileAction(context, peer.id), ]; if (isDesktop && peer.platform != 'Android') { menuItems.add(_tcpTunnelingAction(context, peer.id)); } + // menuItems.add(await _openNewConnInOptAction(peer.id)); menuItems.add(await _forceAlwaysRelayAction(peer.id)); if (peer.platform == 'Windows') { menuItems.add(_rdpAction(context, peer.id)); @@ -1033,12 +1051,13 @@ class MyGroupPeerCard extends BasePeerCard { Future>> _buildMenuItems( BuildContext context) async { final List> menuItems = [ - ..._connectActions(context, peer), + _connectAction(context, peer), _transferFileAction(context, peer.id), ]; if (isDesktop && peer.platform != 'Android') { menuItems.add(_tcpTunnelingAction(context, peer.id)); } + // menuItems.add(await _openNewConnInOptAction(peer.id)); menuItems.add(await _forceAlwaysRelayAction(peer.id)); if (peer.platform == 'Windows') { menuItems.add(_rdpAction(context, peer.id)); diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index f2de21dc7..e8cbd2619 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -312,7 +312,7 @@ class _PeerTabPageState extends State showToast(translate('Successful')); } - deletePeerConfirmDialog(onSubmit); + deletePeerConfirmDialog(onSubmit, translate('Delete')); }, child: Tooltip( message: translate('Delete'), diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 99a87362e..5376196e4 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -22,8 +22,6 @@ const String kAppTypeDesktopRemote = "remote"; const String kAppTypeDesktopFileTransfer = "file transfer"; const String kAppTypeDesktopPortForward = "port forward"; -const bool kCloseMultiWindowByHide = true; - const String kWindowMainWindowOnTop = "main_window_on_top"; const String kWindowGetWindowInfo = "get_window_info"; const String kWindowDisableGrabKeyboard = "disable_grab_keyboard"; @@ -42,7 +40,10 @@ const String kWindowEventGetSessionIdList = "get_session_id_list"; const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window"; const String kWindowEventCloseForSeparateWindow = "close_for_separate_window"; -const String kOptionSeparateRemoteWindow = "allow-separate-remote-window"; +const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs"; +const String kOptionOpenInTabs = "allow-open-in-tabs"; +const String kOptionOpenInWindows = "allow-open-in-windows"; +const String kOptionForceAlwaysRelay = "force-always-relay"; const String kUniLinksPrefix = "rustdesk://"; const String kUrlActionClose = "close"; diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index 45181c4a3..de87317b3 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -68,6 +68,7 @@ class _ConnectionPageState extends State _idController.selection = TextSelection( baseOffset: 0, extentOffset: _idController.value.text.length); }); + Get.put(_idController); windowManager.addListener(this); } @@ -76,6 +77,9 @@ class _ConnectionPageState extends State _idController.dispose(); _updateTimer?.cancel(); windowManager.removeListener(this); + if (Get.isRegistered()) { + Get.delete(); + } super.dispose(); } diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index b87820b97..7bf303a68 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -554,13 +554,7 @@ class _DesktopHomePageState extends State } else if (call.method == kWindowEventShow) { await rustDeskWinManager.registerActiveWindow(call.arguments["id"]); } else if (call.method == kWindowEventHide) { - final wId = call.arguments['id']; - final isSeparateWindowEnabled = - mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow); - if (isSeparateWindowEnabled && !kCloseMultiWindowByHide) { - await rustDeskWinManager.destroyWindow(wId); - } - await rustDeskWinManager.unregisterActiveWindow(wId); + await rustDeskWinManager.unregisterActiveWindow(call.arguments['id']); } else if (call.method == kWindowConnect) { await connectMainDesktop( call.arguments['id'], @@ -568,7 +562,6 @@ class _DesktopHomePageState extends State isTcpTunneling: call.arguments['isTcpTunneling'], isRDP: call.arguments['isRDP'], forceRelay: call.arguments['forceRelay'], - forceSeparateWindow: call.arguments['forceSeparateWindow'], ); } else if (call.method == kWindowEventMoveTabToNewWindow) { final args = call.arguments.split(','); diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 4a4992c58..591efd4c7 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -319,8 +319,8 @@ class _GeneralState extends State<_General> { _OptionCheckBox(context, 'Adaptive bitrate', 'enable-abr'), _OptionCheckBox( context, - 'Separate remote windows', - kOptionSeparateRemoteWindow, + 'Open new connections in tabs', + kOptionOpenNewConnInTabs, isServer: false, ), ]; @@ -995,16 +995,19 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { return false; } } - final old = await bind.mainGetOption(key: 'custom-rendezvous-server'); - if (old.isNotEmpty && old != idServer) { - await gFFI.userModel.logOut(); - } + final oldApiServer = await bind.mainGetApiServer(); + // should set one by one await bind.mainSetOption( key: 'custom-rendezvous-server', value: idServer); await bind.mainSetOption(key: 'relay-server', value: relayServer); await bind.mainSetOption(key: 'api-server', value: apiServer); await bind.mainSetOption(key: 'key', value: key); + + final newApiServer = await bind.mainGetApiServer(); + if (oldApiServer.isNotEmpty && oldApiServer != newApiServer) { + await gFFI.userModel.logOut(apiServer: oldApiServer); + } return true; } diff --git a/flutter/lib/desktop/pages/install_page.dart b/flutter/lib/desktop/pages/install_page.dart index 69cf2ac93..871030e15 100644 --- a/flutter/lib/desktop/pages/install_page.dart +++ b/flutter/lib/desktop/pages/install_page.dart @@ -282,7 +282,7 @@ class _InstallPageBodyState extends State<_InstallPageBody> title: null, content: SelectionArea( child: - msgboxContent('info', 'Warning', 'comfirm_install_cert_tip')), + msgboxContent('info', 'Warning', 'confirm_install_cert_tip')), actions: btns, onCancel: close, ), diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 32faf08ae..28212e4ca 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -116,11 +116,7 @@ class _RemotePageState extends State Wakelock.enable(); } // Register texture. - if (mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow)) { - _renderTexture = renderTexture; - } else { - _renderTexture = RenderTexture(); - } + _renderTexture = RenderTexture(); _renderTexture.create(sessionId); _ffi.ffiModel.updateEventListener(sessionId, widget.id); diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index 2a7d1452e..2645ea0f2 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -582,8 +582,6 @@ class WindowActionPanelState extends State } await windowManager.hide(); } else { - renderTexture.destroy(); - // it's safe to hide the subwindow final controller = WindowController.fromWindowId(kWindowId!); if (Platform.isMacOS && await controller.isFullScreen()) { diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index 48c56e6a5..c99462166 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -66,6 +66,7 @@ class _ConnectionPageState extends State { _idController.addListener(() { _idEmpty.value = _idController.text.isEmpty; }); + Get.put(_idController); } @override @@ -195,6 +196,9 @@ class _ConnectionPageState extends State { @override void dispose() { _idController.dispose(); + if (Get.isRegistered()) { + Get.delete(); + } super.dispose(); } } diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index eea1498db..e7e6daade 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -242,7 +242,7 @@ class _SettingsState extends State with WidgetsBindingObserver { }, ), SettingsTile.switchTile( - title: Text('${translate('Adaptive Bitrate')} (beta)'), + title: Text('${translate('Adaptive bitrate')} (beta)'), initialValue: _enableAbr, onToggle: (v) async { await bind.mainSetOption(key: "enable-abr", value: v ? "" : "N"); diff --git a/flutter/lib/models/desktop_render_texture.dart b/flutter/lib/models/desktop_render_texture.dart index 37d387eb2..f59373623 100644 --- a/flutter/lib/models/desktop_render_texture.dart +++ b/flutter/lib/models/desktop_render_texture.dart @@ -38,9 +38,4 @@ class RenderTexture { _textureKey = -1; } } - - static final RenderTexture instance = RenderTexture(); } - -// Global instance for separate texture -final renderTexture = RenderTexture.instance; diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index c9bcc0477..9c4ff281c 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -425,19 +425,18 @@ class FfiModel with ChangeNotifier { closeConnection(); } - Future.delayed(Duration.zero, () async { - await dialogManager.show( - (setState, close, context) => CustomAlertDialog( - title: null, - content: SelectionArea(child: msgboxContent(type, title, text)), - actions: [ - dialogButton("Cancel", onPressed: onClose, isOutline: true) - ], - onCancel: onClose), - tag: '$sessionId-waiting-for-image', - ); - _waitForImageDialogShow[sessionId] = true; - }); + dialogManager.show( + (setState, close, context) => CustomAlertDialog( + title: null, + content: SelectionArea(child: msgboxContent(type, title, text)), + actions: [ + dialogButton("Cancel", onPressed: onClose, isOutline: true) + ], + onCancel: onClose), + tag: '$sessionId-waiting-for-image', + ); + _waitForImageDialogShow[sessionId] = true; + bind.sessionOnWaitingForImageDialogShow(sessionId: sessionId); } _updateSessionWidthHeight(SessionID sessionId) { @@ -1532,12 +1531,13 @@ class RecordingModel with ChangeNotifier { sessionId: sessionId, start: true, width: width, height: height); } - toggle() { + toggle() async { if (isIOS) return; final sessionId = parent.target?.sessionId; if (sessionId == null) return; _start = !_start; notifyListeners(); + await bind.sessionRecordStatus(sessionId: sessionId, status: _start); if (_start) { bind.sessionRefresh(sessionId: sessionId); } else { @@ -1901,10 +1901,5 @@ Future initializeCursorAndCanvas(FFI ffi) async { } clearWaitingForImage(OverlayDialogManager? dialogManager, SessionID sessionId) { - final durations = [100, 500, 1000, 2000]; - for (var duration in durations) { - Future.delayed(Duration(milliseconds: duration), () { - dialogManager?.dismissByTag('$sessionId-waiting-for-image'); - }); - } + dialogManager?.dismissByTag('$sessionId-waiting-for-image'); } diff --git a/flutter/lib/models/user_model.dart b/flutter/lib/models/user_model.dart index 83df2e632..003577ba0 100644 --- a/flutter/lib/models/user_model.dart +++ b/flutter/lib/models/user_model.dart @@ -101,10 +101,10 @@ class UserModel { await Future.wait([gFFI.abModel.pullAb(), gFFI.groupModel.pull()]); } - Future logOut() async { + Future logOut({String? apiServer}) async { final tag = gFFI.dialogManager.showLoading(translate('Waiting')); try { - final url = await bind.mainGetApiServer(); + final url = apiServer ?? await bind.mainGetApiServer(); final authHeaders = getHttpHeaders(); authHeaders['Content-Type'] = "application/json"; await http @@ -163,15 +163,27 @@ class UserModel { return loginResponse; } - static Future> queryLoginOptions() async { + static Future> queryOidcLoginOptions() async { try { final url = await bind.mainGetApiServer(); if (url.trim().isEmpty) return []; final resp = await http.get(Uri.parse('$url/api/login-options')); - return jsonDecode(resp.body); + final List ops = []; + for (final item in jsonDecode(resp.body)) { + ops.add(item as String); + } + for (final item in ops) { + if (item.startsWith('common-oidc/')) { + return jsonDecode(item.substring('common-oidc/'.length)); + } + } + return ops + .where((item) => item.startsWith('oidc/')) + .map((item) => {'name': item.substring('oidc/'.length)}) + .toList(); } catch (e) { debugPrint( - "queryLoginOptions: jsonDecode resp body failed: ${e.toString()}"); + "queryOidcLoginOptions: jsonDecode resp body failed: ${e.toString()}"); return []; } } diff --git a/flutter/lib/utils/multi_window_manager.dart b/flutter/lib/utils/multi_window_manager.dart index 0a1858c2c..3f7d995b7 100644 --- a/flutter/lib/utils/multi_window_manager.dart +++ b/flutter/lib/utils/multi_window_manager.dart @@ -50,7 +50,7 @@ class RustDeskMultiWindowManager { 'session_id': sessionId, }; await _newSession( - true, + false, WindowType.RemoteDesktop, kWindowEventNewRemoteDesktop, peerId, @@ -81,19 +81,26 @@ class RustDeskMultiWindowManager { } _newSession( - bool separateWindow, + bool openInTabs, WindowType type, String methodName, String remoteId, List windows, String msg, ) async { - if (separateWindow) { - if (kCloseMultiWindowByHide && _inactiveWindows.isNotEmpty) { + if (openInTabs) { + if (windows.isEmpty) { + await newSessionWindow(type, remoteId, msg, windows); + } else { + call(type, methodName, msg); + } + } else { + if (_inactiveWindows.isNotEmpty) { for (final windowId in windows) { if (_inactiveWindows.contains(windowId)) { - await DesktopMultiWindow.invokeMethod( - windowId, methodName, msg); + await restoreWindowPosition(type, + windowId: windowId, peerId: remoteId); + await DesktopMultiWindow.invokeMethod(windowId, methodName, msg); WindowController.fromWindowId(windowId).show(); registerActiveWindow(windowId); return; @@ -101,12 +108,6 @@ class RustDeskMultiWindowManager { } } await newSessionWindow(type, remoteId, msg, windows); - } else { - if (windows.isEmpty) { - await newSessionWindow(type, remoteId, msg, windows); - } else { - call(type, methodName, msg); - } } } @@ -119,7 +120,6 @@ class RustDeskMultiWindowManager { bool? forceRelay, String? switchUuid, bool? isRDP, - bool forceSeparateWindow = false, }) async { var params = { "type": type.index, @@ -136,11 +136,10 @@ class RustDeskMultiWindowManager { final msg = jsonEncode(params); // separate window for file transfer is not supported - bool separateWindow = forceSeparateWindow || - (type != WindowType.FileTransfer && - mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow)); + bool openInTabs = type != WindowType.RemoteDesktop || + mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs); - if (windows.length > 1 || separateWindow) { + if (windows.length > 1 || !openInTabs) { for (final windowId in windows) { if (await DesktopMultiWindow.invokeMethod( windowId, kWindowEventActiveSession, remoteId)) { @@ -149,7 +148,7 @@ class RustDeskMultiWindowManager { } } - await _newSession(separateWindow, type, methodName, remoteId, windows, msg); + await _newSession(openInTabs, type, methodName, remoteId, windows, msg); } Future newRemoteDesktop( @@ -157,7 +156,6 @@ class RustDeskMultiWindowManager { String? password, String? switchUuid, bool? forceRelay, - bool forceSeparateWindow = false, }) async { return await newSession( WindowType.RemoteDesktop, @@ -167,7 +165,6 @@ class RustDeskMultiWindowManager { password: password, forceRelay: forceRelay, switchUuid: switchUuid, - forceSeparateWindow: forceSeparateWindow, ); } diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index dac137e1c..e6862bc80 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -664,6 +664,7 @@ message Misc { PluginFailure plugin_failure = 26; uint32 full_speed_fps = 27; uint32 auto_adjust_fps = 28; + bool client_record_status = 29; } } diff --git a/libs/scrap/examples/benchmark.rs b/libs/scrap/examples/benchmark.rs index f8f2e9436..83fba99d7 100644 --- a/libs/scrap/examples/benchmark.rs +++ b/libs/scrap/examples/benchmark.rs @@ -114,9 +114,9 @@ fn test_vpx( let config = EncoderCfg::VPX(VpxEncoderConfig { width: width as _, height: height as _, - timebase: [1, 1000], quality, codec: codec_id, + keyframe_interval: None, }); let mut encoder = VpxEncoder::new(config).unwrap(); let mut vpxs = vec![]; @@ -161,6 +161,7 @@ fn test_av1(yuvs: &Vec>, width: usize, height: usize, quality: Q, yuv_co width: width as _, height: height as _, quality, + keyframe_interval: None, }); let mut encoder = AomEncoder::new(config).unwrap(); let start = Instant::now(); diff --git a/libs/scrap/examples/record-screen.rs b/libs/scrap/examples/record-screen.rs index 2430e2872..6640d5698 100644 --- a/libs/scrap/examples/record-screen.rs +++ b/libs/scrap/examples/record-screen.rs @@ -113,9 +113,9 @@ fn main() -> io::Result<()> { let mut vpx = vpx_encode::VpxEncoder::new(EncoderCfg::VPX(vpx_encode::VpxEncoderConfig { width, height, - timebase: [1, 1000], quality, codec: vpx_codec, + keyframe_interval: None, })) .unwrap(); diff --git a/libs/scrap/src/common/aom.rs b/libs/scrap/src/common/aom.rs index f677858a7..dcb4968e6 100644 --- a/libs/scrap/src/common/aom.rs +++ b/libs/scrap/src/common/aom.rs @@ -45,6 +45,7 @@ pub struct AomEncoderConfig { pub width: u32, pub height: u32, pub quality: Quality, + pub keyframe_interval: Option, } pub struct AomEncoder { @@ -105,7 +106,12 @@ mod webrtc { c.g_timebase.num = 1; c.g_timebase.den = kRtpTicksPerSecond; c.g_input_bit_depth = kBitDepth; - c.kf_mode = aom_kf_mode::AOM_KF_DISABLED; + if let Some(keyframe_interval) = cfg.keyframe_interval { + c.kf_min_dist = 0; + c.kf_max_dist = keyframe_interval as _; + } else { + c.kf_mode = aom_kf_mode::AOM_KF_DISABLED; + } let (q_min, q_max, b) = AomEncoder::convert_quality(cfg.quality); if q_min > 0 && q_min < q_max && q_max < 64 { c.rc_min_quantizer = q_min; diff --git a/libs/scrap/src/common/codec.rs b/libs/scrap/src/common/codec.rs index fac5f82d7..2c3cbff6c 100644 --- a/libs/scrap/src/common/codec.rs +++ b/libs/scrap/src/common/codec.rs @@ -45,6 +45,7 @@ pub struct HwEncoderConfig { pub width: usize, pub height: usize, pub quality: Quality, + pub keyframe_interval: Option, } #[derive(Debug, Clone)] diff --git a/libs/scrap/src/common/hwcodec.rs b/libs/scrap/src/common/hwcodec.rs index ba834e048..c1fbdfa6e 100644 --- a/libs/scrap/src/common/hwcodec.rs +++ b/libs/scrap/src/common/hwcodec.rs @@ -52,6 +52,7 @@ impl EncoderApi for HwEncoder { if base_bitrate <= 0 { bitrate = base_bitrate; } + let gop = config.keyframe_interval.unwrap_or(DEFAULT_GOP as _) as i32; let ctx = EncodeContext { name: config.name.clone(), width: config.width as _, @@ -60,7 +61,7 @@ impl EncoderApi for HwEncoder { align: HW_STRIDE_ALIGN as _, bitrate: bitrate as i32 * 1000, timebase: DEFAULT_TIME_BASE, - gop: DEFAULT_GOP, + gop, quality: DEFAULT_HW_QUALITY, rc: DEFAULT_RC, thread_count: codec_thread_num() as _, // ffmpeg's thread_count is used for cpu diff --git a/libs/scrap/src/common/vpxcodec.rs b/libs/scrap/src/common/vpxcodec.rs index d17aad676..1bdde0ca6 100644 --- a/libs/scrap/src/common/vpxcodec.rs +++ b/libs/scrap/src/common/vpxcodec.rs @@ -65,8 +65,8 @@ impl EncoderApi for VpxEncoder { c.g_w = config.width; c.g_h = config.height; - c.g_timebase.num = config.timebase[0]; - c.g_timebase.den = config.timebase[1]; + c.g_timebase.num = 1; + c.g_timebase.den = 1000; // Output timestamp precision c.rc_undershoot_pct = 95; // When the data buffer falls below this percentage of fullness, a dropped frame is indicated. Set the threshold to zero (0) to disable this feature. // In dynamic scenes, low bitrate gets low fps while high bitrate gets high fps. @@ -76,9 +76,13 @@ impl EncoderApi for VpxEncoder { // https://developers.google.com/media/vp9/bitrate-modes/ // Constant Bitrate mode (CBR) is recommended for live streaming with VP9. c.rc_end_usage = vpx_rc_mode::VPX_CBR; - // c.kf_min_dist = 0; - // c.kf_max_dist = 999999; - c.kf_mode = vpx_kf_mode::VPX_KF_DISABLED; // reduce bandwidth a lot + if let Some(keyframe_interval) = config.keyframe_interval { + c.kf_min_dist = 0; + c.kf_max_dist = keyframe_interval as _; + } else { + c.kf_mode = vpx_kf_mode::VPX_KF_DISABLED; // reduce bandwidth a lot + } + let (q_min, q_max, b) = Self::convert_quality(config.quality); if q_min > 0 && q_min < q_max && q_max < 64 { c.rc_min_quantizer = q_min; @@ -343,12 +347,12 @@ pub struct VpxEncoderConfig { pub width: c_uint, /// The height (in pixels). pub height: c_uint, - /// The timebase numerator and denominator (in seconds). - pub timebase: [c_int; 2], /// The image quality pub quality: Quality, /// The codec pub codec: VpxVideoCodecId, + /// keyframe interval + pub keyframe_interval: Option, } #[derive(Clone, Copy, Debug)] diff --git a/src/client.rs b/src/client.rs index dfd73455c..9c039464c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1209,6 +1209,10 @@ impl LoginConfigHandler { /// * `k` - key of option /// * `v` - value of option pub fn save_ui_flutter(&mut self, k: String, v: String) { + if self.version == 0 && k == "wm_" { + log::info!("skip saving {k}"); + return; + } let mut config = self.load_config(); config.ui_flutter.insert(k, v); self.save_config(config); diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index c24d0f54c..f8cdb3ea0 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -850,6 +850,11 @@ impl Remote { } pub async fn sync_jobs_status_to_local(&mut self) -> bool { + let peer_version = self.handler.lc.read().unwrap().version; + if peer_version == 0 { + log::info!("skip saving job status"); + return false; + } log::info!("sync transfer job status"); let mut config: PeerConfig = self.handler.load_config(); let mut transfer_metas = TransferSerde::default(); diff --git a/src/flutter.rs b/src/flutter.rs index 88290897d..52190ce2e 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -345,6 +345,14 @@ impl FlutterHandler { *self.notify_rendered.write().unwrap() = false; self.renderer.write().unwrap().set_size(width, height); } + + pub fn on_waiting_for_image_dialog_show(&self) { + #[cfg(any(feature = "flutter_texture_render"))] + { + *self.notify_rendered.write().unwrap() = false; + } + // rgba array render will notify every frame + } } impl InvokeUiSession for FlutterHandler { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index c4e9c2eb3..122af0c2b 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -174,6 +174,12 @@ pub fn session_record_screen(session_id: SessionID, start: bool, width: usize, h } } +pub fn session_record_status(session_id: SessionID, status: bool) { + if let Some(session) = SESSIONS.read().unwrap().get(&session_id) { + session.record_status(status); + } +} + pub fn session_reconnect(session_id: SessionID, force_relay: bool) { if let Some(session) = SESSIONS.read().unwrap().get(&session_id) { session.reconnect(force_relay); @@ -800,6 +806,12 @@ pub fn main_get_peer_option_sync(id: String, key: String) -> SyncReturn SyncReturn(get_peer_option(id, key)) } +// Sometimes we need to get the flutter config of a peer by reading the file. +// Because the session may not be established yet. +pub fn main_get_peer_flutter_config_sync(id: String, k: String) -> SyncReturn { + SyncReturn(get_peer_flutter_config(id, k)) +} + pub fn main_set_peer_option(id: String, key: String, value: String) { set_peer_option(id, key, value) } @@ -1254,6 +1266,12 @@ pub fn session_change_prefer_codec(session_id: SessionID) { } } +pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) { + if let Some(session) = SESSIONS.read().unwrap().get(&session_id) { + session.ui_handler.on_waiting_for_image_dialog_show(); + } +} + pub fn main_set_home_dir(_home: String) { #[cfg(any(target_os = "android", target_os = "ios"))] { diff --git a/src/hbbs_http/account.rs b/src/hbbs_http/account.rs index bc078440a..2afd4b83e 100644 --- a/src/hbbs_http/account.rs +++ b/src/hbbs_http/account.rs @@ -1,8 +1,5 @@ use super::HbbHttpResponse; -use hbb_common::{ - config::{Config, LocalConfig}, - log, ResultType, -}; +use hbb_common::{config::LocalConfig, log, ResultType}; use reqwest::blocking::Client; use serde_derive::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -14,8 +11,6 @@ use std::{ use url::Url; lazy_static::lazy_static! { - static ref API_SERVER: String = crate::get_api_server( - Config::get_option("api-server"), Config::get_option("custom-rendezvous-server")); static ref OIDC_SESSION: Arc> = Arc::new(RwLock::new(OidcSession::new())); } @@ -142,20 +137,30 @@ impl OidcSession { } } - fn auth(op: &str, id: &str, uuid: &str) -> ResultType> { + fn auth( + api_server: &str, + op: &str, + id: &str, + uuid: &str, + ) -> ResultType> { Ok(OIDC_SESSION .read() .unwrap() .client - .post(format!("{}/api/oidc/auth", *API_SERVER)) + .post(format!("{}/api/oidc/auth", api_server)) .json(&HashMap::from([("op", op), ("id", id), ("uuid", uuid)])) .send()? .try_into()?) } - fn query(code: &str, id: &str, uuid: &str) -> ResultType> { + fn query( + api_server: &str, + code: &str, + id: &str, + uuid: &str, + ) -> ResultType> { let url = reqwest::Url::parse_with_params( - &format!("{}/api/oidc/auth-query", *API_SERVER), + &format!("{}/api/oidc/auth-query", api_server), &[("code", code), ("id", id), ("uuid", uuid)], )?; Ok(OIDC_SESSION @@ -189,8 +194,8 @@ impl OidcSession { std::thread::sleep(std::time::Duration::from_secs_f32(secs)); } - fn auth_task(op: String, id: String, uuid: String, remember_me: bool) { - let auth_request_res = Self::auth(&op, &id, &uuid); + fn auth_task(api_server: String, op: String, id: String, uuid: String, remember_me: bool) { + let auth_request_res = Self::auth(&api_server, &op, &id, &uuid); log::info!("Request oidc auth result: {:?}", &auth_request_res); let code_url = match auth_request_res { Ok(HbbHttpResponse::<_>::Data(code_url)) => code_url, @@ -226,7 +231,7 @@ impl OidcSession { let begin = Instant::now(); let query_timeout = OIDC_SESSION.read().unwrap().query_timeout; while OIDC_SESSION.read().unwrap().keep_querying && begin.elapsed() < query_timeout { - match Self::query(&code_url.code, &id, &uuid) { + match Self::query(&api_server, &code_url.code, &id, &uuid) { Ok(HbbHttpResponse::<_>::Data(auth_body)) => { if remember_me { LocalConfig::set_option( @@ -289,12 +294,18 @@ impl OidcSession { } } - pub fn account_auth(op: String, id: String, uuid: String, remember_me: bool) { + pub fn account_auth( + api_server: String, + op: String, + id: String, + uuid: String, + remember_me: bool, + ) { Self::auth_cancel(); Self::wait_stop_querying(); OIDC_SESSION.write().unwrap().before_task(); std::thread::spawn(move || { - Self::auth_task(op, id, uuid, remember_me); + Self::auth_task(api_server, op, id, uuid, remember_me); OIDC_SESSION.write().unwrap().after_task(); }); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 45cee7d83..b3d913b62 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index d2e3ef28a..83060c6af 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "无进行中的传输"), ("Set one-time password length", "设置一次性密码长度"), ("install_cert_tip", "安装 RustDesk 证书"), - ("comfirm_install_cert_tip", "此证书为 RustDesk 测试证书,您可以信任此证书。证书将被用于信任和安装 RustDesk 驱动。"), + ("confirm_install_cert_tip", "此证书为 RustDesk 测试证书,您可以信任此证书。证书将被用于信任和安装 RustDesk 驱动。"), ("RDP Settings", "RDP 设置"), ("Sort by", "排序方式"), ("New Connection", "新连接"), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", "管理的设备数已达到最大值"), ("Sync with recent sessions", "同步最近会话"), ("Sort tags", "对标签进行排序"), - ("Separate remote windows", "使用独立远程窗口"), - ("separate window", "独立窗口"), + ("Open connection in new tab", "在选项卡中打开新连接"), ("Move tab to new window", "将标签页移至新窗口"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 563f27f89..2d7a7ce16 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index c695b164c..0a10551d3 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "Ingen overførsler i gang"), ("Set one-time password length", "Sæt engangsadgangskode længde"), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", "RDP indstillinger"), ("Sort by", "Sortér efter"), ("New Connection", "Ny forbindelse"), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 3401d1517..ffb62a45f 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "Keine Übertragungen im Gange"), ("Set one-time password length", "Länge des Einmalpassworts festlegen"), ("install_cert_tip", "RustDesk-Zertifikat installieren"), - ("comfirm_install_cert_tip", "Dies ist ein RustDesk-Testzertifikat, dem vertraut werden kann. Das Zertifikat wird verwendet, um RustDesk-Treibern bei Bedarf zu vertrauen und diese zu installieren."), + ("confirm_install_cert_tip", "Dies ist ein RustDesk-Testzertifikat, dem vertraut werden kann. Das Zertifikat wird verwendet, um RustDesk-Treibern bei Bedarf zu vertrauen und diese zu installieren."), ("RDP Settings", "RDP-Einstellungen"), ("Sort by", "Sortieren nach"), ("New Connection", "Neue Verbindung"), @@ -506,7 +506,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable", "Aktivieren"), ("Disable", "Deaktivieren"), ("Options", "Einstellungen"), - ("resolution_original_tip", "Originalauflösung"), + ("resolution_original_tip", "Originale Auflösung"), ("resolution_fit_local_tip", "Lokale Auflösung anpassen"), ("resolution_custom_tip", "Benutzerdefinierte Auflösung"), ("Collapse toolbar", "Symbolleiste einklappen"), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", "Sie haben die maximale Anzahl der verwalteten Geräte erreicht."), ("Sync with recent sessions", "Synchronisierung mit den letzten Sitzungen"), ("Sort tags", "Tags sortieren"), - ("Separate remote windows", "Separate entfernte Fenster"), - ("separate window", "Separates Fenster"), + ("Open connection in new tab", "Verbindung in neuem Tab öffnen"), ("Move tab to new window", "Tab in neues Fenster verschieben"), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index ac0539d35..33f4d74b8 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "Δεν υπάρχει μεταφορά σε εξέλιξη"), ("Set one-time password length", "Μέγεθος κωδικού μιας χρήσης"), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", "Ρυθμίσεις RDP"), ("Sort by", "Ταξινόμηση κατά"), ("New Connection", "Νέα σύνδεση"), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index 001d9133d..2a9b40568 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -12,6 +12,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("not_close_tcp_tip", "Don't close this window while you are using the tunnel"), ("setup_server_tip", "For faster connection, please set up your own server"), ("Auto Login", "Auto Login (Only valid if you set \"Lock after session end\")"), + ("Always connect via relay", "Always Connect via Relay"), ("whitelist_tip", "Only whitelisted IP can access me"), ("whitelist_sep", "Separated by comma, semicolon, spaces or new line"), ("Wrong credentials", "Wrong username or password"), @@ -47,7 +48,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("relay_hint_tip", "It may not be possible to connect directly; you can try connecting via relay. Additionally, if you want to use a relay on your first attempt, you can add the \"/r\" suffix to the ID or select the option \"Always connect via relay\" in the card of recent sessions if it exists."), ("No transfers in progress", ""), ("install_cert_tip", "Install RustDesk certificate"), - ("comfirm_install_cert_tip", "This is a RustDesk testing certificate, which can be trusted. The certificate will be used to trust and install RustDesk drivers when required."), + ("confirm_install_cert_tip", "This is a RustDesk testing certificate, which can be trusted. The certificate will be used to trust and install RustDesk drivers when required."), ("empty_recent_tip", "Oops, no recent sessions!\nTime to plan a new one."), ("empty_favorite_tip", "No favorite peers yet?\nLet's find someone to connect with and add it to your favorites!"), ("empty_lan_tip", "Oh no, it looks like we haven't discovered any peers yet."), diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 26a04bc55..8b29e418e 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 894e8e266..27752e989 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "No hay transferencias en curso"), ("Set one-time password length", "Establecer contraseña de un solo uso"), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", "Ajustes RDP"), ("Sort by", "Ordenar por"), ("New Connection", "Nueva conexión"), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", "Has alcanzado el máximo número de dispositivos administrados."), ("Sync with recent sessions", "Sincronizar con sesiones recientes"), ("Sort tags", "Ordenar etiquetas"), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 448fa5bfd..0b56e073f 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "هیچ انتقالی در حال انجام نیست"), ("Set one-time password length", "طول رمز یکبار مصرف را تعیین کنید"), ("install_cert_tip", "RustDesk نصب گواهی"), - ("comfirm_install_cert_tip", "استفاده خواهد شد RustDesk است و شما می توانید به این گواهی اعتماد کنید. این گواهی برای اعتماد و نصب درایورهای RustDesk این گواهینامه یک گواهی تست"), + ("confirm_install_cert_tip", "استفاده خواهد شد RustDesk است و شما می توانید به این گواهی اعتماد کنید. این گواهی برای اعتماد و نصب درایورهای RustDesk این گواهینامه یک گواهی تست"), ("RDP Settings", "RDP تنظیمات"), ("Sort by", "مرتب سازی بر اساس"), ("New Connection", "اتصال جدید"), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 8ec1ba8de..8a2baef55 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "Pas de transfert en cours"), ("Set one-time password length", "Définir la longueur du mot de passe à usage unique"), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", "Configuration RDP"), ("Sort by", "Trier par"), ("New Connection", "Nouvelle connexion"), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 2ef3d2b1f..36c43fec9 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index ddeadc116..c65eee87d 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index e0d79edf8..5a655ce5a 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "Nessun trasferimento in corso"), ("Set one-time password length", "Imposta lunghezza password monouso"), ("install_cert_tip", "Installa certificato RustDesk"), - ("comfirm_install_cert_tip", "Questo è un certificato di test RustDesk, che può essere considerato attendibile.\nIl certificato verrà usato per certificarsi ed installare i driver RustDesk quando richiesto."), + ("confirm_install_cert_tip", "Questo è un certificato di test RustDesk, che può essere considerato attendibile.\nIl certificato verrà usato per certificarsi ed installare i driver RustDesk quando richiesto."), ("RDP Settings", "Impostazioni RDP"), ("Sort by", "Ordina per"), ("New Connection", "Nuova connessione"), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", "Hai raggiunto il numero massimo di dispositivi gestibili."), ("Sync with recent sessions", "Sincronizza con le sessioni recenti"), ("Sort tags", "Ordina etichette"), - ("Separate remote windows", "Separa finestre remote"), - ("separate window", "Separa finestra"), + ("Open connection in new tab", "Apri connessione in una nuova scheda"), ("Move tab to new window", "Sposta scheda nella finestra successiva"), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 3092c9305..6ece9d5c9 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 52604aa2e..6ea9b94ea 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index c253ecdcd..e5d035b03 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 618e554d0..47b70acfd 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "Nevyksta jokių perdavimų"), ("Set one-time password length", "Nustatyti vienkartinio slaptažodžio ilgį"), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", "RDP nustatymai"), ("Sort by", "Rūšiuoti pagal"), ("New Connection", "Naujas ryšys"), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 4e47e2c11..08934cea0 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "Geen overdrachten in uitvoering"), ("Set one-time password length", "Stel de lengte van het eenmalige wachtwoord in"), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", "RDP Instellingen"), ("Sort by", "Sorteren op"), ("New Connection", "Nieuwe Verbinding"), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", "Het maximum aantal gecontroleerde apparaten is bereikt."), ("Sync with recent sessions", "Recente sessies synchroniseren"), ("Sort tags", "Labels sorteren"), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index bfe9ecb3e..84f13c617 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "Brak transferów w toku"), ("Set one-time password length", "Ustaw długość jednorazowego hasła"), ("install_cert_tip", "Instalacja certyfikatu RustDesk"), - ("comfirm_install_cert_tip", "To jest certyfikat testowy RustDesk, któremu można zaufać. Certyfikat jest używany do zaufania i instalowania sterowników RustDesk w razie potrzeby."), + ("confirm_install_cert_tip", "To jest certyfikat testowy RustDesk, któremu można zaufać. Certyfikat jest używany do zaufania i instalowania sterowników RustDesk w razie potrzeby."), ("RDP Settings", "Ustawienia RDP"), ("Sort by", "Sortuj wg"), ("New Connection", "Nowe połączenie"), @@ -518,14 +518,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Exit", "Wyjście"), ("Open", "Otwórz"), ("logout_tip", "Na pewno chcesz się wylogować?"), - ("Service", ""), - ("Start", ""), - ("Stop", ""), - ("exceed_max_devices", ""), - ("Sync with recent sessions", ""), - ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), - ("Move tab to new window", ""), + ("Service", "Usługa"), + ("Start", "Uruchom"), + ("Stop", "Zatrzymaj"), + ("exceed_max_devices", "Przekroczona maks. liczba urządzeń"), + ("Sync with recent sessions", "Synchronizacja z ostatnimi sesjami"), + ("Sort tags", "Znaczniki sortowania"), + ("Open connection in new tab", ""), + ("Move tab to new window", "Przenieś zakładkę do nowego okna"), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 0d7d78eb9..b9e155364 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index abf3aac93..81fd68ac6 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "Nenhuma transferência em andamento"), ("Set one-time password length", "Definir comprimento de senha descartável"), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", "Configurações RDP"), ("Sort by", "Ordenar por"), ("New Connection", "Nova Conexão"), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index b4b372275..ddd141fa4 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "Niciun transfer nu este în desfășurare"), ("Set one-time password length", "Definește lungimea parolei unice"), ("install_cert_tip", "Instalează certificatul RustDesk"), - ("comfirm_install_cert_tip", "Acesta este un certificat de testare RustDesk și este de încredere. Certificatul va fi utilizat pentru a acorda încredere și instala drivere RustDesk atunci când este necesar."), + ("confirm_install_cert_tip", "Acesta este un certificat de testare RustDesk și este de încredere. Certificatul va fi utilizat pentru a acorda încredere și instala drivere RustDesk atunci când este necesar."), ("RDP Settings", "Setări RDP"), ("Sort by", "Sortează după"), ("New Connection", "Conexiune nouă"), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 4f0e693eb..c0f129ab0 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -299,7 +299,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Help", "Помощь"), ("Failed", "Не выполнено"), ("Succeeded", "Выполнено"), - ("Someone turns on privacy mode, exit", "Кто-то включает режим конфиденциальности, выход"), + ("Someone turns on privacy mode, exit", "Кто-то включил режим конфиденциальности, выход"), ("Unsupported", "Не поддерживается"), ("Peer denied", "Отклонено удалённым узлом"), ("Please install plugins", "Установите плагины"), @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "Передача не осуществляется"), ("Set one-time password length", "Установить длину одноразового пароля"), ("install_cert_tip", "Установить сертификат RustDesk"), - ("comfirm_install_cert_tip", "Это тестовый сертификат RustDesk, которому можно доверять. Он будет использоваться только по необходимости для установки драйверов RustDesk."), + ("confirm_install_cert_tip", "Это тестовый сертификат RustDesk, которому можно доверять. Он будет использоваться только по необходимости для установки драйверов RustDesk."), ("RDP Settings", "Настройки RDP"), ("Sort by", "Сортировка"), ("New Connection", "Новое подключение"), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", "Достигнуто максимальне количество управляемых устройств."), ("Sync with recent sessions", "Синхронизация последних сессий"), ("Sort tags", "Сортировка меток"), - ("Separate remote windows", "Отдельные удалённые окна"), - ("separate window", "отдельное окно"), + ("Open connection in new tab", "Открыть подключение в новой вкладке"), ("Move tab to new window", "Переместить вкладку в отдельное окно"), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 549d5acc4..afe52f4ad 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index c92336fc6..dc79f9e44 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index fc311bd37..86f3f3544 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 5b4d6a122..06667f4c5 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 8b7ad3387..2e8ea8713 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 733d45268..ed3f41efc 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index c817d4fe9..09ade050f 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index bab7adf31..629a144fd 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", ""), ("Set one-time password length", ""), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", ""), ("Sort by", ""), ("New Connection", ""), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 52f10c9ba..a86613651 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "沒有正在進行的傳輸"), ("Set one-time password length", "設定一次性密碼長度"), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", "RDP 設定"), ("Sort by", "排序方式"), ("New Connection", "新連線"), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 307d222e3..a12e2fd10 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "Наразі нічого не пересилається"), ("Set one-time password length", "Вказати довжину одноразового пароля"), ("install_cert_tip", "Додати сертифікат Rustdesk"), - ("comfirm_install_cert_tip", "Це сертифікат тестування Rustdesk, якому можна довіряти. За потреби сертифікат буде використано для погодження та встановлення драйверів Rustdesk."), + ("confirm_install_cert_tip", "Це сертифікат тестування Rustdesk, якому можна довіряти. За потреби сертифікат буде використано для погодження та встановлення драйверів Rustdesk."), ("RDP Settings", "Налаштування RDP"), ("Sort by", "Сортувати за"), ("New Connection", "Нове підключення"), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 0c68d1c62..0db48584e 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No transfers in progress", "Không có tệp tin nào đang được truyền"), ("Set one-time password length", "Thiết lập độ dài mật khẩu một lần"), ("install_cert_tip", ""), - ("comfirm_install_cert_tip", ""), + ("confirm_install_cert_tip", ""), ("RDP Settings", "Cài đặt RDP"), ("Sort by", "Sắp xếp theo"), ("New Connection", "Kết nối mới"), @@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("exceed_max_devices", ""), ("Sync with recent sessions", ""), ("Sort tags", ""), - ("Separate remote windows", ""), - ("separate window", ""), + ("Open connection in new tab", ""), ("Move tab to new window", ""), ].iter().cloned().collect(); } diff --git a/src/server/connection.rs b/src/server/connection.rs index f3e059696..e32a4c1c3 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1907,6 +1907,10 @@ impl Connection { .lock() .unwrap() .user_auto_adjust_fps(self.inner.id(), fps), + Some(misc::Union::ClientRecordStatus(status)) => video_service::VIDEO_QOS + .lock() + .unwrap() + .user_record(self.inner.id(), status), _ => {} }, Some(message::Union::AudioFrame(frame)) => { diff --git a/src/server/video_qos.rs b/src/server/video_qos.rs index 2ebcdaa84..e9eb9dc79 100644 --- a/src/server/video_qos.rs +++ b/src/server/video_qos.rs @@ -36,6 +36,7 @@ struct UserData { quality: Option<(i64, Quality)>, // (time, quality) delay: Option, response_delayed: bool, + record: bool, } pub struct VideoQoS { @@ -114,6 +115,10 @@ impl VideoQoS { self.quality } + pub fn record(&self) -> bool { + self.users.iter().any(|u| u.1.record) + } + pub fn abr_enabled() -> bool { "N" != Config::get_option("enable-abr") } @@ -388,6 +393,12 @@ impl VideoQoS { } } + pub fn user_record(&mut self, id: i32, v: bool) { + if let Some(user) = self.users.get_mut(&id) { + user.record = v; + } + } + pub fn on_connection_close(&mut self, id: i32) { self.users.remove(&id); self.refresh(None); diff --git a/src/server/video_service.rs b/src/server/video_service.rs index bb0697409..a74211fcd 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -36,7 +36,7 @@ use hbb_common::{ use scrap::Capturer; use scrap::{ aom::AomEncoderConfig, - codec::{Encoder, EncoderCfg, HwEncoderConfig}, + codec::{Encoder, EncoderCfg, HwEncoderConfig, Quality}, record::{Recorder, RecorderContext}, vpxcodec::{VpxEncoderConfig, VpxVideoCodecId}, CodecName, Display, TraitCapturer, @@ -518,37 +518,13 @@ fn run(sp: GenericService) -> ResultType<()> { let mut spf; let mut quality = video_qos.quality(); let abr = VideoQoS::abr_enabled(); - drop(video_qos); log::info!("init quality={:?}, abr enabled:{}", quality, abr); - - let encoder_cfg = match Encoder::negotiated_codec() { - scrap::CodecName::H264(name) | scrap::CodecName::H265(name) => { - EncoderCfg::HW(HwEncoderConfig { - name, - width: c.width, - height: c.height, - quality, - }) - } - name @ (scrap::CodecName::VP8 | scrap::CodecName::VP9) => { - EncoderCfg::VPX(VpxEncoderConfig { - width: c.width as _, - height: c.height as _, - timebase: [1, 1000], // Output timestamp precision - quality, - codec: if name == scrap::CodecName::VP8 { - VpxVideoCodecId::VP8 - } else { - VpxVideoCodecId::VP9 - }, - }) - } - scrap::CodecName::AV1 => EncoderCfg::AOM(AomEncoderConfig { - width: c.width as _, - height: c.height as _, - quality, - }), - }; + let codec_name = Encoder::negotiated_codec(); + let recorder = get_recorder(c.width, c.height, &codec_name); + let last_recording = + (recorder.lock().unwrap().is_some() || video_qos.record()) && codec_name != CodecName::AV1; + drop(video_qos); + let encoder_cfg = get_encoder_config(&c, quality, last_recording); let mut encoder; match Encoder::new(encoder_cfg) { @@ -597,8 +573,6 @@ fn run(sp: GenericService) -> ResultType<()> { let mut try_gdi = 1; #[cfg(windows)] log::info!("gdi: {}", c.is_gdi()); - let codec_name = Encoder::negotiated_codec(); - let recorder = get_recorder(c.width, c.height, &codec_name); #[cfg(windows)] start_uac_elevation_check(); @@ -617,6 +591,11 @@ fn run(sp: GenericService) -> ResultType<()> { allow_err!(encoder.set_quality(quality)); video_qos.store_bitrate(encoder.bitrate()); } + let recording = (recorder.lock().unwrap().is_some() || video_qos.record()) + && codec_name != CodecName::AV1; + if recording != last_recording { + bail!("SWITCH"); + } drop(video_qos); if *SWITCH.lock().unwrap() { @@ -789,6 +768,41 @@ fn run(sp: GenericService) -> ResultType<()> { Ok(()) } +fn get_encoder_config(c: &CapturerInfo, quality: Quality, recording: bool) -> EncoderCfg { + // https://www.wowza.com/community/t/the-correct-keyframe-interval-in-obs-studio/95162 + let keyframe_interval = if recording { Some(240) } else { None }; + match Encoder::negotiated_codec() { + scrap::CodecName::H264(name) | scrap::CodecName::H265(name) => { + EncoderCfg::HW(HwEncoderConfig { + name, + width: c.width, + height: c.height, + quality, + keyframe_interval, + }) + } + name @ (scrap::CodecName::VP8 | scrap::CodecName::VP9) => { + EncoderCfg::VPX(VpxEncoderConfig { + width: c.width as _, + height: c.height as _, + quality, + codec: if name == scrap::CodecName::VP8 { + VpxVideoCodecId::VP8 + } else { + VpxVideoCodecId::VP9 + }, + keyframe_interval, + }) + } + scrap::CodecName::AV1 => EncoderCfg::AOM(AomEncoderConfig { + width: c.width as _, + height: c.height as _, + quality, + keyframe_interval, + }), + } +} + fn get_recorder( width: usize, height: usize, diff --git a/src/ui/header.tis b/src/ui/header.tis index 666150fb3..2adc37027 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -297,6 +297,7 @@ class Header: Reactor.Component { event click $(span#recording) (_, me) { recording = !recording; header.update(); + handler.record_status(recording); if (recording) handler.refresh_video(); else diff --git a/src/ui/index.tis b/src/ui/index.tis index f0bfc13d2..c6286e903 100644 --- a/src/ui/index.tis +++ b/src/ui/index.tis @@ -215,7 +215,7 @@ class Enhancements: Reactor.Component { return
  • {translate('Enhancements')} {has_hwcodec ?
  • {svg_checkmark}{translate("Hardware Codec")} (beta)
  • : ""} -
  • {svg_checkmark}{translate("Adaptive Bitrate")} (beta)
  • +
  • {svg_checkmark}{translate("Adaptive bitrate")} (beta)
  • {translate("Recording")}
  • ; diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 6826c7e54..4ec0d5a5c 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -451,6 +451,7 @@ impl sciter::EventHandler for SciterSession { fn save_custom_image_quality(i32); fn refresh_video(); fn record_screen(bool, i32, i32); + fn record_status(bool); fn get_toggle_option(String); fn is_privacy_mode_supported(); fn toggle_option(String); diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 2979f4ceb..dbd673880 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -201,6 +201,13 @@ pub fn get_peer_option(id: String, name: String) -> String { c.options.get(&name).unwrap_or(&"".to_owned()).to_owned() } +#[inline] +#[cfg(feature = "flutter")] +pub fn get_peer_flutter_config(id: String, name: String) -> String { + let c = PeerConfig::load(&id); + c.ui_flutter.get(&name).unwrap_or(&"".to_owned()).to_owned() +} + #[inline] pub fn set_peer_option(id: String, name: String, value: String) { let mut c = PeerConfig::load(&id); @@ -912,7 +919,7 @@ fn check_connect_status(reconnect: bool) -> mpsc::UnboundedSender { #[cfg(feature = "flutter")] pub fn account_auth(op: String, id: String, uuid: String, remember_me: bool) { - account::OidcSession::account_auth(op, id, uuid, remember_me); + account::OidcSession::account_auth(get_api_server(), op, id, uuid, remember_me); } #[cfg(feature = "flutter")] diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 704b91cf0..7ffcaac6d 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -202,7 +202,7 @@ impl Session { } pub fn get_flutter_config(&self, k: String) -> String { - self.lc.write().unwrap().get_ui_flutter(&k) + self.lc.read().unwrap().get_ui_flutter(&k) } pub fn toggle_option(&mut self, name: String) { @@ -240,6 +240,14 @@ impl Session { self.send(Data::RecordScreen(start, w, h, self.id.clone())); } + pub fn record_status(&self, status: bool) { + let mut misc = Misc::new(); + misc.set_client_record_status(status); + let mut msg = Message::new(); + msg.set_misc(misc); + self.send(Data::Message(msg)); + } + pub fn save_custom_image_quality(&mut self, custom_image_quality: i32) { let msg = self .lc