Merge branch 'rustdesk:master' into master

This commit is contained in:
Sahil Yeole 2023-08-09 17:52:10 +05:30 committed by GitHub
commit 6c38dc7d4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
84 changed files with 470 additions and 331 deletions

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path fill="#fff" d="M12 0C5.374 0 0 5.373 0 12c0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0 1 12 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z"/></svg>

Before

Width:  |  Height:  |  Size: 792 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><circle cx="512" cy="512" r="512" fill="#999"/><path fill="#fff" d="M407.2 722.1c-10.1-6.7-19-15-26.5-24.5-8.2-9.9-15.7-20.3-22.7-31-16.3-23.9-29.1-50-38-77.5-10.7-32-15.8-62.7-15.8-92.7 0-33.5 7.2-62.7 21.4-87.2 10.4-19.2 26-35.2 44.8-46.5 18.1-11.3 39.2-17.5 60.6-17.9 7.5 0 15.6 1.1 24.1 3.2 6.2 1.7 13.6 4.5 22.8 7.9 11.7 4.5 18.1 7.2 20.3 7.9 6.8 2.6 12.6 3.6 17.1 3.6 3.4 0 8.3-1.1 13.8-2.8 3.1-1.1 9-3 17.3-6.6 8.2-3 14.8-5.5 19.9-7.5 7.9-2.3 15.5-4.5 22.4-5.5 8.3-1.3 16.6-1.7 24.5-1.1 15.1 1.1 29 4.3 41.4 9 21.7 8.7 39.3 22.4 52.4 41.8-5.5 3.4-10.7 7.4-15.5 11.7-10.4 9.2-19.2 20-26.2 32.1-9.2 16.4-13.9 35-13.7 53.7.3 23.1 6.2 43.4 17.9 61 8.3 12.8 19.3 23.8 32.7 32.7 6.6 4.5 12.4 7.6 17.9 9.6-2.6 8-5.4 15.8-8.6 23.5-7.4 17.2-16.2 33.7-26.7 49.3-9.2 13.4-16.5 23.5-22 30.1-8.6 10.2-16.8 17.9-25.2 23.4-9.2 6.1-19.9 9.3-31 9.3-7.5.3-14.9-.6-22-2.7-6.2-2-12.3-4.3-18.3-6.9-6.2-2.9-12.7-5.3-19.3-7.2-8.1-2.1-16.4-3.2-24.8-3.1-8.5 0-16.8 1.1-24.7 3.1-6.6 1.9-13 4.2-19.3 6.9-9 3.7-14.8 6.2-18.2 7.2-6.9 2-14 3.3-21.1 3.7-11.1 0-21.4-3.2-31.7-9.6zm146.1-393.6c-14.5 7.2-28.3 10.3-42.1 9.3-2.1-13.8 0-27.9 5.8-43.4 5.1-13.2 11.9-25.2 21.3-35.8 9.8-11.1 21.5-20.3 34.8-26.9 14.1-7.2 27.5-11.1 40.3-11.7 1.7 14.5 0 28.8-5.3 44.1-4.9 13.6-12.1 26.2-21.3 37.5-9.3 11.1-20.8 20.3-33.8 26.9z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg width="32" height="32"><path d="M29.307 9.932 26.161 0H5.796L2.692 9.932c-1.802 5.75.042 12.271 5.089 16.021L16.01 32l8.208-6.068c5.005-3.75 6.911-10.25 5.089-16.021l-8.214 6.104 3.12 9.938-8.208-6.13-8.208 6.104 3.141-9.911-8.25-6.063 10.177-.063 3.146-9.891 3.141 9.87z"/></svg>

After

Width:  |  Height:  |  Size: 285 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="199"><path fill="#0089d6" d="M118.432 187.698c32.89-5.81 60.055-10.618 60.367-10.684l.568-.12-31.052-36.935c-17.078-20.314-31.051-37.014-31.051-37.11 0-.182 32.063-88.477 32.243-88.792.06-.105 21.88 37.567 52.893 91.32 29.035 50.323 52.973 91.815 53.195 92.203l.405.707-98.684-.012-98.684-.013 59.8-10.564zM0 176.435c0-.052 14.631-25.451 32.514-56.442l32.514-56.347 37.891-31.799C123.76 14.358 140.867.027 140.935.001c.069-.026-.205.664-.609 1.534s-18.919 40.582-41.145 88.25l-40.41 86.67-29.386.037c-16.162.02-29.385-.005-29.385-.057z"/></svg>

After

Width:  |  Height:  |  Size: 604 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120"><path d="M142.554 52.81c0-4.113 1.078-6.374 5.369-11.26 17.207-19.593 57.193-19.593 74.4 0 4.291 4.886 5.37 7.147 5.37 11.26v5.145h-85.14zm71.239-42.863 6.676-6.692 10.462 10.74 25.49-25.453 6.133 6.543-31.536 32.356-17.225-17.494Zm-34.474 3.377c-15.027-5.337-19.348-22.264-8.57-33.575 10.85-11.387 29.85-6.099 34.149 9.503 2.523 9.161-4.38 21.951-12.951 23.995-4.39 1.58-8.73 1.433-12.628.077z" style="fill:#024eff;fill-opacity:1;stroke-width:.999998" transform="translate(-142.554 44.365)"/></svg>

After

Width:  |  Height:  |  Size: 564 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><rect width="512" height="512" fill="#1877f2" rx="76.8"/><path fill="#fff" d="m355.6 330 11.4-74h-71v-48c0-20.2 9.9-40 41.7-40H370v-63s-29.3-5-57.3-5c-58.5 0-96.7 35.4-96.7 99.6V256h-65v74h65v182h80V330z"/></svg>

After

Width:  |  Height:  |  Size: 274 B

View File

@ -0,0 +1 @@
<svg viewBox="0 0 24 24"><path fill="#231f20" d="M12 1A10.89 10.89 0 0 0 1 11.77 10.79 10.79 0 0 0 8.52 22c.55.1.75-.23.75-.52v-1.83c-3.06.65-3.71-1.44-3.71-1.44a2.86 2.86 0 0 0-1.22-1.58c-1-.66.08-.65.08-.65a2.31 2.31 0 0 1 1.68 1.11 2.37 2.37 0 0 0 3.2.89 2.33 2.33 0 0 1 .7-1.44c-2.44-.27-5-1.19-5-5.32a4.15 4.15 0 0 1 1.11-2.91 3.78 3.78 0 0 1 .11-2.84s.93-.29 3 1.1a10.68 10.68 0 0 1 5.5 0c2.1-1.39 3-1.1 3-1.1a3.78 3.78 0 0 1 .11 2.84A4.15 4.15 0 0 1 19 11.2c0 4.14-2.58 5.05-5 5.32a2.5 2.5 0 0 1 .75 2v2.95s.2.63.75.52A10.8 10.8 0 0 0 23 11.77 10.89 10.89 0 0 0 12 1"/></svg>

After

Width:  |  Height:  |  Size: 582 B

View File

@ -0,0 +1 @@
<svg width="48" height="48"><path fill="#ffc107" d="M43.611 20.083H42V20H24v8h11.303c-1.649 4.657-6.08 8-11.303 8-6.627 0-12-5.373-12-12s5.373-12 12-12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 12.955 4 4 12.955 4 24s8.955 20 20 20 20-8.955 20-20c0-1.341-.138-2.65-.389-3.917z"/><path fill="#ff3d00" d="m6.306 14.691 6.571 4.819C14.655 15.108 18.961 12 24 12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 16.318 4 9.656 8.337 6.306 14.691z"/><path fill="#4caf50" d="M24 44c5.166 0 9.86-1.977 13.409-5.192l-6.19-5.238A11.91 11.91 0 0 1 24 36c-5.202 0-9.619-3.317-11.283-7.946l-6.522 5.025C9.505 39.556 16.227 44 24 44z"/><path fill="#1976d2" d="M43.611 20.083H42V20H24v8h11.303a12.04 12.04 0 0 1-4.087 5.571l.003-.002 6.19 5.238C36.971 39.205 44 34 44 24c0-1.341-.138-2.65-.389-3.917z"/></svg>

After

Width:  |  Height:  |  Size: 846 B

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -11,6 +11,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart'; import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/main.dart'; import 'package:flutter_hbb/main.dart';
@ -1240,7 +1241,7 @@ bool option2bool(String option, String value) {
option == "stop-service" || option == "stop-service" ||
option == "direct-server" || option == "direct-server" ||
option == "stop-rendezvous-service" || option == "stop-rendezvous-service" ||
option == "force-always-relay") { option == kOptionForceAlwaysRelay) {
res = value == "Y"; res = value == "Y";
} else { } else {
assert(false); assert(false);
@ -1257,7 +1258,7 @@ String bool2option(String option, bool b) {
option == "stop-service" || option == "stop-service" ||
option == "direct-server" || option == "direct-server" ||
option == "stop-rendezvous-service" || option == "stop-rendezvous-service" ||
option == "force-always-relay") { option == kOptionForceAlwaysRelay) {
res = b ? 'Y' : ''; res = b ? 'Y' : '';
} else { } else {
assert(false); assert(false);
@ -1288,6 +1289,14 @@ bool mainGetLocalBoolOptionSync(String key) {
return option2bool(key, bind.mainGetLocalOption(key: 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<bool> matchPeer(String searchText, Peer peer) async { Future<bool> matchPeer(String searchText, Peer peer) async {
if (searchText.isEmpty) { if (searchText.isEmpty) {
return true; return true;
@ -1416,17 +1425,18 @@ Future<void> saveWindowPosition(WindowType type, {int? windowId}) async {
k: kWindowPrefix + type.name, v: pos.toString()); k: kWindowPrefix + type.name, v: pos.toString());
if (type == WindowType.RemoteDesktop && windowId != null) { 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( final remoteList = await DesktopMultiWindow.invokeMethod(
windowId, kWindowEventGetRemoteList, null); windowId, kWindowEventGetRemoteList, null);
if (remoteList != null) { if (remoteList != null) {
for (final peerId in remoteList.split(',')) { for (final peerId in remoteList.split(',')) {
bind.sessionSetFlutterConfigByPeerId( bind.sessionSetFlutterConfigByPeerId(
id: peerId, k: kWindowPrefix, v: pos.toString()); id: peerId, k: kWindowPrefix + windowType.name, v: pos.toString());
} }
} }
} }
@ -1533,9 +1543,19 @@ Future<bool> restoreWindowPosition(WindowType type,
bool isRemotePeerPos = false; bool isRemotePeerPos = false;
String? pos; 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) { if (type == WindowType.RemoteDesktop && windowId != null && peerId != null) {
// 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( pos = await bind.sessionGetFlutterConfigByPeerId(
id: peerId, k: kWindowPrefix); id: peerId, k: kWindowPrefix + type.name);
}
isRemotePeerPos = pos != null; isRemotePeerPos = pos != null;
} }
pos ??= bind.getLocalFlutterConfig(k: kWindowPrefix + type.name); pos ??= bind.getLocalFlutterConfig(k: kWindowPrefix + type.name);
@ -1545,7 +1565,9 @@ Future<bool> restoreWindowPosition(WindowType type,
debugPrint("no window position saved, ignoring position restoration"); debugPrint("no window position saved, ignoring position restoration");
return false; return false;
} }
if (type == WindowType.RemoteDesktop && !isRemotePeerPos && windowId != null) { if (type == WindowType.RemoteDesktop &&
!isRemotePeerPos &&
windowId != null) {
if (lpos.offsetWidth != null) { if (lpos.offsetWidth != null) {
lpos.offsetWidth = lpos.offsetWidth! + windowId * 20; lpos.offsetWidth = lpos.offsetWidth! + windowId * 20;
} }
@ -1801,14 +1823,13 @@ connectMainDesktop(
required bool isTcpTunneling, required bool isTcpTunneling,
required bool isRDP, required bool isRDP,
bool? forceRelay, bool? forceRelay,
bool forceSeparateWindow = false,
}) async { }) async {
if (isFileTransfer) { if (isFileTransfer) {
await rustDeskWinManager.newFileTransfer(id, forceRelay: forceRelay); await rustDeskWinManager.newFileTransfer(id, forceRelay: forceRelay);
} else if (isTcpTunneling || isRDP) { } else if (isTcpTunneling || isRDP) {
await rustDeskWinManager.newPortForward(id, isRDP, forceRelay: forceRelay); await rustDeskWinManager.newPortForward(id, isRDP, forceRelay: forceRelay);
} else { } else {
await rustDeskWinManager.newRemoteDesktop(id, forceRelay: forceRelay, forceSeparateWindow: forceSeparateWindow); await rustDeskWinManager.newRemoteDesktop(id, forceRelay: forceRelay);
} }
} }
@ -1822,9 +1843,16 @@ connect(
bool isFileTransfer = false, bool isFileTransfer = false,
bool isTcpTunneling = false, bool isTcpTunneling = false,
bool isRDP = false, bool isRDP = false,
bool forceSeparateWindow = false,
}) async { }) async {
if (id == '') return; if (id == '') return;
if (!isDesktop || desktopType == DesktopType.main) {
try {
if (Get.isRegistered<IDTextEditingController>()) {
final idController = Get.find<IDTextEditingController>();
idController.text = formatID(id);
}
} catch (_) {}
}
id = id.replaceAll(' ', ''); id = id.replaceAll(' ', '');
final oldId = id; final oldId = id;
id = await bind.mainHandleRelayId(id: id); id = await bind.mainHandleRelayId(id: id);
@ -1840,7 +1868,6 @@ connect(
isTcpTunneling: isTcpTunneling, isTcpTunneling: isTcpTunneling,
isRDP: isRDP, isRDP: isRDP,
forceRelay: forceRelay, forceRelay: forceRelay,
forceSeparateWindow: forceSeparateWindow,
); );
} else { } else {
await rustDeskWinManager.call(WindowType.Main, kWindowConnect, { await rustDeskWinManager.call(WindowType.Main, kWindowConnect, {
@ -1849,7 +1876,6 @@ connect(
'isTcpTunneling': isTcpTunneling, 'isTcpTunneling': isTcpTunneling,
'isRDP': isRDP, 'isRDP': isRDP,
'forceRelay': forceRelay, 'forceRelay': forceRelay,
'forceSeparateWindow': forceSeparateWindow,
}); });
} }
} else { } 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);
}

View File

@ -35,6 +35,11 @@ class IDTextInputFormatter extends TextInputFormatter {
String formatID(String id) { String formatID(String id) {
String id2 = id.replaceAll(' ', ''); 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; if (int.tryParse(id2) == null) return id;
String newID = ''; String newID = '';
if (id2.length <= 3) { if (id2.length <= 3) {
@ -47,7 +52,7 @@ String formatID(String id) {
newID += " ${id2.substring(i, i + 3)}"; newID += " ${id2.substring(i, i + 3)}";
} }
} }
return newID; return newID + suffix;
} }
String trimID(String id) { String trimID(String id) {

View File

@ -1352,7 +1352,7 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
msgBoxCommon(ffi.dialogManager, 'Custom Image Quality', content, [btnClose]); msgBoxCommon(ffi.dialogManager, 'Custom Image Quality', content, [btnClose]);
} }
void deletePeerConfirmDialog(Function onSubmit) async { void deletePeerConfirmDialog(Function onSubmit, String title) async {
gFFI.dialogManager.show( gFFI.dialogManager.show(
(setState, close, context) { (setState, close, context) {
submit() async { submit() async {
@ -1368,9 +1368,11 @@ void deletePeerConfirmDialog(Function onSubmit) async {
Icons.delete_rounded, Icons.delete_rounded,
color: Colors.red, color: Colors.red,
), ),
Text(translate('Delete')).paddingOnly( Expanded(
child: Text(title, overflow: TextOverflow.ellipsis).paddingOnly(
left: 10, left: 10,
), ),
),
], ],
), ),
content: SizedBox.shrink(), content: SizedBox.shrink(),

View File

@ -12,24 +12,32 @@ import 'package:url_launcher/url_launcher.dart';
import '../../common.dart'; import '../../common.dart';
import './dialog.dart'; import './dialog.dart';
const kOpSvgList = ['github', 'google', 'apple', 'okta', 'facebook', 'azure', 'auth0'];
class _IconOP extends StatelessWidget { class _IconOP extends StatelessWidget {
final String icon; final String op;
final double iconWidth; final String? icon;
final EdgeInsets margin; final EdgeInsets margin;
const _IconOP( const _IconOP(
{Key? key, {Key? key,
required this.op,
required this.icon, required this.icon,
required this.iconWidth,
this.margin = const EdgeInsets.symmetric(horizontal: 4.0)}) this.margin = const EdgeInsets.symmetric(horizontal: 4.0)})
: super(key: key); : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final svgFile = kOpSvgList.contains(op.toLowerCase()) ? op.toLowerCase() : 'default';
return Container( return Container(
margin: margin, margin: margin,
child: SvgPicture.asset( child: icon == null
'assets/$icon.svg', ? SvgPicture.asset(
width: iconWidth, 'assets/auth-$svgFile.svg',
width: 20,
)
: SvgPicture.string(
icon!,
width: 20,
), ),
); );
} }
@ -38,7 +46,7 @@ class _IconOP extends StatelessWidget {
class ButtonOP extends StatelessWidget { class ButtonOP extends StatelessWidget {
final String op; final String op;
final RxString curOP; final RxString curOP;
final double iconWidth; final String? icon;
final Color primaryColor; final Color primaryColor;
final double height; final double height;
final Function() onTap; final Function() onTap;
@ -47,7 +55,7 @@ class ButtonOP extends StatelessWidget {
Key? key, Key? key,
required this.op, required this.op,
required this.curOP, required this.curOP,
required this.iconWidth, required this.icon,
required this.primaryColor, required this.primaryColor,
required this.height, required this.height,
required this.onTap, required this.onTap,
@ -61,7 +69,7 @@ class ButtonOP extends StatelessWidget {
width: 200, width: 200,
child: Obx(() => ElevatedButton( child: Obx(() => ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
primary: curOP.value.isEmpty || curOP.value == op backgroundColor: curOP.value.isEmpty || curOP.value == op
? primaryColor ? primaryColor
: Colors.grey, : Colors.grey,
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)), ).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)),
@ -71,15 +79,19 @@ class ButtonOP extends StatelessWidget {
SizedBox( SizedBox(
width: 30, width: 30,
child: _IconOP( child: _IconOP(
icon: op, op: op,
iconWidth: iconWidth, icon: icon,
margin: EdgeInsets.only(right: 5), margin: EdgeInsets.only(right: 5),
)), ),
),
Expanded( Expanded(
child: FittedBox( child: FittedBox(
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: Center( child: Center(
child: Text('${translate("Continue with")} $op')))), child: Text(
'${translate("Continue with")} ${op.toLowerCase() == "github" ? "GitHub" : toCapitalized(op)}')),
),
),
], ],
))), ))),
), ),
@ -89,8 +101,8 @@ class ButtonOP extends StatelessWidget {
class ConfigOP { class ConfigOP {
final String op; final String op;
final double iconWidth; final String? icon;
ConfigOP({required this.op, required this.iconWidth}); ConfigOP({required this.op, required this.icon});
} }
class WidgetOP extends StatefulWidget { class WidgetOP extends StatefulWidget {
@ -182,7 +194,7 @@ class _WidgetOPState extends State<WidgetOP> {
ButtonOP( ButtonOP(
op: widget.config.op, op: widget.config.op,
curOP: widget.curOP, curOP: widget.curOP,
iconWidth: widget.config.iconWidth, icon: widget.config.icon,
primaryColor: str2color(widget.config.op, 0x7f), primaryColor: str2color(widget.config.op, 0x7f),
height: 36, height: 36,
onTap: () async { onTap: () async {
@ -380,7 +392,7 @@ Future<bool?> loginDialog() async {
final loginOptions = [].obs; final loginOptions = [].obs;
Future.delayed(Duration.zero, () async { Future.delayed(Duration.zero, () async {
loginOptions.value = await UserModel.queryLoginOptions(); loginOptions.value = await UserModel.queryOidcLoginOptions();
}); });
final res = await gFFI.dialogManager.show<bool>((setState, close, context) { final res = await gFFI.dialogManager.show<bool>((setState, close, context) {
@ -460,12 +472,8 @@ Future<bool?> loginDialog() async {
} }
thirdAuthWidget() => Obx(() { thirdAuthWidget() => Obx(() {
final oidcOptions = loginOptions
.where((opt) => opt.startsWith(kAuthReqTypeOidc))
.map((opt) => opt.substring(kAuthReqTypeOidc.length))
.toList();
return Offstage( return Offstage(
offstage: oidcOptions.isEmpty, offstage: loginOptions.isEmpty,
child: Column( child: Column(
children: [ children: [
const SizedBox( const SizedBox(
@ -480,12 +488,8 @@ Future<bool?> loginDialog() async {
height: 8.0, height: 8.0,
), ),
LoginWidgetOP( LoginWidgetOP(
ops: [ ops: loginOptions
ConfigOP(op: 'GitHub', iconWidth: 20), .map((e) => ConfigOP(op: e['name'], icon: e['icon']))
ConfigOP(op: 'Google', iconWidth: 20),
ConfigOP(op: 'Okta', iconWidth: 38),
]
.where((op) => oidcOptions.contains(op.op.toLowerCase()))
.toList(), .toList(),
curOP: curOP, curOP: curOP,
cbLogin: (Map<String, dynamic> authBody) { cbLogin: (Map<String, dynamic> authBody) {

View File

@ -404,7 +404,6 @@ abstract class BasePeerCard extends StatelessWidget {
bool isFileTransfer = false, bool isFileTransfer = false,
bool isTcpTunneling = false, bool isTcpTunneling = false,
bool isRDP = false, bool isRDP = false,
bool forceSeparateWindow = false,
}) { }) {
return MenuEntryButton<String>( return MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text( childBuilder: (TextStyle? style) => Text(
@ -418,7 +417,6 @@ abstract class BasePeerCard extends StatelessWidget {
isFileTransfer: isFileTransfer, isFileTransfer: isFileTransfer,
isTcpTunneling: isTcpTunneling, isTcpTunneling: isTcpTunneling,
isRDP: isRDP, isRDP: isRDP,
forceSeparateWindow: forceSeparateWindow,
); );
}, },
padding: menuPadding, padding: menuPadding,
@ -427,25 +425,13 @@ abstract class BasePeerCard extends StatelessWidget {
} }
@protected @protected
List<MenuEntryBase<String>> _connectActions(BuildContext context, Peer peer) { MenuEntryBase<String> _connectAction(BuildContext context, Peer peer) {
final actions = [_connectAction(context, peer, false)];
if (!mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow)) {
actions.add(_connectAction(context, peer, true));
}
return actions;
}
@protected
MenuEntryBase<String> _connectAction(
BuildContext context, Peer peer, bool forceSeparateWindow) {
return _connectCommonAction( return _connectCommonAction(
context, context,
peer.id, peer.id,
(peer.alias.isEmpty (peer.alias.isEmpty
? translate('Connect') ? translate('Connect')
: '${translate('Connect')} ${peer.id}') + : '${translate('Connect')} ${peer.id}'),
(forceSeparateWindow ? ' (${translate('separate window')})' : ''),
forceSeparateWindow: forceSeparateWindow,
); );
} }
@ -538,15 +524,40 @@ abstract class BasePeerCard extends StatelessWidget {
); );
} }
Future<MenuEntryBase<String>> _openNewConnInAction(
String id, String label, String key) async {
return MenuEntrySwitch<String>(
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 @protected
Future<bool> _isForceAlwaysRelay(String id) async { Future<bool> _isForceAlwaysRelay(String id) async {
return (await bind.mainGetPeerOption(id: id, key: 'force-always-relay')) return (await bind.mainGetPeerOption(id: id, key: kOptionForceAlwaysRelay))
.isNotEmpty; .isNotEmpty;
} }
@protected @protected
Future<MenuEntryBase<String>> _forceAlwaysRelayAction(String id) async { Future<MenuEntryBase<String>> _forceAlwaysRelayAction(String id) async {
const option = 'force-always-relay';
return MenuEntrySwitch<String>( return MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox, switchType: SwitchType.scheckbox,
text: translate('Always connect via relay'), text: translate('Always connect via relay'),
@ -555,7 +566,9 @@ abstract class BasePeerCard extends StatelessWidget {
}, },
setter: (bool v) async { setter: (bool v) async {
await bind.mainSetPeerOption( await bind.mainSetPeerOption(
id: id, key: option, value: bool2option(option, v)); id: id,
key: kOptionForceAlwaysRelay,
value: bool2option(kOptionForceAlwaysRelay, v));
}, },
padding: menuPadding, padding: menuPadding,
dismissOnClicked: true, 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, padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
@ -813,7 +827,7 @@ class RecentPeerCard extends BasePeerCard {
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
BuildContext context) async { BuildContext context) async {
final List<MenuEntryBase<String>> menuItems = [ final List<MenuEntryBase<String>> menuItems = [
..._connectActions(context, peer), _connectAction(context, peer),
_transferFileAction(context, peer.id), _transferFileAction(context, peer.id),
]; ];
@ -822,6 +836,7 @@ class RecentPeerCard extends BasePeerCard {
if (isDesktop && peer.platform != 'Android') { if (isDesktop && peer.platform != 'Android') {
menuItems.add(_tcpTunnelingAction(context, peer.id)); menuItems.add(_tcpTunnelingAction(context, peer.id));
} }
// menuItems.add(await _openNewConnInOptAction(peer.id));
menuItems.add(await _forceAlwaysRelayAction(peer.id)); menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (peer.platform == 'Windows') { if (peer.platform == 'Windows') {
menuItems.add(_rdpAction(context, peer.id)); menuItems.add(_rdpAction(context, peer.id));
@ -869,12 +884,13 @@ class FavoritePeerCard extends BasePeerCard {
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
BuildContext context) async { BuildContext context) async {
final List<MenuEntryBase<String>> menuItems = [ final List<MenuEntryBase<String>> menuItems = [
..._connectActions(context, peer), _connectAction(context, peer),
_transferFileAction(context, peer.id), _transferFileAction(context, peer.id),
]; ];
if (isDesktop && peer.platform != 'Android') { if (isDesktop && peer.platform != 'Android') {
menuItems.add(_tcpTunnelingAction(context, peer.id)); menuItems.add(_tcpTunnelingAction(context, peer.id));
} }
// menuItems.add(await _openNewConnInOptAction(peer.id));
menuItems.add(await _forceAlwaysRelayAction(peer.id)); menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (peer.platform == 'Windows') { if (peer.platform == 'Windows') {
menuItems.add(_rdpAction(context, peer.id)); menuItems.add(_rdpAction(context, peer.id));
@ -919,7 +935,7 @@ class DiscoveredPeerCard extends BasePeerCard {
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
BuildContext context) async { BuildContext context) async {
final List<MenuEntryBase<String>> menuItems = [ final List<MenuEntryBase<String>> menuItems = [
..._connectActions(context, peer), _connectAction(context, peer),
_transferFileAction(context, peer.id), _transferFileAction(context, peer.id),
]; ];
@ -928,6 +944,7 @@ class DiscoveredPeerCard extends BasePeerCard {
if (isDesktop && peer.platform != 'Android') { if (isDesktop && peer.platform != 'Android') {
menuItems.add(_tcpTunnelingAction(context, peer.id)); menuItems.add(_tcpTunnelingAction(context, peer.id));
} }
// menuItems.add(await _openNewConnInOptAction(peer.id));
menuItems.add(await _forceAlwaysRelayAction(peer.id)); menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (peer.platform == 'Windows') { if (peer.platform == 'Windows') {
menuItems.add(_rdpAction(context, peer.id)); menuItems.add(_rdpAction(context, peer.id));
@ -971,12 +988,13 @@ class AddressBookPeerCard extends BasePeerCard {
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
BuildContext context) async { BuildContext context) async {
final List<MenuEntryBase<String>> menuItems = [ final List<MenuEntryBase<String>> menuItems = [
..._connectActions(context, peer), _connectAction(context, peer),
_transferFileAction(context, peer.id), _transferFileAction(context, peer.id),
]; ];
if (isDesktop && peer.platform != 'Android') { if (isDesktop && peer.platform != 'Android') {
menuItems.add(_tcpTunnelingAction(context, peer.id)); menuItems.add(_tcpTunnelingAction(context, peer.id));
} }
// menuItems.add(await _openNewConnInOptAction(peer.id));
menuItems.add(await _forceAlwaysRelayAction(peer.id)); menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (peer.platform == 'Windows') { if (peer.platform == 'Windows') {
menuItems.add(_rdpAction(context, peer.id)); menuItems.add(_rdpAction(context, peer.id));
@ -1033,12 +1051,13 @@ class MyGroupPeerCard extends BasePeerCard {
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
BuildContext context) async { BuildContext context) async {
final List<MenuEntryBase<String>> menuItems = [ final List<MenuEntryBase<String>> menuItems = [
..._connectActions(context, peer), _connectAction(context, peer),
_transferFileAction(context, peer.id), _transferFileAction(context, peer.id),
]; ];
if (isDesktop && peer.platform != 'Android') { if (isDesktop && peer.platform != 'Android') {
menuItems.add(_tcpTunnelingAction(context, peer.id)); menuItems.add(_tcpTunnelingAction(context, peer.id));
} }
// menuItems.add(await _openNewConnInOptAction(peer.id));
menuItems.add(await _forceAlwaysRelayAction(peer.id)); menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (peer.platform == 'Windows') { if (peer.platform == 'Windows') {
menuItems.add(_rdpAction(context, peer.id)); menuItems.add(_rdpAction(context, peer.id));

View File

@ -312,7 +312,7 @@ class _PeerTabPageState extends State<PeerTabPage>
showToast(translate('Successful')); showToast(translate('Successful'));
} }
deletePeerConfirmDialog(onSubmit); deletePeerConfirmDialog(onSubmit, translate('Delete'));
}, },
child: Tooltip( child: Tooltip(
message: translate('Delete'), message: translate('Delete'),

View File

@ -22,8 +22,6 @@ const String kAppTypeDesktopRemote = "remote";
const String kAppTypeDesktopFileTransfer = "file transfer"; const String kAppTypeDesktopFileTransfer = "file transfer";
const String kAppTypeDesktopPortForward = "port forward"; const String kAppTypeDesktopPortForward = "port forward";
const bool kCloseMultiWindowByHide = true;
const String kWindowMainWindowOnTop = "main_window_on_top"; const String kWindowMainWindowOnTop = "main_window_on_top";
const String kWindowGetWindowInfo = "get_window_info"; const String kWindowGetWindowInfo = "get_window_info";
const String kWindowDisableGrabKeyboard = "disable_grab_keyboard"; 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 kWindowEventMoveTabToNewWindow = "move_tab_to_new_window";
const String kWindowEventCloseForSeparateWindow = "close_for_separate_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 kUniLinksPrefix = "rustdesk://";
const String kUrlActionClose = "close"; const String kUrlActionClose = "close";

View File

@ -68,6 +68,7 @@ class _ConnectionPageState extends State<ConnectionPage>
_idController.selection = TextSelection( _idController.selection = TextSelection(
baseOffset: 0, extentOffset: _idController.value.text.length); baseOffset: 0, extentOffset: _idController.value.text.length);
}); });
Get.put<IDTextEditingController>(_idController);
windowManager.addListener(this); windowManager.addListener(this);
} }
@ -76,6 +77,9 @@ class _ConnectionPageState extends State<ConnectionPage>
_idController.dispose(); _idController.dispose();
_updateTimer?.cancel(); _updateTimer?.cancel();
windowManager.removeListener(this); windowManager.removeListener(this);
if (Get.isRegistered<IDTextEditingController>()) {
Get.delete<IDTextEditingController>();
}
super.dispose(); super.dispose();
} }

View File

@ -554,13 +554,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
} else if (call.method == kWindowEventShow) { } else if (call.method == kWindowEventShow) {
await rustDeskWinManager.registerActiveWindow(call.arguments["id"]); await rustDeskWinManager.registerActiveWindow(call.arguments["id"]);
} else if (call.method == kWindowEventHide) { } else if (call.method == kWindowEventHide) {
final wId = call.arguments['id']; await rustDeskWinManager.unregisterActiveWindow(call.arguments['id']);
final isSeparateWindowEnabled =
mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow);
if (isSeparateWindowEnabled && !kCloseMultiWindowByHide) {
await rustDeskWinManager.destroyWindow(wId);
}
await rustDeskWinManager.unregisterActiveWindow(wId);
} else if (call.method == kWindowConnect) { } else if (call.method == kWindowConnect) {
await connectMainDesktop( await connectMainDesktop(
call.arguments['id'], call.arguments['id'],
@ -568,7 +562,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
isTcpTunneling: call.arguments['isTcpTunneling'], isTcpTunneling: call.arguments['isTcpTunneling'],
isRDP: call.arguments['isRDP'], isRDP: call.arguments['isRDP'],
forceRelay: call.arguments['forceRelay'], forceRelay: call.arguments['forceRelay'],
forceSeparateWindow: call.arguments['forceSeparateWindow'],
); );
} else if (call.method == kWindowEventMoveTabToNewWindow) { } else if (call.method == kWindowEventMoveTabToNewWindow) {
final args = call.arguments.split(','); final args = call.arguments.split(',');

View File

@ -319,8 +319,8 @@ class _GeneralState extends State<_General> {
_OptionCheckBox(context, 'Adaptive bitrate', 'enable-abr'), _OptionCheckBox(context, 'Adaptive bitrate', 'enable-abr'),
_OptionCheckBox( _OptionCheckBox(
context, context,
'Separate remote windows', 'Open new connections in tabs',
kOptionSeparateRemoteWindow, kOptionOpenNewConnInTabs,
isServer: false, isServer: false,
), ),
]; ];
@ -995,16 +995,19 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
return false; return false;
} }
} }
final old = await bind.mainGetOption(key: 'custom-rendezvous-server'); final oldApiServer = await bind.mainGetApiServer();
if (old.isNotEmpty && old != idServer) {
await gFFI.userModel.logOut();
}
// should set one by one // should set one by one
await bind.mainSetOption( await bind.mainSetOption(
key: 'custom-rendezvous-server', value: idServer); key: 'custom-rendezvous-server', value: idServer);
await bind.mainSetOption(key: 'relay-server', value: relayServer); await bind.mainSetOption(key: 'relay-server', value: relayServer);
await bind.mainSetOption(key: 'api-server', value: apiServer); await bind.mainSetOption(key: 'api-server', value: apiServer);
await bind.mainSetOption(key: 'key', value: key); 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; return true;
} }

View File

@ -282,7 +282,7 @@ class _InstallPageBodyState extends State<_InstallPageBody>
title: null, title: null,
content: SelectionArea( content: SelectionArea(
child: child:
msgboxContent('info', 'Warning', 'comfirm_install_cert_tip')), msgboxContent('info', 'Warning', 'confirm_install_cert_tip')),
actions: btns, actions: btns,
onCancel: close, onCancel: close,
), ),

View File

@ -116,11 +116,7 @@ class _RemotePageState extends State<RemotePage>
Wakelock.enable(); Wakelock.enable();
} }
// Register texture. // Register texture.
if (mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow)) {
_renderTexture = renderTexture;
} else {
_renderTexture = RenderTexture(); _renderTexture = RenderTexture();
}
_renderTexture.create(sessionId); _renderTexture.create(sessionId);
_ffi.ffiModel.updateEventListener(sessionId, widget.id); _ffi.ffiModel.updateEventListener(sessionId, widget.id);

View File

@ -582,8 +582,6 @@ class WindowActionPanelState extends State<WindowActionPanel>
} }
await windowManager.hide(); await windowManager.hide();
} else { } else {
renderTexture.destroy();
// it's safe to hide the subwindow // it's safe to hide the subwindow
final controller = WindowController.fromWindowId(kWindowId!); final controller = WindowController.fromWindowId(kWindowId!);
if (Platform.isMacOS && await controller.isFullScreen()) { if (Platform.isMacOS && await controller.isFullScreen()) {

View File

@ -66,6 +66,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
_idController.addListener(() { _idController.addListener(() {
_idEmpty.value = _idController.text.isEmpty; _idEmpty.value = _idController.text.isEmpty;
}); });
Get.put<IDTextEditingController>(_idController);
} }
@override @override
@ -195,6 +196,9 @@ class _ConnectionPageState extends State<ConnectionPage> {
@override @override
void dispose() { void dispose() {
_idController.dispose(); _idController.dispose();
if (Get.isRegistered<IDTextEditingController>()) {
Get.delete<IDTextEditingController>();
}
super.dispose(); super.dispose();
} }
} }

View File

@ -242,7 +242,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
}, },
), ),
SettingsTile.switchTile( SettingsTile.switchTile(
title: Text('${translate('Adaptive Bitrate')} (beta)'), title: Text('${translate('Adaptive bitrate')} (beta)'),
initialValue: _enableAbr, initialValue: _enableAbr,
onToggle: (v) async { onToggle: (v) async {
await bind.mainSetOption(key: "enable-abr", value: v ? "" : "N"); await bind.mainSetOption(key: "enable-abr", value: v ? "" : "N");

View File

@ -38,9 +38,4 @@ class RenderTexture {
_textureKey = -1; _textureKey = -1;
} }
} }
static final RenderTexture instance = RenderTexture();
} }
// Global instance for separate texture
final renderTexture = RenderTexture.instance;

View File

@ -425,8 +425,7 @@ class FfiModel with ChangeNotifier {
closeConnection(); closeConnection();
} }
Future.delayed(Duration.zero, () async { dialogManager.show(
await dialogManager.show(
(setState, close, context) => CustomAlertDialog( (setState, close, context) => CustomAlertDialog(
title: null, title: null,
content: SelectionArea(child: msgboxContent(type, title, text)), content: SelectionArea(child: msgboxContent(type, title, text)),
@ -437,7 +436,7 @@ class FfiModel with ChangeNotifier {
tag: '$sessionId-waiting-for-image', tag: '$sessionId-waiting-for-image',
); );
_waitForImageDialogShow[sessionId] = true; _waitForImageDialogShow[sessionId] = true;
}); bind.sessionOnWaitingForImageDialogShow(sessionId: sessionId);
} }
_updateSessionWidthHeight(SessionID sessionId) { _updateSessionWidthHeight(SessionID sessionId) {
@ -1532,12 +1531,13 @@ class RecordingModel with ChangeNotifier {
sessionId: sessionId, start: true, width: width, height: height); sessionId: sessionId, start: true, width: width, height: height);
} }
toggle() { toggle() async {
if (isIOS) return; if (isIOS) return;
final sessionId = parent.target?.sessionId; final sessionId = parent.target?.sessionId;
if (sessionId == null) return; if (sessionId == null) return;
_start = !_start; _start = !_start;
notifyListeners(); notifyListeners();
await bind.sessionRecordStatus(sessionId: sessionId, status: _start);
if (_start) { if (_start) {
bind.sessionRefresh(sessionId: sessionId); bind.sessionRefresh(sessionId: sessionId);
} else { } else {
@ -1901,10 +1901,5 @@ Future<void> initializeCursorAndCanvas(FFI ffi) async {
} }
clearWaitingForImage(OverlayDialogManager? dialogManager, SessionID sessionId) { 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');
});
}
} }

View File

@ -101,10 +101,10 @@ class UserModel {
await Future.wait([gFFI.abModel.pullAb(), gFFI.groupModel.pull()]); await Future.wait([gFFI.abModel.pullAb(), gFFI.groupModel.pull()]);
} }
Future<void> logOut() async { Future<void> logOut({String? apiServer}) async {
final tag = gFFI.dialogManager.showLoading(translate('Waiting')); final tag = gFFI.dialogManager.showLoading(translate('Waiting'));
try { try {
final url = await bind.mainGetApiServer(); final url = apiServer ?? await bind.mainGetApiServer();
final authHeaders = getHttpHeaders(); final authHeaders = getHttpHeaders();
authHeaders['Content-Type'] = "application/json"; authHeaders['Content-Type'] = "application/json";
await http await http
@ -163,15 +163,27 @@ class UserModel {
return loginResponse; return loginResponse;
} }
static Future<List<dynamic>> queryLoginOptions() async { static Future<List<dynamic>> queryOidcLoginOptions() async {
try { try {
final url = await bind.mainGetApiServer(); final url = await bind.mainGetApiServer();
if (url.trim().isEmpty) return []; if (url.trim().isEmpty) return [];
final resp = await http.get(Uri.parse('$url/api/login-options')); final resp = await http.get(Uri.parse('$url/api/login-options'));
return jsonDecode(resp.body); final List<String> 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) { } catch (e) {
debugPrint( debugPrint(
"queryLoginOptions: jsonDecode resp body failed: ${e.toString()}"); "queryOidcLoginOptions: jsonDecode resp body failed: ${e.toString()}");
return []; return [];
} }
} }

View File

@ -50,7 +50,7 @@ class RustDeskMultiWindowManager {
'session_id': sessionId, 'session_id': sessionId,
}; };
await _newSession( await _newSession(
true, false,
WindowType.RemoteDesktop, WindowType.RemoteDesktop,
kWindowEventNewRemoteDesktop, kWindowEventNewRemoteDesktop,
peerId, peerId,
@ -81,19 +81,26 @@ class RustDeskMultiWindowManager {
} }
_newSession( _newSession(
bool separateWindow, bool openInTabs,
WindowType type, WindowType type,
String methodName, String methodName,
String remoteId, String remoteId,
List<int> windows, List<int> windows,
String msg, String msg,
) async { ) async {
if (separateWindow) { if (openInTabs) {
if (kCloseMultiWindowByHide && _inactiveWindows.isNotEmpty) { if (windows.isEmpty) {
await newSessionWindow(type, remoteId, msg, windows);
} else {
call(type, methodName, msg);
}
} else {
if (_inactiveWindows.isNotEmpty) {
for (final windowId in windows) { for (final windowId in windows) {
if (_inactiveWindows.contains(windowId)) { if (_inactiveWindows.contains(windowId)) {
await DesktopMultiWindow.invokeMethod( await restoreWindowPosition(type,
windowId, methodName, msg); windowId: windowId, peerId: remoteId);
await DesktopMultiWindow.invokeMethod(windowId, methodName, msg);
WindowController.fromWindowId(windowId).show(); WindowController.fromWindowId(windowId).show();
registerActiveWindow(windowId); registerActiveWindow(windowId);
return; return;
@ -101,12 +108,6 @@ class RustDeskMultiWindowManager {
} }
} }
await newSessionWindow(type, remoteId, msg, windows); 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, bool? forceRelay,
String? switchUuid, String? switchUuid,
bool? isRDP, bool? isRDP,
bool forceSeparateWindow = false,
}) async { }) async {
var params = { var params = {
"type": type.index, "type": type.index,
@ -136,11 +136,10 @@ class RustDeskMultiWindowManager {
final msg = jsonEncode(params); final msg = jsonEncode(params);
// separate window for file transfer is not supported // separate window for file transfer is not supported
bool separateWindow = forceSeparateWindow || bool openInTabs = type != WindowType.RemoteDesktop ||
(type != WindowType.FileTransfer && mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs);
mainGetLocalBoolOptionSync(kOptionSeparateRemoteWindow));
if (windows.length > 1 || separateWindow) { if (windows.length > 1 || !openInTabs) {
for (final windowId in windows) { for (final windowId in windows) {
if (await DesktopMultiWindow.invokeMethod( if (await DesktopMultiWindow.invokeMethod(
windowId, kWindowEventActiveSession, remoteId)) { 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<dynamic> newRemoteDesktop( Future<dynamic> newRemoteDesktop(
@ -157,7 +156,6 @@ class RustDeskMultiWindowManager {
String? password, String? password,
String? switchUuid, String? switchUuid,
bool? forceRelay, bool? forceRelay,
bool forceSeparateWindow = false,
}) async { }) async {
return await newSession( return await newSession(
WindowType.RemoteDesktop, WindowType.RemoteDesktop,
@ -167,7 +165,6 @@ class RustDeskMultiWindowManager {
password: password, password: password,
forceRelay: forceRelay, forceRelay: forceRelay,
switchUuid: switchUuid, switchUuid: switchUuid,
forceSeparateWindow: forceSeparateWindow,
); );
} }

View File

@ -664,6 +664,7 @@ message Misc {
PluginFailure plugin_failure = 26; PluginFailure plugin_failure = 26;
uint32 full_speed_fps = 27; uint32 full_speed_fps = 27;
uint32 auto_adjust_fps = 28; uint32 auto_adjust_fps = 28;
bool client_record_status = 29;
} }
} }

View File

@ -114,9 +114,9 @@ fn test_vpx(
let config = EncoderCfg::VPX(VpxEncoderConfig { let config = EncoderCfg::VPX(VpxEncoderConfig {
width: width as _, width: width as _,
height: height as _, height: height as _,
timebase: [1, 1000],
quality, quality,
codec: codec_id, codec: codec_id,
keyframe_interval: None,
}); });
let mut encoder = VpxEncoder::new(config).unwrap(); let mut encoder = VpxEncoder::new(config).unwrap();
let mut vpxs = vec![]; let mut vpxs = vec![];
@ -161,6 +161,7 @@ fn test_av1(yuvs: &Vec<Vec<u8>>, width: usize, height: usize, quality: Q, yuv_co
width: width as _, width: width as _,
height: height as _, height: height as _,
quality, quality,
keyframe_interval: None,
}); });
let mut encoder = AomEncoder::new(config).unwrap(); let mut encoder = AomEncoder::new(config).unwrap();
let start = Instant::now(); let start = Instant::now();

View File

@ -113,9 +113,9 @@ fn main() -> io::Result<()> {
let mut vpx = vpx_encode::VpxEncoder::new(EncoderCfg::VPX(vpx_encode::VpxEncoderConfig { let mut vpx = vpx_encode::VpxEncoder::new(EncoderCfg::VPX(vpx_encode::VpxEncoderConfig {
width, width,
height, height,
timebase: [1, 1000],
quality, quality,
codec: vpx_codec, codec: vpx_codec,
keyframe_interval: None,
})) }))
.unwrap(); .unwrap();

View File

@ -45,6 +45,7 @@ pub struct AomEncoderConfig {
pub width: u32, pub width: u32,
pub height: u32, pub height: u32,
pub quality: Quality, pub quality: Quality,
pub keyframe_interval: Option<usize>,
} }
pub struct AomEncoder { pub struct AomEncoder {
@ -105,7 +106,12 @@ mod webrtc {
c.g_timebase.num = 1; c.g_timebase.num = 1;
c.g_timebase.den = kRtpTicksPerSecond; c.g_timebase.den = kRtpTicksPerSecond;
c.g_input_bit_depth = kBitDepth; c.g_input_bit_depth = kBitDepth;
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; c.kf_mode = aom_kf_mode::AOM_KF_DISABLED;
}
let (q_min, q_max, b) = AomEncoder::convert_quality(cfg.quality); let (q_min, q_max, b) = AomEncoder::convert_quality(cfg.quality);
if q_min > 0 && q_min < q_max && q_max < 64 { if q_min > 0 && q_min < q_max && q_max < 64 {
c.rc_min_quantizer = q_min; c.rc_min_quantizer = q_min;

View File

@ -45,6 +45,7 @@ pub struct HwEncoderConfig {
pub width: usize, pub width: usize,
pub height: usize, pub height: usize,
pub quality: Quality, pub quality: Quality,
pub keyframe_interval: Option<usize>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@ -52,6 +52,7 @@ impl EncoderApi for HwEncoder {
if base_bitrate <= 0 { if base_bitrate <= 0 {
bitrate = base_bitrate; bitrate = base_bitrate;
} }
let gop = config.keyframe_interval.unwrap_or(DEFAULT_GOP as _) as i32;
let ctx = EncodeContext { let ctx = EncodeContext {
name: config.name.clone(), name: config.name.clone(),
width: config.width as _, width: config.width as _,
@ -60,7 +61,7 @@ impl EncoderApi for HwEncoder {
align: HW_STRIDE_ALIGN as _, align: HW_STRIDE_ALIGN as _,
bitrate: bitrate as i32 * 1000, bitrate: bitrate as i32 * 1000,
timebase: DEFAULT_TIME_BASE, timebase: DEFAULT_TIME_BASE,
gop: DEFAULT_GOP, gop,
quality: DEFAULT_HW_QUALITY, quality: DEFAULT_HW_QUALITY,
rc: DEFAULT_RC, rc: DEFAULT_RC,
thread_count: codec_thread_num() as _, // ffmpeg's thread_count is used for cpu thread_count: codec_thread_num() as _, // ffmpeg's thread_count is used for cpu

View File

@ -65,8 +65,8 @@ impl EncoderApi for VpxEncoder {
c.g_w = config.width; c.g_w = config.width;
c.g_h = config.height; c.g_h = config.height;
c.g_timebase.num = config.timebase[0]; c.g_timebase.num = 1;
c.g_timebase.den = config.timebase[1]; c.g_timebase.den = 1000; // Output timestamp precision
c.rc_undershoot_pct = 95; 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. // 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. // 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/ // https://developers.google.com/media/vp9/bitrate-modes/
// Constant Bitrate mode (CBR) is recommended for live streaming with VP9. // Constant Bitrate mode (CBR) is recommended for live streaming with VP9.
c.rc_end_usage = vpx_rc_mode::VPX_CBR; c.rc_end_usage = vpx_rc_mode::VPX_CBR;
// c.kf_min_dist = 0; if let Some(keyframe_interval) = config.keyframe_interval {
// c.kf_max_dist = 999999; 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 c.kf_mode = vpx_kf_mode::VPX_KF_DISABLED; // reduce bandwidth a lot
}
let (q_min, q_max, b) = Self::convert_quality(config.quality); let (q_min, q_max, b) = Self::convert_quality(config.quality);
if q_min > 0 && q_min < q_max && q_max < 64 { if q_min > 0 && q_min < q_max && q_max < 64 {
c.rc_min_quantizer = q_min; c.rc_min_quantizer = q_min;
@ -343,12 +347,12 @@ pub struct VpxEncoderConfig {
pub width: c_uint, pub width: c_uint,
/// The height (in pixels). /// The height (in pixels).
pub height: c_uint, pub height: c_uint,
/// The timebase numerator and denominator (in seconds).
pub timebase: [c_int; 2],
/// The image quality /// The image quality
pub quality: Quality, pub quality: Quality,
/// The codec /// The codec
pub codec: VpxVideoCodecId, pub codec: VpxVideoCodecId,
/// keyframe interval
pub keyframe_interval: Option<usize>,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]

View File

@ -1209,6 +1209,10 @@ impl LoginConfigHandler {
/// * `k` - key of option /// * `k` - key of option
/// * `v` - value of option /// * `v` - value of option
pub fn save_ui_flutter(&mut self, k: String, v: String) { 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(); let mut config = self.load_config();
config.ui_flutter.insert(k, v); config.ui_flutter.insert(k, v);
self.save_config(config); self.save_config(config);

View File

@ -850,6 +850,11 @@ impl<T: InvokeUiSession> Remote<T> {
} }
pub async fn sync_jobs_status_to_local(&mut self) -> bool { 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"); log::info!("sync transfer job status");
let mut config: PeerConfig = self.handler.load_config(); let mut config: PeerConfig = self.handler.load_config();
let mut transfer_metas = TransferSerde::default(); let mut transfer_metas = TransferSerde::default();

View File

@ -345,6 +345,14 @@ impl FlutterHandler {
*self.notify_rendered.write().unwrap() = false; *self.notify_rendered.write().unwrap() = false;
self.renderer.write().unwrap().set_size(width, height); 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 { impl InvokeUiSession for FlutterHandler {

View File

@ -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) { pub fn session_reconnect(session_id: SessionID, force_relay: bool) {
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) { if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
session.reconnect(force_relay); session.reconnect(force_relay);
@ -800,6 +806,12 @@ pub fn main_get_peer_option_sync(id: String, key: String) -> SyncReturn<String>
SyncReturn(get_peer_option(id, key)) 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<String> {
SyncReturn(get_peer_flutter_config(id, k))
}
pub fn main_set_peer_option(id: String, key: String, value: String) { pub fn main_set_peer_option(id: String, key: String, value: String) {
set_peer_option(id, key, value) 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) { pub fn main_set_home_dir(_home: String) {
#[cfg(any(target_os = "android", target_os = "ios"))] #[cfg(any(target_os = "android", target_os = "ios"))]
{ {

View File

@ -1,8 +1,5 @@
use super::HbbHttpResponse; use super::HbbHttpResponse;
use hbb_common::{ use hbb_common::{config::LocalConfig, log, ResultType};
config::{Config, LocalConfig},
log, ResultType,
};
use reqwest::blocking::Client; use reqwest::blocking::Client;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
@ -14,8 +11,6 @@ use std::{
use url::Url; use url::Url;
lazy_static::lazy_static! { 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<RwLock<OidcSession>> = Arc::new(RwLock::new(OidcSession::new())); static ref OIDC_SESSION: Arc<RwLock<OidcSession>> = Arc::new(RwLock::new(OidcSession::new()));
} }
@ -142,20 +137,30 @@ impl OidcSession {
} }
} }
fn auth(op: &str, id: &str, uuid: &str) -> ResultType<HbbHttpResponse<OidcAuthUrl>> { fn auth(
api_server: &str,
op: &str,
id: &str,
uuid: &str,
) -> ResultType<HbbHttpResponse<OidcAuthUrl>> {
Ok(OIDC_SESSION Ok(OIDC_SESSION
.read() .read()
.unwrap() .unwrap()
.client .client
.post(format!("{}/api/oidc/auth", *API_SERVER)) .post(format!("{}/api/oidc/auth", api_server))
.json(&HashMap::from([("op", op), ("id", id), ("uuid", uuid)])) .json(&HashMap::from([("op", op), ("id", id), ("uuid", uuid)]))
.send()? .send()?
.try_into()?) .try_into()?)
} }
fn query(code: &str, id: &str, uuid: &str) -> ResultType<HbbHttpResponse<AuthBody>> { fn query(
api_server: &str,
code: &str,
id: &str,
uuid: &str,
) -> ResultType<HbbHttpResponse<AuthBody>> {
let url = reqwest::Url::parse_with_params( 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)], &[("code", code), ("id", id), ("uuid", uuid)],
)?; )?;
Ok(OIDC_SESSION Ok(OIDC_SESSION
@ -189,8 +194,8 @@ impl OidcSession {
std::thread::sleep(std::time::Duration::from_secs_f32(secs)); std::thread::sleep(std::time::Duration::from_secs_f32(secs));
} }
fn auth_task(op: String, id: String, uuid: String, remember_me: bool) { fn auth_task(api_server: String, op: String, id: String, uuid: String, remember_me: bool) {
let auth_request_res = Self::auth(&op, &id, &uuid); let auth_request_res = Self::auth(&api_server, &op, &id, &uuid);
log::info!("Request oidc auth result: {:?}", &auth_request_res); log::info!("Request oidc auth result: {:?}", &auth_request_res);
let code_url = match auth_request_res { let code_url = match auth_request_res {
Ok(HbbHttpResponse::<_>::Data(code_url)) => code_url, Ok(HbbHttpResponse::<_>::Data(code_url)) => code_url,
@ -226,7 +231,7 @@ impl OidcSession {
let begin = Instant::now(); let begin = Instant::now();
let query_timeout = OIDC_SESSION.read().unwrap().query_timeout; let query_timeout = OIDC_SESSION.read().unwrap().query_timeout;
while OIDC_SESSION.read().unwrap().keep_querying && begin.elapsed() < 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)) => { Ok(HbbHttpResponse::<_>::Data(auth_body)) => {
if remember_me { if remember_me {
LocalConfig::set_option( 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::auth_cancel();
Self::wait_stop_querying(); Self::wait_stop_querying();
OIDC_SESSION.write().unwrap().before_task(); OIDC_SESSION.write().unwrap().before_task();
std::thread::spawn(move || { 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(); OIDC_SESSION.write().unwrap().after_task();
}); });
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "无进行中的传输"), ("No transfers in progress", "无进行中的传输"),
("Set one-time password length", "设置一次性密码长度"), ("Set one-time password length", "设置一次性密码长度"),
("install_cert_tip", "安装 RustDesk 证书"), ("install_cert_tip", "安装 RustDesk 证书"),
("comfirm_install_cert_tip", "此证书为 RustDesk 测试证书,您可以信任此证书。证书将被用于信任和安装 RustDesk 驱动。"), ("confirm_install_cert_tip", "此证书为 RustDesk 测试证书,您可以信任此证书。证书将被用于信任和安装 RustDesk 驱动。"),
("RDP Settings", "RDP 设置"), ("RDP Settings", "RDP 设置"),
("Sort by", "排序方式"), ("Sort by", "排序方式"),
("New Connection", "新连接"), ("New Connection", "新连接"),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", "管理的设备数已达到最大值"), ("exceed_max_devices", "管理的设备数已达到最大值"),
("Sync with recent sessions", "同步最近会话"), ("Sync with recent sessions", "同步最近会话"),
("Sort tags", "对标签进行排序"), ("Sort tags", "对标签进行排序"),
("Separate remote windows", "使用独立远程窗口"), ("Open connection in new tab", "在选项卡中打开新连接"),
("separate window", "独立窗口"),
("Move tab to new window", "将标签页移至新窗口"), ("Move tab to new window", "将标签页移至新窗口"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Ingen overførsler i gang"), ("No transfers in progress", "Ingen overførsler i gang"),
("Set one-time password length", "Sæt engangsadgangskode længde"), ("Set one-time password length", "Sæt engangsadgangskode længde"),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", "RDP indstillinger"), ("RDP Settings", "RDP indstillinger"),
("Sort by", "Sortér efter"), ("Sort by", "Sortér efter"),
("New Connection", "Ny forbindelse"), ("New Connection", "Ny forbindelse"),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Keine Übertragungen im Gange"), ("No transfers in progress", "Keine Übertragungen im Gange"),
("Set one-time password length", "Länge des Einmalpassworts festlegen"), ("Set one-time password length", "Länge des Einmalpassworts festlegen"),
("install_cert_tip", "RustDesk-Zertifikat installieren"), ("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"), ("RDP Settings", "RDP-Einstellungen"),
("Sort by", "Sortieren nach"), ("Sort by", "Sortieren nach"),
("New Connection", "Neue Verbindung"), ("New Connection", "Neue Verbindung"),
@ -506,7 +506,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable", "Aktivieren"), ("Enable", "Aktivieren"),
("Disable", "Deaktivieren"), ("Disable", "Deaktivieren"),
("Options", "Einstellungen"), ("Options", "Einstellungen"),
("resolution_original_tip", "Originalauflösung"), ("resolution_original_tip", "Originale Auflösung"),
("resolution_fit_local_tip", "Lokale Auflösung anpassen"), ("resolution_fit_local_tip", "Lokale Auflösung anpassen"),
("resolution_custom_tip", "Benutzerdefinierte Auflösung"), ("resolution_custom_tip", "Benutzerdefinierte Auflösung"),
("Collapse toolbar", "Symbolleiste einklappen"), ("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."), ("exceed_max_devices", "Sie haben die maximale Anzahl der verwalteten Geräte erreicht."),
("Sync with recent sessions", "Synchronisierung mit den letzten Sitzungen"), ("Sync with recent sessions", "Synchronisierung mit den letzten Sitzungen"),
("Sort tags", "Tags sortieren"), ("Sort tags", "Tags sortieren"),
("Separate remote windows", "Separate entfernte Fenster"), ("Open connection in new tab", "Verbindung in neuem Tab öffnen"),
("separate window", "Separates Fenster"),
("Move tab to new window", "Tab in neues Fenster verschieben"), ("Move tab to new window", "Tab in neues Fenster verschieben"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Δεν υπάρχει μεταφορά σε εξέλιξη"), ("No transfers in progress", "Δεν υπάρχει μεταφορά σε εξέλιξη"),
("Set one-time password length", "Μέγεθος κωδικού μιας χρήσης"), ("Set one-time password length", "Μέγεθος κωδικού μιας χρήσης"),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", "Ρυθμίσεις RDP"), ("RDP Settings", "Ρυθμίσεις RDP"),
("Sort by", "Ταξινόμηση κατά"), ("Sort by", "Ταξινόμηση κατά"),
("New Connection", "Νέα σύνδεση"), ("New Connection", "Νέα σύνδεση"),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -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"), ("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"), ("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\")"), ("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_tip", "Only whitelisted IP can access me"),
("whitelist_sep", "Separated by comma, semicolon, spaces or new line"), ("whitelist_sep", "Separated by comma, semicolon, spaces or new line"),
("Wrong credentials", "Wrong username or password"), ("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."), ("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", ""), ("No transfers in progress", ""),
("install_cert_tip", "Install RustDesk certificate"), ("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_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_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."), ("empty_lan_tip", "Oh no, it looks like we haven't discovered any peers yet."),

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "No hay transferencias en curso"), ("No transfers in progress", "No hay transferencias en curso"),
("Set one-time password length", "Establecer contraseña de un solo uso"), ("Set one-time password length", "Establecer contraseña de un solo uso"),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", "Ajustes RDP"), ("RDP Settings", "Ajustes RDP"),
("Sort by", "Ordenar por"), ("Sort by", "Ordenar por"),
("New Connection", "Nueva conexión"), ("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."), ("exceed_max_devices", "Has alcanzado el máximo número de dispositivos administrados."),
("Sync with recent sessions", "Sincronizar con sesiones recientes"), ("Sync with recent sessions", "Sincronizar con sesiones recientes"),
("Sort tags", "Ordenar etiquetas"), ("Sort tags", "Ordenar etiquetas"),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "هیچ انتقالی در حال انجام نیست"), ("No transfers in progress", "هیچ انتقالی در حال انجام نیست"),
("Set one-time password length", "طول رمز یکبار مصرف را تعیین کنید"), ("Set one-time password length", "طول رمز یکبار مصرف را تعیین کنید"),
("install_cert_tip", "RustDesk نصب گواهی"), ("install_cert_tip", "RustDesk نصب گواهی"),
("comfirm_install_cert_tip", "استفاده خواهد شد RustDesk است و شما می توانید به این گواهی اعتماد کنید. این گواهی برای اعتماد و نصب درایورهای RustDesk این گواهینامه یک گواهی تست"), ("confirm_install_cert_tip", "استفاده خواهد شد RustDesk است و شما می توانید به این گواهی اعتماد کنید. این گواهی برای اعتماد و نصب درایورهای RustDesk این گواهینامه یک گواهی تست"),
("RDP Settings", "RDP تنظیمات"), ("RDP Settings", "RDP تنظیمات"),
("Sort by", "مرتب سازی بر اساس"), ("Sort by", "مرتب سازی بر اساس"),
("New Connection", "اتصال جدید"), ("New Connection", "اتصال جدید"),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Pas de transfert en cours"), ("No transfers in progress", "Pas de transfert en cours"),
("Set one-time password length", "Définir la longueur du mot de passe à usage unique"), ("Set one-time password length", "Définir la longueur du mot de passe à usage unique"),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", "Configuration RDP"), ("RDP Settings", "Configuration RDP"),
("Sort by", "Trier par"), ("Sort by", "Trier par"),
("New Connection", "Nouvelle connexion"), ("New Connection", "Nouvelle connexion"),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Nessun trasferimento in corso"), ("No transfers in progress", "Nessun trasferimento in corso"),
("Set one-time password length", "Imposta lunghezza password monouso"), ("Set one-time password length", "Imposta lunghezza password monouso"),
("install_cert_tip", "Installa certificato RustDesk"), ("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"), ("RDP Settings", "Impostazioni RDP"),
("Sort by", "Ordina per"), ("Sort by", "Ordina per"),
("New Connection", "Nuova connessione"), ("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."), ("exceed_max_devices", "Hai raggiunto il numero massimo di dispositivi gestibili."),
("Sync with recent sessions", "Sincronizza con le sessioni recenti"), ("Sync with recent sessions", "Sincronizza con le sessioni recenti"),
("Sort tags", "Ordina etichette"), ("Sort tags", "Ordina etichette"),
("Separate remote windows", "Separa finestre remote"), ("Open connection in new tab", "Apri connessione in una nuova scheda"),
("separate window", "Separa finestra"),
("Move tab to new window", "Sposta scheda nella finestra successiva"), ("Move tab to new window", "Sposta scheda nella finestra successiva"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Nevyksta jokių perdavimų"), ("No transfers in progress", "Nevyksta jokių perdavimų"),
("Set one-time password length", "Nustatyti vienkartinio slaptažodžio ilgį"), ("Set one-time password length", "Nustatyti vienkartinio slaptažodžio ilgį"),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", "RDP nustatymai"), ("RDP Settings", "RDP nustatymai"),
("Sort by", "Rūšiuoti pagal"), ("Sort by", "Rūšiuoti pagal"),
("New Connection", "Naujas ryšys"), ("New Connection", "Naujas ryšys"),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Geen overdrachten in uitvoering"), ("No transfers in progress", "Geen overdrachten in uitvoering"),
("Set one-time password length", "Stel de lengte van het eenmalige wachtwoord in"), ("Set one-time password length", "Stel de lengte van het eenmalige wachtwoord in"),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", "RDP Instellingen"), ("RDP Settings", "RDP Instellingen"),
("Sort by", "Sorteren op"), ("Sort by", "Sorteren op"),
("New Connection", "Nieuwe Verbinding"), ("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."), ("exceed_max_devices", "Het maximum aantal gecontroleerde apparaten is bereikt."),
("Sync with recent sessions", "Recente sessies synchroniseren"), ("Sync with recent sessions", "Recente sessies synchroniseren"),
("Sort tags", "Labels sorteren"), ("Sort tags", "Labels sorteren"),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Brak transferów w toku"), ("No transfers in progress", "Brak transferów w toku"),
("Set one-time password length", "Ustaw długość jednorazowego hasła"), ("Set one-time password length", "Ustaw długość jednorazowego hasła"),
("install_cert_tip", "Instalacja certyfikatu RustDesk"), ("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"), ("RDP Settings", "Ustawienia RDP"),
("Sort by", "Sortuj wg"), ("Sort by", "Sortuj wg"),
("New Connection", "Nowe połączenie"), ("New Connection", "Nowe połączenie"),
@ -518,14 +518,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Exit", "Wyjście"), ("Exit", "Wyjście"),
("Open", "Otwórz"), ("Open", "Otwórz"),
("logout_tip", "Na pewno chcesz się wylogować?"), ("logout_tip", "Na pewno chcesz się wylogować?"),
("Service", ""), ("Service", "Usługa"),
("Start", ""), ("Start", "Uruchom"),
("Stop", ""), ("Stop", "Zatrzymaj"),
("exceed_max_devices", ""), ("exceed_max_devices", "Przekroczona maks. liczba urządzeń"),
("Sync with recent sessions", ""), ("Sync with recent sessions", "Synchronizacja z ostatnimi sesjami"),
("Sort tags", ""), ("Sort tags", "Znaczniki sortowania"),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""), ("Move tab to new window", "Przenieś zakładkę do nowego okna"),
("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Nenhuma transferência em andamento"), ("No transfers in progress", "Nenhuma transferência em andamento"),
("Set one-time password length", "Definir comprimento de senha descartável"), ("Set one-time password length", "Definir comprimento de senha descartável"),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", "Configurações RDP"), ("RDP Settings", "Configurações RDP"),
("Sort by", "Ordenar por"), ("Sort by", "Ordenar por"),
("New Connection", "Nova Conexão"), ("New Connection", "Nova Conexão"),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -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"), ("No transfers in progress", "Niciun transfer nu este în desfășurare"),
("Set one-time password length", "Definește lungimea parolei unice"), ("Set one-time password length", "Definește lungimea parolei unice"),
("install_cert_tip", "Instalează certificatul RustDesk"), ("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"), ("RDP Settings", "Setări RDP"),
("Sort by", "Sortează după"), ("Sort by", "Sortează după"),
("New Connection", "Conexiune nouă"), ("New Connection", "Conexiune nouă"),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -299,7 +299,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Help", "Помощь"), ("Help", "Помощь"),
("Failed", "Не выполнено"), ("Failed", "Не выполнено"),
("Succeeded", "Выполнено"), ("Succeeded", "Выполнено"),
("Someone turns on privacy mode, exit", "Кто-то включает режим конфиденциальности, выход"), ("Someone turns on privacy mode, exit", "Кто-то включил режим конфиденциальности, выход"),
("Unsupported", "Не поддерживается"), ("Unsupported", "Не поддерживается"),
("Peer denied", "Отклонено удалённым узлом"), ("Peer denied", "Отклонено удалённым узлом"),
("Please install plugins", "Установите плагины"), ("Please install plugins", "Установите плагины"),
@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Передача не осуществляется"), ("No transfers in progress", "Передача не осуществляется"),
("Set one-time password length", "Установить длину одноразового пароля"), ("Set one-time password length", "Установить длину одноразового пароля"),
("install_cert_tip", "Установить сертификат RustDesk"), ("install_cert_tip", "Установить сертификат RustDesk"),
("comfirm_install_cert_tip", "Это тестовый сертификат RustDesk, которому можно доверять. Он будет использоваться только по необходимости для установки драйверов RustDesk."), ("confirm_install_cert_tip", "Это тестовый сертификат RustDesk, которому можно доверять. Он будет использоваться только по необходимости для установки драйверов RustDesk."),
("RDP Settings", "Настройки RDP"), ("RDP Settings", "Настройки RDP"),
("Sort by", "Сортировка"), ("Sort by", "Сортировка"),
("New Connection", "Новое подключение"), ("New Connection", "Новое подключение"),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", "Достигнуто максимальне количество управляемых устройств."), ("exceed_max_devices", "Достигнуто максимальне количество управляемых устройств."),
("Sync with recent sessions", "Синхронизация последних сессий"), ("Sync with recent sessions", "Синхронизация последних сессий"),
("Sort tags", "Сортировка меток"), ("Sort tags", "Сортировка меток"),
("Separate remote windows", "Отдельные удалённые окна"), ("Open connection in new tab", "Открыть подключение в новой вкладке"),
("separate window", "отдельное окно"),
("Move tab to new window", "Переместить вкладку в отдельное окно"), ("Move tab to new window", "Переместить вкладку в отдельное окно"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", ""), ("No transfers in progress", ""),
("Set one-time password length", ""), ("Set one-time password length", ""),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", ""), ("RDP Settings", ""),
("Sort by", ""), ("Sort by", ""),
("New Connection", ""), ("New Connection", ""),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "沒有正在進行的傳輸"), ("No transfers in progress", "沒有正在進行的傳輸"),
("Set one-time password length", "設定一次性密碼長度"), ("Set one-time password length", "設定一次性密碼長度"),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", "RDP 設定"), ("RDP Settings", "RDP 設定"),
("Sort by", "排序方式"), ("Sort by", "排序方式"),
("New Connection", "新連線"), ("New Connection", "新連線"),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Наразі нічого не пересилається"), ("No transfers in progress", "Наразі нічого не пересилається"),
("Set one-time password length", "Вказати довжину одноразового пароля"), ("Set one-time password length", "Вказати довжину одноразового пароля"),
("install_cert_tip", "Додати сертифікат Rustdesk"), ("install_cert_tip", "Додати сертифікат Rustdesk"),
("comfirm_install_cert_tip", "Це сертифікат тестування Rustdesk, якому можна довіряти. За потреби сертифікат буде використано для погодження та встановлення драйверів Rustdesk."), ("confirm_install_cert_tip", "Це сертифікат тестування Rustdesk, якому можна довіряти. За потреби сертифікат буде використано для погодження та встановлення драйверів Rustdesk."),
("RDP Settings", "Налаштування RDP"), ("RDP Settings", "Налаштування RDP"),
("Sort by", "Сортувати за"), ("Sort by", "Сортувати за"),
("New Connection", "Нове підключення"), ("New Connection", "Нове підключення"),
@ -524,8 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("exceed_max_devices", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -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"), ("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"), ("Set one-time password length", "Thiết lập độ dài mật khẩu một lần"),
("install_cert_tip", ""), ("install_cert_tip", ""),
("comfirm_install_cert_tip", ""), ("confirm_install_cert_tip", ""),
("RDP Settings", "Cài đặt RDP"), ("RDP Settings", "Cài đặt RDP"),
("Sort by", "Sắp xếp theo"), ("Sort by", "Sắp xếp theo"),
("New Connection", "Kết nối mới"), ("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", ""), ("exceed_max_devices", ""),
("Sync with recent sessions", ""), ("Sync with recent sessions", ""),
("Sort tags", ""), ("Sort tags", ""),
("Separate remote windows", ""), ("Open connection in new tab", ""),
("separate window", ""),
("Move tab to new window", ""), ("Move tab to new window", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -1907,6 +1907,10 @@ impl Connection {
.lock() .lock()
.unwrap() .unwrap()
.user_auto_adjust_fps(self.inner.id(), fps), .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)) => { Some(message::Union::AudioFrame(frame)) => {

View File

@ -36,6 +36,7 @@ struct UserData {
quality: Option<(i64, Quality)>, // (time, quality) quality: Option<(i64, Quality)>, // (time, quality)
delay: Option<Delay>, delay: Option<Delay>,
response_delayed: bool, response_delayed: bool,
record: bool,
} }
pub struct VideoQoS { pub struct VideoQoS {
@ -114,6 +115,10 @@ impl VideoQoS {
self.quality self.quality
} }
pub fn record(&self) -> bool {
self.users.iter().any(|u| u.1.record)
}
pub fn abr_enabled() -> bool { pub fn abr_enabled() -> bool {
"N" != Config::get_option("enable-abr") "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) { pub fn on_connection_close(&mut self, id: i32) {
self.users.remove(&id); self.users.remove(&id);
self.refresh(None); self.refresh(None);

View File

@ -36,7 +36,7 @@ use hbb_common::{
use scrap::Capturer; use scrap::Capturer;
use scrap::{ use scrap::{
aom::AomEncoderConfig, aom::AomEncoderConfig,
codec::{Encoder, EncoderCfg, HwEncoderConfig}, codec::{Encoder, EncoderCfg, HwEncoderConfig, Quality},
record::{Recorder, RecorderContext}, record::{Recorder, RecorderContext},
vpxcodec::{VpxEncoderConfig, VpxVideoCodecId}, vpxcodec::{VpxEncoderConfig, VpxVideoCodecId},
CodecName, Display, TraitCapturer, CodecName, Display, TraitCapturer,
@ -518,37 +518,13 @@ fn run(sp: GenericService) -> ResultType<()> {
let mut spf; let mut spf;
let mut quality = video_qos.quality(); let mut quality = video_qos.quality();
let abr = VideoQoS::abr_enabled(); let abr = VideoQoS::abr_enabled();
drop(video_qos);
log::info!("init quality={:?}, abr enabled:{}", quality, abr); log::info!("init quality={:?}, abr enabled:{}", quality, abr);
let codec_name = Encoder::negotiated_codec();
let encoder_cfg = match Encoder::negotiated_codec() { let recorder = get_recorder(c.width, c.height, &codec_name);
scrap::CodecName::H264(name) | scrap::CodecName::H265(name) => { let last_recording =
EncoderCfg::HW(HwEncoderConfig { (recorder.lock().unwrap().is_some() || video_qos.record()) && codec_name != CodecName::AV1;
name, drop(video_qos);
width: c.width, let encoder_cfg = get_encoder_config(&c, quality, last_recording);
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 mut encoder; let mut encoder;
match Encoder::new(encoder_cfg) { match Encoder::new(encoder_cfg) {
@ -597,8 +573,6 @@ fn run(sp: GenericService) -> ResultType<()> {
let mut try_gdi = 1; let mut try_gdi = 1;
#[cfg(windows)] #[cfg(windows)]
log::info!("gdi: {}", c.is_gdi()); log::info!("gdi: {}", c.is_gdi());
let codec_name = Encoder::negotiated_codec();
let recorder = get_recorder(c.width, c.height, &codec_name);
#[cfg(windows)] #[cfg(windows)]
start_uac_elevation_check(); start_uac_elevation_check();
@ -617,6 +591,11 @@ fn run(sp: GenericService) -> ResultType<()> {
allow_err!(encoder.set_quality(quality)); allow_err!(encoder.set_quality(quality));
video_qos.store_bitrate(encoder.bitrate()); 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); drop(video_qos);
if *SWITCH.lock().unwrap() { if *SWITCH.lock().unwrap() {
@ -789,6 +768,41 @@ fn run(sp: GenericService) -> ResultType<()> {
Ok(()) 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( fn get_recorder(
width: usize, width: usize,
height: usize, height: usize,

View File

@ -297,6 +297,7 @@ class Header: Reactor.Component {
event click $(span#recording) (_, me) { event click $(span#recording) (_, me) {
recording = !recording; recording = !recording;
header.update(); header.update();
handler.record_status(recording);
if (recording) if (recording)
handler.refresh_video(); handler.refresh_video();
else else

View File

@ -215,7 +215,7 @@ class Enhancements: Reactor.Component {
return <li>{translate('Enhancements')} return <li>{translate('Enhancements')}
<menu #enhancements-menu> <menu #enhancements-menu>
{has_hwcodec ? <li #enable-hwcodec><span>{svg_checkmark}</span>{translate("Hardware Codec")} (beta)</li> : ""} {has_hwcodec ? <li #enable-hwcodec><span>{svg_checkmark}</span>{translate("Hardware Codec")} (beta)</li> : ""}
<li #enable-abr><span>{svg_checkmark}</span>{translate("Adaptive Bitrate")} (beta)</li> <li #enable-abr><span>{svg_checkmark}</span>{translate("Adaptive bitrate")} (beta)</li>
<li #screen-recording>{translate("Recording")}</li> <li #screen-recording>{translate("Recording")}</li>
</menu> </menu>
</li>; </li>;

View File

@ -451,6 +451,7 @@ impl sciter::EventHandler for SciterSession {
fn save_custom_image_quality(i32); fn save_custom_image_quality(i32);
fn refresh_video(); fn refresh_video();
fn record_screen(bool, i32, i32); fn record_screen(bool, i32, i32);
fn record_status(bool);
fn get_toggle_option(String); fn get_toggle_option(String);
fn is_privacy_mode_supported(); fn is_privacy_mode_supported();
fn toggle_option(String); fn toggle_option(String);

View File

@ -201,6 +201,13 @@ pub fn get_peer_option(id: String, name: String) -> String {
c.options.get(&name).unwrap_or(&"".to_owned()).to_owned() 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] #[inline]
pub fn set_peer_option(id: String, name: String, value: String) { pub fn set_peer_option(id: String, name: String, value: String) {
let mut c = PeerConfig::load(&id); let mut c = PeerConfig::load(&id);
@ -912,7 +919,7 @@ fn check_connect_status(reconnect: bool) -> mpsc::UnboundedSender<ipc::Data> {
#[cfg(feature = "flutter")] #[cfg(feature = "flutter")]
pub fn account_auth(op: String, id: String, uuid: String, remember_me: bool) { 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")] #[cfg(feature = "flutter")]

View File

@ -202,7 +202,7 @@ impl<T: InvokeUiSession> Session<T> {
} }
pub fn get_flutter_config(&self, k: String) -> String { 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) { pub fn toggle_option(&mut self, name: String) {
@ -240,6 +240,14 @@ impl<T: InvokeUiSession> Session<T> {
self.send(Data::RecordScreen(start, w, h, self.id.clone())); 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) { pub fn save_custom_image_quality(&mut self, custom_image_quality: i32) {
let msg = self let msg = self
.lc .lc