Merge branch 'rustdesk:master' into master

This commit is contained in:
Andrzej Rudnik 2023-04-08 16:34:25 +02:00 committed by GitHub
commit 6915b88d8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
102 changed files with 5981 additions and 3217 deletions

View File

@ -10,7 +10,7 @@
"features": {
"ghcr.io/devcontainers/features/java:1": {},
"ghcr.io/akhildevelops/devcontainer-features/android-cli:latest": {
"PACKAGES": "platform-tools,ndk;22.1.7171670"
"PACKAGES": "platform-tools,ndk;23.2.8568313"
}
},
"customizations": {

View File

@ -13,7 +13,7 @@ jobs:
job:
- {
target: x86_64-unknown-linux-gnu,
os: ubuntu-18.04,
os: ubuntu-20.04,
extra-build-args: "",
}
steps:

1602
.github/workflows/flutter-build.yml vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,7 @@ on:
jobs:
run-ci:
uses: ./.github/workflows/flutter-nightly.yml
uses: ./.github/workflows/flutter-build.yml
with:
upload-artifact: false

File diff suppressed because it is too large Load Diff

56
Cargo.lock generated
View File

@ -4159,6 +4159,38 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "pam"
version = "0.7.0"
source = "git+https://github.com/fufesou/pam#10da2cbbabe32cbc9de22a66abe44738e7ec0ea0"
dependencies = [
"libc",
"pam-macros",
"pam-sys",
"users 0.10.0",
]
[[package]]
name = "pam-macros"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c94f3b9b97df3c6d4e51a14916639b24e02c7d15d1dba686ce9b1118277cb811"
dependencies = [
"proc-macro2 1.0.54",
"quote 1.0.26",
"syn 1.0.109",
]
[[package]]
name = "pam-sys"
version = "1.0.0-alpha4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e9dfd42858f6a6bb1081079fd9dc259ca3e2aaece6cb689fd36b1058046c969"
dependencies = [
"bindgen 0.59.2",
"libc",
]
[[package]]
name = "pango"
version = "0.16.5"
@ -5124,6 +5156,7 @@ dependencies = [
"objc",
"objc_id",
"os-version",
"pam",
"parity-tokio-ipc",
"rdev",
"repng",
@ -5149,6 +5182,7 @@ dependencies = [
"tray-icon",
"trayicon",
"url",
"users 0.11.0",
"uuid",
"virtual_display",
"whoami",
@ -6343,7 +6377,7 @@ dependencies = [
[[package]]
name = "trayicon"
version = "0.1.3-1"
source = "git+https://github.com/open-trade/trayicon-rs#8d9c4489287752cc5be4a35c103198f7111112f9"
source = "git+https://github.com/open-trade/trayicon-rs#35bd01963271b45a0b6a0f65f1ce03a5f35bc691"
dependencies = [
"winapi 0.3.9",
"winit",
@ -6443,6 +6477,26 @@ dependencies = [
"serde 1.0.159",
]
[[package]]
name = "users"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa4227e95324a443c9fcb06e03d4d85e91aabe9a5a02aa818688b6918b6af486"
dependencies = [
"libc",
"log",
]
[[package]]
name = "users"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
dependencies = [
"libc",
"log",
]
[[package]]
name = "utf8parse"
version = "0.2.1"

View File

@ -30,6 +30,7 @@ flutter = ["flutter_rust_bridge"]
default = ["use_dasp"]
hwcodec = ["scrap/hwcodec"]
mediacodec = ["scrap/mediacodec"]
linux_headless = ["pam", "users"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -122,6 +123,8 @@ mouce = { git="https://github.com/fufesou/mouce.git" }
evdev = { git="https://github.com/fufesou/evdev" }
dbus = "0.9"
dbus-crossroads = "0.5"
pam = { git="https://github.com/fufesou/pam", optional = true }
users = { version = "0.11.0", optional = true }
[target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.11"

View File

@ -287,6 +287,8 @@ def build_flutter_deb(version, features):
system2('flutter build linux --release')
system2('mkdir -p tmpdeb/usr/bin/')
system2('mkdir -p tmpdeb/usr/lib/rustdesk')
system2('mkdir -p tmpdeb/etc/rustdesk/')
system2('mkdir -p tmpdeb/etc/pam.d/')
system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
system2('mkdir -p tmpdeb/usr/share/applications/')
system2('mkdir -p tmpdeb/usr/share/polkit-1/actions')
@ -303,6 +305,12 @@ def build_flutter_deb(version, features):
'cp ../res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop')
system2(
'cp ../res/com.rustdesk.RustDesk.policy tmpdeb/usr/share/polkit-1/actions/')
system2(
'cp ../res/startwm.sh tmpdeb/etc/rustdesk/')
system2(
'cp ../res/xorg.conf tmpdeb/etc/rustdesk/')
system2(
'cp ../res/pam.d/rustdesk.debian tmpdeb/etc/pam.d/rustdesk')
system2(
"echo \"#!/bin/sh\" >> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit")
@ -553,12 +561,21 @@ def main():
'cp res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop')
system2(
'cp res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop')
system2('cp -a res/DEBIAN/* tmpdeb/DEBIAN/')
os.system('mkdir -p tmpdeb/etc/rustdesk/')
os.system('cp -a res/startwm.sh tmpdeb/etc/rustdesk/')
os.system('mkdir -p tmpdeb/etc/X11/rustdesk/')
os.system('cp res/xorg.conf tmpdeb/etc/X11/rustdesk/')
os.system('cp -a DEBIAN/* tmpdeb/DEBIAN/')
os.system('mkdir -p tmpdeb/etc/pam.d/')
os.system('cp pam.d/rustdesk.debian tmpdeb/etc/pam.d/rustdesk')
system2('strip tmpdeb/usr/bin/rustdesk')
system2('mkdir -p tmpdeb/usr/lib/rustdesk')
system2('mv tmpdeb/usr/bin/rustdesk tmpdeb/usr/lib/rustdesk/')
system2('cp libsciter-gtk.so tmpdeb/usr/lib/rustdesk/')
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
md5_file('etc/rustdesk/startwm.sh')
md5_file('etc/X11/rustdesk/xorg.conf')
md5_file('etc/pam.d/rustdesk')
md5_file('usr/lib/rustdesk/libsciter-gtk.so')
system2('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/')
os.rename('rustdesk.deb', 'rustdesk-%s.deb' % version)

View File

@ -58,13 +58,12 @@ function build {
fi
make clean
./configure --target=$LIBVPX_TARGET \
--enable-pic --disable-vp8 \
--enable-pic
--disable-webm-io \
--disable-unit-tests \
--disable-examples \
--disable-libyuv \
--disable-postproc \
--disable-vp8 \
--disable-tools \
--disable-docs \
--prefix=$PREFIX

View File

@ -456,7 +456,7 @@ void enterPasswordDialog(String id, OverlayDialogManager dialogManager) async {
await _connectDialog(
id,
dialogManager,
peerPasswordController: TextEditingController(),
passwordController: TextEditingController(),
);
}
@ -464,8 +464,8 @@ void enterUserLoginDialog(String id, OverlayDialogManager dialogManager) async {
await _connectDialog(
id,
dialogManager,
usernameController: TextEditingController(),
passwordController: TextEditingController(),
osUsernameController: TextEditingController(),
osPasswordController: TextEditingController(),
);
}
@ -474,20 +474,27 @@ void enterUserLoginAndPasswordDialog(
await _connectDialog(
id,
dialogManager,
usernameController: TextEditingController(),
osUsernameController: TextEditingController(),
osPasswordController: TextEditingController(),
passwordController: TextEditingController(),
peerPasswordController: TextEditingController(),
);
}
_connectDialog(
String id,
OverlayDialogManager dialogManager, {
TextEditingController? usernameController,
TextEditingController? osUsernameController,
TextEditingController? osPasswordController,
TextEditingController? passwordController,
TextEditingController? peerPasswordController,
}) async {
var remember = await bind.sessionGetRemember(id: id) ?? false;
var rememberPassword = false;
if (passwordController != null) {
rememberPassword = await bind.sessionGetRemember(id: id) ?? false;
}
var rememberAccount = false;
if (osUsernameController != null) {
rememberAccount = await bind.sessionGetRemember(id: id) ?? false;
}
dialogManager.dismissAll();
dialogManager.show((setState, close) {
cancel() {
@ -496,82 +503,129 @@ _connectDialog(
}
submit() {
// to-do:
// username and password are about remote OS account.
// If the remote side is headless.
// The client side should login to remote OS account, to enable X desktop session.
// `username` and `password` will be used in the near future.
final username = usernameController?.text.trim() ?? '';
final osUsername = osUsernameController?.text.trim() ?? '';
final osPassword = osPasswordController?.text.trim() ?? '';
final password = passwordController?.text.trim() ?? '';
final peerPassword = peerPasswordController?.text.trim() ?? '';
if (peerPasswordController != null && peerPassword.isEmpty) return;
if (passwordController != null && password.isEmpty) return;
if (rememberAccount) {
bind.sessionPeerOption(id: id, name: 'os-username', value: osUsername);
bind.sessionPeerOption(id: id, name: 'os-password', value: osPassword);
}
gFFI.login(
// username,
// password,
osUsername,
osPassword,
id,
peerPassword,
remember,
password,
rememberPassword,
);
close();
dialogManager.showLoading(translate('Logging in...'),
onCancel: closeConnection);
}
descWidget(String text) {
return Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
text,
maxLines: 3,
softWrap: true,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 16),
),
),
Container(
height: 8,
),
],
);
}
rememberWidget(
String desc,
bool remember,
ValueChanged<bool?>? onChanged,
) {
return CheckboxListTile(
contentPadding: const EdgeInsets.all(0),
dense: true,
controlAffinity: ListTileControlAffinity.leading,
title: Text(desc),
value: remember,
onChanged: onChanged,
);
}
osAccountWidget() {
if (osUsernameController == null || osPasswordController == null) {
return Offstage();
}
return Column(
children: [
descWidget(translate('login_linux_tip')),
DialogTextField(
title: translate(DialogTextField.kUsernameTitle),
controller: osUsernameController,
prefixIcon: DialogTextField.kUsernameIcon,
errorText: null,
),
PasswordWidget(
controller: osPasswordController,
autoFocus: false,
),
rememberWidget(
translate('remember_account_tip'),
rememberAccount,
(v) {
if (v != null) {
setState(() => rememberAccount = v);
}
},
),
],
);
}
passwdWidget() {
if (passwordController == null) {
return Offstage();
}
return Column(
children: [
descWidget(translate('verify_rustdesk_password_tip')),
PasswordWidget(
controller: passwordController,
autoFocus: osUsernameController == null,
),
rememberWidget(
translate('Remember password'),
rememberPassword,
(v) {
if (v != null) {
setState(() => rememberPassword = v);
}
},
),
],
);
}
return CustomAlertDialog(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.password_rounded, color: MyTheme.accent),
(usernameController == null
? Text(translate('Password Required'))
: Tooltip(
message: translate('login_linux_tooltip_tip'),
child: Text(translate('login_linux_tip')),
))
.paddingOnly(left: 10),
Text(translate('Password Required')).paddingOnly(left: 10),
],
),
content: Column(mainAxisSize: MainAxisSize.min, children: [
usernameController == null
osAccountWidget(),
osUsernameController == null || passwordController == null
? Offstage()
: DialogTextField(
title: translate(DialogTextField.kUsernameTitle),
controller: usernameController,
prefixIcon: DialogTextField.kUsernameIcon,
errorText: null,
),
passwordController == null
? Offstage()
: PasswordWidget(
controller: passwordController,
autoFocus: false,
),
usernameController == null || peerPasswordController == null
? Offstage()
: const Divider(),
peerPasswordController == null
? Offstage()
: PasswordWidget(
controller: peerPasswordController,
autoFocus: usernameController == null,
hintText: 'enter_rustdesk_passwd_tip',
),
peerPasswordController == null
? Offstage()
: CheckboxListTile(
contentPadding: const EdgeInsets.all(0),
dense: true,
controlAffinity: ListTileControlAffinity.leading,
title: Text(
translate('remember_rustdesk_passwd_tip'),
),
value: remember,
onChanged: (v) {
if (v != null) {
setState(() => remember = v);
}
},
),
: Container(height: 12),
passwdWidget(),
]),
actions: [
dialogButton(
@ -847,3 +901,149 @@ void showRestartRemoteDevice(
));
if (res == true) bind.sessionRestartRemoteDevice(id: id);
}
showSetOSPassword(
String id,
bool login,
OverlayDialogManager dialogManager,
) async {
final controller = TextEditingController();
var password = await bind.sessionGetOption(id: id, arg: 'os-password') ?? '';
var autoLogin = await bind.sessionGetOption(id: id, arg: 'auto-login') != '';
controller.text = password;
dialogManager.show((setState, close) {
submit() {
var text = controller.text.trim();
bind.sessionPeerOption(id: id, name: 'os-password', value: text);
bind.sessionPeerOption(
id: id, name: 'auto-login', value: autoLogin ? 'Y' : '');
if (text != '' && login) {
bind.sessionInputOsPassword(id: id, value: text);
}
close();
}
return CustomAlertDialog(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.password_rounded, color: MyTheme.accent),
Text(translate('OS Password')).paddingOnly(left: 10),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
PasswordWidget(controller: controller),
CheckboxListTile(
contentPadding: const EdgeInsets.all(0),
dense: true,
controlAffinity: ListTileControlAffinity.leading,
title: Text(
translate('Auto Login'),
),
value: autoLogin,
onChanged: (v) {
if (v == null) return;
setState(() => autoLogin = v);
},
),
],
),
actions: [
dialogButton(
"Cancel",
icon: Icon(Icons.close_rounded),
onPressed: close,
isOutline: true,
),
dialogButton(
"OK",
icon: Icon(Icons.done_rounded),
onPressed: submit,
),
],
onSubmit: submit,
onCancel: close,
);
});
}
showSetOSAccount(
String id,
OverlayDialogManager dialogManager,
) async {
final usernameController = TextEditingController();
final passwdController = TextEditingController();
var username = await bind.sessionGetOption(id: id, arg: 'os-username') ?? '';
var password = await bind.sessionGetOption(id: id, arg: 'os-password') ?? '';
usernameController.text = username;
passwdController.text = password;
dialogManager.show((setState, close) {
submit() {
final username = usernameController.text.trim();
final password = usernameController.text.trim();
bind.sessionPeerOption(id: id, name: 'os-username', value: username);
bind.sessionPeerOption(id: id, name: 'os-password', value: password);
close();
}
descWidget(String text) {
return Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
text,
maxLines: 3,
softWrap: true,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 16),
),
),
Container(
height: 8,
),
],
);
}
return CustomAlertDialog(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.password_rounded, color: MyTheme.accent),
Text(translate('OS Account')).paddingOnly(left: 10),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
descWidget(translate("os_account_desk_tip")),
DialogTextField(
title: translate(DialogTextField.kUsernameTitle),
controller: usernameController,
prefixIcon: DialogTextField.kUsernameIcon,
errorText: null,
),
PasswordWidget(controller: passwdController),
],
),
actions: [
dialogButton(
"Cancel",
icon: Icon(Icons.close_rounded),
onPressed: close,
isOutline: true,
),
dialogButton(
"OK",
icon: Icon(Icons.done_rounded),
onPressed: submit,
),
],
onSubmit: submit,
onCancel: close,
);
});
}

View File

@ -388,6 +388,15 @@ class BlockableOverlayState extends OverlayKeyState {
_middleBlocked.value = blocked;
}
}
void applyFfi(FFI ffi) {
ffi.dialogManager.setOverlayState(this);
ffi.chatModel.setOverlayState(this);
// make remote page penetrable automatically, effective for chat over remote
onMiddleBlockedClick = () {
setMiddleBlocked(false);
};
}
}
class BlockableOverlay extends StatelessWidget {

View File

@ -261,7 +261,7 @@ class _PeerTabPageState extends State<PeerTabPage>
},
child: Icon(
peerCardUiType.value == PeerUiType.grid
? Icons.list_rounded
? Icons.list
: Icons.grid_view_rounded,
size: 18,
color: textColor,

View File

@ -225,9 +225,7 @@ class _ConnectionPageState extends State<ConnectionPage>
children: [
Button(
isOutline: true,
onTap: () {
onConnect(isFileTransfer: true);
},
onTap: () => onConnect(isFileTransfer: true),
text: "Transfer File",
),
const SizedBox(

View File

@ -1228,9 +1228,9 @@ class _DisplayState extends State<_Display> {
children: [
Slider(
value: fpsValue.value,
min: 10.0,
min: 5.0,
max: 120.0,
divisions: 22,
divisions: 23,
onChanged: (double value) async {
fpsValue.value = value;
await bind.mainSetUserDefaultOption(
@ -1258,9 +1258,6 @@ class _DisplayState extends State<_Display> {
}
Widget codec(BuildContext context) {
if (!bind.mainHasHwcodec()) {
return Offstage();
}
final key = 'codec-preference';
onChanged(String value) async {
await bind.mainSetUserDefaultOption(key: key, value: value);
@ -1268,28 +1265,45 @@ class _DisplayState extends State<_Display> {
}
final groupValue = bind.mainGetUserDefaultOption(key: key);
var hwRadios = [];
try {
final Map codecsJson = jsonDecode(bind.mainSupportedHwdecodings());
final h264 = codecsJson['h264'] ?? false;
final h265 = codecsJson['h265'] ?? false;
if (h264) {
hwRadios.add(_Radio(context,
value: 'h264',
groupValue: groupValue,
label: 'H264',
onChanged: onChanged));
}
if (h265) {
hwRadios.add(_Radio(context,
value: 'h265',
groupValue: groupValue,
label: 'H265',
onChanged: onChanged));
}
} catch (e) {
debugPrint("failed to parse supported hwdecodings, err=$e");
}
return _Card(title: 'Default Codec', children: [
_Radio(context,
value: 'auto',
groupValue: groupValue,
label: 'Auto',
onChanged: onChanged),
_Radio(context,
value: 'vp8',
groupValue: groupValue,
label: 'VP8',
onChanged: onChanged),
_Radio(context,
value: 'vp9',
groupValue: groupValue,
label: 'VP9',
onChanged: onChanged),
_Radio(context,
value: 'h264',
groupValue: groupValue,
label: 'H264',
onChanged: onChanged),
_Radio(context,
value: 'h265',
groupValue: groupValue,
label: 'H265',
onChanged: onChanged),
...hwRadios,
]);
}

View File

@ -158,12 +158,7 @@ class _RemotePageState extends State<RemotePage>
// _isCustomCursorInited = true;
// }
_ffi.dialogManager.setOverlayState(_blockableOverlayState);
_ffi.chatModel.setOverlayState(_blockableOverlayState);
// make remote page penetrable automatically, effective for chat over remote
_blockableOverlayState.onMiddleBlockedClick = () {
_blockableOverlayState.setMiddleBlocked(false);
};
_blockableOverlayState.applyFfi(_ffi);
}
@override

View File

@ -320,6 +320,8 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
PeerInfo get pi => widget.ffi.ffiModel.pi;
FfiModel get ffiModel => widget.ffi.ffiModel;
triggerAutoHide() => _debouncerHide.value = _debouncerHide.value + 1;
@override
initState() {
super.initState();
@ -332,7 +334,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
widget.onEnterOrLeaveImageSetter((enter) {
if (enter) {
_debouncerHide.value = 0;
triggerAutoHide();
_isCursorOverImage = true;
} else {
_isCursorOverImage = false;
@ -367,7 +369,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
Widget _buildDraggableShowHide(BuildContext context) {
return Obx(() {
if (show.isTrue && _dragging.isFalse) {
_debouncerHide.value = 1;
triggerAutoHide();
}
return Align(
alignment: FractionalOffset(_fractionX.value, 0),
@ -637,7 +639,7 @@ class _ControlMenu extends StatelessWidget {
ffi: ffi,
menuChildren: [
requestElevation(),
osPassword(),
ffi.ffiModel.pi.is_headless ? osAccount() : osPassword(),
transferFile(context),
tcpTunneling(context),
note(),
@ -660,78 +662,20 @@ class _ControlMenu extends StatelessWidget {
onPressed: () => showRequestElevationDialog(id, ffi.dialogManager));
}
osAccount() {
return _MenuItemButton(
child: Text(translate('OS Account')),
trailingIcon: Transform.scale(scale: 0.8, child: Icon(Icons.edit)),
ffi: ffi,
onPressed: () => showSetOSAccount(id, ffi.dialogManager));
}
osPassword() {
return _MenuItemButton(
child: Text(translate('OS Password')),
trailingIcon: Transform.scale(scale: 0.8, child: Icon(Icons.edit)),
ffi: ffi,
onPressed: () => _showSetOSPassword(id, false, ffi.dialogManager));
}
_showSetOSPassword(
String id, bool login, OverlayDialogManager dialogManager) async {
final controller = TextEditingController();
var password =
await bind.sessionGetOption(id: id, arg: 'os-password') ?? '';
var autoLogin =
await bind.sessionGetOption(id: id, arg: 'auto-login') != '';
controller.text = password;
dialogManager.show((setState, close) {
submit() {
var text = controller.text.trim();
bind.sessionPeerOption(id: id, name: 'os-password', value: text);
bind.sessionPeerOption(
id: id, name: 'auto-login', value: autoLogin ? 'Y' : '');
if (text != '' && login) {
bind.sessionInputOsPassword(id: id, value: text);
}
close();
}
return CustomAlertDialog(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.password_rounded, color: MyTheme.accent),
Text(translate('OS Password')).paddingOnly(left: 10),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
PasswordWidget(controller: controller),
CheckboxListTile(
contentPadding: const EdgeInsets.all(0),
dense: true,
controlAffinity: ListTileControlAffinity.leading,
title: Text(
translate('Auto Login'),
),
value: autoLogin,
onChanged: (v) {
if (v == null) return;
setState(() => autoLogin = v);
},
),
],
),
actions: [
dialogButton(
"Cancel",
icon: Icon(Icons.close_rounded),
onPressed: close,
isOutline: true,
),
dialogButton(
"OK",
icon: Icon(Icons.done_rounded),
onPressed: submit,
),
],
onSubmit: submit,
onCancel: close,
);
});
onPressed: () => showSetOSPassword(id, false, ffi.dialogManager));
}
transferFile(BuildContext context) {
@ -1293,7 +1237,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
final fpsOption =
await bind.sessionGetOption(id: widget.id, arg: 'custom-fps');
fpsInitValue = fpsOption == null ? 30 : double.tryParse(fpsOption) ?? 30;
if (fpsInitValue < 10 || fpsInitValue > 120) {
if (fpsInitValue < 5 || fpsInitValue > 120) {
fpsInitValue = 30;
}
final RxDouble fpsSliderValue = RxDouble(fpsInitValue);
@ -1316,9 +1260,9 @@ class _DisplayMenuState extends State<_DisplayMenu> {
children: [
Obx((() => Slider(
value: fpsSliderValue.value,
min: 10,
min: 5,
max: 120,
divisions: 22,
divisions: 23,
onChanged: (double value) {
fpsSliderValue.value = value;
debouncerFps.value = value;
@ -1349,29 +1293,30 @@ class _DisplayMenuState extends State<_DisplayMenu> {
codec() {
return futureBuilder(future: () async {
final supportedHwcodec =
await bind.sessionSupportedHwcodec(id: widget.id);
final alternativeCodecs =
await bind.sessionAlternativeCodecs(id: widget.id);
final codecPreference =
await bind.sessionGetOption(id: widget.id, arg: 'codec-preference') ??
'';
return {
'supportedHwcodec': supportedHwcodec,
'alternativeCodecs': alternativeCodecs,
'codecPreference': codecPreference
};
}(), hasData: (data) {
final List<bool> codecs = [];
try {
final Map codecsJson = jsonDecode(data['supportedHwcodec']);
final Map codecsJson = jsonDecode(data['alternativeCodecs']);
final vp8 = codecsJson['vp8'] ?? false;
final h264 = codecsJson['h264'] ?? false;
final h265 = codecsJson['h265'] ?? false;
codecs.add(vp8);
codecs.add(h264);
codecs.add(h265);
} catch (e) {
debugPrint("Show Codec Preference err=$e");
}
final visible = bind.mainHasHwcodec() &&
codecs.length == 2 &&
(codecs[0] || codecs[1]);
final visible =
codecs.length == 3 && (codecs[0] || codecs[1] || codecs[2]);
if (!visible) return Offstage();
final groupValue = data['codecPreference'] as String;
onChanged(String? value) async {
@ -1392,6 +1337,13 @@ class _DisplayMenuState extends State<_DisplayMenu> {
onChanged: onChanged,
ffi: widget.ffi,
),
_RadioMenuButton<String>(
child: Text(translate('VP8')),
value: 'vp8',
groupValue: groupValue,
onChanged: codecs[0] ? onChanged : null,
ffi: widget.ffi,
),
_RadioMenuButton<String>(
child: Text(translate('VP9')),
value: 'vp9',
@ -1403,14 +1355,14 @@ class _DisplayMenuState extends State<_DisplayMenu> {
child: Text(translate('H264')),
value: 'h264',
groupValue: groupValue,
onChanged: codecs[0] ? onChanged : null,
onChanged: codecs[1] ? onChanged : null,
ffi: widget.ffi,
),
_RadioMenuButton<String>(
child: Text(translate('H265')),
value: 'h265',
groupValue: groupValue,
onChanged: codecs[1] ? onChanged : null,
onChanged: codecs[2] ? onChanged : null,
ffi: widget.ffi,
),
]);

View File

@ -20,7 +20,6 @@ import '../../models/input_model.dart';
import '../../models/model.dart';
import '../../models/platform_model.dart';
import '../../utils/image.dart';
import '../widgets/dialog.dart';
import '../widgets/gestures.dart';
final initText = '\1' * 1024;
@ -43,6 +42,8 @@ class _RemotePageState extends State<RemotePage> {
double _mouseScrollIntegral = 0; // mouse scroll speed controller
Orientation? _currentOrientation;
final _blockableOverlayState = BlockableOverlayState();
final keyboardVisibilityController = KeyboardVisibilityController();
late final StreamSubscription<bool> keyboardSubscription;
final FocusNode _mobileFocusNode = FocusNode();
@ -67,6 +68,7 @@ class _RemotePageState extends State<RemotePage> {
gFFI.qualityMonitorModel.checkShowQualityMonitor(widget.id);
keyboardSubscription =
keyboardVisibilityController.onChange.listen(onSoftKeyboardChanged);
_blockableOverlayState.applyFfi(gFFI);
}
@override
@ -548,19 +550,39 @@ class _RemotePageState extends State<RemotePage> {
more.add(PopupMenuItem<String>(
child: Text(translate('Refresh')), value: 'refresh'));
}
more.add(PopupMenuItem<String>(
child: Row(
children: ([
Text(translate('OS Password')),
TextButton(
style: flatButtonStyle,
onPressed: () {
showSetOSPassword(id, false, gFFI.dialogManager);
},
child: Icon(Icons.edit, color: MyTheme.accent),
)
])),
value: 'enter_os_password'));
if (gFFI.ffiModel.pi.is_headless) {
more.add(
PopupMenuItem<String>(
child: Row(
children: ([
Text(translate('OS Account')),
TextButton(
style: flatButtonStyle,
onPressed: () {
showSetOSAccount(id, gFFI.dialogManager);
},
child: Icon(Icons.edit, color: MyTheme.accent),
)
])),
value: 'enter_os_account'),
);
} else {
more.add(
PopupMenuItem<String>(
child: Row(
children: ([
Text(translate('OS Password')),
TextButton(
style: flatButtonStyle,
onPressed: () {
showSetOSPassword(id, false, gFFI.dialogManager);
},
child: Icon(Icons.edit, color: MyTheme.accent),
)
])),
value: 'enter_os_password'),
);
}
if (!isWebDesktop) {
if (perms['keyboard'] != false && perms['clipboard'] != false) {
more.add(PopupMenuItem<String>(
@ -657,6 +679,8 @@ class _RemotePageState extends State<RemotePage> {
} else {
showSetOSPassword(id, true, gFFI.dialogManager);
}
} else if (value == 'enter_os_account') {
showSetOSAccount(id, gFFI.dialogManager);
} else if (value == 'reset_canvas') {
gFFI.cursorModel.reset();
} else if (value == 'restart') {
@ -970,17 +994,17 @@ void showOptions(
final perms = gFFI.ffiModel.permissions;
final hasHwcodec = bind.mainHasHwcodec();
final List<bool> codecs = [];
if (hasHwcodec) {
try {
final Map codecsJson =
jsonDecode(await bind.sessionSupportedHwcodec(id: id));
final h264 = codecsJson['h264'] ?? false;
final h265 = codecsJson['h265'] ?? false;
codecs.add(h264);
codecs.add(h265);
} catch (e) {
debugPrint("Show Codec Preference err=$e");
}
try {
final Map codecsJson =
jsonDecode(await bind.sessionAlternativeCodecs(id: id));
final vp8 = codecsJson['vp8'] ?? false;
final h264 = codecsJson['h264'] ?? false;
final h265 = codecsJson['h265'] ?? false;
codecs.add(vp8);
codecs.add(h264);
codecs.add(h265);
} catch (e) {
debugPrint("Show Codec Preference err=$e");
}
dialogManager.show((setState, close) {
@ -1041,15 +1065,16 @@ void showOptions(
const Divider(color: MyTheme.border)
];
if (hasHwcodec && codecs.length == 2 && (codecs[0] || codecs[1])) {
radios.addAll([
getRadio(translate('Auto'), 'auto', codec, setCodec),
getRadio('VP9', 'vp9', codec, setCodec),
]);
if (codecs.length == 3 && (codecs[0] || codecs[1] || codecs[2])) {
radios.add(getRadio(translate('Auto'), 'auto', codec, setCodec));
if (codecs[0]) {
radios.add(getRadio('VP8', 'vp8', codec, setCodec));
}
radios.add(getRadio('VP9', 'vp9', codec, setCodec));
if (codecs[1]) {
radios.add(getRadio('H264', 'h264', codec, setCodec));
}
if (codecs[1]) {
if (codecs[2]) {
radios.add(getRadio('H265', 'h265', codec, setCodec));
}
radios.add(const Divider(color: MyTheme.border));
@ -1071,50 +1096,6 @@ void showOptions(
}, clickMaskDismiss: true, backDismiss: true);
}
void showSetOSPassword(
String id, bool login, OverlayDialogManager dialogManager) async {
final controller = TextEditingController();
var password = await bind.sessionGetOption(id: id, arg: "os-password") ?? "";
var autoLogin = await bind.sessionGetOption(id: id, arg: "auto-login") != "";
controller.text = password;
dialogManager.show((setState, close) {
return CustomAlertDialog(
title: Text(translate('OS Password')),
content: Column(mainAxisSize: MainAxisSize.min, children: [
PasswordWidget(controller: controller),
CheckboxListTile(
contentPadding: const EdgeInsets.all(0),
dense: true,
controlAffinity: ListTileControlAffinity.leading,
title: Text(
translate('Auto Login'),
),
value: autoLogin,
onChanged: (v) {
if (v == null) return;
setState(() => autoLogin = v);
},
),
]),
actions: [
dialogButton('Cancel', onPressed: close, isOutline: true),
dialogButton(
'OK',
onPressed: () {
var text = controller.text.trim();
bind.sessionPeerOption(id: id, name: "os-password", value: text);
bind.sessionPeerOption(
id: id, name: "auto-login", value: autoLogin ? 'Y' : '');
if (text != "" && login) {
bind.sessionInputOsPassword(id: id, value: text);
}
close();
},
),
]);
});
}
void sendPrompt(bool isMac, String key) {
final old = isMac ? gFFI.inputModel.command : gFFI.inputModel.ctrl;
if (isMac) {

View File

@ -64,7 +64,7 @@ class InputModel {
InputModel(this.parent);
KeyEventResult handleRawKeyEvent(FocusNode data, RawKeyEvent e) {
if (!stateGlobal.grabKeyboard) {
if (isDesktop && !stateGlobal.grabKeyboard) {
return KeyEventResult.handled;
}
@ -117,44 +117,41 @@ class InputModel {
}
void mapKeyboardMode(RawKeyEvent e) {
int scanCode;
int keyCode;
int positionCode = -1;
int platformCode = -1;
bool down;
if (e.data is RawKeyEventDataMacOs) {
RawKeyEventDataMacOs newData = e.data as RawKeyEventDataMacOs;
scanCode = newData.keyCode;
keyCode = newData.keyCode;
positionCode = newData.keyCode;
platformCode = newData.keyCode;
} else if (e.data is RawKeyEventDataWindows) {
RawKeyEventDataWindows newData = e.data as RawKeyEventDataWindows;
scanCode = newData.scanCode;
keyCode = newData.keyCode;
positionCode = newData.scanCode;
platformCode = newData.keyCode;
} else if (e.data is RawKeyEventDataLinux) {
RawKeyEventDataLinux newData = e.data as RawKeyEventDataLinux;
// scanCode and keyCode of RawKeyEventDataLinux are incorrect.
// 1. scanCode means keycode
// 2. keyCode means keysym
scanCode = 0;
keyCode = newData.scanCode;
positionCode = newData.scanCode;
platformCode = newData.keyCode;
} else if (e.data is RawKeyEventDataAndroid) {
RawKeyEventDataAndroid newData = e.data as RawKeyEventDataAndroid;
scanCode = newData.scanCode + 8;
keyCode = newData.keyCode;
} else {
scanCode = -1;
keyCode = -1;
}
positionCode = newData.scanCode + 8;
platformCode = newData.keyCode;
} else {}
if (e is RawKeyDownEvent) {
down = true;
} else {
down = false;
}
inputRawKey(e.character ?? '', keyCode, scanCode, down);
inputRawKey(e.character ?? '', platformCode, positionCode, down);
}
/// Send raw Key Event
void inputRawKey(String name, int keyCode, int scanCode, bool down) {
void inputRawKey(String name, int platformCode, int positionCode, bool down) {
const capslock = 1;
const numlock = 2;
const scrolllock = 3;
@ -174,8 +171,8 @@ class InputModel {
bind.sessionHandleFlutterKeyEvent(
id: id,
name: name,
keycode: keyCode,
scancode: scanCode,
platformCode: platformCode,
positionCode: positionCode,
lockModes: lockModes,
downOrUp: down);
}

View File

@ -293,11 +293,11 @@ class FfiModel with ChangeNotifier {
wrongPasswordDialog(id, dialogManager, type, title, text);
} else if (type == 'input-password') {
enterPasswordDialog(id, dialogManager);
} else if (type == 'xsession-login' || type == 'xsession-re-login') {
// to-do
} else if (type == 'xsession-login-password' ||
type == 'xsession-login-password') {
// to-do
} else if (type == 'session-login' || type == 'session-re-login') {
enterUserLoginDialog(id, dialogManager);
} else if (type == 'session-login-password' ||
type == 'session-login-password') {
enterUserLoginAndPasswordDialog(id, dialogManager);
} else if (type == 'restarting') {
showMsgBox(id, type, title, text, link, false, dialogManager,
hasCancel: false);
@ -1631,8 +1631,14 @@ class FFI {
}
/// Login with [password], choose if the client should [remember] it.
void login(String id, String password, bool remember) {
bind.sessionLogin(id: id, password: password, remember: remember);
void login(String osUsername, String osPassword, String id, String password,
bool remember) {
bind.sessionLogin(
id: id,
osUsername: osUsername,
osPassword: osPassword,
password: password,
remember: remember);
}
/// Close the remote session.
@ -1719,6 +1725,7 @@ class PeerInfo {
Map<String, dynamic> platform_additions = {};
bool get is_wayland => platform_additions['is_wayland'] == true;
bool get is_headless => platform_additions['headless'] == true;
}
const canvasKey = 'canvas';

View File

@ -1236,10 +1236,10 @@ packages:
dependency: "direct main"
description:
name: texture_rgba_renderer
sha256: "52bc9f217b7b07a760ee837d5a17329ad1f78ae8ed1e3fa612c6f1bed3c77f79"
sha256: cb048abdd800468ca40749ca10d1db9d1e6a055d1cde6234c05191293f0c7d61
url: "https://pub.dev"
source: hosted
version: "0.0.13"
version: "0.0.16"
timing:
dependency: transitive
description:

View File

@ -1,3 +1,4 @@
#![allow(dead_code)]
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731
//
// JP/KR mapping https://github.com/TigerVNC/tigervnc/blob/1a008c1380305648ab50f1d99e73439747e9d61d/vncviewer/win32.c#L267

View File

@ -247,15 +247,51 @@ impl KeyboardControllable for Enigo {
}
fn key_down(&mut self, key: Key) -> crate::ResultType {
let code = self.key_to_keycode(key);
if code == 0 || code == 65535 {
return Err("".into());
}
let res = keybd_event(0, code, 0);
if res == 0 {
let err = get_error();
if !err.is_empty() {
return Err(err.into());
match &key {
Key::Layout(c) => {
// to-do: dup code
// https://github.com/rustdesk/rustdesk/blob/1bc0dd791ed8344997024dc46626bd2ca7df73d2/src/server/input_service.rs#L1348
let code = self.get_layoutdependent_keycode(*c);
if code as u16 != 0xFFFF {
let vk = code & 0x00FF;
let flag = code >> 8;
let modifiers = [Key::Shift, Key::Control, Key::Alt];
let mod_len = modifiers.len();
for pos in 0..mod_len {
if flag & (0x0001 << pos) != 0 {
self.key_down(modifiers[pos])?;
}
}
let res = keybd_event(0, vk, 0);
let err = if res == 0 { get_error() } else { "".to_owned() };
for pos in 0..mod_len {
let rpos = mod_len - 1 - pos;
if flag & (0x0001 << rpos) != 0 {
self.key_up(modifiers[pos]);
}
}
if !err.is_empty() {
return Err(err.into());
}
} else {
return Err(format!("Failed to get keycode of {}", c).into());
}
}
_ => {
let code = self.key_to_keycode(key);
if code == 0 || code == 65535 {
return Err("".into());
}
let res = keybd_event(0, code, 0);
if res == 0 {
let err = get_error();
if !err.is_empty() {
return Err(err.into());
}
}
}
}
Ok(())
@ -411,30 +447,21 @@ impl Enigo {
Key::RightAlt => EVK_RMENU,
Key::Raw(raw_keycode) => raw_keycode,
Key::Layout(c) => self.get_layoutdependent_keycode(c.to_string()),
Key::Super | Key::Command | Key::Windows | Key::Meta => EVK_LWIN,
Key::Layout(..) => {
// unreachable
0
}
}
}
fn get_layoutdependent_keycode(&self, string: String) -> u16 {
// get the first char from the string ignore the rest
// ensure its not a multybyte char
if let Some(chr) = string.chars().nth(0) {
// NOTE VkKeyScanW uses the current keyboard LAYOUT
// to specify a LAYOUT use VkKeyScanExW and GetKeyboardLayout
// or load one with LoadKeyboardLayoutW
let current_window_thread_id =
unsafe { GetWindowThreadProcessId(GetForegroundWindow(), std::ptr::null_mut()) };
unsafe { LAYOUT = GetKeyboardLayout(current_window_thread_id) };
let keycode_and_shiftstate = unsafe { VkKeyScanExW(chr as _, LAYOUT) };
if keycode_and_shiftstate == (EVK_DECIMAL as i16) && chr == '.' {
// a workaround of italian keyboard shift + '.' issue
EVK_PERIOD as _
} else {
keycode_and_shiftstate as _
}
} else {
0
}
fn get_layoutdependent_keycode(&self, chr: char) -> u16 {
// NOTE VkKeyScanW uses the current keyboard LAYOUT
// to specify a LAYOUT use VkKeyScanExW and GetKeyboardLayout
// or load one with LoadKeyboardLayoutW
let current_window_thread_id =
unsafe { GetWindowThreadProcessId(GetForegroundWindow(), std::ptr::null_mut()) };
unsafe { LAYOUT = GetKeyboardLayout(current_window_thread_id) };
unsafe { VkKeyScanExW(chr as _, LAYOUT) as _ }
}
}

View File

@ -24,6 +24,7 @@ message VideoFrame {
YUV yuv = 8;
EncodedVideoFrames h264s = 10;
EncodedVideoFrames h265s = 11;
EncodedVideoFrames vp8s = 12;
}
}
@ -52,6 +53,11 @@ message FileTransfer {
bool show_hidden = 2;
}
message OSLogin {
string username = 1;
string password = 2;
}
message LoginRequest {
string username = 1;
bytes password = 2;
@ -65,6 +71,7 @@ message LoginRequest {
bool video_ack_required = 9;
uint64 session_id = 10;
string version = 11;
OSLogin os_login = 12;
}
message ChatMessage { string text = 1; }
@ -76,6 +83,7 @@ message Features {
message SupportedEncoding {
bool h264 = 1;
bool h265 = 2;
bool vp8 = 3;
}
message PeerInfo {
@ -203,11 +211,13 @@ message KeyEvent {
bool press = 2;
oneof union {
ControlKey control_key = 3;
// high word, sym key code. win: virtual-key code, linux: keysym ?, macos:
// low word, position key code. win: scancode, linux: key code, macos: key code
// position key code. win: scancode, linux: key code, macos: key code
uint32 chr = 4;
uint32 unicode = 5;
string seq = 6;
// high word. virtual keycode
// low word. unicode
uint32 win2win_hotkey = 7;
}
repeated ControlKey modifiers = 8;
KeyboardMode mode = 9;
@ -457,18 +467,20 @@ enum ImageQuality {
Best = 4;
}
message VideoCodecState {
message SupportedDecoding {
enum PreferCodec {
Auto = 0;
VPX = 1;
VP9 = 1;
H264 = 2;
H265 = 3;
VP8 = 4;
}
int32 score_vpx = 1;
int32 score_h264 = 2;
int32 score_h265 = 3;
int32 ability_vp9 = 1;
int32 ability_h264 = 2;
int32 ability_h265 = 3;
PreferCodec prefer = 4;
int32 ability_vp8 = 5;
}
message OptionMessage {
@ -486,7 +498,7 @@ message OptionMessage {
BoolOption disable_audio = 7;
BoolOption disable_clipboard = 8;
BoolOption enable_file_transfer = 9;
VideoCodecState video_codec_state = 10;
SupportedDecoding supported_decoding = 10;
int32 custom_fps = 11;
BoolOption disable_keyboard = 12;
}

View File

@ -64,11 +64,15 @@ lazy_static::lazy_static! {
pub static ref APP_HOME_DIR: Arc<RwLock<String>> = Default::default();
}
// #[cfg(any(target_os = "android", target_os = "ios"))]
pub const LINK_DOCS_HOME: &str = "https://rustdesk.com/docs/en/";
pub const LINK_DOCS_X11_REQUIRED: &str = "https://rustdesk.com/docs/en/manual/linux/#x11-required";
pub const LINK_HEADLESS_LINUX_SUPPORT: &str =
"https://github.com/rustdesk/rustdesk/wiki/Headless-Linux-Support";
lazy_static::lazy_static! {
pub static ref HELPER_URL: HashMap<&'static str, &'static str> = HashMap::from([
("rustdesk docs home", "https://rustdesk.com/docs/en/"),
("rustdesk docs x11-required", "https://rustdesk.com/docs/en/manual/linux/#x11-required"),
("rustdesk docs home", LINK_DOCS_HOME),
("rustdesk docs x11-required", LINK_DOCS_X11_REQUIRED),
("rustdesk x11 headless", LINK_HEADLESS_LINUX_SUPPORT),
]);
}
@ -915,9 +919,10 @@ impl PeerConfig {
decrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION);
config.password = password;
store = store || store2;
for opt in ["rdp_password", "os-password"] {
for opt in ["rdp_password", "os-username", "os-password"] {
if let Some(v) = config.options.get_mut(opt) {
let (encrypted, _, store2) = decrypt_str_or_original(v, PASSWORD_ENC_VERSION);
let (encrypted, _, store2) =
decrypt_str_or_original(v, PASSWORD_ENC_VERSION);
*v = encrypted;
store = store || store2;
}
@ -938,7 +943,7 @@ impl PeerConfig {
let _lock = CONFIG.read().unwrap();
let mut config = self.clone();
config.password = encrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION);
for opt in ["rdp_password", "os-password"] {
for opt in ["rdp_password", "os-username", "os-password"] {
if let Some(v) = config.options.get_mut(opt) {
*v = encrypt_str_or_original(v, PASSWORD_ENC_VERSION)
}
@ -1356,9 +1361,9 @@ impl UserDefaultConfig {
"view_style" => self.get_string(key, "original", vec!["adaptive"]),
"scroll_style" => self.get_string(key, "scrollauto", vec!["scrollbar"]),
"image_quality" => self.get_string(key, "balanced", vec!["best", "low", "custom"]),
"codec-preference" => self.get_string(key, "auto", vec!["vp9", "h264", "h265"]),
"codec-preference" => self.get_string(key, "auto", vec!["vp8", "vp9", "h264", "h265"]),
"custom_image_quality" => self.get_double_string(key, 50.0, 10.0, 100.0),
"custom-fps" => self.get_double_string(key, 30.0, 10.0, 120.0),
"custom-fps" => self.get_double_string(key, 30.0, 5.0, 120.0),
_ => self
.options
.get(key)

View File

@ -4,11 +4,15 @@ pub mod linux;
#[cfg(target_os = "macos")]
pub mod macos;
#[cfg(not(debug_assertions))]
use crate::{config::Config, log};
#[cfg(not(debug_assertions))]
use std::process::exit;
#[cfg(not(debug_assertions))]
static mut GLOBAL_CALLBACK: Option<Box<dyn Fn()>> = None;
#[cfg(not(debug_assertions))]
extern "C" fn breakdown_signal_handler(sig: i32) {
let mut stack = vec![];
backtrace::trace(|frame| {
@ -29,6 +33,16 @@ extern "C" fn breakdown_signal_handler(sig: i32) {
info = "Always use software rendering will be set.".to_string();
log::info!("{}", info);
}
if stack.iter().any(|s| {
s.to_lowercase().contains("nvidia")
|| s.to_lowercase().contains("amf")
|| s.to_lowercase().contains("mfx")
|| s.contains("cuProfilerStop")
}) {
Config::set_option("enable-hwcodec".to_string(), "N".to_string());
info = "Perhaps hwcodec causing the crash, disable it first".to_string();
log::info!("{}", info);
}
log::error!(
"Got signal {} and exit. stack:\n{}",
sig,
@ -51,6 +65,7 @@ extern "C" fn breakdown_signal_handler(sig: i32) {
exit(0);
}
#[cfg(not(debug_assertions))]
pub fn register_breakdown_handler<T>(callback: T)
where
T: Fn() + 'static,

View File

@ -3,7 +3,8 @@ use hbb_common::env_logger::{init_from_env, Env, DEFAULT_FILTER_ENV};
use scrap::{
codec::{EncoderApi, EncoderCfg},
Capturer, Display, TraitCapturer, VpxDecoder, VpxDecoderConfig, VpxEncoder, VpxEncoderConfig,
VpxVideoCodecId, STRIDE_ALIGN,
VpxVideoCodecId::{self, *},
STRIDE_ALIGN,
};
use std::{io::Write, time::Instant};
@ -49,7 +50,7 @@ fn main() {
"benchmark {}x{} bitrate:{}k hw_pixfmt:{:?}",
width, height, bitrate_k, args.flag_hw_pixfmt
);
test_vp9(&yuvs, width, height, bitrate_k, yuv_count);
[VP8, VP9].map(|c| test_vpx(c, &yuvs, width, height, bitrate_k, yuv_count));
#[cfg(feature = "hwcodec")]
{
use hwcodec::AVPixelFormat;
@ -57,7 +58,7 @@ fn main() {
Pixfmt::I420 => AVPixelFormat::AV_PIX_FMT_YUV420P,
Pixfmt::NV12 => AVPixelFormat::AV_PIX_FMT_NV12,
};
let yuvs = hw::vp9_yuv_to_hw_yuv(yuvs, width, height, hw_pixfmt);
let yuvs = hw::vpx_yuv_to_hw_yuv(yuvs, width, height, hw_pixfmt);
hw::test(&yuvs, width, height, bitrate_k, yuv_count, hw_pixfmt);
}
}
@ -87,13 +88,20 @@ fn capture_yuv(yuv_count: usize) -> (Vec<Vec<u8>>, usize, usize) {
}
}
fn test_vp9(yuvs: &Vec<Vec<u8>>, width: usize, height: usize, bitrate_k: usize, yuv_count: usize) {
fn test_vpx(
codec_id: VpxVideoCodecId,
yuvs: &Vec<Vec<u8>>,
width: usize,
height: usize,
bitrate_k: usize,
yuv_count: usize,
) {
let config = EncoderCfg::VPX(VpxEncoderConfig {
width: width as _,
height: height as _,
timebase: [1, 1000],
bitrate: bitrate_k as _,
codec: VpxVideoCodecId::VP9,
codec: codec_id,
num_threads: (num_cpus::get() / 2) as _,
});
let mut encoder = VpxEncoder::new(config).unwrap();
@ -104,35 +112,43 @@ fn test_vp9(yuvs: &Vec<Vec<u8>>, width: usize, height: usize, bitrate_k: usize,
.unwrap();
let _ = encoder.flush().unwrap();
}
println!("vp9 encode: {:?}", start.elapsed() / yuv_count as _);
println!(
"{:?} encode: {:?}",
codec_id,
start.elapsed() / yuv_count as _
);
// prepare data separately
let mut vp9s = vec![];
let mut vpxs = vec![];
let start = Instant::now();
for yuv in yuvs {
for ref frame in encoder
.encode(start.elapsed().as_millis() as _, yuv, STRIDE_ALIGN)
.unwrap()
{
vp9s.push(frame.data.to_vec());
vpxs.push(frame.data.to_vec());
}
for ref frame in encoder.flush().unwrap() {
vp9s.push(frame.data.to_vec());
vpxs.push(frame.data.to_vec());
}
}
assert_eq!(vp9s.len(), yuv_count);
assert_eq!(vpxs.len(), yuv_count);
let mut decoder = VpxDecoder::new(VpxDecoderConfig {
codec: VpxVideoCodecId::VP9,
codec: codec_id,
num_threads: (num_cpus::get() / 2) as _,
})
.unwrap();
let start = Instant::now();
for vp9 in vp9s {
let _ = decoder.decode(&vp9);
for vpx in vpxs {
let _ = decoder.decode(&vpx);
let _ = decoder.flush();
}
println!("vp9 decode: {:?}", start.elapsed() / yuv_count as _);
println!(
"{:?} decode: {:?}",
codec_id,
start.elapsed() / yuv_count as _
);
}
#[cfg(feature = "hwcodec")]
@ -267,7 +283,7 @@ mod hw {
Some(info.clone()) == best.h264 || Some(info.clone()) == best.h265
}
pub fn vp9_yuv_to_hw_yuv(
pub fn vpx_yuv_to_hw_yuv(
yuvs: Vec<Vec<u8>>,
width: usize,
height: usize,

View File

@ -50,6 +50,7 @@ impl crate::TraitCapturer for Capturer {
pub enum Frame<'a> {
RAW(&'a [u8]),
VP8(&'a [u8]),
VP9(&'a [u8]),
Empty,
}

View File

@ -1,7 +1,6 @@
use std::ops::{Deref, DerefMut};
#[cfg(feature = "hwcodec")]
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
sync::{Arc, Mutex},
};
@ -11,30 +10,31 @@ use crate::hwcodec::*;
use crate::mediacodec::{
MediaCodecDecoder, MediaCodecDecoders, H264_DECODER_SUPPORT, H265_DECODER_SUPPORT,
};
use crate::{vpxcodec::*, ImageFormat};
use crate::{vpxcodec::*, CodecName, ImageFormat};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::sysinfo::{System, SystemExt};
use hbb_common::{
anyhow::anyhow,
config::PeerConfig,
log,
message_proto::{video_frame, EncodedVideoFrames, Message, VideoCodecState},
message_proto::{
supported_decoding::PreferCodec, video_frame, EncodedVideoFrames, Message,
SupportedDecoding, SupportedEncoding,
},
ResultType,
};
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
use hbb_common::{
config::{Config2, PeerConfig},
lazy_static,
message_proto::video_codec_state::PreferCodec,
};
use hbb_common::{config::Config2, lazy_static};
#[cfg(feature = "hwcodec")]
lazy_static::lazy_static! {
static ref PEER_DECODER_STATES: Arc<Mutex<HashMap<i32, VideoCodecState>>> = Default::default();
static ref PEER_DECODINGS: Arc<Mutex<HashMap<i32, SupportedDecoding>>> = Default::default();
static ref CODEC_NAME: Arc<Mutex<CodecName>> = Arc::new(Mutex::new(CodecName::VP9));
}
const SCORE_VPX: i32 = 90;
#[derive(Debug, Clone)]
pub struct HwEncoderConfig {
pub codec_name: String,
pub name: String,
pub width: usize,
pub height: usize,
pub bitrate: i32,
@ -58,10 +58,6 @@ pub trait EncoderApi {
fn set_bitrate(&mut self, bitrate: u32) -> ResultType<()>;
}
pub struct DecoderCfg {
pub vpx: VpxDecoderConfig,
}
pub struct Encoder {
pub codec: Box<dyn EncoderApi>,
}
@ -81,7 +77,8 @@ impl DerefMut for Encoder {
}
pub struct Decoder {
vpx: VpxDecoder,
vp8: VpxDecoder,
vp9: VpxDecoder,
#[cfg(feature = "hwcodec")]
hw: HwDecoders,
#[cfg(feature = "hwcodec")]
@ -91,10 +88,10 @@ pub struct Decoder {
}
#[derive(Debug, Clone)]
pub enum EncoderUpdate {
State(VideoCodecState),
pub enum EncodingUpdate {
New(SupportedDecoding),
Remove,
DisableHwIfNotExist,
NewOnlyVP9,
}
impl Encoder {
@ -111,7 +108,8 @@ impl Encoder {
codec: Box::new(hw),
}),
Err(e) => {
check_config_process(true);
check_config_process();
*CODEC_NAME.lock().unwrap() = CodecName::VP9;
Err(e)
}
},
@ -120,172 +118,158 @@ impl Encoder {
}
}
// TODO
pub fn update_video_encoder(id: i32, update: EncoderUpdate) {
pub fn update(id: i32, update: EncodingUpdate) {
let mut decodings = PEER_DECODINGS.lock().unwrap();
match update {
EncodingUpdate::New(decoding) => {
decodings.insert(id, decoding);
}
EncodingUpdate::Remove => {
decodings.remove(&id);
}
EncodingUpdate::NewOnlyVP9 => {
decodings.insert(
id,
SupportedDecoding {
ability_vp9: 1,
..Default::default()
},
);
}
}
let vp8_useable = decodings.len() > 0 && decodings.iter().all(|(_, s)| s.ability_vp8 > 0);
#[allow(unused_mut)]
let mut h264_name = None;
#[allow(unused_mut)]
let mut h265_name = None;
#[cfg(feature = "hwcodec")]
{
let mut states = PEER_DECODER_STATES.lock().unwrap();
match update {
EncoderUpdate::State(state) => {
states.insert(id, state);
}
EncoderUpdate::Remove => {
states.remove(&id);
}
EncoderUpdate::DisableHwIfNotExist => {
if !states.contains_key(&id) {
states.insert(id, VideoCodecState::default());
}
}
}
let name = HwEncoder::current_name();
if states.len() > 0 {
if enable_hwcodec_option() {
let best = HwEncoder::best();
let enabled_h264 = best.h264.is_some()
&& states.len() > 0
&& states.iter().all(|(_, s)| s.score_h264 > 0);
let enabled_h265 = best.h265.is_some()
&& states.len() > 0
&& states.iter().all(|(_, s)| s.score_h265 > 0);
// Preference first
let mut preference = PreferCodec::Auto;
let preferences: Vec<_> = states
.iter()
.filter(|(_, s)| {
s.prefer == PreferCodec::VPX.into()
|| s.prefer == PreferCodec::H264.into() && enabled_h264
|| s.prefer == PreferCodec::H265.into() && enabled_h265
})
.map(|(_, s)| s.prefer)
.collect();
if preferences.len() > 0 && preferences.iter().all(|&p| p == preferences[0]) {
preference = preferences[0].enum_value_or(PreferCodec::Auto);
let h264_useable =
decodings.len() > 0 && decodings.iter().all(|(_, s)| s.ability_h264 > 0);
let h265_useable =
decodings.len() > 0 && decodings.iter().all(|(_, s)| s.ability_h265 > 0);
if h264_useable {
h264_name = best.h264.map_or(None, |c| Some(c.name));
}
match preference {
PreferCodec::VPX => *name.lock().unwrap() = None,
PreferCodec::H264 => {
*name.lock().unwrap() = best.h264.map_or(None, |c| Some(c.name))
}
PreferCodec::H265 => {
*name.lock().unwrap() = best.h265.map_or(None, |c| Some(c.name))
}
PreferCodec::Auto => {
// score encoder
let mut score_vpx = SCORE_VPX;
let mut score_h264 = best.h264.as_ref().map_or(0, |c| c.score);
let mut score_h265 = best.h265.as_ref().map_or(0, |c| c.score);
// score decoder
score_vpx += states.iter().map(|s| s.1.score_vpx).sum::<i32>();
if enabled_h264 {
score_h264 += states.iter().map(|s| s.1.score_h264).sum::<i32>();
}
if enabled_h265 {
score_h265 += states.iter().map(|s| s.1.score_h265).sum::<i32>();
}
if enabled_h265 && score_h265 >= score_vpx && score_h265 >= score_h264 {
*name.lock().unwrap() = best.h265.map_or(None, |c| Some(c.name));
} else if enabled_h264
&& score_h264 >= score_vpx
&& score_h264 >= score_h265
{
*name.lock().unwrap() = best.h264.map_or(None, |c| Some(c.name));
} else {
*name.lock().unwrap() = None;
}
}
if h265_useable {
h265_name = best.h265.map_or(None, |c| Some(c.name));
}
log::info!(
"connection count:{}, used preference:{:?}, encoder:{:?}",
states.len(),
preference,
name.lock().unwrap()
)
} else {
*name.lock().unwrap() = None;
}
}
#[cfg(not(feature = "hwcodec"))]
{
let _ = id;
let _ = update;
let mut name = CODEC_NAME.lock().unwrap();
let mut preference = PreferCodec::Auto;
let preferences: Vec<_> = decodings
.iter()
.filter(|(_, s)| {
s.prefer == PreferCodec::VP9.into()
|| s.prefer == PreferCodec::VP8.into() && vp8_useable
|| s.prefer == PreferCodec::H264.into() && h264_name.is_some()
|| s.prefer == PreferCodec::H265.into() && h265_name.is_some()
})
.map(|(_, s)| s.prefer)
.collect();
if preferences.len() > 0 && preferences.iter().all(|&p| p == preferences[0]) {
preference = preferences[0].enum_value_or(PreferCodec::Auto);
}
}
#[inline]
pub fn current_hw_encoder_name() -> Option<String> {
#[cfg(feature = "hwcodec")]
if enable_hwcodec_option() {
return HwEncoder::current_name().lock().unwrap().clone();
} else {
return None;
#[allow(unused_mut)]
let mut auto_codec = CodecName::VP9;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if vp8_useable && System::new_all().total_memory() <= 4 * 1024 * 1024 * 1024 {
// 4 Gb
auto_codec = CodecName::VP8
}
#[cfg(not(feature = "hwcodec"))]
return None;
match preference {
PreferCodec::VP8 => *name = CodecName::VP8,
PreferCodec::VP9 => *name = CodecName::VP9,
PreferCodec::H264 => *name = h264_name.map_or(auto_codec, |c| CodecName::H264(c)),
PreferCodec::H265 => *name = h265_name.map_or(auto_codec, |c| CodecName::H265(c)),
PreferCodec::Auto => *name = auto_codec,
}
log::info!(
"connection count:{}, used preference:{:?}, encoder:{:?}",
decodings.len(),
preference,
*name
)
}
pub fn supported_encoding() -> (bool, bool) {
#[inline]
pub fn negotiated_codec() -> CodecName {
CODEC_NAME.lock().unwrap().clone()
}
pub fn supported_encoding() -> SupportedEncoding {
#[allow(unused_mut)]
let mut encoding = SupportedEncoding {
vp8: true,
..Default::default()
};
#[cfg(feature = "hwcodec")]
if enable_hwcodec_option() {
let best = HwEncoder::best();
(
best.h264.as_ref().map_or(false, |c| c.score > 0),
best.h265.as_ref().map_or(false, |c| c.score > 0),
)
} else {
(false, false)
encoding.h264 = best.h264.is_some();
encoding.h265 = best.h265.is_some();
}
#[cfg(not(feature = "hwcodec"))]
(false, false)
encoding
}
}
impl Decoder {
pub fn video_codec_state(_id: &str) -> VideoCodecState {
pub fn supported_decodings(id_for_perfer: Option<&str>) -> SupportedDecoding {
#[allow(unused_mut)]
let mut decoding = SupportedDecoding {
ability_vp8: 1,
ability_vp9: 1,
prefer: id_for_perfer
.map_or(PreferCodec::Auto, |id| Self::codec_preference(id))
.into(),
..Default::default()
};
#[cfg(feature = "hwcodec")]
if enable_hwcodec_option() {
let best = HwDecoder::best();
return VideoCodecState {
score_vpx: SCORE_VPX,
score_h264: best.h264.map_or(0, |c| c.score),
score_h265: best.h265.map_or(0, |c| c.score),
prefer: Self::codec_preference(_id).into(),
..Default::default()
};
decoding.ability_h264 = if best.h264.is_some() { 1 } else { 0 };
decoding.ability_h265 = if best.h265.is_some() { 1 } else { 0 };
}
#[cfg(feature = "mediacodec")]
if enable_hwcodec_option() {
let score_h264 = if H264_DECODER_SUPPORT.load(std::sync::atomic::Ordering::SeqCst) {
92
} else {
0
};
let score_h265 = if H265_DECODER_SUPPORT.load(std::sync::atomic::Ordering::SeqCst) {
94
} else {
0
};
return VideoCodecState {
score_vpx: SCORE_VPX,
score_h264,
score_h265,
prefer: Self::codec_preference(_id).into(),
..Default::default()
};
}
VideoCodecState {
score_vpx: SCORE_VPX,
..Default::default()
decoding.ability_h264 =
if H264_DECODER_SUPPORT.load(std::sync::atomic::Ordering::SeqCst) {
1
} else {
0
};
decoding.ability_h265 =
if H265_DECODER_SUPPORT.load(std::sync::atomic::Ordering::SeqCst) {
1
} else {
0
};
}
decoding
}
pub fn new(config: DecoderCfg) -> Decoder {
let vpx = VpxDecoder::new(config.vpx).unwrap();
pub fn new() -> Decoder {
let vp8 = VpxDecoder::new(VpxDecoderConfig {
codec: VpxVideoCodecId::VP8,
num_threads: (num_cpus::get() / 2) as _,
})
.unwrap();
let vp9 = VpxDecoder::new(VpxDecoderConfig {
codec: VpxVideoCodecId::VP9,
num_threads: (num_cpus::get() / 2) as _,
})
.unwrap();
Decoder {
vpx,
vp8,
vp9,
#[cfg(feature = "hwcodec")]
hw: if enable_hwcodec_option() {
HwDecoder::new_decoders()
@ -310,8 +294,11 @@ impl Decoder {
rgb: &mut Vec<u8>,
) -> ResultType<bool> {
match frame {
video_frame::Union::Vp8s(vp8s) => {
Decoder::handle_vpxs_video_frame(&mut self.vp8, vp8s, fmt, rgb)
}
video_frame::Union::Vp9s(vp9s) => {
Decoder::handle_vp9s_video_frame(&mut self.vpx, vp9s, fmt, rgb)
Decoder::handle_vpxs_video_frame(&mut self.vp9, vp9s, fmt, rgb)
}
#[cfg(feature = "hwcodec")]
video_frame::Union::H264s(h264s) => {
@ -349,15 +336,15 @@ impl Decoder {
}
}
fn handle_vp9s_video_frame(
fn handle_vpxs_video_frame(
decoder: &mut VpxDecoder,
vp9s: &EncodedVideoFrames,
vpxs: &EncodedVideoFrames,
fmt: (ImageFormat, usize),
rgb: &mut Vec<u8>,
) -> ResultType<bool> {
let mut last_frame = Image::new();
for vp9 in vp9s.frames.iter() {
for frame in decoder.decode(&vp9.data)? {
for vpx in vpxs.frames.iter() {
for frame in decoder.decode(&vpx.data)? {
drop(last_frame);
last_frame = frame;
}
@ -408,14 +395,15 @@ impl Decoder {
return Ok(false);
}
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
fn codec_preference(id: &str) -> PreferCodec {
let codec = PeerConfig::load(id)
.options
.get("codec-preference")
.map_or("".to_owned(), |c| c.to_owned());
if codec == "vp9" {
PreferCodec::VPX
if codec == "vp8" {
PreferCodec::VP8
} else if codec == "vp9" {
PreferCodec::VP9
} else if codec == "h264" {
PreferCodec::H264
} else if codec == "h265" {

View File

@ -7,7 +7,7 @@ use hbb_common::{
anyhow::{anyhow, Context},
bytes::Bytes,
config::HwCodecConfig,
lazy_static, log,
log,
message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame},
ResultType,
};
@ -19,11 +19,6 @@ use hwcodec::{
Quality::{self, *},
RateControl::{self, *},
};
use std::sync::{Arc, Mutex};
lazy_static::lazy_static! {
static ref HW_ENCODER_NAME: Arc<Mutex<Option<String>>> = Default::default();
}
const CFG_KEY_ENCODER: &str = "bestHwEncoders";
const CFG_KEY_DECODER: &str = "bestHwDecoders";
@ -49,7 +44,7 @@ impl EncoderApi for HwEncoder {
match cfg {
EncoderCfg::HW(config) => {
let ctx = EncodeContext {
name: config.codec_name.clone(),
name: config.name.clone(),
width: config.width as _,
height: config.height as _,
pixfmt: DEFAULT_PIXFMT,
@ -60,12 +55,12 @@ impl EncoderApi for HwEncoder {
quality: DEFAULT_HW_QUALITY,
rc: DEFAULT_RC,
};
let format = match Encoder::format_from_name(config.codec_name.clone()) {
let format = match Encoder::format_from_name(config.name.clone()) {
Ok(format) => format,
Err(_) => {
return Err(anyhow!(format!(
"failed to get format from name:{}",
config.codec_name
config.name
)))
}
};
@ -133,10 +128,6 @@ impl HwEncoder {
})
}
pub fn current_name() -> Arc<Mutex<Option<String>>> {
HW_ENCODER_NAME.clone()
}
pub fn encode(&mut self, bgra: &[u8]) -> ResultType<Vec<EncodeFrame>> {
match self.pixfmt {
AVPixelFormat::AV_PIX_FMT_YUV420P => hw::hw_bgra_to_i420(
@ -208,7 +199,7 @@ impl HwDecoder {
}
}
if fail {
check_config_process(true);
check_config_process();
}
HwDecoders { h264, h265 }
}
@ -332,13 +323,11 @@ pub fn check_config() {
log::error!("Failed to serialize codec info");
}
pub fn check_config_process(force_reset: bool) {
pub fn check_config_process() {
use hbb_common::sysinfo::{ProcessExt, System, SystemExt};
std::thread::spawn(move || {
if force_reset {
HwCodecConfig::remove();
}
HwCodecConfig::remove();
if let Ok(exe) = std::env::current_exe() {
if let Some(file_name) = exe.file_name().to_owned() {
let s = System::new_all();

View File

@ -1,4 +1,5 @@
pub use self::vpxcodec::*;
use hbb_common::message_proto::{video_frame, VideoFrame};
cfg_if! {
if #[cfg(quartz)] {
@ -92,3 +93,55 @@ pub fn is_cursor_embedded() -> bool {
pub fn is_cursor_embedded() -> bool {
false
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CodecName {
VP8,
VP9,
H264(String),
H265(String),
}
#[derive(PartialEq, Debug, Clone)]
pub enum CodecFormat {
VP8,
VP9,
H264,
H265,
Unknown,
}
impl From<&VideoFrame> for CodecFormat {
fn from(it: &VideoFrame) -> Self {
match it.union {
Some(video_frame::Union::Vp8s(_)) => CodecFormat::VP8,
Some(video_frame::Union::Vp9s(_)) => CodecFormat::VP9,
Some(video_frame::Union::H264s(_)) => CodecFormat::H264,
Some(video_frame::Union::H265s(_)) => CodecFormat::H265,
_ => CodecFormat::Unknown,
}
}
}
impl From<&CodecName> for CodecFormat {
fn from(value: &CodecName) -> Self {
match value {
CodecName::VP8 => Self::VP8,
CodecName::VP9 => Self::VP9,
CodecName::H264(_) => Self::H264,
CodecName::H265(_) => Self::H265,
}
}
}
impl ToString for CodecFormat {
fn to_string(&self) -> String {
match self {
CodecFormat::VP8 => "VP8".into(),
CodecFormat::VP9 => "VP9".into(),
CodecFormat::H264 => "H264".into(),
CodecFormat::H265 => "H265".into(),
CodecFormat::Unknown => "Unknow".into(),
}
}
}

View File

@ -1,3 +1,4 @@
use crate::CodecFormat;
#[cfg(feature = "hwcodec")]
use hbb_common::anyhow::anyhow;
use hbb_common::{
@ -21,13 +22,6 @@ use webm::mux::{self, Segment, Track, VideoTrack, Writer};
const MIN_SECS: u64 = 1;
#[derive(Debug, Clone, PartialEq)]
pub enum RecordCodecID {
VP9,
H264,
H265,
}
#[derive(Debug, Clone)]
pub struct RecorderContext {
pub server: bool,
@ -36,7 +30,7 @@ pub struct RecorderContext {
pub filename: String,
pub width: usize,
pub height: usize,
pub codec_id: RecordCodecID,
pub format: CodecFormat,
pub tx: Option<Sender<RecordState>>,
}
@ -55,8 +49,9 @@ impl RecorderContext {
}
let file = if self.server { "s" } else { "c" }.to_string()
+ &self.id.clone()
+ &chrono::Local::now().format("_%Y%m%d%H%M%S").to_string()
+ if self.codec_id == RecordCodecID::VP9 {
+ &chrono::Local::now().format("_%Y%m%d%H%M%S_").to_string()
+ &self.format.to_string()
+ if self.format == CodecFormat::VP9 || self.format == CodecFormat::VP8 {
".webm"
} else {
".mp4"
@ -107,8 +102,8 @@ impl DerefMut for Recorder {
impl Recorder {
pub fn new(mut ctx: RecorderContext) -> ResultType<Self> {
ctx.set_filename()?;
let recorder = match ctx.codec_id {
RecordCodecID::VP9 => Recorder {
let recorder = match ctx.format {
CodecFormat::VP8 | CodecFormat::VP9 => Recorder {
inner: Box::new(WebmRecorder::new(ctx.clone())?),
ctx,
},
@ -126,8 +121,8 @@ impl Recorder {
fn change(&mut self, mut ctx: RecorderContext) -> ResultType<()> {
ctx.set_filename()?;
self.inner = match ctx.codec_id {
RecordCodecID::VP9 => Box::new(WebmRecorder::new(ctx.clone())?),
self.inner = match ctx.format {
CodecFormat::VP8 | CodecFormat::VP9 => Box::new(WebmRecorder::new(ctx.clone())?),
#[cfg(feature = "hwcodec")]
_ => Box::new(HwRecorder::new(ctx.clone())?),
#[cfg(not(feature = "hwcodec"))]
@ -148,10 +143,19 @@ impl Recorder {
pub fn write_frame(&mut self, frame: &video_frame::Union) -> ResultType<()> {
match frame {
video_frame::Union::Vp9s(vp9s) => {
if self.ctx.codec_id != RecordCodecID::VP9 {
video_frame::Union::Vp8s(vp8s) => {
if self.ctx.format != CodecFormat::VP8 {
self.change(RecorderContext {
codec_id: RecordCodecID::VP9,
format: CodecFormat::VP8,
..self.ctx.clone()
})?;
}
vp8s.frames.iter().map(|f| self.write_video(f)).count();
}
video_frame::Union::Vp9s(vp9s) => {
if self.ctx.format != CodecFormat::VP9 {
self.change(RecorderContext {
format: CodecFormat::VP9,
..self.ctx.clone()
})?;
}
@ -159,25 +163,25 @@ impl Recorder {
}
#[cfg(feature = "hwcodec")]
video_frame::Union::H264s(h264s) => {
if self.ctx.codec_id != RecordCodecID::H264 {
if self.ctx.format != CodecFormat::H264 {
self.change(RecorderContext {
codec_id: RecordCodecID::H264,
format: CodecFormat::H264,
..self.ctx.clone()
})?;
}
if self.ctx.codec_id == RecordCodecID::H264 {
if self.ctx.format == CodecFormat::H264 {
h264s.frames.iter().map(|f| self.write_video(f)).count();
}
}
#[cfg(feature = "hwcodec")]
video_frame::Union::H265s(h265s) => {
if self.ctx.codec_id != RecordCodecID::H265 {
if self.ctx.format != CodecFormat::H265 {
self.change(RecorderContext {
codec_id: RecordCodecID::H265,
format: CodecFormat::H265,
..self.ctx.clone()
})?;
}
if self.ctx.codec_id == RecordCodecID::H265 {
if self.ctx.format == CodecFormat::H265 {
h265s.frames.iter().map(|f| self.write_video(f)).count();
}
}
@ -221,7 +225,11 @@ impl RecorderApi for WebmRecorder {
ctx.width as _,
ctx.height as _,
None,
mux::VideoCodecId::VP9,
if ctx.format == CodecFormat::VP9 {
mux::VideoCodecId::VP9
} else {
mux::VideoCodecId::VP8
},
);
Ok(WebmRecorder {
vt,
@ -279,7 +287,7 @@ impl RecorderApi for HwRecorder {
filename: ctx.filename.clone(),
width: ctx.width,
height: ctx.height,
is265: ctx.codec_id == RecordCodecID::H265,
is265: ctx.format == CodecFormat::H265,
framerate: crate::hwcodec::DEFAULT_TIME_BASE[1] as _,
})
.map_err(|_| anyhow!("Failed to create hardware muxer"))?;

View File

@ -30,6 +30,7 @@ pub struct VpxEncoder {
ctx: vpx_codec_ctx_t,
width: usize,
height: usize,
id: VpxVideoCodecId,
}
pub struct VpxDecoder {
@ -97,15 +98,10 @@ impl EncoderApi for VpxEncoder {
{
match cfg {
crate::codec::EncoderCfg::VPX(config) => {
let i;
if cfg!(feature = "VP8") {
i = match config.codec {
VpxVideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_cx()),
VpxVideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_cx()),
};
} else {
i = call_vpx_ptr!(vpx_codec_vp9_cx());
}
let i = match config.codec {
VpxVideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_cx()),
VpxVideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_cx()),
};
let mut c = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
call_vpx!(vpx_codec_enc_config_default(i, &mut c, 0));
@ -187,12 +183,17 @@ impl EncoderApi for VpxEncoder {
VP9E_SET_TILE_COLUMNS as _,
4 as c_int
));
} else if config.codec == VpxVideoCodecId::VP8 {
// https://github.com/webmproject/libvpx/blob/972149cafeb71d6f08df89e91a0130d6a38c4b15/vpx/vp8cx.h#L172
// https://groups.google.com/a/webmproject.org/g/webm-discuss/c/DJhSrmfQ61M
call_vpx!(vpx_codec_control_(&mut ctx, VP8E_SET_CPUUSED as _, 12,));
}
Ok(Self {
ctx,
width: config.width as _,
height: config.height as _,
id: config.codec,
})
}
_ => Err(anyhow!("encoder type mismatch")),
@ -213,7 +214,7 @@ impl EncoderApi for VpxEncoder {
// to-do: flush periodically, e.g. 1 second
if frames.len() > 0 {
Ok(VpxEncoder::create_msg(frames))
Ok(VpxEncoder::create_msg(self.id, frames))
} else {
Err(anyhow!("no valid frame"))
}
@ -280,13 +281,17 @@ impl VpxEncoder {
}
#[inline]
fn create_msg(vp9s: Vec<EncodedVideoFrame>) -> Message {
pub fn create_msg(codec_id: VpxVideoCodecId, frames: Vec<EncodedVideoFrame>) -> Message {
let mut msg_out = Message::new();
let mut vf = VideoFrame::new();
vf.set_vp9s(EncodedVideoFrames {
frames: vp9s.into(),
let vpxs = EncodedVideoFrames {
frames: frames.into(),
..Default::default()
});
};
match codec_id {
VpxVideoCodecId::VP8 => vf.set_vp8s(vpxs),
VpxVideoCodecId::VP9 => vf.set_vp9s(vpxs),
}
msg_out.set_video_frame(vf);
msg_out
}
@ -382,15 +387,10 @@ impl VpxDecoder {
pub fn new(config: VpxDecoderConfig) -> Result<Self> {
// This is sound because `vpx_codec_ctx` is a repr(C) struct without any field that can
// cause UB if uninitialized.
let i;
if cfg!(feature = "VP8") {
i = match config.codec {
VpxVideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_dx()),
VpxVideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_dx()),
};
} else {
i = call_vpx_ptr!(vpx_codec_vp9_dx());
}
let i = match config.codec {
VpxVideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_dx()),
VpxVideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_dx()),
};
let mut ctx = Default::default();
let cfg = vpx_codec_dec_cfg_t {
threads: if config.num_threads == 0 {

View File

@ -0,0 +1,5 @@
#%PAM-1.0
@include common-auth
@include common-account
@include common-session
@include common-password

5
res/pam.d/rustdesk.suse Normal file
View File

@ -0,0 +1,5 @@
#%PAM-1.0
auth include common-auth
account include common-account
session include common-session
password include common-password

130
res/startwm.sh Executable file
View File

@ -0,0 +1,130 @@
#!/usr/bin/env bash
# This script is derived from https://github.com/neutrinolabs/xrdp/sesman/startwm.sh.
#
# This script is an example. You might need to edit this script
# depending on your distro if it doesn't work for you.
#
# Uncomment the following line for debug:
# exec xterm
# Execution sequence for interactive login shell - pseudocode
#
# IF /etc/profile is readable THEN
# execute ~/.bash_profile
# END IF
# IF ~/.bash_profile is readable THEN
# execute ~/.bash_profile
# ELSE
# IF ~/.bash_login is readable THEN
# execute ~/.bash_login
# ELSE
# IF ~/.profile is readable THEN
# execute ~/.profile
# END IF
# END IF
# END IF
pre_start()
{
if [ -r /etc/profile ]; then
. /etc/profile
fi
if [ -r ~/.bash_profile ]; then
. ~/.bash_profile
else
if [ -r ~/.bash_login ]; then
. ~/.bash_login
else
if [ -r ~/.profile ]; then
. ~/.profile
fi
fi
fi
return 0
}
# When loging out from the interactive shell, the execution sequence is:
#
# IF ~/.bash_logout exists THEN
# execute ~/.bash_logout
# END IF
post_start()
{
if [ -r ~/.bash_logout ]; then
. ~/.bash_logout
fi
return 0
}
#start the window manager
wm_start()
{
if [ -r /etc/default/locale ]; then
. /etc/default/locale
export LANG LANGUAGE
fi
# debian
if [ -r /etc/X11/Xsession ]; then
pre_start
. /etc/X11/Xsession
post_start
exit 0
fi
# alpine
# Don't use /etc/X11/xinit/Xsession - it doesn't work
if [ -f /etc/alpine-release ]; then
if [ -f /etc/X11/xinit/xinitrc ]; then
pre_start
/etc/X11/xinit/xinitrc
post_start
else
echo "** xinit package isn't installed" >&2
exit 1
fi
fi
# el
if [ -r /etc/X11/xinit/Xsession ]; then
pre_start
. /etc/X11/xinit/Xsession
post_start
exit 0
fi
# suse
if [ -r /etc/X11/xdm/Xsession ]; then
# since the following script run a user login shell,
# do not execute the pseudo login shell scripts
. /etc/X11/xdm/Xsession
exit 0
elif [ -r /usr/etc/X11/xdm/Xsession ]; then
. /usr/etc/X11/xdm/Xsession
exit 0
fi
pre_start
xterm
post_start
}
#. /etc/environment
#export PATH=$PATH
#export LANG=$LANG
# change PATH to be what your environment needs usually what is in
# /etc/environment
#PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games"
#export PATH=$PATH
# for PATH and LANG from /etc/environment
# pam will auto process the environment file if /etc/pam.d/xrdp-sesman
# includes
# auth required pam_env.so readenv=1
wm_start
exit 1

30
res/xorg.conf Normal file
View File

@ -0,0 +1,30 @@
Section "Monitor"
Identifier "Dummy Monitor"
# Default HorizSync 31.50 - 48.00 kHz
HorizSync 5.0 - 150.0
# Default VertRefresh 50.00 - 70.00 Hz
VertRefresh 5.0 - 100.0
# Taken from https://www.xpra.org/xorg.conf
Modeline "1920x1080" 23.53 1920 1952 2040 2072 1080 1106 1108 1135
Modeline "1280x720" 27.41 1280 1312 1416 1448 720 737 740 757
EndSection
Section "Device"
Identifier "Dummy VideoCard"
Driver "dummy"
# Default VideoRam 4096
# (1920 * 1080 * 4) / 1024 = 8100
VideoRam 8100
EndSection
Section "Screen"
Identifier "Dummy Screen"
Device "Dummy VideoCard"
Monitor "Dummy Monitor"
SubSectionSub "Display"
Depth 24
Modes "1920x1080" "1280x720"
EndSubSection
EndSection

View File

@ -48,18 +48,24 @@ impl Interface for Session {
}
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str) {
if msgtype == "input-password" {
self.sender
.send(Data::Login((self.password.clone(), true)))
.ok();
} else if msgtype == "re-input-password" {
log::error!("{}: {}", title, text);
let pass = rpassword::prompt_password("Enter password: ").unwrap();
self.sender.send(Data::Login((pass, true))).ok();
} else if msgtype.contains("error") {
log::error!("{}: {}: {}", msgtype, title, text);
} else {
log::info!("{}: {}: {}", msgtype, title, text);
match msgtype {
"input-password" => {
self.sender
.send(Data::Login((self.password.clone(), true)))
.ok();
}
"re-input-password" => {
log::error!("{}: {}", title, text);
let password = rpassword::prompt_password("Enter password: ").unwrap();
let login_data = Data::Login((password, true));
self.sender.send(login_data).ok();
}
msg if msg.contains("error") => {
log::error!("{}: {}: {}", msgtype, title, text);
}
_ => {
log::info!("{}: {}: {}", msgtype, title, text);
}
}
}
@ -79,8 +85,8 @@ impl Interface for Session {
handle_hash(self.lc.clone(), &pass, hash, self, peer).await;
}
async fn handle_login_from_ui(&mut self, password: String, remember: bool, peer: &mut Stream) {
handle_login_from_ui(self.lc.clone(), password, remember, peer).await;
async fn handle_login_from_ui(&mut self, os_username: String, os_password: String, password: String, remember: bool, peer: &mut Stream) {
handle_login_from_ui(self.lc.clone(), os_username, os_password, password, remember, peer).await;
}
async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream) {

View File

@ -3,7 +3,10 @@ use std::{
net::SocketAddr,
ops::Deref,
str::FromStr,
sync::{mpsc, Arc, Mutex, RwLock},
sync::{
atomic::{AtomicUsize, Ordering},
mpsc, Arc, Mutex, RwLock,
},
};
pub use async_trait::async_trait;
@ -44,9 +47,9 @@ use hbb_common::{
};
pub use helper::*;
use scrap::{
codec::{Decoder, DecoderCfg},
codec::Decoder,
record::{Recorder, RecorderContext},
ImageFormat, VpxDecoderConfig, VpxVideoCodecId,
ImageFormat,
};
use crate::{
@ -917,12 +920,7 @@ impl VideoHandler {
/// Create a new video handler.
pub fn new() -> Self {
VideoHandler {
decoder: Decoder::new(DecoderCfg {
vpx: VpxDecoderConfig {
codec: VpxVideoCodecId::VP9,
num_threads: (num_cpus::get() / 2) as _,
},
}),
decoder: Decoder::new(),
rgb: Default::default(),
recorder: Default::default(),
record: false,
@ -954,12 +952,7 @@ impl VideoHandler {
/// Reset the decoder.
pub fn reset(&mut self) {
self.decoder = Decoder::new(DecoderCfg {
vpx: VpxDecoderConfig {
codec: VpxVideoCodecId::VP9,
num_threads: 1,
},
});
self.decoder = Decoder::new();
}
/// Start or stop screen record.
@ -973,7 +966,7 @@ impl VideoHandler {
filename: "".to_owned(),
width: w as _,
height: h as _,
codec_id: scrap::record::RecordCodecID::VP9,
format: scrap::CodecFormat::VP9,
tx: None,
})
.map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))));
@ -999,7 +992,7 @@ pub struct LoginConfigHandler {
pub conn_id: i32,
features: Option<Features>,
session_id: u64,
pub supported_encoding: Option<(bool, bool)>,
pub supported_encoding: SupportedEncoding,
pub restarting_remote_device: bool,
pub force_relay: bool,
pub direct: Option<bool>,
@ -1047,7 +1040,7 @@ impl LoginConfigHandler {
self.remember = !config.password.is_empty();
self.config = config;
self.session_id = rand::random();
self.supported_encoding = None;
self.supported_encoding = Default::default();
self.restarting_remote_device = false;
self.force_relay = !self.get_option("force-always-relay").is_empty() || force_relay;
self.direct = None;
@ -1301,11 +1294,12 @@ impl LoginConfigHandler {
config.custom_image_quality[0]
};
msg.custom_image_quality = quality << 8;
#[cfg(feature = "flutter")]
if let Some(custom_fps) = self.options.get("custom-fps") {
msg.custom_fps = custom_fps.parse().unwrap_or(30);
}
n += 1;
}
if let Some(custom_fps) = self.options.get("custom-fps") {
msg.custom_fps = custom_fps.parse().unwrap_or(30);
}
let view_only = self.get_toggle_option("view-only");
if view_only {
msg.disable_keyboard = BoolOption::Yes.into();
@ -1331,8 +1325,8 @@ impl LoginConfigHandler {
msg.disable_clipboard = BoolOption::Yes.into();
n += 1;
}
let state = Decoder::video_codec_state(&self.id);
msg.video_codec_state = hbb_common::protobuf::MessageField::some(state);
msg.supported_decoding =
hbb_common::protobuf::MessageField::some(Decoder::supported_decodings(Some(&self.id)));
n += 1;
if n > 0 {
@ -1565,10 +1559,7 @@ impl LoginConfigHandler {
self.conn_id = pi.conn_id;
// no matter if change, for update file time
self.save_config(config);
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
{
self.supported_encoding = Some((pi.encoding.h264, pi.encoding.h265));
}
self.supported_encoding = pi.encoding.clone().unwrap_or_default();
}
pub fn get_remote_dir(&self) -> String {
@ -1591,7 +1582,12 @@ impl LoginConfigHandler {
}
/// Create a [`Message`] for login.
fn create_login_msg(&self, password: Vec<u8>) -> Message {
fn create_login_msg(
&self,
os_username: String,
os_password: String,
password: Vec<u8>,
) -> Message {
#[cfg(any(target_os = "android", target_os = "ios"))]
let my_id = Config::get_id_or(crate::common::DEVICE_ID.lock().unwrap().clone());
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@ -1604,6 +1600,12 @@ impl LoginConfigHandler {
option: self.get_option_message(true).into(),
session_id: self.session_id,
version: crate::VERSION.to_string(),
os_login: Some(OSLogin {
username: os_username,
password: os_password,
..Default::default()
})
.into(),
..Default::default()
};
match self.conn_type {
@ -1626,10 +1628,10 @@ impl LoginConfigHandler {
}
pub fn change_prefer_codec(&self) -> Message {
let state = scrap::codec::Decoder::video_codec_state(&self.id);
let decoding = scrap::codec::Decoder::supported_decodings(Some(&self.id));
let mut misc = Misc::new();
misc.set_option(OptionMessage {
video_codec_state: hbb_common::protobuf::MessageField::some(state),
supported_decoding: hbb_common::protobuf::MessageField::some(decoding),
..Default::default()
});
let mut msg_out = Message::new();
@ -1679,7 +1681,12 @@ pub type MediaSender = mpsc::Sender<MediaData>;
/// * `video_callback` - The callback for video frame. Being called when a video frame is ready.
pub fn start_video_audio_threads<F>(
video_callback: F,
) -> (MediaSender, MediaSender, Arc<ArrayQueue<VideoFrame>>)
) -> (
MediaSender,
MediaSender,
Arc<ArrayQueue<VideoFrame>>,
Arc<AtomicUsize>,
)
where
F: 'static + FnMut(&mut Vec<u8>) + Send,
{
@ -1687,21 +1694,48 @@ where
let video_queue = Arc::new(ArrayQueue::<VideoFrame>::new(VIDEO_QUEUE_SIZE));
let video_queue_cloned = video_queue.clone();
let mut video_callback = video_callback;
let mut duration = std::time::Duration::ZERO;
let mut count = 0;
let fps = Arc::new(AtomicUsize::new(0));
let decode_fps = fps.clone();
let mut skip_beginning = 0;
std::thread::spawn(move || {
let mut video_handler = VideoHandler::new();
loop {
if let Ok(data) = video_receiver.recv() {
match data {
MediaData::VideoFrame(vf) => {
if let Ok(true) = video_handler.handle_frame(*vf) {
MediaData::VideoFrame(_) | MediaData::VideoQueue => {
let vf = if let MediaData::VideoFrame(vf) = data {
*vf
} else {
if let Some(vf) = video_queue.pop() {
vf
} else {
continue;
}
};
let start = std::time::Instant::now();
if let Ok(true) = video_handler.handle_frame(vf) {
video_callback(&mut video_handler.rgb);
}
}
MediaData::VideoQueue => {
if let Some(vf) = video_queue.pop() {
if let Ok(true) = video_handler.handle_frame(vf) {
video_callback(&mut video_handler.rgb);
// fps calculation
// The first frame will be very slow
if skip_beginning < 5 {
skip_beginning += 1;
continue;
}
duration += start.elapsed();
count += 1;
if count % 10 == 0 {
fps.store(
(count * 1000 / duration.as_millis()) as usize,
Ordering::Relaxed,
);
}
// Clear to get real-time fps
if count > 300 {
count = 0;
duration = Duration::ZERO;
}
}
}
@ -1720,7 +1754,7 @@ where
log::info!("Video decoder loop exits");
});
let audio_sender = start_audio_thread();
return (video_sender, audio_sender, video_queue_cloned);
return (video_sender, audio_sender, video_queue_cloned, decode_fps);
}
/// Start an audio thread
@ -1901,6 +1935,71 @@ fn _input_os_password(p: String, activate: bool, interface: impl Interface) {
interface.send(Data::Message(msg_out));
}
#[derive(Copy, Clone)]
struct LoginErrorMsgBox {
msgtype: &'static str,
title: &'static str,
text: &'static str,
link: &'static str,
try_again: bool,
}
lazy_static::lazy_static! {
static ref LOGIN_ERROR_MAP: Arc<HashMap<&'static str, LoginErrorMsgBox>> = {
use hbb_common::config::LINK_HEADLESS_LINUX_SUPPORT;
let map = HashMap::from([(crate::server::LOGIN_MSG_DESKTOP_SESSION_NOT_READY, LoginErrorMsgBox{
msgtype: "session-login",
title: "",
text: "",
link: "",
try_again: true,
}), (crate::server::LOGIN_MSG_DESKTOP_XSESSION_FAILED, LoginErrorMsgBox{
msgtype: "session-re-login",
title: "",
text: "",
link: "",
try_again: true,
}), (crate::server::LOGIN_MSG_DESKTOP_SESSION_ANOTHER_USER, LoginErrorMsgBox{
msgtype: "info-nocancel",
title: "another_user_login_title_tip",
text: "another_user_login_text_tip",
link: "",
try_again: false,
}), (crate::server::LOGIN_MSG_DESKTOP_XORG_NOT_FOUND, LoginErrorMsgBox{
msgtype: "info-nocancel",
title: "xorg_not_found_title_tip",
text: "xorg_not_found_text_tip",
link: LINK_HEADLESS_LINUX_SUPPORT,
try_again: true,
}), (crate::server::LOGIN_MSG_DESKTOP_NO_DESKTOP, LoginErrorMsgBox{
msgtype: "info-nocancel",
title: "no_desktop_title_tip",
text: "no_desktop_text_tip",
link: LINK_HEADLESS_LINUX_SUPPORT,
try_again: true,
}), (crate::server::LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_EMPTY, LoginErrorMsgBox{
msgtype: "session-login-password",
title: "",
text: "",
link: "",
try_again: true,
}), (crate::server::LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_WRONG, LoginErrorMsgBox{
msgtype: "session-login-re-password",
title: "",
text: "",
link: "",
try_again: true,
}), (crate::server::LOGIN_MSG_NO_PASSWORD_ACCESS, LoginErrorMsgBox{
msgtype: "wait-remote-accept-nook",
title: "Prompt",
text: "Please wait for the remote side to accept your session request...",
link: "",
try_again: true,
})]);
Arc::new(map)
};
}
/// Handle login error.
/// Return true if the password is wrong, return false if there's an actual error.
pub fn handle_login_error(
@ -1908,19 +2007,27 @@ pub fn handle_login_error(
err: &str,
interface: &impl Interface,
) -> bool {
if err == "Wrong Password" {
if err == crate::server::LOGIN_MSG_PASSWORD_EMPTY {
lc.write().unwrap().password = Default::default();
interface.msgbox("input-password", "Password Required", "", "");
true
} else if err == crate::server::LOGIN_MSG_PASSWORD_WRONG {
lc.write().unwrap().password = Default::default();
interface.msgbox("re-input-password", err, "Do you want to enter again?", "");
true
} else if err == "No Password Access" {
lc.write().unwrap().password = Default::default();
interface.msgbox(
"wait-remote-accept-nook",
"Prompt",
"Please wait for the remote side to accept your session request...",
"",
);
true
} else if LOGIN_ERROR_MAP.contains_key(err) {
if let Some(msgbox_info) = LOGIN_ERROR_MAP.get(err) {
interface.msgbox(
msgbox_info.msgtype,
msgbox_info.title,
msgbox_info.text,
msgbox_info.link,
);
msgbox_info.try_again
} else {
// unreachable!
false
}
} else {
if err.contains(SCRAP_X11_REQUIRED) {
interface.msgbox("error", "Login Error", err, SCRAP_X11_REF_URL);
@ -1968,16 +2075,21 @@ pub async fn handle_hash(
if password.is_empty() {
password = lc.read().unwrap().config.password.clone();
}
if password.is_empty() {
let password = if password.is_empty() {
// login without password, the remote side can click accept
send_login(lc.clone(), Vec::new(), peer).await;
interface.msgbox("input-password", "Password Required", "", "");
Vec::new()
} else {
let mut hasher = Sha256::new();
hasher.update(&password);
hasher.update(&hash.challenge);
send_login(lc.clone(), hasher.finalize()[..].into(), peer).await;
}
hasher.finalize()[..].into()
};
let os_username = lc.read().unwrap().get_option("os-username");
let os_password = lc.read().unwrap().get_option("os-password");
send_login(lc.clone(), os_username, os_password, password, peer).await;
lc.write().unwrap().hash = hash;
}
@ -1986,10 +2098,21 @@ pub async fn handle_hash(
/// # Arguments
///
/// * `lc` - Login config.
/// * `os_username` - OS username.
/// * `os_password` - OS password.
/// * `password` - Password.
/// * `peer` - [`Stream`] for communicating with peer.
async fn send_login(lc: Arc<RwLock<LoginConfigHandler>>, password: Vec<u8>, peer: &mut Stream) {
let msg_out = lc.read().unwrap().create_login_msg(password);
async fn send_login(
lc: Arc<RwLock<LoginConfigHandler>>,
os_username: String,
os_password: String,
password: Vec<u8>,
peer: &mut Stream,
) {
let msg_out = lc
.read()
.unwrap()
.create_login_msg(os_username, os_password, password);
allow_err!(peer.send(&msg_out).await);
}
@ -1998,25 +2121,40 @@ async fn send_login(lc: Arc<RwLock<LoginConfigHandler>>, password: Vec<u8>, peer
/// # Arguments
///
/// * `lc` - Login config.
/// * `os_username` - OS username.
/// * `os_password` - OS password.
/// * `password` - Password.
/// * `remember` - Whether to remember password.
/// * `peer` - [`Stream`] for communicating with peer.
pub async fn handle_login_from_ui(
lc: Arc<RwLock<LoginConfigHandler>>,
os_username: String,
os_password: String,
password: String,
remember: bool,
peer: &mut Stream,
) {
let mut hasher = Sha256::new();
hasher.update(password);
hasher.update(&lc.read().unwrap().hash.salt);
let res = hasher.finalize();
lc.write().unwrap().remember = remember;
lc.write().unwrap().password = res[..].into();
let mut hash_password = if password.is_empty() {
let mut password2 = lc.read().unwrap().password.clone();
if password2.is_empty() {
password2 = lc.read().unwrap().config.password.clone();
}
password2
} else {
let mut hasher = Sha256::new();
hasher.update(password);
hasher.update(&lc.read().unwrap().hash.salt);
let res = hasher.finalize();
lc.write().unwrap().remember = remember;
lc.write().unwrap().password = res[..].into();
res[..].into()
};
let mut hasher2 = Sha256::new();
hasher2.update(&res[..]);
hasher2.update(&hash_password[..]);
hasher2.update(&lc.read().unwrap().hash.challenge);
send_login(lc.clone(), hasher2.finalize()[..].into(), peer).await;
hash_password = hasher2.finalize()[..].to_vec();
send_login(lc.clone(), os_username, os_password, hash_password, peer).await;
}
async fn send_switch_login_request(
@ -2030,7 +2168,7 @@ async fn send_switch_login_request(
lr: hbb_common::protobuf::MessageField::some(
lc.read()
.unwrap()
.create_login_msg(vec![])
.create_login_msg("".to_owned(), "".to_owned(), vec![])
.login_request()
.to_owned(),
),
@ -2051,7 +2189,14 @@ pub trait Interface: Send + Clone + 'static + Sized {
self.msgbox("error", "Error", err, "");
}
async fn handle_hash(&mut self, pass: &str, hash: Hash, peer: &mut Stream);
async fn handle_login_from_ui(&mut self, password: String, remember: bool, peer: &mut Stream);
async fn handle_login_from_ui(
&mut self,
os_username: String,
os_password: String,
password: String,
remember: bool,
peer: &mut Stream,
);
async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream);
fn get_login_config_handler(&self) -> Arc<RwLock<LoginConfigHandler>>;
@ -2071,7 +2216,7 @@ pub trait Interface: Send + Clone + 'static + Sized {
#[derive(Clone)]
pub enum Data {
Close,
Login((String, bool)),
Login((String, String, String, bool)),
Message(Message),
SendFiles((i32, String, String, i32, bool, bool)),
RemoveDirAll((i32, String, bool, bool)),

View File

@ -1,37 +1,8 @@
use hbb_common::{
get_time,
message_proto::{video_frame, Message, VideoFrame, VoiceCallRequest, VoiceCallResponse},
message_proto::{Message, VoiceCallRequest, VoiceCallResponse},
};
#[derive(PartialEq, Debug, Clone)]
pub enum CodecFormat {
VP9,
H264,
H265,
Unknown,
}
impl From<&VideoFrame> for CodecFormat {
fn from(it: &VideoFrame) -> Self {
match it.union {
Some(video_frame::Union::Vp9s(_)) => CodecFormat::VP9,
Some(video_frame::Union::H264s(_)) => CodecFormat::H264,
Some(video_frame::Union::H265s(_)) => CodecFormat::H265,
_ => CodecFormat::Unknown,
}
}
}
impl ToString for CodecFormat {
fn to_string(&self) -> String {
match self {
CodecFormat::VP9 => "VP9".into(),
CodecFormat::H264 => "H264".into(),
CodecFormat::H265 => "H265".into(),
CodecFormat::Unknown => "Unknow".into(),
}
}
}
use scrap::CodecFormat;
#[derive(Debug, Default)]
pub struct QualityStatus {

View File

@ -29,10 +29,10 @@ use hbb_common::tokio::{
time::{self, Duration, Instant, Interval},
};
use hbb_common::{allow_err, fs, get_time, log, message_proto::*, Stream};
use scrap::CodecFormat;
use crate::client::{
new_voice_call_request, Client, CodecFormat, MediaData, MediaSender, QualityStatus, MILLI1,
SEC30,
new_voice_call_request, Client, MediaData, MediaSender, QualityStatus, MILLI1, SEC30,
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::common::{self, update_clipboard};
@ -65,6 +65,8 @@ pub struct Remote<T: InvokeUiSession> {
frame_count: Arc<AtomicUsize>,
video_format: CodecFormat,
elevation_requested: bool,
fps_control: FpsControl,
decode_fps: Arc<AtomicUsize>,
}
impl<T: InvokeUiSession> Remote<T> {
@ -76,6 +78,7 @@ impl<T: InvokeUiSession> Remote<T> {
receiver: mpsc::UnboundedReceiver<Data>,
sender: mpsc::UnboundedSender<Data>,
frame_count: Arc<AtomicUsize>,
decode_fps: Arc<AtomicUsize>,
) -> Self {
Self {
handler,
@ -100,6 +103,8 @@ impl<T: InvokeUiSession> Remote<T> {
stop_voice_call_sender: None,
voice_call_request_timestamp: None,
elevation_requested: false,
fps_control: Default::default(),
decode_fps,
}
}
@ -147,6 +152,7 @@ impl<T: InvokeUiSession> Remote<T> {
let mut rx_clip_client = rx_clip_client_lock.lock().await;
let mut status_timer = time::interval(Duration::new(1, 0));
let mut fps_instant = Instant::now();
loop {
tokio::select! {
@ -224,9 +230,18 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
_ = status_timer.tick() => {
let speed = self.data_count.swap(0, Ordering::Relaxed);
self.fps_control();
let elapsed = fps_instant.elapsed().as_millis();
if elapsed < 1000 {
continue;
}
fps_instant = Instant::now();
let mut speed = self.data_count.swap(0, Ordering::Relaxed);
speed = speed * 1000 / elapsed as usize;
let speed = format!("{:.2}kB/s", speed as f32 / 1024 as f32);
let fps = self.frame_count.swap(0, Ordering::Relaxed) as _;
let mut fps = self.frame_count.swap(0, Ordering::Relaxed) as _;
// Correcting the inaccuracy of status_timer
fps = fps * 1000 / elapsed as i32;
self.handler.update_quality_status(QualityStatus {
speed:Some(speed),
fps:Some(fps),
@ -360,9 +375,9 @@ impl<T: InvokeUiSession> Remote<T> {
allow_err!(peer.send(&msg).await);
return false;
}
Data::Login((password, remember)) => {
Data::Login((os_username, os_password, password, remember)) => {
self.handler
.handle_login_from_ui(password, remember, peer)
.handle_login_from_ui(os_username, os_password, password, remember, peer)
.await;
}
Data::ToggleClipboardFile => {
@ -817,16 +832,62 @@ impl<T: InvokeUiSession> Remote<T> {
}
fn contains_key_frame(vf: &VideoFrame) -> bool {
use video_frame::Union::*;
match &vf.union {
Some(vf) => match vf {
video_frame::Union::Vp9s(f) => f.frames.iter().any(|e| e.key),
video_frame::Union::H264s(f) => f.frames.iter().any(|e| e.key),
video_frame::Union::H265s(f) => f.frames.iter().any(|e| e.key),
Vp8s(f) | Vp9s(f) | H264s(f) | H265s(f) => f.frames.iter().any(|e| e.key),
_ => false,
},
None => false,
}
}
#[inline]
fn fps_control(&mut self) {
let len = self.video_queue.len();
let ctl = &mut self.fps_control;
// Current full speed decoding fps
let decode_fps = self.decode_fps.load(std::sync::atomic::Ordering::Relaxed);
// 500ms
let debounce = if decode_fps > 10 { decode_fps / 2 } else { 5 };
if len < debounce || decode_fps == 0 {
return;
}
let mut refresh = false;
// First setting , or the length of the queue still increases after setting, or exceed the size of the last setting again
if ctl.set_times < 10 // enough
&& (ctl.set_times == 0
|| (len > ctl.last_queue_size && ctl.last_set_instant.elapsed().as_secs() > 30))
{
// 80% fps to ensure decoding is faster than encoding
let mut custom_fps = decode_fps as i32 * 4 / 5;
if custom_fps < 1 {
custom_fps = 1;
}
// send custom fps
let mut misc = Misc::new();
misc.set_option(OptionMessage {
custom_fps,
..Default::default()
});
let mut msg = Message::new();
msg.set_misc(misc);
self.sender.send(Data::Message(msg)).ok();
ctl.last_queue_size = len;
ctl.set_times += 1;
ctl.last_set_instant = Instant::now();
refresh = true;
}
// send refresh
if ctl.refresh_times < 10 // enough
&& (refresh
|| (len > self.video_queue.len() / 2
&& ctl.last_refresh_instant.elapsed().as_secs() > 30))
{
self.handler.refresh_video();
ctl.refresh_times += 1;
ctl.last_refresh_instant = Instant::now();
}
}
async fn handle_msg_from_peer(&mut self, data: &[u8], peer: &mut Stream) -> bool {
if let Ok(msg_in) = Message::parse_from_bytes(&data) {
@ -1256,6 +1317,7 @@ impl<T: InvokeUiSession> Remote<T> {
},
Some(message::Union::MessageBox(msgbox)) => {
let mut link = msgbox.link;
// Links from the remote side must be verified.
if !link.starts_with("rustdesk://") {
if let Some(v) = hbb_common::config::HELPER_URL.get(&link as &str) {
link = v.to_string();
@ -1489,3 +1551,23 @@ impl RemoveJob {
}
}
}
struct FpsControl {
last_queue_size: usize,
set_times: usize,
refresh_times: usize,
last_set_instant: Instant,
last_refresh_instant: Instant,
}
impl Default for FpsControl {
fn default() -> Self {
Self {
last_queue_size: Default::default(),
set_times: Default::default(),
refresh_times: Default::default(),
last_set_instant: Instant::now(),
last_refresh_instant: Instant::now(),
}
}
}

View File

@ -818,3 +818,21 @@ pub async fn get_key(sync: bool) -> String {
}
key
}
pub fn is_peer_version_ge(v: &str) -> bool {
#[cfg(not(any(feature = "flutter", feature = "cli")))]
if let Some(session) = crate::ui::CUR_SESSION.lock().unwrap().as_ref() {
return session.get_peer_version() >= hbb_common::get_version_number(v);
}
#[cfg(feature = "flutter")]
if let Some(session) = crate::flutter::SESSIONS
.read()
.unwrap()
.get(&*crate::flutter::CUR_SESSION_ID.read().unwrap())
{
return session.get_peer_version() >= hbb_common::get_version_number(v);
}
false
}

View File

@ -1,5 +1,8 @@
#[cfg(not(debug_assertions))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::platform::breakdown_callback;
use hbb_common::log;
#[cfg(not(debug_assertions))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::platform::register_breakdown_handler;
@ -39,6 +42,7 @@ pub fn core_main() -> Option<Vec<String>> {
}
i += 1;
}
#[cfg(not(debug_assertions))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
register_breakdown_handler(breakdown_callback);
#[cfg(target_os = "linux")]
@ -224,6 +228,11 @@ pub fn core_main() -> Option<Vec<String>> {
// call connection manager to establish connections
// meanwhile, return true to call flutter window to show control panel
crate::ui_interface::start_option_status_sync();
} else if args[0] == "--cm-no-ui" {
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
crate::flutter::connection_manager::start_cm_no_ui();
return None;
}
}
//_async_logger_holder.map(|x| x.flush());

View File

@ -218,7 +218,7 @@ impl VideoRenderer {
}
pub fn on_rgba(&self, rgba: &Vec<u8>) {
if self.ptr == usize::default() {
if self.ptr == usize::default() || self.width == 0 || self.height == 0 {
return;
}
if let Some(func) = &self.on_rgba_func {
@ -815,8 +815,20 @@ pub mod connection_manager {
}
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn start_cm_no_ui() {
start_listen_ipc(false);
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn start_listen_ipc_thread() {
start_listen_ipc(true);
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn start_listen_ipc(new_thread: bool) {
use crate::ui_cm_interface::{start_ipc, ConnectionManager};
#[cfg(target_os = "linux")]
@ -825,7 +837,11 @@ pub mod connection_manager {
let cm = ConnectionManager {
ui_handler: FlutterHandler {},
};
std::thread::spawn(move || start_ipc(cm));
if new_thread {
std::thread::spawn(move || start_ipc(cm));
} else {
start_ipc(cm);
}
}
#[cfg(target_os = "android")]

View File

@ -136,9 +136,15 @@ pub fn session_get_option(id: String, arg: String) -> Option<String> {
}
}
pub fn session_login(id: String, password: String, remember: bool) {
pub fn session_login(
id: String,
os_username: String,
os_password: String,
password: String,
remember: bool,
) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.login(password, remember);
session.login(os_username, os_password, password, remember);
}
}
@ -326,13 +332,13 @@ pub fn session_switch_display(id: String, value: i32) {
pub fn session_handle_flutter_key_event(
id: String,
name: String,
keycode: i32,
scancode: i32,
platform_code: i32,
position_code: i32,
lock_modes: i32,
down_or_up: bool,
) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.handle_flutter_key_event(&name, keycode, scancode, lock_modes, down_or_up);
session.handle_flutter_key_event(&name, platform_code, position_code, lock_modes, down_or_up);
}
}
@ -969,6 +975,13 @@ pub fn main_has_hwcodec() -> SyncReturn<bool> {
SyncReturn(has_hwcodec())
}
pub fn main_supported_hwdecodings() -> SyncReturn<String> {
let decoding = supported_hwdecodings();
let msg = HashMap::from([("h264", decoding.0), ("h265", decoding.1)]);
SyncReturn(serde_json::ser::to_string(&msg).unwrap_or("".to_owned()))
}
pub fn main_is_root() -> bool {
is_root()
}
@ -1054,10 +1067,10 @@ pub fn session_send_note(id: String, note: String) {
}
}
pub fn session_supported_hwcodec(id: String) -> String {
pub fn session_alternative_codecs(id: String) -> String {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
let (h264, h265) = session.supported_hwcodec();
let msg = HashMap::from([("h264", h264), ("h265", h265)]);
let (vp8, h264, h265) = session.alternative_codecs();
let msg = HashMap::from([("vp8", vp8), ("h264", h264), ("h265", h265)]);
serde_json::ser::to_string(&msg).unwrap_or("".to_owned())
} else {
String::new()

View File

@ -4,7 +4,7 @@ use crate::common::GrabState;
#[cfg(feature = "flutter")]
use crate::flutter::{CUR_SESSION_ID, SESSIONS};
#[cfg(target_os = "windows")]
use crate::platform::windows::get_char_by_vk;
use crate::platform::windows::{get_char_from_vk, get_unicode_from_vk};
#[cfg(not(any(feature = "flutter", feature = "cli")))]
use crate::ui::CUR_SESSION;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@ -339,24 +339,8 @@ pub fn get_keyboard_mode_enum() -> KeyboardMode {
"translate" => KeyboardMode::Translate,
"legacy" => KeyboardMode::Legacy,
_ => {
// Set "map" as default mode if version > 1.2.0.
let mut is_peer_version_gt_1_2_0 = false;
#[cfg(not(any(feature = "flutter", feature = "cli")))]
if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() {
is_peer_version_gt_1_2_0 =
session.get_peer_version() > hbb_common::get_version_number("1.2.0");
}
#[cfg(feature = "flutter")]
if let Some(session) = SESSIONS
.read()
.unwrap()
.get(&*CUR_SESSION_ID.read().unwrap())
{
is_peer_version_gt_1_2_0 =
session.get_peer_version() > hbb_common::get_version_number("1.2.0");
}
if is_peer_version_gt_1_2_0 {
// Set "map" as default mode if version >= 1.2.0.
if crate::is_peer_version_ge("1.2.0") {
KeyboardMode::Map
} else {
KeyboardMode::Legacy
@ -365,6 +349,21 @@ pub fn get_keyboard_mode_enum() -> KeyboardMode {
}
}
#[inline]
pub fn is_modifier(key: &rdev::Key) -> bool {
matches!(
key,
Key::ShiftLeft
| Key::ShiftRight
| Key::ControlLeft
| Key::ControlRight
| Key::MetaLeft
| Key::MetaRight
| Key::Alt
| Key::AltGr
)
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn is_numpad_key(event: &Event) -> bool {
@ -850,6 +849,7 @@ fn try_fill_unicode(_peer: &str, event: &Event, key_event: &KeyEvent, events: &m
if name.len() > 0 {
let mut evt = key_event.clone();
evt.set_seq(name.to_string());
evt.down = true;
events.push(evt);
}
}
@ -859,9 +859,10 @@ fn try_fill_unicode(_peer: &str, event: &Event, key_event: &KeyEvent, events: &m
#[cfg(target_os = "windows")]
if _peer == OS_LOWER_LINUX {
if is_hot_key_modifiers_down() && unsafe { !IS_0X021D_DOWN } {
if let Some(chr) = get_char_by_vk(event.platform_code as u32) {
if let Some(chr) = get_char_from_vk(event.platform_code as u32) {
let mut evt = key_event.clone();
evt.set_seq(chr.to_string());
evt.down = true;
events.push(evt);
}
}
@ -870,6 +871,36 @@ fn try_fill_unicode(_peer: &str, event: &Event, key_event: &KeyEvent, events: &m
}
}
#[cfg(target_os = "windows")]
fn try_file_win2win_hotkey(
peer: &str,
event: &Event,
key_event: &KeyEvent,
events: &mut Vec<KeyEvent>,
) {
if peer == OS_LOWER_WINDOWS && is_hot_key_modifiers_down() && unsafe { !IS_0X021D_DOWN } {
let mut down = false;
let win2win_hotkey = match event.event_type {
EventType::KeyPress(..) => {
down = true;
if let Some(unicode) = get_unicode_from_vk(event.platform_code as u32) {
Some((unicode as u32 & 0x0000FFFF) | (event.platform_code << 16))
} else {
None
}
}
EventType::KeyRelease(..) => Some(event.platform_code << 16),
_ => None,
};
if let Some(code) = win2win_hotkey {
let mut evt = key_event.clone();
evt.set_win2win_hotkey(code);
evt.down = down;
events.push(evt);
}
}
}
#[cfg(target_os = "windows")]
fn is_hot_key_modifiers_down() -> bool {
if rdev::get_modifier(Key::ControlLeft) || rdev::get_modifier(Key::ControlRight) {
@ -884,25 +915,6 @@ fn is_hot_key_modifiers_down() -> bool {
return false;
}
#[inline]
#[cfg(target_os = "windows")]
pub fn translate_key_code(peer: &str, event: &Event, key_event: KeyEvent) -> Option<KeyEvent> {
let mut key_event = map_keyboard_mode(peer, event, key_event)?;
let chr = if peer == OS_LOWER_WINDOWS {
(key_event.chr() & 0x0000FFFF) | ((event.platform_code as u32) << 16)
} else {
key_event.chr()
};
key_event.set_chr(chr);
Some(key_event)
}
#[inline]
#[cfg(not(target_os = "windows"))]
pub fn translate_key_code(peer: &str, event: &Event, key_event: KeyEvent) -> Option<KeyEvent> {
map_keyboard_mode(peer, event, key_event)
}
#[inline]
#[cfg(any(target_os = "linux", target_os = "windows"))]
fn is_altgr(event: &Event) -> bool {
@ -927,8 +939,10 @@ fn is_press(event: &Event) -> bool {
matches!(event.event_type, EventType::KeyPress(_))
}
// https://github.com/fufesou/rustdesk/wiki/Keyboard-mode----Translate-Mode
pub fn translate_keyboard_mode(peer: &str, event: &Event, key_event: KeyEvent) -> Vec<KeyEvent> {
let mut events: Vec<KeyEvent> = Vec::new();
if let Some(unicode_info) = &event.unicode {
if unicode_info.is_dead {
#[cfg(target_os = "macos")]
@ -945,7 +959,7 @@ pub fn translate_keyboard_mode(peer: &str, event: &Event, key_event: KeyEvent) -
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if is_numpad_key(&event) {
if let Some(evt) = translate_key_code(peer, event, key_event) {
if let Some(evt) = map_keyboard_mode(peer, event, key_event) {
events.push(evt);
}
return events;
@ -967,11 +981,15 @@ pub fn translate_keyboard_mode(peer: &str, event: &Event, key_event: KeyEvent) -
return events;
}
#[cfg(target_os = "windows")]
try_file_win2win_hotkey(peer, event, &key_event, &mut events);
#[cfg(any(target_os = "linux", target_os = "windows"))]
if is_press(event) {
if events.is_empty() && is_press(event) {
try_fill_unicode(peer, event, &key_event, &mut events);
}
// If AltGr is down, no need to send events other than unicode.
#[cfg(target_os = "windows")]
unsafe {
if IS_0X021D_DOWN {
@ -985,9 +1003,19 @@ pub fn translate_keyboard_mode(peer: &str, event: &Event, key_event: KeyEvent) -
}
if events.is_empty() {
if let Some(evt) = translate_key_code(peer, event, key_event) {
if let Some(evt) = map_keyboard_mode(peer, event, key_event) {
events.push(evt);
}
}
events
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn keycode_to_rdev_key(keycode: u32) -> Key {
#[cfg(target_os = "windows")]
return rdev::win_key_from_scancode(keycode);
#[cfg(target_os = "linux")]
return rdev::linux_key_from_code(keycode);
#[cfg(target_os = "macos")]
return rdev::macos_key_from_code(keycode.try_into().unwrap_or_default());
}

View File

@ -5,12 +5,12 @@ mod cn;
mod cs;
mod da;
mod de;
mod el;
mod en;
mod eo;
mod es;
mod fa;
mod fr;
mod el;
mod hu;
mod id;
mod it;
@ -32,6 +32,7 @@ mod tr;
mod tw;
mod ua;
mod vn;
mod lt;
pub const LANGS: &[(&str, &str)] = &[
("en", "English"),
@ -66,6 +67,7 @@ pub const LANGS: &[(&str, &str)] = &[
("th", "ภาษาไทย"),
("sl", "Slovenščina"),
("ro", "Română"),
("lt", "Lietuvių"),
];
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@ -129,6 +131,7 @@ pub fn translate_locale(name: String, locale: &str) -> String {
"th" => th::T.deref(),
"sl" => sl::T.deref(),
"ro" => ro::T.deref(),
"lt" => lt::T.deref(),
_ => en::T.deref(),
};
if let Some(v) = m.get(&name as &str) {

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,9 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", "此文件与对方的一致"),
("show_monitors_tip", ""),
("View Mode", "浏览模式"),
("enter_rustdesk_passwd_tip", "请输入 RustDesk 密码"),
("remember_rustdesk_passwd_tip", "记住 RustDesk 密码"),
("login_linux_tip", "登录被控端的 Linux 账户"),
("login_linux_tooltip_tip", "登录被控端的 Linux 账户,才能启用 X 桌面"),
("login_linux_tip", "登录被控端的 Linux 账户,才能启用 X 桌面"),
("verify_rustdesk_password_tip", "验证 RustDesk 密码"),
("remember_account_tip", "记住此账户"),
("os_account_desk_tip", "在无显示器的环境下,此账户用于登录被控系统,并启用桌面"),
("OS Account", "系统账户"),
("another_user_login_title_tip", "其他用户已登录"),
("another_user_login_text_tip", "断开"),
("xorg_not_found_title_tip", "Xorg 未安装"),
("xorg_not_found_text_tip", "请安装 Xorg"),
("no_desktop_title_tip", "desktop 未安装"),
("no_desktop_text_tip", "请安装 desktop"),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,9 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", "Diese Datei ist identisch mit der Datei der Gegenstelle."),
("show_monitors_tip", "Monitore in der Symbolleiste anzeigen"),
("View Mode", "Ansichtsmodus"),
("enter_rustdesk_passwd_tip", "RustDesk-Passwort eingeben."),
("remember_rustdesk_passwd_tip", "RustDesk-Passwort merken."),
("login_linux_tip", "Anmeldung am entfernten Linux-Konto"),
("login_linux_tooltip_tip", "Sie müssen sich an einem entfernten Linux-Konto anmelden, um eine X-Desktop-Sitzung zu eröffnen."),
("login_linux_tip", "Sie müssen sich an einem entfernten Linux-Konto anmelden, um eine X-Desktop-Sitzung zu eröffnen."),
("verify_rustdesk_password_tip", "RustDesk-Passwort bestätigen"),
("remember_account_tip", "Dieses Konto merken"),
("os_account_desk_tip", "Dieses Konto wird verwendet, um sich beim entfernten Betriebssystem anzumelden und die Desktop-Sitzung im Headless-Modus zu aktivieren."),
("OS Account", "Betriebssystem-Konto"),
("another_user_login_title_tip", "Ein anderer Benutzer ist bereits angemeldet."),
("another_user_login_text_tip", "Trennen"),
("xorg_not_found_title_tip", "Xorg nicht gefunden."),
("xorg_not_found_text_tip", "Bitte installieren Sie Xorg."),
("no_desktop_title_tip", "Es ist kein Desktop verfügbar."),
("no_desktop_text_tip", "Bitte installieren Sie den GNOME-Desktop."),
].iter().cloned().collect();
}

View File

@ -480,9 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", "Το αρχείο είναι πανομοιότυπο με αυτό του άλλου υπολογιστή."),
("show_monitors_tip", "Εμφάνιση οθονών στη γραμμή εργαλείων"),
("View Mode", "Λειτουργία προβολής"),
("enter_rustdesk_passwd_tip", "Εισαγωγή του κωδικού RustDesk."),
("remember_rustdesk_passwd_tip", "Να θυμάσαι τον κωδικό του RustDesk."),
("login_linux_tip", "Είσοδος σε απομακρυσμένο λογαριασμό Linux"),
("login_linux_tooltip_tip", "Απαιτείται είσοδος σε απομακρυσμένο λογαριασμό Linux για την ενεργοποίηση του περιβάλλον εργασίας Χ."),
("login_linux_tip", "Απαιτείται είσοδος σε απομακρυσμένο λογαριασμό Linux για την ενεργοποίηση του περιβάλλον εργασίας Χ."),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -54,9 +54,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("empty_address_book_tip", "Oh dear, it appears that there are currently no peers listed in your address book."),
("identical_file_tip", "This file is identical with the peer's one."),
("show_monitors_tip", "Show monitors in toolbar."),
("enter_rustdesk_passwd_tip", "Enter RustDesk password."),
("remember_rustdesk_passwd_tip", "Remember RustDesk password."),
("login_linux_tip", "Login to remote Linux account"),
("login_linux_tooltip_tip", "You need to login to remote Linux account to enable a X desktop session."),
("enter_rustdesk_passwd_tip", "Enter RustDesk password"),
("remember_rustdesk_passwd_tip", "Remember RustDesk password"),
("login_linux_tip", "You need to login to remote Linux account to enable a X desktop session"),
("verify_rustdesk_password_tip", "Verify RustDesk password"),
("remember_account_tip", "Remember this account"),
("os_account_desk_tip", "This account is used to login the remote OS and enable the desktop session in headless"),
("another_user_login_title_tip", "Another user already logged in"),
("another_user_login_text_tip", "Disconnect"),
("xorg_not_found_title_tip", "Xorg not found"),
("xorg_not_found_text_tip", "Please install Xorg"),
("no_desktop_title_tip", "No desktop is avaliable"),
("no_desktop_text_tip", "Please install GNOME desktop"),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", "Este archivo es idéntico al del par."),
("show_monitors_tip", "Mostrar monitores en la barra de herramientas"),
("View Mode", "Modo Vista"),
("enter_rustdesk_passwd_tip", "Introduzca la contraseña de RustDesk"),
("remember_rustdesk_passwd_tip", "Recordar la contraseña de RustDesk"),
("login_linux_tip", "Iniciar sesión para la cuenta remota de Linux"),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -479,9 +479,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Me", "من"),
("identical_file_tip", "این فایل با فایل همتا یکسان است."),
("show_monitors_tip", "نمایش مانیتورها در نوار ابزار"),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("View Mode", "حالت مشاهده"),
("login_linux_tip", "برای فعال کردن دسکتاپ X، باید به حساب لینوکس راه دور وارد شوید"),
("verify_rustdesk_password_tip", "رمز عبور RustDesk را تأیید کنید"),
("remember_account_tip", "این حساب را به خاطر بسپارید"),
("os_account_desk_tip", "این حساب برای ورود به سیستم عامل راه دور و فعال کردن جلسه دسکتاپ در هدلس استفاده می شود"),
("OS Account", "حساب کاربری سیستم عامل"),
("another_user_login_title_tip", "کاربر دیگری قبلاً وارد شده است"),
("another_user_login_text_tip", "قطع شدن"),
("xorg_not_found_title_tip", "پیدا نشد Xorg"),
("xorg_not_found_text_tip", "لطفا Xorg را نصب کنید"),
("no_desktop_title_tip", "هیچ دسکتاپی در دسترس نیست"),
("no_desktop_text_tip", "لطفا دسکتاپ گنوم را نصب کنید"),
].iter().cloned().collect();
}

View File

@ -213,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Fermé manuellement par le pair"),
("Enable remote configuration modification", "Autoriser la modification de la configuration à distance"),
("Run without install", "Exécuter sans installer"),
("Connect via relay", ""),
("Connect via relay", "Connexion via relais"),
("Always connect via relay", "Forcer la connexion relais"),
("whitelist_tip", "Seule une IP de la liste blanche peut accéder à mon appareil"),
("Login", "Connexion"),
@ -288,8 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_service_will_start_tip", "L'activation de la capture d'écran démarrera automatiquement le service, permettant à d'autres appareils de demander une connexion à partir de cet appareil."),
("android_stop_service_tip", "La fermeture du service fermera automatiquement toutes les connexions établies."),
("android_version_audio_tip", "La version actuelle d'Android ne prend pas en charge la capture audio, veuillez passer à Android 10 ou supérieur."),
("android_start_service_tip", ""),
("android_permission_may_not_change_tip", ""),
("android_start_service_tip", "Appuyez sur [Démarrer le service] ou activez l'autorisation [Capture d'écran] pour démarrer le service de partage d'écran."),
("android_permission_may_not_change_tip", "Les autorisations pour les connexions établies peuvent ne pas être prisent en compte instantanément ou avant la reconnection."),
("Account", "Compte"),
("Overwrite", "Écraser"),
("This file exists, skip or overwrite this file?", "Ce fichier existe, ignorer ou écraser ce fichier ?"),
@ -311,8 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Gardez le service RustDesk en arrière plan"),
("Ignore Battery Optimizations", "Ignorer les optimisations batterie"),
("android_open_battery_optimizations_tip", "Conseil android d'optimisation de batterie"),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Start on Boot", "Lancer au démarrage"),
("Start the screen sharing service on boot, requires special permissions", "Lancer le service de partage d'écran au démarrage, nécessite des autorisations spéciales"),
("Connection not allowed", "Connexion non autorisée"),
("Legacy mode", "Mode hérité"),
("Map mode", ""),
@ -348,7 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Security", "Sécurité"),
("Theme", "Thème"),
("Dark Theme", "Thème somble"),
("Light Theme", ""),
("Light Theme", "Thème clair"),
("Dark", "Sombre"),
("Light", "Clair"),
("Follow System", "Suivi système"),
@ -421,7 +421,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("software_render_tip", "Si vous avez une carte graphique NVIDIA et que la fenêtre distante se ferme immédiatement après la connexion, l'installation du pilote Nouveau et le choix d'utiliser le rendu du logiciel peuvent aider. Un redémarrage du logiciel est requis."),
("Always use software rendering", "Utiliser toujours le rendu logiciel"),
("config_input", "Afin de contrôler le bureau à distance avec le clavier, vous devez accorder à RustDesk l'autorisation \"Surveillance de lentrée\"."),
("config_microphone", ""),
("config_microphone", "Pour discuter à distance, vous devez accorder à RustDesk les autorisations « Enregistrer l'audio »."),
("request_elevation_tip", "Vous pouvez également demander une augmentation des privilèges s'il y a quelqu'un du côté distant."),
("Wait", "En cours"),
("Elevation Error", "Erreur d'augmentation des privilèges"),
@ -451,37 +451,45 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("FPS", "FPS"),
("Auto", "Auto"),
("Other Default Options", "Autres options par défaut"),
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
("Restore", ""),
("Minimize", ""),
("Maximize", ""),
("Your Device", ""),
("empty_recent_tip", ""),
("empty_favorite_tip", ""),
("empty_lan_tip", ""),
("empty_address_book_tip", ""),
("eg: admin", ""),
("Empty Username", ""),
("Empty Password", ""),
("Me", ""),
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("Voice call", "Appel voix"),
("Text chat", "Conversation textuelfle"),
("Stop voice call", "Stopper l'appel voix"),
("relay_hint_tip", "Il se peut qu'il ne doit pas possible de se connecter directement, vous pouvez essayer de vous connecter via un relais. \nEn outre, si vous souhaitez utiliser directement le relais, vous pouvez ajouter le suffixe \"/r\" à l'ID ou sélectionner l'option \"Toujours se connecter via le relais\" dans la fiche pair."),
("Reconnect", "Se reconnecter"),
("Codec", "Codec"),
("Resolution", "Résolution"),
("No transfers in progress", "Pas de transfert en cours"),
("Set one-time password length", "Définir la longueur du mot de passe à usage unique"),
("idd_driver_tip", "Installez le pilote d'affichage virtuel pour être utilisé lorsque vous n'avez pas d'affichage physique."),
("confirm_idd_driver_tip", "L'option d'installation du pilote d'affichage virtuel est cochée. Notez qu'un certificat de test sera installé pour faire confiance au pilote d'affichage virtuel. Ce certificat de test ne sera utilisé que pour faire confiance aux pilotes Rustdesk."),
("RDP Settings", "Configuration RDP"),
("Sort by", "Trier par"),
("New Connection", "Nouvelle connexion"),
("Restore", "Restaurer"),
("Minimize", "Minimiser"),
("Maximize", "Maximiser"),
("Your Device", "Votre appareil"),
("empty_recent_tip", "Oups, pas de sessions récentes!\nIl est temps d'en prévoir une nouvelle."),
("empty_favorite_tip", "Vous n'avez pas encore de pairs favoris?\nTrouvons quelqu'un avec qui vous connecter et ajoutez-le à vos favoris!"),
("empty_lan_tip", "Oh non, il semble que nous n'ayons pas encore de pairs découverts."),
("empty_address_book_tip", "Ouh là là! il semble qu'il n'y ait actuellement aucun pair répertorié dans votre carnet d'adresses."),
("eg: admin", "ex: admin"),
("Empty Username", "Nom d'utilisation non spécifié"),
("Empty Password", "Mot de passe non spécifié"),
("Me", "Moi"),
("identical_file_tip", "Ce fichier est identique à celui du pair."),
("show_monitors_tip", "Afficher les moniteurs dans la barre d'outils."),
("View Mode", "Mode vue"),
("login_linux_tip", "Se connecter au compte Linux distant"),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", "Questo file è identico a quello del peer."),
("show_monitors_tip", "Mostra schermi nella barra degli strumenti"),
("View Mode", "Modalità di visualizzazione"),
("enter_rustdesk_passwd_tip", "Inserisci la password di RustDesk."),
("remember_rustdesk_passwd_tip", "Ricorda la passowrd di RustDesk."),
("login_linux_tip", "Effettua l'accesso sul tuo account Linux"),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

495
src/lang/lt.rs Normal file
View File

@ -0,0 +1,495 @@
lazy_static::lazy_static! {
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[
("Status", "Būsena"),
("Your Desktop", "Jūsų darbalaukis"),
("desk_tip", "Jūsų darbalaukis pasiekiamas naudojant šį ID ir slaptažodį"),
("Password", "Slaptažodis"),
("Ready", "Pasiruošęs"),
("Established", "Įsteigta"),
("connecting_status", "Prisijungiama prie RustDesk tinklo..."),
("Enable Service", "Įgalinti paslaugą"),
("Start Service", "Pradėti paslaugą"),
("Service is running", "Paslauga veikia"),
("Service is not running", "Paslauga neveikia"),
("not_ready_status", "Neprisijungęs. Patikrinkite ryšį."),
("Control Remote Desktop", "Nuotolinio darbalaukio valdymas"),
("Transfer File", "Perkelti failą"),
("Connect", "Prisijungti"),
("Recent Sessions", "Seansų istorija"),
("Address Book", "Adresų knyga"),
("Confirmation", "Patvirtinimas"),
("TCP Tunneling", "TCP tuneliavimas"),
("Remove", "Pašalinti"),
("Refresh random password", "Atnaujinti atsitiktinį slaptažodį"),
("Set your own password", "Nustatykite savo slaptažodį"),
("Enable Keyboard/Mouse", "Įgalinti klaviatūrą/pelę"),
("Enable Clipboard", "Įgalinti iškarpinę"),
("Enable File Transfer", "Įgalinti failų perdavimą"),
("Enable TCP Tunneling", "Įgalinti TCP tuneliavimą"),
("IP Whitelisting", "IP baltasis sąrašas"),
("ID/Relay Server", "ID / perdavimo serveris"),
("Import Server Config", "Importuoti serverio konfigūraciją"),
("Export Server Config", "Eksportuoti serverio konfigūraciją"),
("Import server configuration successfully", "Sėkmingai importuoti serverio konfigūraciją"),
("Export server configuration successfully", "Sėkmingai eksportuoti serverio konfigūraciją"),
("Invalid server configuration", "Netinkama serverio konfigūracija"),
("Clipboard is empty", "Iškarpinė tuščia"),
("Stop service", "Sustabdyti paslaugą"),
("Change ID", "Keisti ID"),
("Your new ID", "Jūsų naujasis ID"),
("length %min% to %max%", "ilgis %min% iki %max%"),
("starts with a letter", "prasideda raide"),
("allowed characters", "leistini simboliai"),
("id_change_tip", "Leidžiami tik simboliai az, AZ, 09 ir _ (pabraukimas). Pirmoji raidė turi būti a-z, A-Z. Ilgis nuo 6 iki 16."),
("Website", "Interneto svetainė"),
("About", "Apie"),
("Slogan_tip", "Sukurta su siela šiame beprotiškame pasaulyje!"),
("Privacy Statement", "Privatumo pareiškimas"),
("Mute", "Nutildyti"),
("Build Date", "Sukūrimo data"),
("Version", "Versija"),
("Home", "Namai"),
("Audio Input", "Garso įvestis"),
("Enhancements", "Patobulinimai"),
("Hardware Codec", "Aparatinės įrangos paspartinimas"),
("Adaptive Bitrate", "Adaptyvusis pralaidumas"),
("ID Server", "ID serveris"),
("Relay Server", "Perdavimo serveris"),
("API Server", "API serveris"),
("invalid_http", "Turi prasidėti http:// arba https://"),
("Invalid IP", "Netinkamas IP"),
("Invalid format", "Neteisingas formatas"),
("server_not_support", "Serveris dar nepalaikomas"),
("Not available", "Nepasiekiamas"),
("Too frequent", "Per dažnai"),
("Cancel", "Atšaukti"),
("Skip", "Praleisti"),
("Close", "Uždaryti"),
("Retry", "Bandykite dar kartą"),
("OK", "GERAI"),
("Password Required", "Reikalingas slaptažodis"),
("Please enter your password", "Prašome įvesti savo slaptažodį"),
("Remember password", "Prisiminti slaptažodį"),
("Wrong Password", "Neteisingas slaptažodis"),
("Do you want to enter again?", "Ar norite įeiti dar kartą?"),
("Connection Error", "Ryšio klaida"),
("Error", "Klaida"),
("Reset by the peer", "Atmetė nuotolinis kompiuteris"),
("Connecting...", "Jungiamasi..."),
("Connection in progress. Please wait.", "Jungiamasi. Palaukite."),
("Please try 1 minute later", "Prašome pabandyti po 1 minutės"),
("Login Error", "Prisijungimo klaida"),
("Successful", "Sėkmingai"),
("Connected, waiting for image...", "Prisijungta, laukiama vaizdo..."),
("Name", "Vardas"),
("Type", "Tipas"),
("Modified", "Pakeista"),
("Size", "Dydis"),
("Show Hidden Files", "Rodyti paslėptus failus"),
("Receive", "Gauti"),
("Send", "Siųsti"),
("Refresh File", "Atnaujinti failą"),
("Local", "Vietinis"),
("Remote", "Nuotolinis"),
("Remote Computer", "Nuotolinis kompiuteris"),
("Local Computer", "Šis kompiuteris"),
("Confirm Delete", "Patvirtinti ištrynimą"),
("Delete", "Ištrinti"),
("Properties", "Ypatybės"),
("Multi Select", "Keli pasirinkimas"),
("Select All", "Pasirinkti viską"),
("Unselect All", "Atšaukti visų pasirinkimą"),
("Empty Directory", "Tuščias katalogas"),
("Not an empty directory", "Ne tuščias katalogas"),
("Are you sure you want to delete this file?", "Ar tikrai norite ištrinti šį failą?"),
("Are you sure you want to delete this empty directory?", "Ar tikrai norite ištrinti šį tuščią katalogą?"),
("Are you sure you want to delete the file of this directory?", "Ar tikrai norite ištrinti šio katalogo failą?"),
("Do this for all conflicts", "Taikyti visiems konfliktams"),
("This is irreversible!", "Tai negrįžtama!"),
("Deleting", "Ištrinama"),
("files", "failai"),
("Waiting", "Laukiu"),
("Finished", "Baigta"),
("Speed", "Greitis"),
("Custom Image Quality", "Tinkinta vaizdo kokybė"),
("Privacy mode", "Privatumo režimas"),
("Block user input", "Blokuoti naudotojo įvestį"),
("Unblock user input", "Atblokuoti naudotojo įvestį"),
("Adjust Window", "Koreguoti langą"),
("Original", "Originalas"),
("Shrink", "Susitraukti"),
("Stretch", "Ištempti"),
("Scrollbar", "Slinkties juosta"),
("ScrollAuto", "Automatinis slinkimas"),
("Good image quality", "Gera vaizdo kokybė"),
("Balanced", "Subalansuotas"),
("Optimize reaction time", "Optimizuoti reakcijos laiką"),
("Custom", "Tinkintas"),
("Show remote cursor", "Rodyti nuotolinį žymeklį"),
("Show quality monitor", "Rodyti kokybės monitorių"),
("Disable clipboard", "Išjungti mainų sritį"),
("Lock after session end", "Užrakinti pasibaigus seansui"),
("Insert", "Įdėti"),
("Insert Lock", "Įterpti užraktą"),
("Refresh", "Atnaujinti"),
("ID does not exist", "ID neegzistuoja"),
("Failed to connect to rendezvous server", "Nepavyko prisijungti prie susitikimo serverio"),
("Please try later", "Prašome pabandyti vėliau"),
("Remote desktop is offline", "Nuotolinis darbalaukis neprisijungęs"),
("Key mismatch", "Raktų neatitikimas"),
("Timeout", "Laikas baigėsi"),
("Failed to connect to relay server", "Nepavyko prisijungti prie perdavimo serverio"),
("Failed to connect via rendezvous server", "Nepavyko prisijungti per susitikimo serverį"),
("Failed to connect via relay server", "Nepavyko prisijungti per perdavimo serverį"),
("Failed to make direct connection to remote desktop", "Nepavyko tiesiogiai prisijungti prie nuotolinio darbalaukio"),
("Set Password", "Nustatyti slaptažodį"),
("OS Password", "OS slaptažodis"),
("install_tip", "Kai kuriais atvejais UAC gali priversti RustDesk netinkamai veikti nuotoliniame pagrindiniame kompiuteryje. Norėdami apeiti UAC, spustelėkite toliau esantį mygtuką, kad įdiegtumėte RustDesk į savo kompiuterį."),
("Click to upgrade", "Spustelėkite, jei norite atnaujinti"),
("Click to download", "Spustelėkite norėdami atsisiųsti"),
("Click to update", "Spustelėkite norėdami atnaujinti"),
("Configure", "Konfigūruoti"),
("config_acc", "Norėdami nuotoliniu būdu valdyti darbalaukį, turite suteikti RustDesk \"prieigos\" leidimus"),
("config_screen", "Norėdami nuotoliniu būdu pasiekti darbalaukį, turite suteikti RustDesk leidimus \"ekrano kopija\""),
("Installing ...", "Diegiama ..."),
("Install", "Diegti"),
("Installation", "Įdiegimas"),
("Installation Path", "Įdiegimo kelias"),
("Create start menu shortcuts", "Sukurti pradžios meniu sparčiuosius klavišus"),
("Create desktop icon", "Sukurti darbalaukio piktogramą"),
("agreement_tip", "Pradėdami diegimą sutinkate su licencijos sutarties sąlygomis"),
("Accept and Install", "Priimti ir įdiegti"),
("End-user license agreement", "Galutinio vartotojo licencijos sutartis"),
("Generating ...", "Generuojamas..."),
("Your installation is lower version.", "Jūsų įdiegta versija senesnė."),
("not_close_tcp_tip", "Naudodami tunelį neuždarykite šio lango"),
("Listening ...", "Laukimas..."),
("Remote Host", "Nuotolinis pagrindinis kompiuteris"),
("Remote Port", "Nuotolinis prievadas"),
("Action", "Veiksmas"),
("Add", "Papildyti"),
("Local Port", "Vietinis prievadas"),
("Local Address", "Vietinis adresas"),
("Change Local Port", "Keisti vietinį prievadą"),
("setup_server_tip", "Kad ryšys būtų greitesnis, nustatykite savo serverį"),
("Too short, at least 6 characters.", "Per trumpas, mažiausiai 6 simboliai."),
("The confirmation is not identical.", "Patvirtinimas nėra tapatus."),
("Permissions", "Leidimai"),
("Accept", "Priimti"),
("Dismiss", "Atmesti"),
("Disconnect", "Atjungti"),
("Allow using keyboard and mouse", "Leisti naudojant klaviatūrą ir pelę"),
("Allow using clipboard", "Leisti naudoti mainų sritį"),
("Allow hearing sound", "Leisti girdėti garsą"),
("Allow file copy and paste", "Leisti failą kopijuoti ir įklijuoti"),
("Connected", "Prisijungta"),
("Direct and encrypted connection", "Tiesioginis ir šifruotas ryšys"),
("Relayed and encrypted connection", "Perduotas ir šifruotas ryšys"),
("Direct and unencrypted connection", "Tiesioginis ir nešifruotas ryšys"),
("Relayed and unencrypted connection", "Perduotas ir nešifruotas ryšys"),
("Enter Remote ID", "Įveskite nuotolinio ID"),
("Enter your password", "Įveskite savo slaptažodį"),
("Logging in...", "Prisijungiama..."),
("Enable RDP session sharing", "Įgalinti RDP seansų bendrinimą"),
("Auto Login", "Automatinis prisijungimas"),
("Enable Direct IP Access", "Įgalinti tiesioginę IP prieigą"),
("Rename", "Pervardyti"),
("Space", "Erdvė"),
("Create Desktop Shortcut", "Sukurti nuorodą darbalaukyje"),
("Change Path", "Keisti kelią"),
("Create Folder", "Sukurti aplanką"),
("Please enter the folder name", "Įveskite aplanko pavadinimą"),
("Fix it", "Pataisyk tai"),
("Warning", "Įspėjimas"),
("Login screen using Wayland is not supported", "Prisijungimo ekranas naudojant Wayland nepalaikomas"),
("Reboot required", "Reikia paleisti iš naujo"),
("Unsupported display server", "Nepalaikomas rodymo serveris"),
("x11 expected", "reikalingas x11"),
("Port", "Prievadas"),
("Settings", "Nustatymai"),
("Username", "Vartotojo vardas"),
("Invalid port", "Netinkamas prievadas"),
("Closed manually by the peer", "Partneris atmetė prašymą prisijungti"),
("Enable remote configuration modification", "Įgalinti nuotolinį konfigūracijos modifikavimą"),
("Run without install", "Vykdyti be diegimo"),
("Connect via relay", "Prisijungti per relę"),
("Always connect via relay", "Visada prisijunkite per relę"),
("whitelist_tip", "Mane gali pasiekti tik baltajame sąraše esantys IP adresai"),
("Login", "Prisijungti"),
("Verify", "Patvirtinti"),
("Remember me", "Prisimink mane"),
("Trust this device", "Pasitikėk šiuo įrenginiu"),
("Verification code", "Patvirtinimo kodas"),
("verification_tip", "Aptiktas naujas įrenginys ir registruotu el. pašto adresu išsiųstas patvirtinimo kodas. Įveskite jį norėdami tęsti prisijungimą."),
("Logout", "Atsijungti"),
("Tags", "Žymos"),
("Search ID", "Paieškos ID"),
("whitelist_sep", "Atskirti kableliu, kabliataškiu, tarpu arba nauja eilute"),
("Add ID", "Pridėti ID"),
("Add Tag", "Pridėti žymą"),
("Unselect all tags", "Atšaukti visų žymų pasirinkimą"),
("Network error", "Tinklo klaida"),
("Username missed", "Prarastas vartotojo vardas"),
("Password missed", "Slaptažodis praleistas"),
("Wrong credentials", "Klaidingi kredencialai"),
("Edit Tag", "Redaguoti žymą"),
("Unremember Password", "Nebeprisiminti slaptažodžio"),
("Favorites", "Parankiniai"),
("Add to Favorites", "Įtraukti į parankinius"),
("Remove from Favorites", "Pašalinti iš parankinių"),
("Empty", "Tuščia"),
("Invalid folder name", "Neteisingas aplanko pavadinimas"),
("Socks5 Proxy", "Socks5 Proxy"),
("Hostname", "Pagrindinio kompiuterio pavadinimas"),
("Discovered", "Aptikta tinkle"),
("install_daemon_tip", "Norėdami, kad RustDesk startuotų automatiškai, turite ją įdiegti"),
("Remote ID", "Nuotolinis ID"),
("Paste", "Įklijuoti"),
("Paste here?", "Įklijuoti čia?"),
("Are you sure to close the connection?", "Ar tikrai norite atsijungti?"),
("Download new version", "Atsisiųsti naują versiją"),
("Touch mode", "Palietimo režimas"),
("Mouse mode", "Pelės režimas"),
("One-Finger Tap", "Palietimas vienu pirštu"),
("Left Mouse", "Kairysis pelės kl."),
("One-Long Tap", "Vienas palietimas"),
("Two-Finger Tap", "Palietimas dviem pirštais"),
("Right Mouse", "Dešinysis pelės kl."),
("One-Finger Move", "Vieno piršto judesys"),
("Double Tap & Move", "Dukart palieskite ir perkelkite"),
("Mouse Drag", "Pelės vilkimas"),
("Three-Finger vertically", "Trys pirštai vertikaliai"),
("Mouse Wheel", "Pelės ratukas"),
("Two-Finger Move", "Dviejų pirštų judesys"),
("Canvas Move", "Drobės perkėlimas"),
("Pinch to Zoom", "Suimkite, kad padidintumėte"),
("Canvas Zoom", "Drobės mastelis"),
("Reset canvas", "Atstatyti drobę"),
("No permission of file transfer", "Nėra leidimo perkelti failus"),
("Note", "Pastaba"),
("Connection", "Ryšys"),
("Share Screen", "Bendrinti ekraną"),
("Chat", "Pokalbis"),
("Total", "Iš viso"),
("items", "elementai"),
("Selected", "Pasirinkta"),
("Screen Capture", "Ekrano nuotrauka"),
("Input Control", "Įvesties valdymas"),
("Audio Capture", "Garso fiksavimas"),
("File Connection", "Failo ryšys"),
("Screen Connection", "Ekrano jungtis"),
("Do you accept?", "Ar sutinki?"),
("Open System Setting", "Atviros sistemos nustatymas"),
("How to get Android input permission?", "Kaip gauti Android įvesties leidimą?"),
("android_input_permission_tip1", "Kad nuotolinis įrenginys galėtų valdyti Android įrenginį pele arba liesti, turite leisti RustDesk naudoti \"Prieinamumo\" paslaugą."),
("android_input_permission_tip2", "Eikite į kitą sistemos nustatymų puslapį, suraskite \"Įdiegtos paslaugos\" ir įgalinkite \"RustDesk įvestis\" paslaugą."),
("android_new_connection_tip", "Gauta nauja užklausa tvarkyti dabartinį įrenginį."),
("android_service_will_start_tip", "Įgalinus ekrano fiksavimo paslaugą, kiti įrenginiai gali pateikti užklausą prisijungti prie to įrenginio."),
("android_stop_service_tip", "Uždarius paslaugą automatiškai bus uždaryti visi užmegzti ryšiai."),
("android_version_audio_tip", "Dabartinė Android versija nepalaiko garso įrašymo, atnaujinkite į Android 10 ar naujesnę versiją."),
("android_start_service_tip", "Spustelėkite [Paleisti paslaugą] arba įjunkite [Fiksuoti ekraną], kad paleistumėte ekrano bendrinimo paslaugą."),
("android_permission_may_not_change_tip", "Užmegztų ryšių leidimų keisti negalima, reikia prisijungti iš naujo."),
("Account", "Paskyra"),
("Overwrite", "Perrašyti"),
("This file exists, skip or overwrite this file?", "Šis failas egzistuoja, praleisti arba perrašyti šį failą?"),
("Quit", "Išeiti"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/"),
("Help", "Pagalba"),
("Failed", "Nepavyko"),
("Succeeded", "Pavyko"),
("Someone turns on privacy mode, exit", "Kažkas įjungė privatumo režimą, išeiti"),
("Unsupported", "Nepalaikomas"),
("Peer denied", "Atšaukė"),
("Please install plugins", "Įdiekite papildinius"),
("Peer exit", "Nuotolinis mazgas neveikia"),
("Failed to turn off", "Nepavyko išjungti"),
("Turned off", "Išjungti"),
("In privacy mode", "Privatumo režimas"),
("Out privacy mode", "Išėjimas iš privatumo režimo"),
("Language", "Kalba"),
("Keep RustDesk background service", "Palikti RustDesk fonine paslauga"),
("Ignore Battery Optimizations", "Ignoruoti akumuliatoriaus optimizavimą"),
("android_open_battery_optimizations_tip", "Eikite į kitą nustatymų puslapį"),
("Start on Boot", "Pradėti paleidžiant"),
("Start the screen sharing service on boot, requires special permissions", "Paleiskite ekrano bendrinimo paslaugą įkrovos metu, reikia specialių leidimų"),
("Connection not allowed", "Ryšys neleidžiamas"),
("Legacy mode", "Senasis režimas"),
("Map mode", "Žemėlapio režimas"),
("Translate mode", "Vertimo režimas"),
("Use permanent password", "Naudoti nuolatinį slaptažodį"),
("Use both passwords", "Naudoti abu slaptažodžius"),
("Set permanent password", "Nustatyti nuolatinį slaptažodį"),
("Enable Remote Restart", "Įgalinti nuotolinį paleidimą iš naujo"),
("Allow remote restart", "Leisti nuotolinį paleidimą iš naujo"),
("Restart Remote Device", "Paleisti nuotolinį įrenginį iš naujo"),
("Are you sure you want to restart", "Ar tikrai norite paleisti iš naujo"),
("Restarting Remote Device", "Nuotolinio įrenginio paleidimas iš naujo"),
("remote_restarting_tip", "Nuotolinis įrenginys paleidžiamas iš naujo. Uždarykite šį pranešimą ir po kurio laiko vėl prisijunkite naudodami nuolatinį slaptažodį."),
("Copied", "Nukopijuota"),
("Exit Fullscreen", "Išeiti iš pilno ekrano"),
("Fullscreen", "Per visą ekraną"),
("Mobile Actions", "Veiksmai mobiliesiems"),
("Select Monitor", "Pasirinkite monitorių"),
("Control Actions", "Valdymo veiksmai"),
("Display Settings", "Ekrano nustatymai"),
("Ratio", "Santykis"),
("Image Quality", "Vaizdo kokybė"),
("Scroll Style", "Slinkimo stilius"),
("Show Menubar", "Rodyti meniu juostą"),
("Hide Menubar", "Slėpti meniu juostą"),
("Direct Connection", "Tiesioginis ryšys"),
("Relay Connection", "Tarpinė jungtis"),
("Secure Connection", "Saugus ryšys"),
("Insecure Connection", "Nesaugus ryšys"),
("Scale original", "Pakeisti originalų mastelį"),
("Scale adaptive", "Pritaikomas mastelis"),
("General", "Bendra"),
("Security", "Sauga"),
("Theme", "Tema"),
("Dark Theme", "Tamsioji tema"),
("Light Theme", "Šviesi tema"),
("Dark", "Tamsi"),
("Light", "Šviesi"),
("Follow System", "Kaip sistemos"),
("Enable hardware codec", "Įgalinti"),
("Unlock Security Settings", "Atrakinti saugos nustatymus"),
("Enable Audio", "Įgalinti garsą"),
("Unlock Network Settings", "Atrakinti tinklo nustatymus"),
("Server", "Serveris"),
("Direct IP Access", "Tiesioginė IP prieiga"),
("Proxy", "Tarpinis serveris"),
("Apply", "Taikyti"),
("Disconnect all devices?", "Atjungti visus įrenginius?"),
("Clear", "Išvalyti"),
("Audio Input Device", "Garso įvestis"),
("Deny remote access", "Uždrausti nuotolinę prieigą"),
("Use IP Whitelisting", "Naudoti patikimą IP sąrašą"),
("Network", "Tinklas"),
("Enable RDP", "Įgalinti RDP"),
("Pin menubar", "Prisegti meniu juostą"),
("Unpin menubar", "Atsegti meniu juostą"),
("Recording", "Įrašymas"),
("Directory", "Katalogas"),
("Automatically record incoming sessions", "Automatiškai įrašyti įeinančius seansus"),
("Change", "Keisti"),
("Start session recording", "Pradėti seanso įrašymą"),
("Stop session recording", "Sustabdyti seanso įrašymą"),
("Enable Recording Session", "Įgalinti įrašymo seansą"),
("Allow recording session", "Leisti įrašymo sesiją"),
("Enable LAN Discovery", "Įgalinti LAN aptikimą"),
("Deny LAN Discovery", "Neleisti LAN aptikimo"),
("Write a message", "Rašyti žinutę"),
("Prompt", "Užuomina"),
("Please wait for confirmation of UAC...", "Palaukite UAC patvirtinimo..."),
("elevated_foreground_window_tip", "Dabartinis nuotolinio darbalaukio langas reikalauja didesnių privilegijų, todėl laikinai neįmanoma naudoti pelės ir klaviatūros. Galite paprašyti nuotolinio vartotojo sumažinti dabartinį langą arba spustelėti aukščio mygtuką ryšio valdymo lange. Norint išvengti šios problemos ateityje, rekomenduojama programinę įrangą įdiegti nuotoliniame įrenginyje."),
("Disconnected", "Atjungtas"),
("Other", "Kita"),
("Confirm before closing multiple tabs", "Patvirtinti prieš uždarant kelis skirtukus"),
("Keyboard Settings", "Klaviatūros nustatymai"),
("Full Access", "Pilna prieiga"),
("Screen Share", "Ekrano bendrinimas"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland reikalauja Ubuntu 21.04 arba naujesnės versijos."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland reikalinga naujesnės Linux Distro versijos. Išbandykite X11 darbalaukį arba pakeiskite OS."),
("JumpLink", "Peržiūra"),
("Please Select the screen to be shared(Operate on the peer side).", "Prašome pasirinkti ekraną, kurį norite bendrinti (veikiantį kitoje pusėje)."),
("Show RustDesk", "Rodyti RustDesk"),
("This PC", "Šis kompiuteris"),
("or", "arba"),
("Continue with", "Tęsti su"),
("Elevate", "Pakelti"),
("Zoom cursor", "Mastelio keitimo žymeklis"),
("Accept sessions via password", "Priimti seansus naudojant slaptažodį"),
("Accept sessions via click", "Priimti seansus spustelėjus"),
("Accept sessions via both", "Priimti seansus abiem variantais"),
("Please wait for the remote side to accept your session request...", "Palaukite, kol nuotolinė pusė priims jūsų seanso užklausą..."),
("One-time Password", "Vienkartinis slaptažodis"),
("Use one-time password", "Naudoti vienkartinį slaptažodį"),
("One-time password length", "Vienkartinio slaptažodžio ilgis"),
("Request access to your device", "Prašo leidimo valdyti jūsų įrenginį"),
("Hide connection management window", "Slėpti ryšio valdymo langą"),
("hide_cm_tip", "Leisti paslėpti didžiąją ir mažąją raidę, jei priimamos slaptažodžio sesijos arba naudojamas nuolatinis slaptažodis"),
("wayland_experiment_tip", "Wayland palaikymas yra eksperimentinis, naudokite X11, jei jums reikalingas automatinis prisijungimas."),
("Right click to select tabs", "Dešiniuoju pelės mygtuku spustelėkite, kad pasirinktumėte skirtukus"),
("Skipped", "Praleisti"),
("Add to Address Book", "Pridėti prie adresų knygos"),
("Group", "Grupė"),
("Search", "Paieška"),
("Closed manually by web console", "Uždaryta rankiniu būdu naudojant žiniatinklio konsolę"),
("Local keyboard type", "Vietinės klaviatūros tipas"),
("Select local keyboard type", "Pasirinkite vietinės klaviatūros tipą"),
("software_render_tip", "Jei turite Nvidia vaizdo plokštę ir nuotolinis langas iškart užsidaro prisijungus, gali padėti „Nouveau“ tvarkyklės įdiegimas ir programinės įrangos atvaizdavimo pasirinkimas. Būtina paleisti iš naujo."),
("Always use software rendering", "Visada naudoti programinį spartintuvą"),
("config_input", "Norėdami valdyti nuotolinį darbalaukį naudodami klaviatūrą, turite suteikti RustDesk leidimus \"Įvesties monitoringas\"."),
("config_microphone", "Norėdami kalbėtis su nuotoline puse, turite suteikti RustDesk leidimą \"Įrašyti garsą\"."),
("request_elevation_tip", "Taip pat galite prašyti tesių suteikimo, jeigu kas nors yra nuotolinėje pusėje."),
("Wait", "Laukti"),
("Elevation Error", "Teisių suteikimo klaida"),
("Ask the remote user for authentication", "Klauskite nuotolinio vartotojo autentifikavimo"),
("Choose this if the remote account is administrator", "Pasirinkite tai, jei nuotolinė paskyra yra administratorius"),
("Transmit the username and password of administrator", "Persiųsti administratoriaus vartotojo vardą ir slaptažodį"),
("still_click_uac_tip", "Vis tiek reikia, kad nuotolinis vartotojas paleidžiant RustDesk UAC lange paspaustų \"OK\"."),
("Request Elevation", "Prašyti teisių"),
("wait_accept_uac_tip", "Palaukite, kol nuotolinis vartotojas patvirtins UAC užklausą."),
("Elevate successfully", "Teisės suteiktos"),
("uppercase", "didžiosios raidės"),
("lowercase", "mažosios raidės"),
("digit", "skaitmuo"),
("special character", "specialusis simbolis"),
("length>=8", "ilgis>=8"),
("Weak", "Silpnas"),
("Medium", "Vidutinis"),
("Strong", "Stiprus"),
("Switch Sides", "Perjungti puses"),
("Please confirm if you want to share your desktop?", "Prašome patvirtinti, jeigu norite bendrinti darbalaukį?"),
("Display", "Ekranas"),
("Default View Style", "Numatytasis peržiūros stilius"),
("Default Scroll Style", "Numatytasis slinkties stilius"),
("Default Image Quality", "Numatytoji vaizdo kokybė"),
("Default Codec", "Numatytasis kodekas"),
("Bitrate", "Sparta"),
("FPS", "FPS"),
("Auto", "Automatinis"),
("Other Default Options", "Kitos numatytosios parinktys"),
("Voice call", "Balso skambutis"),
("Text chat", "Tekstinis pokalbis"),
("Stop voice call", "Sustabdyti balso skambutį"),
("relay_hint_tip", "Tiesioginis ryšys gali būti neįmanomas. Tokiu atveju galite pabandyti prisijungti per perdavimo serverį. \nArba, jei norite iš karto naudoti perdavimo serverį, prie ID galite pridėti priesagą \"/r\" arba nuotolinio pagrindinio kompiuterio nustatymuose įgalinti \"Visada prisijungti per relę\"."),
("Reconnect", "Prisijungti iš naujo"),
("Codec", "Kodekas"),
("Resolution", "Rezoliucija"),
("No transfers in progress", "Nevyksta jokių perdavimų"),
("Set one-time password length", "Nustatyti vienkartinio slaptažodžio ilgį"),
("idd_driver_tip", "Įdiekite virtualaus ekrano tvarkyklę (naudojama, kai nėra fizinių ekranų)"),
("confirm_idd_driver_tip", "Įjungta virtualaus ekrano tvarkyklės diegimo funkcija. Atminkite, kad bus įdiegtas bandomasis sertifikatas, kad būtų galima pasitikėti tvarkykle. Šis sertifikatas bus naudojamas tik pasitikėjimui Rustdesk tvarkyklėmis patikrinti."),
("RDP Settings", "RDP nustatymai"),
("Sort by", "Rūšiuoti pagal"),
("New Connection", "Naujas ryšys"),
("Restore", "Atkurti"),
("Minimize", "Sumažinti"),
("Maximize", "Padidinti"),
("Your Device", "Jūsų įrenginys"),
("empty_recent_tip", "Nėra paskutinių seansų!\nLaikas suplanuoti naują."),
("empty_favorite_tip", "Dar neturite parankinių nuotolinių seansų."),
("empty_lan_tip", "Nuotolinių mazgų nerasta."),
("empty_address_book_tip", "Adresų knygelėje nėra nuotolinių kompiuterių."),
("eg: admin", "pvz.: administratorius"),
("Empty Username", "Tuščias naudotojo vardas"),
("Empty Password", "Tuščias slaptažodis"),
("Me", ""),
("identical_file_tip", "Failas yra identiškas nuotoliniame kompiuteryje esančiam failui."),
("show_monitors_tip", "Rodyti monitorius įrankių juostoje"),
("View Mode", "Peržiūros režimas"),
("login_linux_tip", "Norėdami įjungti X darbalaukio seansą, turite būti prisijungę prie nuotolinės Linux paskyros."),
("verify_rustdesk_password_tip", "Įveskite kliento RustDesk slaptažodį"),
("remember_account_tip", "Prisiminti šią paskyrą"),
("os_account_desk_tip", "Ši paskyra naudojama norint prisijungti prie nuotolinės OS ir įgalinti darbalaukio seansą režimu headless"),
("OS Account", "OS paskyra"),
("another_user_login_title_tip", "Kitas vartotojas jau yra prisijungęs"),
("another_user_login_text_tip", "Atjungti"),
("xorg_not_found_title_tip", "Xorg nerastas"),
("xorg_not_found_text_tip", "Prašom įdiegti Xorg"),
("no_desktop_title_tip", "Nėra pasiekiamų nuotolinių darbalaukių"),
("no_desktop_text_tip", "Prašom įdiegti GNOME Desktop"),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", "Dit bestand is identiek aan het bestand van het externe station."),
("show_monitors_tip", "Monitoren weergeven in de werkbalk"),
("View Mode", "Weergave Mode"),
("enter_rustdesk_passwd_tip", "Geef het RustDesk-wachtwoord op."),
("remember_rustdesk_passwd_tip", "RustDesk Wachtwoord onthouden."),
("login_linux_tip", "Je moet inloggen op een Linux Account op afstand om een X desktop sessie te openen."),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", "Ten plik jest identyczny jak plik na drugim komputerze."),
("show_monitors_tip", "Pokaż monitory w zasobniku"),
("View Mode", "Tryb widoku"),
("enter_rustdesk_passwd_tip", "Podaj hasło RustDesk."),
("remember_rustdesk_passwd_tip", "Zapamiętaj hasło RustDesk."),
("login_linux_tip", "Zaloguj do zdalnego konta Linux"),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,9 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", "Файл идентичен файлу на удалённом узле."),
("show_monitors_tip", "Показывать мониторы на панели инструментов"),
("View Mode", "Режим просмотра"),
("enter_rustdesk_passwd_tip", "Введите пароль RustDesk"),
("remember_rustdesk_passwd_tip", "Запомнить пароль RustDesk"),
("login_linux_tip", "Вход в удалённый аккаунт Linux"),
("login_linux_tooltip_tip", "Чтобы включить сеанс рабочего стола X, необходимо войти в удалённый аккаунт Linux."),
("login_linux_tip", "Чтобы включить сеанс рабочего стола X, необходимо войти в удалённый аккаунт Linux."),
("verify_rustdesk_password_tip", "Подтвердить пароль RustDesk"),
("remember_account_tip", "Запомнить этот аккаунт"),
("os_account_desk_tip", "Эта учетная запись используется для входа в удаленную ОС и включения сеанса рабочего стола в режиме headless"),
("OS Account", "Аккаунт ОС"),
("another_user_login_title_tip", "Другой пользователь уже вошел в систему"),
("another_user_login_text_tip", "Отключить"),
("xorg_not_found_title_tip", "Xorg не найден"),
("xorg_not_found_text_tip", "Установите, пожалуйста, Xorg"),
("no_desktop_title_tip", "Нет доступных рабочих столов"),
("no_desktop_text_tip", "Установите, пожалуйста, GNOME Desktop"),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,9 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("login_linux_tooltip_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,9 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", "此檔案與對方的檔案一致"),
("show_monitors_tip", "在工具列中顯示顯示器"),
("View Mode", "瀏覽模式"),
("enter_rustdesk_passwd_tip", "輸入 RustDesk 密碼"),
("remember_rustdesk_passwd_tip", "記住 RustDesk 密碼"),
("login_linux_tip", "登入到遠端 Linux 使用者帳戶"),
("login_linux_tooltip_tip", "需要登入到遠端 Linux 使用者帳戶才能啟用 X 介面。"),
("login_linux_tip", "需要登入到遠端 Linux 使用者帳戶才能啟用 X 介面"),
("verify_rustdesk_password_tip", "驗證 RustDesk 密碼"),
("remember_account_tip", "記住此使用者帳戶"),
("os_account_desk_tip", "此使用者帳戶將用於登入遠端作業系統並啟用無頭模式的桌面連線"),
("OS Account", "作業系統使用者帳戶"),
("another_user_login_title_tip", "另一個使用者已經登入"),
("another_user_login_text_tip", "斷開連線"),
("xorg_not_found_title_tip", "未找到 Xorg"),
("xorg_not_found_text_tip", "請安裝 Xorg"),
("no_desktop_title_tip", "沒有可用的桌面"),
("no_desktop_text_tip", "請安裝 GNOME 桌面"),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -480,8 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("enter_rustdesk_passwd_tip", ""),
("remember_rustdesk_passwd_tip", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
].iter().cloned().collect();
}

View File

@ -3,6 +3,8 @@
pub mod platform;
mod keyboard;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub use keyboard::keycode_to_rdev_key;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub use platform::{get_cursor, get_cursor_data, get_cursor_pos, start_os_service};
#[cfg(not(any(target_os = "ios")))]
/// cbindgen:ignore

View File

@ -4,26 +4,13 @@ use hbb_common::ResultType;
use license::*;
fn gen_name(lic: &License) -> ResultType<String> {
let tmp = serde_json::to_vec::<License>(lic)?;
let tmp = URL_SAFE_NO_PAD.encode(&tmp);
let tmp: String = tmp.chars().rev().collect();
Ok(tmp)
let tmp = URL_SAFE_NO_PAD.encode(&serde_json::to_vec(lic)?);
Ok(tmp.chars().rev().collect())
}
fn main() {
let mut args = Vec::new();
let mut i = 0;
for arg in std::env::args() {
if i > 0 {
args.push(arg);
}
i += 1;
}
let api = if args.len() < 3 {
"".to_owned()
} else {
args[2].clone()
};
let args: Vec<_> = std::env::args().skip(1).collect();
let api = args.get(2).cloned().unwrap_or_default();
if args.len() >= 2 {
println!(
"rustdesk-licensed-{}.exe",

View File

@ -225,6 +225,16 @@ fn stop_rustdesk_servers() {
));
}
#[inline]
fn stop_subprocess() {
let _ = run_cmds(&format!(
r##"ps -ef | grep '/etc/rustdesk/xorg.conf' | grep -v grep | awk '{{printf("kill -9 %d\n", $2)}}' | bash"##,
));
let _ = run_cmds(&format!(
r##"ps -ef | grep -E 'rustdesk +--cm-no-ui' | grep -v grep | awk '{{printf("kill -9 %d\n", $2)}}' | bash"##,
));
}
fn should_start_server(
try_x11: bool,
uid: &mut String,
@ -295,6 +305,7 @@ fn force_stop_server() {
pub fn start_os_service() {
stop_rustdesk_servers();
stop_subprocess();
start_uinput_service();
let running = Arc::new(AtomicBool::new(true));
@ -329,6 +340,7 @@ pub fn start_os_service() {
&mut last_restart,
&mut server,
) {
stop_subprocess();
force_stop_server();
start_server(None, &mut server);
}
@ -345,6 +357,7 @@ pub fn start_os_service() {
&mut last_restart,
&mut user_server,
) {
stop_subprocess();
force_stop_server();
start_server(
Some((desktop.uid.clone(), desktop.username.clone())),
@ -454,9 +467,10 @@ pub fn get_env_var(k: &str) -> String {
}
}
// Headless is enabled, always return true.
pub fn is_prelogin() -> bool {
let (uid, uname) = get_active_user_id_name();
uid.len() >= 4 || uname == "root"
let n = get_active_userid().len();
n < 4 && n > 1
}
pub fn is_root() -> bool {
@ -769,6 +783,7 @@ mod desktop {
pub protocal: String,
pub display: String,
pub xauth: String,
pub is_rustdesk_subprocess: bool,
}
impl Desktop {
@ -784,7 +799,7 @@ mod desktop {
#[inline]
pub fn is_headless(&self) -> bool {
self.sid.is_empty()
self.sid.is_empty() || self.is_rustdesk_subprocess
}
fn get_display(&mut self) {
@ -901,6 +916,16 @@ mod desktop {
last
}
fn set_is_subprocess(&mut self) {
self.is_rustdesk_subprocess = false;
let cmd = "ps -ef | grep 'rustdesk/xorg.conf' | grep -v grep | wc -l";
if let Ok(res) = run_cmds(cmd) {
if res.trim() != "0" {
self.is_rustdesk_subprocess = true;
}
}
}
pub fn refresh(&mut self) {
if !self.sid.is_empty() && is_active(&self.sid) {
return;
@ -909,6 +934,7 @@ mod desktop {
let seat0_values = get_values_of_seat0(&[0, 1, 2]);
if seat0_values[0].is_empty() {
*self = Self::default();
self.is_rustdesk_subprocess = false;
return;
}
@ -919,11 +945,13 @@ mod desktop {
if self.is_login_wayland() {
self.display = "".to_owned();
self.xauth = "".to_owned();
self.is_rustdesk_subprocess = false;
return;
}
self.get_display();
self.get_xauth();
self.set_is_subprocess();
}
}
}

View File

@ -0,0 +1,733 @@
use super::{linux::*, ResultType};
use crate::server::{
LOGIN_MSG_DESKTOP_NO_DESKTOP, LOGIN_MSG_DESKTOP_SESSION_ANOTHER_USER,
LOGIN_MSG_DESKTOP_SESSION_NOT_READY, LOGIN_MSG_DESKTOP_XORG_NOT_FOUND,
LOGIN_MSG_DESKTOP_XSESSION_FAILED,
};
use hbb_common::{allow_err, bail, log, rand::prelude::*, tokio::time};
use pam;
use std::{
collections::HashMap,
os::unix::process::CommandExt,
path::Path,
process::{Child, Command},
sync::{
atomic::{AtomicBool, Ordering},
mpsc::{sync_channel, SyncSender},
Arc, Mutex,
},
time::{Duration, Instant},
};
use users::{get_user_by_name, os::unix::UserExt, User};
lazy_static::lazy_static! {
static ref DESKTOP_RUNNING: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
static ref DESKTOP_MANAGER: Arc<Mutex<Option<DesktopManager>>> = Arc::new(Mutex::new(None));
}
#[derive(Debug)]
struct DesktopManager {
seat0_username: String,
seat0_display_server: String,
child_username: String,
child_exit: Arc<AtomicBool>,
is_child_running: Arc<AtomicBool>,
}
fn check_desktop_manager() {
let mut desktop_manager = DESKTOP_MANAGER.lock().unwrap();
if let Some(desktop_manager) = &mut (*desktop_manager) {
if desktop_manager.is_child_running.load(Ordering::SeqCst) {
return;
}
desktop_manager.child_exit.store(true, Ordering::SeqCst);
}
}
// --server process
pub fn start_xdesktop() {
std::thread::spawn(|| {
*DESKTOP_MANAGER.lock().unwrap() = Some(DesktopManager::new());
let interval = time::Duration::from_millis(super::SERVICE_INTERVAL);
DESKTOP_RUNNING.store(true, Ordering::SeqCst);
while DESKTOP_RUNNING.load(Ordering::SeqCst) {
check_desktop_manager();
std::thread::sleep(interval);
}
log::info!("xdesktop child thread exit");
});
}
pub fn stop_xdesktop() {
DESKTOP_RUNNING.store(false, Ordering::SeqCst);
*DESKTOP_MANAGER.lock().unwrap() = None;
}
fn detect_headless() -> Option<&'static str> {
match run_cmds(&format!("which {}", DesktopManager::get_xorg())) {
Ok(output) => {
if output.trim().is_empty() {
return Some(LOGIN_MSG_DESKTOP_XORG_NOT_FOUND);
}
}
_ => {
return Some(LOGIN_MSG_DESKTOP_XORG_NOT_FOUND);
}
}
match run_cmds("ls /usr/share/xsessions/") {
Ok(output) => {
if output.trim().is_empty() {
return Some(LOGIN_MSG_DESKTOP_NO_DESKTOP);
}
}
_ => {
return Some(LOGIN_MSG_DESKTOP_NO_DESKTOP);
}
}
None
}
pub fn try_start_desktop(_username: &str, _passsword: &str) -> String {
if _username.is_empty() {
let username = get_username();
if username.is_empty() {
if let Some(msg) = detect_headless() {
msg
} else {
LOGIN_MSG_DESKTOP_SESSION_NOT_READY
}
} else {
""
}
.to_owned()
} else {
let username = get_username();
if username == _username {
// No need to verify password here.
return "".to_owned();
}
if let Some(msg) = detect_headless() {
return msg.to_owned();
}
match try_start_x_session(_username, _passsword) {
Ok((username, x11_ready)) => {
if x11_ready {
if _username != username {
LOGIN_MSG_DESKTOP_SESSION_ANOTHER_USER.to_owned()
} else {
"".to_owned()
}
} else {
LOGIN_MSG_DESKTOP_SESSION_NOT_READY.to_owned()
}
}
Err(e) => {
log::error!("Failed to start xsession {}", e);
LOGIN_MSG_DESKTOP_XSESSION_FAILED.to_owned()
}
}
}
}
fn try_start_x_session(username: &str, password: &str) -> ResultType<(String, bool)> {
let mut desktop_manager = DESKTOP_MANAGER.lock().unwrap();
if let Some(desktop_manager) = &mut (*desktop_manager) {
if let Some(seat0_username) = desktop_manager.get_supported_display_seat0_username() {
return Ok((seat0_username, true));
}
let _ = desktop_manager.try_start_x_session(username, password)?;
log::debug!(
"try_start_x_session, username: {}, {:?}",
&username,
&desktop_manager
);
Ok((
desktop_manager.child_username.clone(),
desktop_manager.is_running(),
))
} else {
bail!(crate::server::LOGIN_MSG_DESKTOP_NOT_INITED);
}
}
#[inline]
pub fn is_headless() -> bool {
DESKTOP_MANAGER
.lock()
.unwrap()
.as_ref()
.map_or(false, |manager| {
manager.get_supported_display_seat0_username().is_none()
})
}
pub fn get_username() -> String {
match &*DESKTOP_MANAGER.lock().unwrap() {
Some(manager) => {
if let Some(seat0_username) = manager.get_supported_display_seat0_username() {
seat0_username
} else {
if manager.is_running() && !manager.child_username.is_empty() {
manager.child_username.clone()
} else {
"".to_owned()
}
}
}
None => "".to_owned(),
}
}
impl Drop for DesktopManager {
fn drop(&mut self) {
self.stop_children();
}
}
impl DesktopManager {
fn fatal_exit() {
std::process::exit(0);
}
pub fn new() -> Self {
let mut seat0_username = "".to_owned();
let mut seat0_display_server = "".to_owned();
let seat0_values = get_values_of_seat0(&[0, 2]);
if !seat0_values[0].is_empty() {
seat0_username = seat0_values[1].clone();
seat0_display_server = get_display_server_of_session(&seat0_values[0]);
}
Self {
seat0_username,
seat0_display_server,
child_username: "".to_owned(),
child_exit: Arc::new(AtomicBool::new(true)),
is_child_running: Arc::new(AtomicBool::new(false)),
}
}
fn get_supported_display_seat0_username(&self) -> Option<String> {
if is_gdm_user(&self.seat0_username) && self.seat0_display_server == DISPLAY_SERVER_WAYLAND
{
None
} else if self.seat0_username.is_empty() {
None
} else {
Some(self.seat0_username.clone())
}
}
#[inline]
fn get_xauth() -> String {
let xauth = get_env_var("XAUTHORITY");
if xauth.is_empty() {
"/tmp/.Xauthority".to_owned()
} else {
xauth
}
}
#[inline]
fn is_running(&self) -> bool {
self.is_child_running.load(Ordering::SeqCst)
}
fn try_start_x_session(&mut self, username: &str, password: &str) -> ResultType<()> {
match get_user_by_name(username) {
Some(userinfo) => {
let mut client = pam::Client::with_password(pam_get_service_name())?;
client
.conversation_mut()
.set_credentials(username, password);
match client.authenticate() {
Ok(_) => {
if self.is_running() {
return Ok(());
}
match self.start_x_session(&userinfo, username, password) {
Ok(_) => {
log::info!("Succeeded to start x11");
self.child_username = username.to_string();
Ok(())
}
Err(e) => {
bail!("failed to start x session, {}", e);
}
}
}
Err(e) => {
bail!("failed to check user pass for {}, {}", username, e);
}
}
}
None => {
bail!("failed to get userinfo of {}", username);
}
}
}
// The logic mainly fron https://github.com/neutrinolabs/xrdp/blob/34fe9b60ebaea59e8814bbc3ca5383cabaa1b869/sesman/session.c#L334.
fn get_avail_display() -> ResultType<u32> {
let display_range = 0..51;
for i in display_range.clone() {
if Self::is_x_server_running(i) {
continue;
}
return Ok(i);
}
bail!("No avaliable display found in range {:?}", display_range)
}
#[inline]
fn is_x_server_running(display: u32) -> bool {
Path::new(&format!("/tmp/.X11-unix/X{}", display)).exists()
|| Path::new(&format!("/tmp/.X{}-lock", display)).exists()
}
fn start_x_session(
&mut self,
userinfo: &User,
username: &str,
password: &str,
) -> ResultType<()> {
self.stop_children();
let display_num = Self::get_avail_display()?;
// "xServer_ip:display_num.screen_num"
let uid = userinfo.uid();
let gid = userinfo.primary_group_id();
let envs = HashMap::from([
("SHELL", userinfo.shell().to_string_lossy().to_string()),
("PATH", "/sbin:/bin:/usr/bin:/usr/local/bin".to_owned()),
("USER", username.to_string()),
("UID", userinfo.uid().to_string()),
("HOME", userinfo.home_dir().to_string_lossy().to_string()),
(
"XDG_RUNTIME_DIR",
format!("/run/user/{}", userinfo.uid().to_string()),
),
// ("DISPLAY", self.display.clone()),
// ("XAUTHORITY", self.xauth.clone()),
// (ENV_DESKTOP_PROTOCAL, XProtocal::X11.to_string()),
]);
self.child_exit.store(false, Ordering::SeqCst);
let is_child_running = self.is_child_running.clone();
let (tx_res, rx_res) = sync_channel(1);
let password = password.to_string();
let username = username.to_string();
// start x11
std::thread::spawn(move || {
match Self::start_x_session_thread(
tx_res.clone(),
is_child_running,
uid,
gid,
display_num,
username,
password,
envs,
) {
Ok(_) => {}
Err(e) => {
log::error!("Failed to start x session thread");
allow_err!(tx_res.send(format!("Failed to start x session thread, {}", e)));
}
}
});
// wait x11
match rx_res.recv_timeout(Duration::from_millis(10_000)) {
Ok(res) => {
if res == "" {
Ok(())
} else {
bail!(res)
}
}
Err(e) => {
bail!("Failed to recv x11 result {}", e)
}
}
}
#[inline]
fn display_from_num(num: u32) -> String {
format!(":{num}")
}
fn start_x_session_thread(
tx_res: SyncSender<String>,
is_child_running: Arc<AtomicBool>,
uid: u32,
gid: u32,
display_num: u32,
username: String,
password: String,
envs: HashMap<&str, String>,
) -> ResultType<()> {
let mut client = pam::Client::with_password(pam_get_service_name())?;
client
.conversation_mut()
.set_credentials(&username, &password);
client.authenticate()?;
client.set_item(pam::PamItemType::TTY, &Self::display_from_num(display_num))?;
client.open_session()?;
// fixme: FreeBSD kernel needs to login here.
// see: https://github.com/neutrinolabs/xrdp/blob/a64573b596b5fb07ca3a51590c5308d621f7214e/sesman/session.c#L556
let (child_xorg, child_wm) = Self::start_x11(uid, gid, username, display_num, &envs)?;
is_child_running.store(true, Ordering::SeqCst);
log::info!("Start xorg and wm done, notify and wait xtop x11");
allow_err!(tx_res.send("".to_owned()));
Self::wait_stop_x11(child_xorg, child_wm);
log::info!("Wait x11 stop done");
Ok(())
}
fn wait_xorg_exit(child_xorg: &mut Child) -> ResultType<String> {
if let Ok(_) = child_xorg.kill() {
for _ in 0..3 {
match child_xorg.try_wait() {
Ok(Some(status)) => return Ok(format!("Xorg exit with {}", status)),
Ok(None) => {}
Err(e) => {
// fatal error
log::error!("Failed to wait xorg process, {}", e);
bail!("Failed to wait xorg process, {}", e)
}
}
std::thread::sleep(std::time::Duration::from_millis(1_000));
}
log::error!("Failed to wait xorg process, not exit");
bail!("Failed to wait xorg process, not exit")
} else {
Ok("Xorg is already exited".to_owned())
}
}
fn add_xauth_cookie(
file: &str,
display: &str,
uid: u32,
gid: u32,
envs: &HashMap<&str, String>,
) -> ResultType<()> {
let randstr = (0..16)
.map(|_| format!("{:02x}", random::<u8>()))
.collect::<String>();
let output = Command::new("xauth")
.uid(uid)
.gid(gid)
.envs(envs)
.args(vec!["-q", "-f", file, "add", display, ".", &randstr])
.output()?;
// xauth run success, even the following error occurs.
// Ok(Output { status: ExitStatus(unix_wait_status(0)), stdout: "", stderr: "xauth: file .Xauthority does not exist\n" })
let errmsg = String::from_utf8_lossy(&output.stderr).to_string();
if !errmsg.is_empty() {
if !errmsg.contains("does not exist") {
bail!("Failed to launch xauth, {}", errmsg)
}
}
Ok(())
}
fn wait_x_server_running(pid: u32, display_num: u32, max_wait_secs: u64) -> ResultType<()> {
let wait_begin = Instant::now();
loop {
if run_cmds(&format!("ls /proc/{}", pid))?.is_empty() {
bail!("X server exit");
}
if Self::is_x_server_running(display_num) {
return Ok(());
}
if wait_begin.elapsed().as_secs() > max_wait_secs {
bail!("Failed to wait xserver after {} seconds", max_wait_secs);
}
std::thread::sleep(Duration::from_millis(300));
}
}
fn start_x11(
uid: u32,
gid: u32,
username: String,
display_num: u32,
envs: &HashMap<&str, String>,
) -> ResultType<(Child, Child)> {
log::debug!("envs of user {}: {:?}", &username, &envs);
let xauth = Self::get_xauth();
let display = Self::display_from_num(display_num);
Self::add_xauth_cookie(&xauth, &display, uid, gid, &envs)?;
// Start Xorg
let mut child_xorg = Self::start_x_server(&xauth, &display, uid, gid, &envs)?;
log::info!("xorg started, wait 10 secs to ensuer x server is running");
let max_wait_secs = 10;
// wait x server running
if let Err(e) = Self::wait_x_server_running(child_xorg.id(), display_num, max_wait_secs) {
match Self::wait_xorg_exit(&mut child_xorg) {
Ok(msg) => log::info!("{}", msg),
Err(e) => {
log::error!("{}", e);
Self::fatal_exit();
}
}
bail!(e)
}
log::info!(
"xorg is running, start x window manager with DISPLAY: {}, XAUTHORITY: {}",
&display,
&xauth
);
std::env::set_var("DISPLAY", &display);
std::env::set_var("XAUTHORITY", &xauth);
// start window manager (startwm.sh)
let child_wm = match Self::start_x_window_manager(uid, gid, &envs) {
Ok(c) => c,
Err(e) => {
match Self::wait_xorg_exit(&mut child_xorg) {
Ok(msg) => log::info!("{}", msg),
Err(e) => {
log::error!("{}", e);
Self::fatal_exit();
}
}
bail!(e)
}
};
log::info!("x window manager is started");
Ok((child_xorg, child_wm))
}
fn try_wait_x11_child_exit(child_xorg: &mut Child, child_wm: &mut Child) -> bool {
match child_xorg.try_wait() {
Ok(Some(status)) => {
log::info!("Xorg exit with {}", status);
return true;
}
Ok(None) => {}
Err(e) => log::error!("Failed to wait xorg process, {}", e),
}
match child_wm.try_wait() {
Ok(Some(status)) => {
// Logout may result "wm exit with signal: 11 (SIGSEGV) (core dumped)"
log::info!("wm exit with {}", status);
return true;
}
Ok(None) => {}
Err(e) => log::error!("Failed to wait xorg process, {}", e),
}
false
}
fn wait_x11_children_exit(child_xorg: &mut Child, child_wm: &mut Child) {
log::debug!("Try kill child process xorg");
if let Ok(_) = child_xorg.kill() {
let mut exited = false;
for _ in 0..2 {
match child_xorg.try_wait() {
Ok(Some(status)) => {
log::info!("Xorg exit with {}", status);
exited = true;
break;
}
Ok(None) => {}
Err(e) => {
log::error!("Failed to wait xorg process, {}", e);
Self::fatal_exit();
}
}
std::thread::sleep(std::time::Duration::from_millis(1_000));
}
if !exited {
log::error!("Failed to wait child xorg, after kill()");
// try kill -9?
}
}
log::debug!("Try kill child process wm");
if let Ok(_) = child_wm.kill() {
let mut exited = false;
for _ in 0..2 {
match child_wm.try_wait() {
Ok(Some(status)) => {
// Logout may result "wm exit with signal: 11 (SIGSEGV) (core dumped)"
log::info!("wm exit with {}", status);
exited = true;
}
Ok(None) => {}
Err(e) => {
log::error!("Failed to wait wm process, {}", e);
Self::fatal_exit();
}
}
std::thread::sleep(std::time::Duration::from_millis(1_000));
}
if !exited {
log::error!("Failed to wait child xorg, after kill()");
// try kill -9?
}
}
}
fn try_wait_stop_x11(child_xorg: &mut Child, child_wm: &mut Child) -> bool {
let mut desktop_manager = DESKTOP_MANAGER.lock().unwrap();
let mut exited = true;
if let Some(desktop_manager) = &mut (*desktop_manager) {
if desktop_manager.child_exit.load(Ordering::SeqCst) {
exited = true;
} else {
exited = Self::try_wait_x11_child_exit(child_xorg, child_wm);
}
if exited {
log::debug!("Wait x11 children exiting");
Self::wait_x11_children_exit(child_xorg, child_wm);
desktop_manager
.is_child_running
.store(false, Ordering::SeqCst);
desktop_manager.child_exit.store(true, Ordering::SeqCst);
}
}
exited
}
fn wait_stop_x11(mut child_xorg: Child, mut child_wm: Child) {
loop {
if Self::try_wait_stop_x11(&mut child_xorg, &mut child_wm) {
break;
}
std::thread::sleep(Duration::from_millis(super::SERVICE_INTERVAL));
}
}
fn get_xorg() -> &'static str {
// Fedora 26 or later
let xorg = "/usr/libexec/Xorg";
if Path::new(xorg).is_file() {
return xorg;
}
// Debian 9 or later
let xorg = "/usr/lib/xorg/Xorg";
if Path::new(xorg).is_file() {
return xorg;
}
// Ubuntu 16.04 or later
let xorg = "/usr/lib/xorg/Xorg";
if Path::new(xorg).is_file() {
return xorg;
}
// Arch Linux
let xorg = "/usr/lib/xorg-server/Xorg";
if Path::new(xorg).is_file() {
return xorg;
}
// Arch Linux
let xorg = "/usr/lib/Xorg";
if Path::new(xorg).is_file() {
return xorg;
}
// CentOS 7 /usr/bin/Xorg or param=Xorg
log::warn!("Failed to find xorg, use default Xorg.\n Please add \"allowed_users=anybody\" to \"/etc/X11/Xwrapper.config\".");
"Xorg"
}
fn start_x_server(
xauth: &str,
display: &str,
uid: u32,
gid: u32,
envs: &HashMap<&str, String>,
) -> ResultType<Child> {
let xorg = Self::get_xorg();
log::info!("Use xorg: {}", &xorg);
match Command::new(xorg)
.envs(envs)
.uid(uid)
.gid(gid)
.args(vec![
"-noreset",
"+extension",
"GLX",
"+extension",
"RANDR",
"+extension",
"RENDER",
//"-logfile",
//"/tmp/RustDesk_xorg.log",
"-config",
"/etc/rustdesk/xorg.conf",
"-auth",
xauth,
display,
])
.spawn()
{
Ok(c) => Ok(c),
Err(e) => {
bail!("Failed to start Xorg with display {}, {}", display, e);
}
}
}
fn start_x_window_manager(
uid: u32,
gid: u32,
envs: &HashMap<&str, String>,
) -> ResultType<Child> {
match Command::new("/etc/rustdesk/startwm.sh")
.envs(envs)
.uid(uid)
.gid(gid)
.spawn()
{
Ok(c) => Ok(c),
Err(e) => {
bail!("Failed to start window manager, {}", e);
}
}
}
fn stop_children(&mut self) {
self.child_exit.store(true, Ordering::SeqCst);
for _i in 1..10 {
if !self.is_child_running.load(Ordering::SeqCst) {
break;
}
std::thread::sleep(Duration::from_millis(super::SERVICE_INTERVAL));
}
if self.is_child_running.load(Ordering::SeqCst) {
log::warn!("xdesktop child is still running!");
}
}
}
fn pam_get_service_name() -> &'static str {
if Path::new("/etc/pam.d/rustdesk").is_file() {
"rustdesk"
} else {
"gdm"
}
}

View File

@ -17,6 +17,9 @@ pub mod delegate;
#[cfg(target_os = "linux")]
pub mod linux;
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
pub mod linux_desktop_manager;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::{message_proto::CursorData, ResultType};
#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))]
@ -37,7 +40,7 @@ pub fn breakdown_callback() {
#[cfg(target_os = "linux")]
crate::input_service::clear_remapped_keycode();
#[cfg(not(any(target_os = "android", target_os = "ios")))]
crate::input_service::release_modifiers();
crate::input_service::release_device_modifiers();
}
// Android

View File

@ -9,16 +9,17 @@ use hbb_common::{
message_proto::Resolution,
sleep, timeout, tokio,
};
use std::io::prelude::*;
use std::ptr::null_mut;
use std::{
collections::HashMap,
ffi::OsString,
fs, io, mem,
fs, io,
io::prelude::*,
mem,
os::windows::process::CommandExt,
path::PathBuf,
path::*,
ptr::null_mut,
sync::{Arc, Mutex},
time::{Duration, Instant},
collections::HashMap
};
use winapi::{
ctypes::c_void,
@ -717,7 +718,7 @@ pub fn is_share_rdp() -> bool {
}
pub fn set_share_rdp(enable: bool) {
let (subkey, _, _, _) = get_install_info();
let (subkey, _, _, _, _) = get_install_info();
let cmd = format!(
"reg add {} /f /v share_rdp /t REG_SZ /d \"{}\"",
subkey,
@ -810,11 +811,11 @@ fn get_valid_subkey() -> String {
return get_subkey(&app_name, false);
}
pub fn get_install_info() -> (String, String, String, String) {
pub fn get_install_info() -> (String, String, String, String, String) {
get_install_info_with_subkey(get_valid_subkey())
}
fn get_default_install_info() -> (String, String, String, String) {
fn get_default_install_info() -> (String, String, String, String, String) {
get_install_info_with_subkey(get_subkey(&crate::get_app_name(), false))
}
@ -883,7 +884,7 @@ pub fn check_update_broker_process() -> ResultType<()> {
Ok(())
}
fn get_install_info_with_subkey(subkey: String) -> (String, String, String, String) {
fn get_install_info_with_subkey(subkey: String) -> (String, String, String, String, String) {
let mut path = get_reg_of(&subkey, "InstallLocation");
if path.is_empty() {
path = get_default_install_path();
@ -894,26 +895,32 @@ fn get_install_info_with_subkey(subkey: String) -> (String, String, String, Stri
crate::get_app_name()
);
let exe = format!("{}\\{}.exe", path, crate::get_app_name());
(subkey, path, start_menu, exe)
let dll = format!("{}\\sciter.dll", path);
(subkey, path, start_menu, exe, dll)
}
pub fn copy_exe_cmd(src_exe: &str, _exe: &str, path: &str) -> String {
pub fn copy_raw_cmd(src_raw: &str, _raw: &str, _path: &str) -> String {
#[cfg(feature = "flutter")]
let main_exe = format!(
let main_raw = format!(
"XCOPY \"{}\" \"{}\" /Y /E /H /C /I /K /R /Z",
PathBuf::from(src_exe)
PathBuf::from(src_raw)
.parent()
.unwrap()
.to_string_lossy()
.to_string(),
path
_path
);
#[cfg(not(feature = "flutter"))]
let main_exe = format!(
"copy /Y \"{src_exe}\" \"{exe}\"",
src_exe = src_exe,
exe = _exe
let main_raw = format!(
"copy /Y \"{src_raw}\" \"{raw}\"",
src_raw = src_raw,
raw = _raw
);
return main_raw;
}
pub fn copy_exe_cmd(src_exe: &str, exe: &str, path: &str) -> String {
let main_exe = copy_raw_cmd(src_exe, exe, path);
return format!(
"
@ -929,8 +936,20 @@ pub fn copy_exe_cmd(src_exe: &str, _exe: &str, path: &str) -> String {
}
pub fn update_me() -> ResultType<()> {
let (_, path, _, exe) = get_install_info();
let (_, path, _, exe, _dll) = get_install_info();
let src_exe = std::env::current_exe()?.to_str().unwrap_or("").to_owned();
#[cfg(not(feature = "flutter"))]
let src_dll = std::env::current_exe()?
.parent()
.unwrap_or(&Path::new(&get_default_install_path()))
.join("sciter.dll")
.to_str()
.unwrap_or("")
.to_owned();
#[cfg(feature = "flutter")]
let copy_dll = "".to_string();
#[cfg(not(feature = "flutter"))]
let copy_dll = copy_raw_cmd(&src_dll, &_dll, &path);
let cmds = format!(
"
chcp 65001
@ -938,10 +957,12 @@ pub fn update_me() -> ResultType<()> {
taskkill /F /IM {broker_exe}
taskkill /F /IM {app_name}.exe /FI \"PID ne {cur_pid}\"
{copy_exe}
{copy_dll}
sc start {app_name}
{lic}
",
copy_exe = copy_exe_cmd(&src_exe, &exe, &path),
copy_dll = copy_dll,
broker_exe = crate::win_privacy::INJECTED_PROCESS_EXE,
app_name = crate::get_app_name(),
lic = register_licence(),
@ -980,12 +1001,14 @@ fn get_after_install(exe: &str) -> String {
pub fn install_me(options: &str, path: String, silent: bool, debug: bool) -> ResultType<()> {
let uninstall_str = get_uninstall(false);
let mut path = path.trim_end_matches('\\').to_owned();
let (subkey, _path, start_menu, exe) = get_default_install_info();
let (subkey, _path, start_menu, exe, dll) = get_default_install_info();
let mut exe = exe;
let mut _dll = dll;
if path.is_empty() {
path = _path;
} else {
exe = exe.replace(&_path, &path);
_dll = _dll.replace(&_path, &path);
}
let mut version_major = "0";
let mut version_minor = "0";
@ -1109,6 +1132,18 @@ if exist \"{tmp_path}\\{app_name} Tray.lnk\" del /f /q \"{tmp_path}\\{app_name}
app_name = crate::get_app_name(),
);
let src_exe = std::env::current_exe()?.to_str().unwrap_or("").to_string();
#[cfg(not(feature = "flutter"))]
let src_dll = std::env::current_exe()?
.parent()
.unwrap_or(&Path::new(&get_default_install_path()))
.join("sciter.dll")
.to_str()
.unwrap_or("")
.to_owned();
#[cfg(feature = "flutter")]
let copy_dll = "".to_string();
#[cfg(not(feature = "flutter"))]
let copy_dll = copy_raw_cmd(&src_dll, &_dll, &path);
let install_cert = if options.contains("driverCert") {
format!("\"{}\" --install-cert \"RustDeskIddDriver.cer\"", src_exe)
@ -1122,6 +1157,7 @@ if exist \"{tmp_path}\\{app_name} Tray.lnk\" del /f /q \"{tmp_path}\\{app_name}
chcp 65001
md \"{path}\"
{copy_exe}
{copy_dll}
reg add {subkey} /f
reg add {subkey} /f /v DisplayIcon /t REG_SZ /d \"{exe}\"
reg add {subkey} /f /v DisplayName /t REG_SZ /d \"{app_name}\"
@ -1181,6 +1217,7 @@ sc delete {app_name}
&dels
},
copy_exe = copy_exe_cmd(&src_exe, &exe, &path),
copy_dll = copy_dll
);
run_cmds(cmds, debug, "install")?;
std::thread::sleep(std::time::Duration::from_millis(2000));
@ -1193,7 +1230,7 @@ sc delete {app_name}
}
pub fn run_after_install() -> ResultType<()> {
let (_, _, _, exe) = get_install_info();
let (_, _, _, exe, _) = get_install_info();
run_cmds(get_after_install(&exe), true, "after_install")
}
@ -1227,7 +1264,7 @@ fn get_before_uninstall(kill_self: bool) -> String {
}
fn get_uninstall(kill_self: bool) -> String {
let (subkey, path, start_menu, _) = get_install_info();
let (subkey, path, start_menu, _, _) = get_install_info();
format!(
"
{before_uninstall}
@ -1331,7 +1368,7 @@ pub fn is_installed() -> bool {
service::ServiceAccess,
service_manager::{ServiceManager, ServiceManagerAccess},
};
let (_, _, _, exe) = get_install_info();
let (_, _, _, exe, _) = get_install_info();
if !std::fs::metadata(exe).is_ok() {
return false;
}
@ -1347,7 +1384,7 @@ pub fn is_installed() -> bool {
}
pub fn get_installed_version() -> String {
let (_, _, _, exe) = get_install_info();
let (_, _, _, exe, _) = get_install_info();
if let Ok(output) = std::process::Command::new(exe).arg("--version").output() {
for line in String::from_utf8_lossy(&output.stdout).lines() {
return line.to_owned();
@ -1357,7 +1394,7 @@ pub fn get_installed_version() -> String {
}
fn get_reg(name: &str) -> String {
let (subkey, _, _, _) = get_install_info();
let (subkey, _, _, _, _) = get_install_info();
get_reg_of(&subkey, name)
}
@ -1408,7 +1445,7 @@ pub fn bootstrap() {
}
fn register_licence() -> String {
let (subkey, _, _, _) = get_install_info();
let (subkey, _, _, _, _) = get_install_info();
if let Ok(lic) = get_license_from_exe_name() {
format!(
"
@ -1765,8 +1802,11 @@ pub fn send_message_to_hnwd(
pub fn create_process_with_logon(user: &str, pwd: &str, exe: &str, arg: &str) -> ResultType<()> {
let last_error_table = HashMap::from([
(ERROR_LOGON_FAILURE, "The user name or password is incorrect."),
(ERROR_ACCESS_DENIED, "Access is denied.")
(
ERROR_LOGON_FAILURE,
"The user name or password is incorrect.",
),
(ERROR_ACCESS_DENIED, "Access is denied."),
]);
unsafe {
@ -2091,7 +2131,25 @@ mod cert {
}
}
pub fn get_char_by_vk(vk: u32) -> Option<char> {
#[inline]
pub fn get_char_from_vk(vk: u32) -> Option<char> {
get_char_from_unicode(get_unicode_from_vk(vk)?)
}
pub fn get_char_from_unicode(unicode: u16) -> Option<char> {
let buff = [unicode];
if let Some(chr) = String::from_utf16(&buff[..1]).ok()?.chars().next() {
if chr.is_control() {
return None;
} else {
Some(chr)
}
} else {
None
}
}
pub fn get_unicode_from_vk(vk: u32) -> Option<u16> {
const BUF_LEN: i32 = 32;
let mut buff = [0_u16; BUF_LEN as usize];
let buff_ptr = buff.as_mut_ptr();
@ -2116,19 +2174,7 @@ pub fn get_char_by_vk(vk: u32) -> Option<char> {
ToUnicodeEx(vk, 0x00, &state as _, buff_ptr, BUF_LEN, 0, layout)
};
if len == 1 {
if let Some(chr) = String::from_utf16(&buff[..len as usize])
.ok()?
.chars()
.next()
{
if chr.is_control() {
return None;
} else {
Some(chr)
}
} else {
None
}
Some(buff[0])
} else {
None
}
@ -2151,10 +2197,10 @@ mod tests {
}
#[test]
fn test_get_char_by_vk() {
let chr = get_char_by_vk(0x41); // VK_A
fn test_get_unicode_char_by_vk() {
let chr = get_char_from_vk(0x41); // VK_A
assert_eq!(chr, Some('a'));
let chr = get_char_by_vk(VK_ESCAPE as u32); // VK_ESC
let chr = get_char_from_vk(VK_ESCAPE as u32); // VK_ESC
assert_eq!(chr, None)
}
}

View File

@ -199,8 +199,8 @@ async fn connect_and_login_2(
},
d = ui_receiver.recv() => {
match d {
Some(Data::Login((password, remember))) => {
interface.handle_login_from_ui(password, remember, &mut stream).await;
Some(Data::Login((os_username, os_password, password, remember))) => {
interface.handle_login_from_ui(os_username, os_password, password, remember, &mut stream).await;
}
_ => {}
}

View File

@ -72,6 +72,8 @@ impl RendezvousMediator {
allow_err!(super::lan::start_listening());
});
}
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
crate::platform::linux_desktop_manager::start_xdesktop();
loop {
Config::reset_online();
if Config::get_option("stop-service").is_empty() {
@ -96,6 +98,10 @@ impl RendezvousMediator {
}
sleep(1.).await;
}
// It should be better to call stop_xdesktop.
// But for server, it also is Ok without calling this method.
// #[cfg(all(target_os = "linux", feature = "linux_headless"))]
// crate::platform::linux_desktop_manager::stop_xdesktop();
}
pub async fn start(server: ServerPtr, host: String) -> ResultType<()> {

View File

@ -389,7 +389,7 @@ pub async fn start_server(is_server: bool) {
use std::sync::Once;
static ONCE: Once = Once::new();
ONCE.call_once(|| {
scrap::hwcodec::check_config_process(false);
scrap::hwcodec::check_config_process();
})
}

View File

@ -3,6 +3,8 @@ use super::{input_service::*, *};
use crate::clipboard_file::*;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::common::update_clipboard;
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
use crate::platform::linux_desktop_manager;
#[cfg(windows)]
use crate::portable_service::client as portable_client;
use crate::{
@ -16,6 +18,8 @@ use crate::{
use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel};
use crate::{ipc, VERSION};
use cidr_utils::cidr::IpCidr;
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
use hbb_common::platform::linux::run_cmds;
use hbb_common::{
config::Config,
fs,
@ -45,6 +49,9 @@ use std::{
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use system_shutdown;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use std::collections::HashSet;
pub type Sender = mpsc::UnboundedSender<(Instant, Arc<Message>)>;
lazy_static::lazy_static! {
@ -56,6 +63,22 @@ lazy_static::lazy_static! {
pub static CLICK_TIME: AtomicI64 = AtomicI64::new(0);
pub static MOUSE_MOVE_TIME: AtomicI64 = AtomicI64::new(0);
pub const LOGIN_MSG_DESKTOP_NOT_INITED: &str = "Desktop env is not inited";
pub const LOGIN_MSG_DESKTOP_SESSION_NOT_READY: &str = "Desktop session not ready";
pub const LOGIN_MSG_DESKTOP_XSESSION_FAILED: &str = "Desktop xsession failed";
pub const LOGIN_MSG_DESKTOP_SESSION_ANOTHER_USER: &str = "Desktop session another user login";
pub const LOGIN_MSG_DESKTOP_XORG_NOT_FOUND: &str = "Desktop xorg not found";
// ls /usr/share/xsessions/
pub const LOGIN_MSG_DESKTOP_NO_DESKTOP: &str = "Desktop none";
pub const LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_EMPTY: &str =
"Desktop session not ready, password empty";
pub const LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_WRONG: &str =
"Desktop session not ready, password wrong";
pub const LOGIN_MSG_PASSWORD_EMPTY: &str = "Empty Password";
pub const LOGIN_MSG_PASSWORD_WRONG: &str = "Wrong Password";
pub const LOGIN_MSG_NO_PASSWORD_ACCESS: &str = "No Password Access";
pub const LOGIN_MSG_OFFLINE: &str = "Offline";
#[derive(Clone, Default)]
pub struct ConnInner {
id: i32,
@ -132,6 +155,12 @@ pub struct Connection {
voice_call_request_timestamp: Option<NonZeroI64>,
audio_input_device_before_voice_call: Option<String>,
options_in_login: Option<OptionMessage>,
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pressed_modifiers: HashSet<rdev::Key>,
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
rx_cm_stream_ready: mpsc::Receiver<()>,
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
tx_desktop_ready: mpsc::Sender<()>,
}
impl ConnInner {
@ -192,6 +221,10 @@ impl Connection {
let (tx_video, mut rx_video) = mpsc::unbounded_channel::<(Instant, Arc<Message>)>();
let (tx_input, _rx_input) = std_mpsc::channel();
let mut hbbs_rx = crate::hbbs_http::sync::signal_receiver();
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let (tx_cm_stream_ready, _rx_cm_stream_ready) = mpsc::channel(1);
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let (_tx_desktop_ready, rx_desktop_ready) = mpsc::channel(1);
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let tx_cloned = tx.clone();
@ -243,10 +276,18 @@ impl Connection {
voice_call_request_timestamp: None,
audio_input_device_before_voice_call: None,
options_in_login: None,
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pressed_modifiers: Default::default(),
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
rx_cm_stream_ready: _rx_cm_stream_ready,
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
tx_desktop_ready: _tx_desktop_ready,
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
tokio::spawn(async move {
if let Err(err) = start_ipc(rx_to_cm, tx_from_cm).await {
if let Err(err) =
start_ipc(rx_to_cm, tx_from_cm, rx_desktop_ready, tx_cm_stream_ready).await
{
log::error!("ipc to connection manager exit: {}", err);
}
});
@ -533,7 +574,7 @@ impl Connection {
let _ = privacy_mode::turn_off_privacy(0);
}
video_service::notify_video_frame_fetched(id, None);
scrap::codec::Encoder::update_video_encoder(id, scrap::codec::EncoderUpdate::Remove);
scrap::codec::Encoder::update(id, scrap::codec::EncodingUpdate::Remove);
video_service::VIDEO_QOS.lock().unwrap().reset();
if conn.authorized {
password::update_temporary_password();
@ -618,7 +659,6 @@ impl Connection {
}
#[cfg(target_os = "linux")]
clear_remapped_keycode();
release_modifiers();
log::info!("Input thread exited");
}
@ -854,22 +894,17 @@ impl Connection {
if crate::platform::current_is_wayland() {
platform_additions.insert("is_wayland".into(), json!(true));
}
#[cfg(feature = "linux_headless")]
if linux_desktop_manager::is_headless() {
platform_additions.insert("headless".into(), json!(true));
}
if !platform_additions.is_empty() {
pi.platform_additions =
serde_json::to_string(&platform_additions).unwrap_or("".into());
}
}
#[cfg(feature = "hwcodec")]
{
let (h264, h265) = scrap::codec::Encoder::supported_encoding();
pi.encoding = Some(SupportedEncoding {
h264,
h265,
..Default::default()
})
.into();
}
pi.encoding = Some(scrap::codec::Encoder::supported_encoding()).into();
if self.port_forward_socket.is_some() {
let mut msg_out = Message::new();
@ -881,7 +916,9 @@ impl Connection {
#[cfg(target_os = "linux")]
if !self.file_transfer.is_some() && !self.port_forward_socket.is_some() {
let dtype = crate::platform::linux::get_display_server();
if dtype != "x11" && dtype != "wayland" {
if dtype != crate::platform::linux::DISPLAY_SERVER_X11
&& dtype != crate::platform::linux::DISPLAY_SERVER_WAYLAND
{
res.set_error(format!(
"Unsupported display server type \"{}\", x11 or wayland expected",
dtype
@ -1145,21 +1182,21 @@ impl Connection {
self.lr = lr.clone();
if let Some(o) = lr.option.as_ref() {
self.options_in_login = Some(o.clone());
if let Some(q) = o.video_codec_state.clone().take() {
scrap::codec::Encoder::update_video_encoder(
if let Some(q) = o.supported_decoding.clone().take() {
scrap::codec::Encoder::update(
self.inner.id(),
scrap::codec::EncoderUpdate::State(q),
scrap::codec::EncodingUpdate::New(q),
);
} else {
scrap::codec::Encoder::update_video_encoder(
scrap::codec::Encoder::update(
self.inner.id(),
scrap::codec::EncoderUpdate::DisableHwIfNotExist,
scrap::codec::EncodingUpdate::NewOnlyVP9,
);
}
} else {
scrap::codec::Encoder::update_video_encoder(
scrap::codec::Encoder::update(
self.inner.id(),
scrap::codec::EncoderUpdate::DisableHwIfNotExist,
scrap::codec::EncodingUpdate::NewOnlyVP9,
);
}
self.video_ack_required = lr.video_ack_required;
@ -1223,8 +1260,28 @@ impl Connection {
}
_ => {}
}
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
let desktop_err = match lr.os_login.as_ref() {
Some(os_login) => {
linux_desktop_manager::try_start_desktop(&os_login.username, &os_login.password)
}
None => linux_desktop_manager::try_start_desktop("", ""),
};
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
let is_headless = linux_desktop_manager::is_headless();
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
let wait_ipc_timeout = 10_000;
// If err is LOGIN_MSG_DESKTOP_SESSION_NOT_READY, just keep this msg and go on checking password.
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
if !desktop_err.is_empty() && desktop_err != LOGIN_MSG_DESKTOP_SESSION_NOT_READY {
self.send_login_error(desktop_err).await;
return true;
}
if !hbb_common::is_ipv4_str(&lr.username) && lr.username != Config::get_id() {
self.send_login_error("Offline").await;
self.send_login_error(LOGIN_MSG_OFFLINE).await;
} else if password::approve_mode() == ApproveMode::Click
|| password::approve_mode() == ApproveMode::Both && !password::has_valid_password()
{
@ -1232,7 +1289,7 @@ impl Connection {
if hbb_common::get_version_number(&lr.version)
>= hbb_common::get_version_number("1.2.0")
{
self.send_login_error("No Password Access").await;
self.send_login_error(LOGIN_MSG_NO_PASSWORD_ACCESS).await;
}
return true;
} else if password::approve_mode() == ApproveMode::Password
@ -1241,12 +1298,38 @@ impl Connection {
self.send_login_error("Connection not allowed").await;
return false;
} else if self.is_recent_session() {
self.try_start_cm(lr.my_id, lr.my_name, true);
self.send_logon_response().await;
if self.port_forward_socket.is_some() {
return false;
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
if desktop_err.is_empty() {
#[cfg(target_os = "linux")]
if is_headless {
self.tx_desktop_ready.send(()).await.ok();
let _res = timeout(wait_ipc_timeout, self.rx_cm_stream_ready.recv()).await;
}
self.try_start_cm(lr.my_id, lr.my_name, true);
self.send_logon_response().await;
if self.port_forward_socket.is_some() {
return false;
}
} else {
self.send_login_error(desktop_err).await;
}
#[cfg(not(all(target_os = "linux", feature = "linux_headless")))]
{
self.try_start_cm(lr.my_id, lr.my_name, true);
self.send_logon_response().await;
if self.port_forward_socket.is_some() {
return false;
}
}
} else if lr.password.is_empty() {
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
if desktop_err.is_empty() {
self.try_start_cm(lr.my_id, lr.my_name, false);
} else {
self.send_login_error(LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_EMPTY)
.await;
}
#[cfg(not(all(target_os = "linux", feature = "linux_headless")))]
self.try_start_cm(lr.my_id, lr.my_name, false);
} else {
let mut failure = LOGIN_FAILURES
@ -1288,16 +1371,46 @@ impl Connection {
.lock()
.unwrap()
.insert(self.ip.clone(), failure);
self.send_login_error("Wrong Password").await;
self.try_start_cm(lr.my_id, lr.my_name, false);
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
if desktop_err.is_empty() {
self.send_login_error(LOGIN_MSG_PASSWORD_WRONG).await;
self.try_start_cm(lr.my_id, lr.my_name, false);
} else {
self.send_login_error(LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_WRONG)
.await;
}
#[cfg(not(all(target_os = "linux", feature = "linux_headless")))]
{
self.send_login_error(LOGIN_MSG_PASSWORD_WRONG).await;
self.try_start_cm(lr.my_id, lr.my_name, false);
}
} else {
if failure.0 != 0 {
LOGIN_FAILURES.lock().unwrap().remove(&self.ip);
}
self.try_start_cm(lr.my_id, lr.my_name, true);
self.send_logon_response().await;
if self.port_forward_socket.is_some() {
return false;
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
if desktop_err.is_empty() {
#[cfg(target_os = "linux")]
if is_headless {
self.tx_desktop_ready.send(()).await.ok();
let _res =
timeout(wait_ipc_timeout, self.rx_cm_stream_ready.recv()).await;
}
self.send_logon_response().await;
self.try_start_cm(lr.my_id, lr.my_name, true);
if self.port_forward_socket.is_some() {
return false;
}
} else {
self.send_login_error(desktop_err).await;
}
#[cfg(not(all(target_os = "linux", feature = "linux_headless")))]
{
self.send_logon_response().await;
self.try_start_cm(lr.my_id, lr.my_name, true);
if self.port_forward_socket.is_some() {
return false;
}
}
}
}
@ -1366,6 +1479,30 @@ impl Connection {
} else {
me.press
};
let key = match me.mode.enum_value_or_default() {
KeyboardMode::Map => {
Some(crate::keyboard::keycode_to_rdev_key(me.chr()))
}
KeyboardMode::Translate => {
if let Some(key_event::Union::Chr(code)) = me.union {
Some(crate::keyboard::keycode_to_rdev_key(code & 0x0000FFFF))
} else {
None
}
}
_ => None,
}
.filter(crate::keyboard::is_modifier);
if let Some(key) = key {
if is_press {
self.pressed_modifiers.insert(key);
} else {
self.pressed_modifiers.remove(&key);
}
}
if is_press {
match me.union {
Some(key_event::Union::Unicode(_))
@ -1758,11 +1895,8 @@ impl Connection {
.unwrap()
.update_user_fps(o.custom_fps as _);
}
if let Some(q) = o.video_codec_state.clone().take() {
scrap::codec::Encoder::update_video_encoder(
self.inner.id(),
scrap::codec::EncoderUpdate::State(q),
);
if let Some(q) = o.supported_decoding.clone().take() {
scrap::codec::Encoder::update(self.inner.id(), scrap::codec::EncodingUpdate::New(q));
}
if let Ok(q) = o.lock_after_session_end.enum_value() {
if q != BoolOption::NotSet {
@ -2023,6 +2157,14 @@ impl Connection {
})
.count();
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn release_pressed_modifiers(&mut self) {
for modifier in self.pressed_modifiers.iter() {
rdev::simulate(&rdev::EventType::KeyRelease(*modifier)).ok();
}
self.pressed_modifiers.clear();
}
}
pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
@ -2036,6 +2178,8 @@ pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
async fn start_ipc(
mut rx_to_cm: mpsc::UnboundedReceiver<ipc::Data>,
tx_from_cm: mpsc::UnboundedSender<ipc::Data>,
mut _rx_desktop_ready: mpsc::Receiver<()>,
tx_stream_ready: mpsc::Sender<()>,
) -> ResultType<()> {
loop {
if !crate::platform::is_prelogin() {
@ -2051,6 +2195,34 @@ async fn start_ipc(
if password::hide_cm() {
args.push("--hide");
};
#[cfg(target_os = "linux")]
#[cfg(not(feature = "linux_headless"))]
let user = None;
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
let mut user = None;
// Cm run as user, wait until desktop session is ready.
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
if linux_desktop_manager::is_headless() {
let mut username = linux_desktop_manager::get_username();
loop {
if !username.is_empty() {
break;
}
let _res = timeout(1_000, _rx_desktop_ready.recv()).await;
username = linux_desktop_manager::get_username();
}
let uid = {
let output = run_cmds(&format!("id -u {}", &username))?;
let output = output.trim();
if output.is_empty() || !output.parse::<i32>().is_ok() {
bail!("Invalid username {}", &username);
}
output.to_string()
};
user = Some((uid, username));
args = vec!["--cm-no-ui"];
}
let run_done;
if crate::platform::is_root() {
let mut res = Ok(None);
@ -2063,7 +2235,7 @@ async fn start_ipc(
#[cfg(target_os = "linux")]
{
log::debug!("Start cm");
res = crate::platform::run_as_user(args.clone(), None);
res = crate::platform::run_as_user(args.clone(), user.clone());
}
if res.is_ok() {
break;
@ -2095,6 +2267,8 @@ async fn start_ipc(
bail!("Failed to connect to connection manager");
}
}
let _res = tx_stream_ready.send(()).await;
let mut stream = stream.unwrap();
loop {
tokio::select! {
@ -2220,3 +2394,10 @@ impl Default for PortableState {
}
}
}
impl Drop for Connection {
fn drop(&mut self) {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
self.release_pressed_modifiers();
}
}

View File

@ -16,9 +16,19 @@ use std::{
thread,
time::{self, Instant},
};
#[cfg(target_os = "windows")]
use winapi::um::winuser::{
ActivateKeyboardLayout, GetForegroundWindow, GetKeyboardLayout, GetWindowThreadProcessId,
VkKeyScanW,
};
const INVALID_CURSOR_POS: i32 = i32::MIN;
#[cfg(target_os = "windows")]
lazy_static::lazy_static! {
static ref LAST_HKL: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
}
#[derive(Default)]
struct StateCursor {
hcursor: u64,
@ -551,7 +561,7 @@ fn record_key_to_key(record_key: u64) -> Option<Key> {
}
}
pub fn release_modifiers() {
pub fn release_device_modifiers() {
let mut en = ENIGO.lock().unwrap();
for modifier in [
Key::Shift,
@ -1242,7 +1252,45 @@ fn translate_process_code(code: u32, down: bool) {
};
}
#[cfg(target_os = "windows")]
fn check_update_input_layout() {
unsafe {
let foreground_thread_id =
GetWindowThreadProcessId(GetForegroundWindow(), std::ptr::null_mut());
let layout = GetKeyboardLayout(foreground_thread_id);
let layout_u32 = layout as u32;
let mut last_layout_lock = LAST_HKL.lock().unwrap();
if *last_layout_lock == 0 || *last_layout_lock != layout_u32 {
let res = ActivateKeyboardLayout(layout, 0);
if res == layout {
*last_layout_lock = layout_u32;
} else {
log::error!("Failed to call ActivateKeyboardLayout, {}", layout_u32);
}
}
}
}
fn translate_keyboard_mode(evt: &KeyEvent) {
// --server could not detect the input layout change.
// This is a temporary workaround.
//
// There may be a better way to detect and handle the input layout change.
// while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
// {
// ...
// if (msg.message == WM_INPUTLANGCHANGE)
// {
// // handle WM_INPUTLANGCHANGE message here
// check_update_input_layout();
// }
// TranslateMessage(&msg);
// DispatchMessage(&msg);
// ...
// }
#[cfg(target_os = "windows")]
check_update_input_layout();
match &evt.union {
Some(key_event::Union::Seq(seq)) => {
// Fr -> US
@ -1280,28 +1328,117 @@ fn translate_keyboard_mode(evt: &KeyEvent) {
Some(key_event::Union::Unicode(..)) => {
// Do not handle unicode for now.
}
#[cfg(target_os = "windows")]
Some(key_event::Union::Win2winHotkey(code)) => {
simulate_win2win_hotkey(*code, evt.down);
}
_ => {
log::debug!("Unreachable. Unexpected key event {:?}", &evt);
}
}
}
#[cfg(target_os = "windows")]
fn simulate_win2win_hotkey(code: u32, down: bool) {
let unicode: u16 = (code & 0x0000FFFF) as u16;
if down {
// Try convert unicode to virtual keycode first.
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-vkkeyscanw
let res = unsafe { VkKeyScanW(unicode) };
if res as u16 != 0xFFFF {
let vk = res & 0x00FF;
let flag = res >> 8;
let modifiers = [rdev::Key::ShiftLeft, rdev::Key::ControlLeft, rdev::Key::Alt];
let mod_len = modifiers.len();
for pos in 0..mod_len {
if flag & (0x0001 << pos) != 0 {
allow_err!(rdev::simulate(&EventType::KeyPress(modifiers[pos])));
}
}
allow_err!(rdev::simulate_code(Some(vk as _), None, true));
allow_err!(rdev::simulate_code(Some(vk as _), None, false));
for pos in 0..mod_len {
let rpos = mod_len - 1 - pos;
if flag & (0x0001 << rpos) != 0 {
allow_err!(rdev::simulate(&EventType::KeyRelease(modifiers[rpos])));
}
}
return;
}
}
let keycode: u16 = ((code >> 16) & 0x0000FFFF) as u16;
allow_err!(rdev::simulate_code(Some(keycode), None, down));
}
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
fn skip_led_sync_control_key(_evt: &KeyEvent) -> bool {
false
}
// LockModesHandler should not be created when single meta is pressing and releasing.
// Because the drop function may insert "CapsLock Click" and "NumLock Click", which breaks single meta click.
// https://github.com/rustdesk/rustdesk/issues/3928#issuecomment-1496936687
// https://github.com/rustdesk/rustdesk/issues/3928#issuecomment-1500415822
// https://github.com/rustdesk/rustdesk/issues/3928#issuecomment-1500773473
#[cfg(any(target_os = "windows", target_os = "linux"))]
fn skip_led_sync_control_key(key: &ControlKey) -> bool {
[
ControlKey::Control,
ControlKey::Meta,
ControlKey::Shift,
ControlKey::Alt,
ControlKey::Tab,
ControlKey::Return,
]
.contains(key)
}
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
fn skip_led_sync_rdev_key(_evt: &KeyEvent) -> bool {
false
}
#[cfg(any(target_os = "windows", target_os = "linux"))]
fn skip_led_sync_rdev_key(key: &RdevKey) -> bool {
[
RdevKey::ControlLeft,
RdevKey::ControlRight,
RdevKey::MetaLeft,
RdevKey::MetaRight,
RdevKey::ShiftRight,
RdevKey::ShiftRight,
RdevKey::Alt,
RdevKey::AltGr,
RdevKey::Tab,
RdevKey::Return,
]
.contains(key)
}
pub fn handle_key_(evt: &KeyEvent) {
if EXITING.load(Ordering::SeqCst) {
return;
}
let _lock_mode_handler = match &evt.union {
Some(key_event::Union::Unicode(..)) | Some(key_event::Union::Seq(..)) => {
Some(LockModesHandler::new(&evt))
let mut _lock_mode_handler = None;
match (&evt.union, evt.mode.enum_value_or(KeyboardMode::Legacy)) {
(Some(key_event::Union::Unicode(..)) | Some(key_event::Union::Seq(..)), _) => {
_lock_mode_handler = Some(LockModesHandler::new(&evt));
}
_ => {
if evt.down {
Some(LockModesHandler::new(&evt))
} else {
None
(Some(key_event::Union::ControlKey(ck)), _) => {
let key = ck.enum_value_or(ControlKey::Unknown);
if !skip_led_sync_control_key(&key) {
_lock_mode_handler = Some(LockModesHandler::new(&evt));
}
}
(Some(key_event::Union::Chr(code)), KeyboardMode::Map | KeyboardMode::Translate) => {
let key = crate::keycode_to_rdev_key(*code);
if !skip_led_sync_rdev_key(&key) {
_lock_mode_handler = Some(LockModesHandler::new(evt));
}
}
_ => {}
};
match evt.mode.unwrap() {

View File

@ -189,17 +189,6 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
}
}
#[inline]
fn wait_prelogin(&self) {
#[cfg(target_os = "linux")]
while self.active() {
if crate::platform::linux::is_prelogin() {
break;
}
thread::sleep(time::Duration::from_millis(300));
}
}
pub fn repeat<S, F>(&self, interval_ms: u64, callback: F)
where
F: 'static + FnMut(Self, &mut S) -> ResultType<()> + Send,
@ -209,8 +198,6 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
let mut callback = callback;
let sp = self.clone();
let thread = thread::spawn(move || {
sp.wait_prelogin();
let mut state = S::default();
let mut may_reset = false;
while sp.active() {
@ -234,6 +221,7 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
thread::sleep(interval - elapsed);
}
}
log::info!("Service {} exit", sp.name());
});
self.0.write().unwrap().handle = Some(thread);
}
@ -245,8 +233,6 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
let sp = self.clone();
let mut callback = callback;
let thread = thread::spawn(move || {
sp.wait_prelogin();
let mut error_timeout = HIBERNATE_TIMEOUT;
while sp.active() {
if sp.has_subscribes() {
@ -271,6 +257,7 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
}
thread::sleep(time::Duration::from_millis(HIBERNATE_TIMEOUT));
}
log::info!("Service {} exit", sp.name());
});
self.0.write().unwrap().handle = Some(thread);
}

View File

@ -1,7 +1,7 @@
use super::*;
use std::time::Duration;
pub const FPS: u8 = 30;
pub const MIN_FPS: u8 = 10;
pub const MIN_FPS: u8 = 1;
pub const MAX_FPS: u8 = 120;
trait Percent {
fn as_percent(&self) -> u32;
@ -221,7 +221,9 @@ impl VideoQoS {
}
pub fn reset(&mut self) {
*self = Default::default();
self.fps = FPS;
self.user_fps = FPS;
self.updated = true;
}
pub fn check_abr_config(&mut self) -> bool {

View File

@ -31,7 +31,7 @@ use scrap::{
codec::{Encoder, EncoderCfg, HwEncoderConfig},
record::{Recorder, RecorderContext},
vpxcodec::{VpxEncoderConfig, VpxVideoCodecId},
Display, TraitCapturer,
CodecName, Display, TraitCapturer,
};
#[cfg(windows)]
use std::sync::Once;
@ -468,21 +468,29 @@ fn run(sp: GenericService) -> ResultType<()> {
drop(video_qos);
log::info!("init bitrate={}, abr enabled:{}", bitrate, abr);
let encoder_cfg = match Encoder::current_hw_encoder_name() {
Some(codec_name) => EncoderCfg::HW(HwEncoderConfig {
codec_name,
width: c.width,
height: c.height,
bitrate: bitrate as _,
}),
None => EncoderCfg::VPX(VpxEncoderConfig {
width: c.width as _,
height: c.height as _,
timebase: [1, 1000], // Output timestamp precision
bitrate,
codec: VpxVideoCodecId::VP9,
num_threads: (num_cpus::get() / 2) as _,
}),
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,
bitrate: bitrate as _,
})
}
name @ (scrap::CodecName::VP8 | scrap::CodecName::VP9) => {
EncoderCfg::VPX(VpxEncoderConfig {
width: c.width as _,
height: c.height as _,
timebase: [1, 1000], // Output timestamp precision
bitrate,
codec: if name == scrap::CodecName::VP8 {
VpxVideoCodecId::VP8
} else {
VpxVideoCodecId::VP9
},
num_threads: (num_cpus::get() / 2) as _,
})
}
};
let mut encoder;
@ -526,7 +534,7 @@ fn run(sp: GenericService) -> ResultType<()> {
let mut try_gdi = 1;
#[cfg(windows)]
log::info!("gdi: {}", c.is_gdi());
let codec_name = Encoder::current_hw_encoder_name();
let codec_name = Encoder::negotiated_codec();
let recorder = get_recorder(c.width, c.height, &codec_name);
#[cfg(windows)]
start_uac_elevation_check();
@ -539,7 +547,7 @@ fn run(sp: GenericService) -> ResultType<()> {
check_uac_switch(c.privacy_mode_id, c._capturer_privacy_mode_id)?;
let mut video_qos = VIDEO_QOS.lock().unwrap();
if video_qos.check_if_updated() {
if video_qos.check_if_updated() && video_qos.target_bitrate > 0 {
log::debug!(
"qos is updated, target_bitrate:{}, fps:{}",
video_qos.target_bitrate,
@ -557,7 +565,7 @@ fn run(sp: GenericService) -> ResultType<()> {
*SWITCH.lock().unwrap() = true;
bail!("SWITCH");
}
if codec_name != Encoder::current_hw_encoder_name() {
if codec_name != Encoder::negotiated_codec() {
bail!("SWITCH");
}
#[cfg(windows)]
@ -603,8 +611,14 @@ fn run(sp: GenericService) -> ResultType<()> {
let time = now - start;
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
match frame {
scrap::Frame::VP8(data) => {
let send_conn_ids =
handle_one_frame_encoded(VpxVideoCodecId::VP8, &sp, data, ms)?;
frame_controller.set_send(now, send_conn_ids);
}
scrap::Frame::VP9(data) => {
let send_conn_ids = handle_one_frame_encoded(&sp, data, ms)?;
let send_conn_ids =
handle_one_frame_encoded(VpxVideoCodecId::VP9, &sp, data, ms)?;
frame_controller.set_send(now, send_conn_ids);
}
scrap::Frame::RAW(data) => {
@ -717,12 +731,11 @@ fn run(sp: GenericService) -> ResultType<()> {
fn get_recorder(
width: usize,
height: usize,
codec_name: &Option<String>,
codec_name: &CodecName,
) -> Arc<Mutex<Option<Recorder>>> {
#[cfg(not(target_os = "ios"))]
let recorder = if !Config::get_option("allow-auto-record-incoming").is_empty() {
use crate::hbbs_http::record_upload;
use scrap::record::RecordCodecID::*;
let tx = if record_upload::is_enable() {
let (tx, rx) = std::sync::mpsc::channel();
@ -731,16 +744,6 @@ fn get_recorder(
} else {
None
};
let codec_id = match codec_name {
Some(name) => {
if name.contains("264") {
H264
} else {
H265
}
}
None => VP9,
};
Recorder::new(RecorderContext {
server: true,
id: Config::get_id(),
@ -748,7 +751,7 @@ fn get_recorder(
filename: "".to_owned(),
width,
height,
codec_id,
format: codec_name.into(),
tx,
})
.map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))))
@ -775,19 +778,6 @@ fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> Resu
Ok(())
}
#[inline]
#[cfg(any(target_os = "android", target_os = "ios"))]
fn create_msg(vp9s: Vec<EncodedVideoFrame>) -> Message {
let mut msg_out = Message::new();
let mut vf = VideoFrame::new();
vf.set_vp9s(EncodedVideoFrames {
frames: vp9s.into(),
..Default::default()
});
msg_out.set_video_frame(vf);
msg_out
}
#[inline]
fn handle_one_frame(
sp: &GenericService,
@ -820,6 +810,7 @@ fn handle_one_frame(
#[inline]
#[cfg(any(target_os = "android", target_os = "ios"))]
pub fn handle_one_frame_encoded(
codec: VpxVideoCodecId,
sp: &GenericService,
frame: &[u8],
ms: i64,
@ -831,13 +822,13 @@ pub fn handle_one_frame_encoded(
}
Ok(())
})?;
let vp9_frame = EncodedVideoFrame {
let vpx_frame = EncodedVideoFrame {
data: frame.to_vec().into(),
key: true,
pts: ms,
..Default::default()
};
let send_conn_ids = sp.send_video_frame(create_msg(vec![vp9_frame]));
let send_conn_ids = sp.send_video_frame(scrap::VpxEncoder::create_msg(codec, vec![vpx_frame]));
Ok(send_conn_ids)
}

View File

@ -142,6 +142,10 @@ div.password input {
font-size: 1.2em;
}
div.username input {
font-size: 1.2em;
}
svg {
background: none;
}

View File

@ -252,7 +252,7 @@ function msgbox(type, title, content, link="", callback=null, height=180, width=
view.close();
return;
}
handler.login(res.password, res.remember);
handler.login("", "", res.password, res.remember);
if (!is_port_forward) {
// Specially handling file transfer for no permission hanging issue (including 60ms
// timer in setPermission.
@ -262,6 +262,30 @@ function msgbox(type, title, content, link="", callback=null, height=180, width=
else msgbox("connecting", "Connecting...", "Logging in...");
}
};
} else if (type == "session-login" || type == "session-re-login") {
callback = function (res) {
if (!res) {
view.close();
return;
}
handler.login(res.osusername, res.ospassword, "", false);
if (!is_port_forward) {
if (is_file_transfer) handler.msgbox("connecting", "Connecting...", "Logging in...");
else msgbox("connecting", "Connecting...", "Logging in...");
}
};
} else if (type.indexOf("session-login") >= 0) {
callback = function (res) {
if (!res) {
view.close();
return;
}
handler.login(res.osusername, res.ospassword, res.password, res.remember);
if (!is_port_forward) {
if (is_file_transfer) handler.msgbox("connecting", "Connecting...", "Logging in...");
else msgbox("connecting", "Connecting...", "Logging in...");
}
};
} else if (type.indexOf("custom") < 0 && !is_port_forward && !callback) {
callback = function() { view.close(); }
} else if (type == 'wait-remote-accept-nook') {

View File

@ -161,8 +161,8 @@ class Header: Reactor.Component {
}
function renderDisplayPop() {
var codecs = handler.supported_hwcodec();
var show_codec = handler.has_hwcodec() && (codecs[0] || codecs[1]);
var codecs = handler.alternative_codecs();
var show_codec = codecs[0] || codecs[1] || codecs[2];
var cursor_embedded = false;
if ((pi.displays || []).length > 0) {
@ -186,9 +186,10 @@ class Header: Reactor.Component {
{show_codec ? <div>
<div .separator />
<li #auto type="codec-preference"><span>{svg_checkmark}</span>Auto</li>
{codecs[0] ? <li #vp8 type="codec-preference"><span>{svg_checkmark}</span>VP8</li> : ""}
<li #vp9 type="codec-preference"><span>{svg_checkmark}</span>VP9</li>
{codecs[0] ? <li #h264 type="codec-preference"><span>{svg_checkmark}</span>H264</li> : ""}
{codecs[1] ? <li #h265 type="codec-preference"><span>{svg_checkmark}</span>H265</li> : ""}
{codecs[1] ? <li #h264 type="codec-preference"><span>{svg_checkmark}</span>H264</li> : ""}
{codecs[2] ? <li #h265 type="codec-preference"><span>{svg_checkmark}</span>H265</li> : ""}
</div> : ""}
<div .separator />
{!cursor_embedded && <li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>{translate('Show remote cursor')}</li>}

View File

@ -32,7 +32,7 @@ class MsgboxComponent: Reactor.Component {
}
function getIcon(color) {
if (this.type == "input-password") {
if (this.type == "input-password" || this.type == "session-login" || this.type == "session-login-password") {
return <svg viewBox="0 0 505 505"><circle cx="252.5" cy="252.5" r="252.5" fill={color}/><path d="M271.9 246.1c29.2 17.5 67.6 13.6 92.7-11.5 29.7-29.7 29.7-77.8 0-107.4s-77.8-29.7-107.4 0c-25.1 25.1-29 63.5-11.5 92.7L118.1 347.4l26.2 26.2 26.4 26.4 10.6-10.6-10.1-10.1 9.7-9.7 10.1 10.1 10.6-10.6-10.1-10 9.7-9.7 10.1 10.1 10.6-10.6-26.4-26.3 76.4-76.5z" fill="#fff"/><circle cx="337.4" cy="154.4" r="17.7" fill={color}/></svg>;
}
if (this.type == "connecting") {
@ -41,7 +41,7 @@ class MsgboxComponent: Reactor.Component {
if (this.type == "success") {
return <svg viewBox="0 0 512 512"><circle cx="256" cy="256" r="256" fill={color} /><path fill="#fff" d="M235.472 392.08l-121.04-94.296 34.416-44.168 74.328 57.904 122.672-177.016 46.032 31.888z"/></svg>;
}
if (this.type.indexOf("error") >= 0 || this.type == "re-input-password") {
if (this.type.indexOf("error") >= 0 || this.type == "re-input-password" || this.type == "session-re-login" || this.type == "session-login-re-password") {
return <svg viewBox="0 0 512 512"><ellipse cx="256" cy="256" rx="256" ry="255.832" fill={color}/><g fill="#fff"><path d="M376.812 337.18l-39.592 39.593-201.998-201.999 39.592-39.592z"/><path d="M376.818 174.825L174.819 376.824l-39.592-39.592 201.999-201.999z"/></g></svg>;
}
return null;
@ -56,11 +56,36 @@ class MsgboxComponent: Reactor.Component {
</div>;
}
function getInputUserPasswordContent() {
return <div .form>
<div>{translate("OS Username")}</div>
<div .username><input name='osusername' type='text' .outline-focus /></div>
<div>{translate("OS Password")}</div>
<PasswordComponent name='ospassword' />
<div></div>
</div>;
}
function getXsessionPasswordContent() {
return <div .form>
<div>{translate("OS Username")}</div>
<div .username><input name='osusername' type='text' .outline-focus /></div>
<div>{translate("OS Password")}</div>
<PasswordComponent name='ospassword' />
<div>{translate('Please enter your password')}</div>
<PasswordComponent />
<div><button|checkbox(remember) {ts}>{translate('Remember password')}</button></div>
</div>;
}
function getContent() {
if (this.type == "input-password") {
return this.getInputPasswordContent();
}
if (this.type == "custom-os-password") {
} else if (this.type == "session-login") {
return this.getInputUserPasswordContent();
} else if (this.type == "session-login-password") {
return this.getXsessionPasswordContent();
} else if (this.type == "custom-os-password") {
var ts = this.auto_login ? { checked: true } : {};
return <div .form>
<PasswordComponent value={this.content} />
@ -71,13 +96,13 @@ class MsgboxComponent: Reactor.Component {
}
function getColor() {
if (this.type == "input-password" || this.type == "custom-os-password") {
if (this.type == "input-password" || this.type == "custom-os-password" || this.type == "session-login" || this.type == "session-login-password") {
return "#AD448E";
}
if (this.type == "success") {
return "#32bea6";
}
if (this.type.indexOf("error") >= 0 || this.type == "re-input-password") {
if (this.type.indexOf("error") >= 0 || this.type == "re-input-password" || this.type == "session-re-login" || this.type == "session-login-re-password") {
return "#e04f5f";
}
return "#2C8CFF";
@ -177,6 +202,16 @@ class MsgboxComponent: Reactor.Component {
this.update();
return;
}
if (this.type == "session-re-login") {
this.type = "session-login";
this.update();
return;
}
if (this.type == "session-login-re-password") {
this.type = "session-login-password";
this.update();
return;
}
var values = this.getValues();
if (this.callback) {
var self = this;
@ -238,6 +273,21 @@ class MsgboxComponent: Reactor.Component {
return;
}
}
if (this.type == "session-login") {
values.osusername = (values.osusername || "").trim();
values.ospassword = (values.ospassword || "").trim();
if (!values.osusername || !values.ospassword) {
return;
}
}
if (this.type == "session-login-password") {
values.password = (values.password || "").trim();
values.osusername = (values.osusername || "").trim();
values.ospassword = (values.ospassword || "").trim();
if (!values.osusername || !values.ospassword || !values.password) {
return;
}
}
return values;
}

View File

@ -20,7 +20,6 @@ use hbb_common::{
use crate::{
client::*,
ui_interface::has_hwcodec,
ui_session_interface::{InvokeUiSession, Session},
};
@ -197,7 +196,14 @@ impl InvokeUiSession for SciterHandler {
self.call("confirmDeleteFiles", &make_args!(id, i, name));
}
fn override_file_confirm(&self, id: i32, file_num: i32, to: String, is_upload: bool, is_identical: bool) {
fn override_file_confirm(
&self,
id: i32,
file_num: i32,
to: String,
is_upload: bool,
is_identical: bool,
) {
self.call(
"overrideFileConfirm",
&make_args!(id, file_num, to, is_upload, is_identical),
@ -398,7 +404,7 @@ impl sciter::EventHandler for SciterSession {
fn is_file_transfer();
fn is_port_forward();
fn is_rdp();
fn login(String, bool);
fn login(String, String, String, bool);
fn new_rdp();
fn send_mouse(i32, i32, i32, bool, bool, bool, bool);
fn enter();
@ -451,8 +457,7 @@ impl sciter::EventHandler for SciterSession {
fn set_write_override(i32, i32, bool, bool, bool);
fn get_keyboard_mode();
fn save_keyboard_mode(String);
fn has_hwcodec();
fn supported_hwcodec();
fn alternative_codecs();
fn change_prefer_codec();
fn restart_remote_device();
fn request_voice_call();
@ -504,10 +509,6 @@ impl SciterSession {
v
}
fn has_hwcodec(&self) -> bool {
has_hwcodec()
}
pub fn t(&self, name: String) -> String {
crate::client::translate(name)
}
@ -516,9 +517,10 @@ impl SciterSession {
super::get_icon()
}
fn supported_hwcodec(&self) -> Value {
let (h264, h265) = self.0.supported_hwcodec();
fn alternative_codecs(&self) -> Value {
let (vp8, h264, h265) = self.0.alternative_codecs();
let mut v = Value::array(0);
v.push(vp8);
v.push(h264);
v.push(h265);
v

Some files were not shown because too many files have changed in this diff Show More