Merge branch 'rustdesk:master' into master

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

View File

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

Before

Width:  |  Height:  |  Size: 792 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

After

Width:  |  Height:  |  Size: 285 B

View File

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

After

Width:  |  Height:  |  Size: 604 B

View File

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

After

Width:  |  Height:  |  Size: 564 B

View File

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

After

Width:  |  Height:  |  Size: 274 B

View File

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

After

Width:  |  Height:  |  Size: 582 B

View File

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

After

Width:  |  Height:  |  Size: 846 B

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -11,6 +11,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/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);
}

View File

@ -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) {

View File

@ -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,
),
),
],
),

View File

@ -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) {

View File

@ -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));

View File

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

View File

@ -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";

View File

@ -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();
}

View File

@ -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(',');

View File

@ -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;
}

View File

@ -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,
),

View File

@ -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);

View File

@ -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()) {

View File

@ -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();
}
}

View File

@ -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");

View File

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

View File

@ -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');
}

View File

@ -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 [];
}
}

View File

@ -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,
);
}

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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();

View File

@ -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;

View File

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

View File

@ -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

View File

@ -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)]

View File

@ -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);

View File

@ -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();

View File

@ -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 {

View File

@ -174,6 +174,12 @@ pub fn session_record_screen(session_id: SessionID, start: bool, width: usize, h
}
}
pub fn session_record_status(session_id: SessionID, status: bool) {
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
session.record_status(status);
}
}
pub fn session_reconnect(session_id: SessionID, force_relay: bool) {
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"))]
{

View File

@ -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();
});
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Ingen overførsler i gang"),
("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();
}

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Keine Übertragungen im Gange"),
("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();
}

View File

@ -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();
}

View File

@ -12,6 +12,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("not_close_tcp_tip", "Don't close this window while you are using the tunnel"),
("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."),

View File

@ -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();
}

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "No hay transferencias en curso"),
("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();
}

View File

@ -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();
}

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Pas de transfert en cours"),
("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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Nessun trasferimento in corso"),
("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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Geen overdrachten in uitvoering"),
("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();
}

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Brak transferów w toku"),
("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();
}

View File

@ -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();
}

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Nenhuma transferência em andamento"),
("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();
}

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Niciun transfer nu este în desfășurare"),
("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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -461,7 +461,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No transfers in progress", "Không có tệp tin nào đang được truyền"),
("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();
}

View File

@ -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)) => {

View File

@ -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);

View File

@ -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,

View File

@ -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

View File

@ -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>;

View File

@ -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);

View File

@ -201,6 +201,13 @@ pub fn get_peer_option(id: String, name: String) -> String {
c.options.get(&name).unwrap_or(&"".to_owned()).to_owned()
}
#[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")]

View File

@ -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