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