Merge pull request #3902 from fufesou/feat/linux_virtual_display
Feat/linux virtual display (headless linux)
This commit is contained in:
commit
cb66c6f9f4
10
.github/workflows/flutter-build.yml
vendored
10
.github/workflows/flutter-build.yml
vendored
@ -402,7 +402,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt-get -qq install -y git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake libclang-dev ninja-build libappindicator3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libvdpau-dev libva-dev libclang-dev llvm-dev libclang-10-dev llvm-10-dev pkg-config tree g++ libc6-dev gcc-multilib g++-multilib openjdk-11-jdk-headless
|
||||
sudo apt-get -qq install -y git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake libclang-dev ninja-build libappindicator3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libvdpau-dev libva-dev libpam0g-dev libclang-dev llvm-dev libclang-10-dev llvm-10-dev pkg-config tree g++ libc6-dev gcc-multilib g++-multilib openjdk-11-jdk-headless
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
- name: Install flutter
|
||||
@ -654,7 +654,7 @@ jobs:
|
||||
install: |
|
||||
apt update -y
|
||||
echo -e "installing deps"
|
||||
apt-get -qq install -y git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake libclang-dev ninja-build libappindicator3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libvdpau-dev libva-dev libclang-dev llvm-dev libclang-10-dev llvm-10-dev pkg-config tree g++ gcc libvpx-dev tree > /dev/null
|
||||
apt-get -qq install -y git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake libclang-dev ninja-build libappindicator3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libvdpau-dev libva-dev libpam0g-dev libclang-dev llvm-dev libclang-10-dev llvm-10-dev pkg-config tree g++ gcc libvpx-dev tree > /dev/null
|
||||
# we have libopus compiled by us.
|
||||
apt remove -y libopus-dev || true
|
||||
# output devs
|
||||
@ -687,7 +687,7 @@ jobs:
|
||||
x86_64)
|
||||
# no need mock on x86_64
|
||||
export VCPKG_ROOT=/opt/artifacts/vcpkg
|
||||
cargo build --lib --features hwcodec,flutter,flutter_texture_render,${{ matrix.job.extra-build-features }} --release
|
||||
cargo build --lib --features hwcodec,flutter,flutter_texture_render,linux_headless,${{ matrix.job.extra-build-features }} --release
|
||||
;;
|
||||
esac
|
||||
|
||||
@ -816,7 +816,7 @@ jobs:
|
||||
install: |
|
||||
apt update -y
|
||||
echo -e "installing deps"
|
||||
apt-get -qq install -y git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake libclang-dev ninja-build libappindicator3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libvdpau-dev libva-dev libclang-dev llvm-dev libclang-10-dev llvm-10-dev pkg-config tree g++ gcc libvpx-dev tree > /dev/null
|
||||
apt-get -qq install -y git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake libclang-dev ninja-build libappindicator3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libvdpau-dev libva-dev libpam0g-dev libclang-dev llvm-dev libclang-10-dev llvm-10-dev pkg-config tree g++ gcc libvpx-dev tree > /dev/null
|
||||
# we have libopus compiled by us.
|
||||
apt remove -y libopus-dev || true
|
||||
# output devs
|
||||
@ -947,7 +947,7 @@ jobs:
|
||||
apt update -y
|
||||
apt-get -qq install -y git cmake g++ gcc build-essential nasm yasm curl unzip xz-utils python3 wget pkg-config ninja-build pkg-config libgtk-3-dev liblzma-dev clang libappindicator3-dev rpm libclang-dev
|
||||
apt-get -qq install -y libdbus-1-dev pkg-config nasm yasm libglib2.0-dev libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev
|
||||
apt-get -qq install -y libpulse-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libvpx-dev libvdpau-dev libva-dev
|
||||
apt-get -qq install -y libpulse-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libvpx-dev libvdpau-dev libva-dev libpam0g-dev
|
||||
run: |
|
||||
# disable git safe.directory
|
||||
git config --global --add safe.directory "*"
|
||||
|
1
.github/workflows/flutter-nightly.yml
vendored
1
.github/workflows/flutter-nightly.yml
vendored
@ -11,4 +11,3 @@ jobs:
|
||||
uses: ./.github/workflows/flutter-build.yml
|
||||
with:
|
||||
upload-artifact: true
|
||||
|
54
Cargo.lock
generated
54
Cargo.lock
generated
@ -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",
|
||||
@ -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"
|
||||
|
@ -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"
|
||||
|
19
build.py
19
build.py
@ -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)
|
||||
|
@ -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,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -639,7 +639,7 @@ class _ControlMenu extends StatelessWidget {
|
||||
ffi: ffi,
|
||||
menuChildren: [
|
||||
requestElevation(),
|
||||
osPassword(),
|
||||
ffi.ffiModel.pi.is_headless ? osAccount() : osPassword(),
|
||||
transferFile(context),
|
||||
tcpTunneling(context),
|
||||
note(),
|
||||
@ -662,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) {
|
||||
|
@ -548,19 +548,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 +677,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') {
|
||||
@ -1072,50 +1094,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) {
|
||||
|
@ -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';
|
||||
|
@ -53,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;
|
||||
@ -66,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; }
|
||||
|
@ -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,7 +919,7 @@ 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);
|
||||
@ -939,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)
|
||||
}
|
||||
|
5
res/pam.d/rustdesk.debian
Normal file
5
res/pam.d/rustdesk.debian
Normal 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
5
res/pam.d/rustdesk.suse
Normal 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
130
res/startwm.sh
Executable 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
30
res/xorg.conf
Normal 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
|
@ -85,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) {
|
||||
|
178
src/client.rs
178
src/client.rs
@ -1578,7 +1578,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")))]
|
||||
@ -1591,6 +1596,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 {
|
||||
@ -1888,6 +1899,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: "xsession_failed_title_tip",
|
||||
text: "xsession_failed_text_tip",
|
||||
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: "session_not_ready_no_password_title_tip",
|
||||
text: "session_not_ready_no_password_text_tip",
|
||||
link: "",
|
||||
try_again: true,
|
||||
}), (crate::server::LOGIN_MSG_DESKTOP_SESSION_NOT_READY_PASSWORD_WRONG, LoginErrorMsgBox{
|
||||
msgtype: "session-login-re-password",
|
||||
title: "session_not_ready_wrong_password_title_tip",
|
||||
text: "session_not_ready_wrong_password_text_tip",
|
||||
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(
|
||||
@ -1895,19 +1971,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);
|
||||
@ -1955,16 +2039,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;
|
||||
}
|
||||
|
||||
@ -1973,10 +2062,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);
|
||||
}
|
||||
|
||||
@ -1985,25 +2085,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(
|
||||
@ -2017,7 +2132,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(),
|
||||
),
|
||||
@ -2038,7 +2153,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>>;
|
||||
@ -2058,7 +2180,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)),
|
||||
|
@ -360,9 +360,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 => {
|
||||
@ -1255,6 +1255,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();
|
||||
|
@ -224,6 +224,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());
|
||||
|
@ -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")]
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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", ""),
|
||||
("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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -480,9 +480,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("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"),
|
||||
("enter_rustdesk_passwd_tip", "Saisissez le mot de passe RustDesk."),
|
||||
("remember_rustdesk_passwd_tip", "Se rappeler du mot de passe RustDesk."),
|
||||
("login_linux_tip", "Se connecter au compte Linux distant"),
|
||||
("login_linux_tooltip_tip", "Vous devez vous connecter à un compte Linux distant pour activer une session de bureau X."),
|
||||
("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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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", ""),
|
||||
("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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
983
src/lang/tw.rs
983
src/lang/tw.rs
@ -1,488 +1,495 @@
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "狀態"),
|
||||
("Your Desktop", "您的桌面"),
|
||||
("desk_tip", "您可以透過此 ID 及密碼存取您的桌面"),
|
||||
("Password", "密碼"),
|
||||
("Ready", "就緒"),
|
||||
("Established", "已建立"),
|
||||
("connecting_status", "正在連線到 RustDesk 網路 ..."),
|
||||
("Enable Service", "啟用服務"),
|
||||
("Start Service", "啟動服務"),
|
||||
("Service is running", "服務正在執行"),
|
||||
("Service is not running", "服務尚未執行"),
|
||||
("not_ready_status", "尚未就緒,請檢查您的網路連線。"),
|
||||
("Control Remote Desktop", "控制遠端桌面"),
|
||||
("Transfer File", "傳輸檔案"),
|
||||
("Connect", "連線"),
|
||||
("Recent Sessions", "近期的工作階段"),
|
||||
("Address Book", "通訊錄"),
|
||||
("Confirmation", "確認"),
|
||||
("TCP Tunneling", "TCP 通道"),
|
||||
("Remove", "移除"),
|
||||
("Refresh random password", "重新產生隨機密碼"),
|
||||
("Set your own password", "自行設定密碼"),
|
||||
("Enable Keyboard/Mouse", "啟用鍵盤和滑鼠"),
|
||||
("Enable Clipboard", "啟用剪貼簿"),
|
||||
("Enable File Transfer", "啟用檔案傳輸"),
|
||||
("Enable TCP Tunneling", "啟用 TCP 通道"),
|
||||
("IP Whitelisting", "IP 白名單"),
|
||||
("ID/Relay Server", "ID / 轉送伺服器"),
|
||||
("Import Server Config", "匯入伺服器設定"),
|
||||
("Export Server Config", "匯出伺服器設定"),
|
||||
("Import server configuration successfully", "匯入伺服器設定成功"),
|
||||
("Export server configuration successfully", "匯出伺服器設定成功"),
|
||||
("Invalid server configuration", "無效的伺服器設定"),
|
||||
("Clipboard is empty", "剪貼簿是空的"),
|
||||
("Stop service", "停止服務"),
|
||||
("Change ID", "更改 ID"),
|
||||
("Your new ID", "您的新 ID"),
|
||||
("length %min% to %max%", "長度在 %min% 與 %max% 之間"),
|
||||
("starts with a letter", "以字母開頭"),
|
||||
("allowed characters", "使用允許的字元"),
|
||||
("id_change_tip", "僅能使用以下字元:a-z、A-Z、0-9、_ (底線)。首字元必須為 a-z 或 A-Z。長度介於 6 到 16 之間。"),
|
||||
("Website", "網站"),
|
||||
("About", "關於"),
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", "隱私權聲明"),
|
||||
("Mute", "靜音"),
|
||||
("Build Date", "構建日期"),
|
||||
("Version", "版本"),
|
||||
("Home", "首頁"),
|
||||
("Audio Input", "音訊輸入"),
|
||||
("Enhancements", "增強功能"),
|
||||
("Hardware Codec", "硬體編解碼器"),
|
||||
("Adaptive Bitrate", "自適應位元速率"),
|
||||
("ID Server", "ID 伺服器"),
|
||||
("Relay Server", "轉送伺服器"),
|
||||
("API Server", "API 伺服器"),
|
||||
("invalid_http", "開頭必須為 http:// 或 https://"),
|
||||
("Invalid IP", "IP 無效"),
|
||||
("Invalid format", "格式無效"),
|
||||
("server_not_support", "伺服器暫不支持"),
|
||||
("Not available", "無法使用"),
|
||||
("Too frequent", "修改過於頻繁,請稍後再試。"),
|
||||
("Cancel", "取消"),
|
||||
("Skip", "跳過"),
|
||||
("Close", "關閉"),
|
||||
("Retry", "重試"),
|
||||
("OK", "確定"),
|
||||
("Password Required", "需要密碼"),
|
||||
("Please enter your password", "請輸入您的密碼"),
|
||||
("Remember password", "記住密碼"),
|
||||
("Wrong Password", "密碼錯誤"),
|
||||
("Do you want to enter again?", "您要重新輸入嗎?"),
|
||||
("Connection Error", "連線錯誤"),
|
||||
("Error", "錯誤"),
|
||||
("Reset by the peer", "對方重設了連線"),
|
||||
("Connecting...", "正在連線 ..."),
|
||||
("Connection in progress. Please wait.", "正在連線,請稍候。"),
|
||||
("Please try 1 minute later", "請於 1 分鐘後再試"),
|
||||
("Login Error", "登入錯誤"),
|
||||
("Successful", "成功"),
|
||||
("Connected, waiting for image...", "已連線,等待畫面傳輸 ..."),
|
||||
("Name", "名稱"),
|
||||
("Type", "類型"),
|
||||
("Modified", "修改時間"),
|
||||
("Size", "大小"),
|
||||
("Show Hidden Files", "顯示隱藏檔案"),
|
||||
("Receive", "接收"),
|
||||
("Send", "傳送"),
|
||||
("Refresh File", "重新整理檔案"),
|
||||
("Local", "本地"),
|
||||
("Remote", "遠端"),
|
||||
("Remote Computer", "遠端電腦"),
|
||||
("Local Computer", "本地電腦"),
|
||||
("Confirm Delete", "確認刪除"),
|
||||
("Delete", "刪除"),
|
||||
("Properties", "屬性"),
|
||||
("Multi Select", "多選"),
|
||||
("Select All", "全選"),
|
||||
("Unselect All", "取消全選"),
|
||||
("Empty Directory", "空資料夾"),
|
||||
("Not an empty directory", "不是一個空資料夾"),
|
||||
("Are you sure you want to delete this file?", "您確定要刪除此檔案嗎?"),
|
||||
("Are you sure you want to delete this empty directory?", "您確定要刪除此空資料夾嗎?"),
|
||||
("Are you sure you want to delete the file of this directory?", "您確定要刪除此資料夾中的檔案嗎?"),
|
||||
("Do this for all conflicts", "套用到其他衝突"),
|
||||
("This is irreversible!", "此操作不可逆!"),
|
||||
("Deleting", "正在刪除 ..."),
|
||||
("files", "檔案"),
|
||||
("Waiting", "正在等候 ..."),
|
||||
("Finished", "已完成"),
|
||||
("Speed", "速度"),
|
||||
("Custom Image Quality", "自訂畫面品質"),
|
||||
("Privacy mode", "隱私模式"),
|
||||
("Block user input", "封鎖使用者輸入"),
|
||||
("Unblock user input", "取消封鎖使用者輸入"),
|
||||
("Adjust Window", "調整視窗"),
|
||||
("Original", "原始"),
|
||||
("Shrink", "縮減"),
|
||||
("Stretch", "延展"),
|
||||
("Scrollbar", "滾動條"),
|
||||
("ScrollAuto", "自動滾動"),
|
||||
("Good image quality", "最佳化畫面品質"),
|
||||
("Balanced", "平衡"),
|
||||
("Optimize reaction time", "最佳化反應時間"),
|
||||
("Custom", "自訂"),
|
||||
("Show remote cursor", "顯示遠端游標"),
|
||||
("Show quality monitor", "顯示質量監測"),
|
||||
("Disable clipboard", "停用剪貼簿"),
|
||||
("Lock after session end", "工作階段結束後鎖定電腦"),
|
||||
("Insert", "插入"),
|
||||
("Insert Lock", "鎖定遠端電腦"),
|
||||
("Refresh", "重新載入"),
|
||||
("ID does not exist", "ID 不存在"),
|
||||
("Failed to connect to rendezvous server", "無法連線到 rendezvous 伺服器"),
|
||||
("Please try later", "請稍候再試"),
|
||||
("Remote desktop is offline", "遠端桌面已離線"),
|
||||
("Key mismatch", "金鑰不符"),
|
||||
("Timeout", "逾時"),
|
||||
("Failed to connect to relay server", "無法連線到轉送伺服器"),
|
||||
("Failed to connect via rendezvous server", "無法透過 rendezvous 伺服器連線"),
|
||||
("Failed to connect via relay server", "無法透過轉送伺服器連線"),
|
||||
("Failed to make direct connection to remote desktop", "無法直接連線到遠端桌面"),
|
||||
("Set Password", "設定密碼"),
|
||||
("OS Password", "作業系統密碼"),
|
||||
("install_tip", "UAC 會導致 RustDesk 在某些情況下無法正常以遠端電腦執行。若要避開 UAC,請點擊下方按鈕將 RustDesk 安裝到系統中。"),
|
||||
("Click to upgrade", "點擊以升級"),
|
||||
("Click to download", "點擊以下載"),
|
||||
("Click to update", "點擊以更新"),
|
||||
("Configure", "設定"),
|
||||
("config_acc", "您需要授予 RustDesk「協助工具」權限才能存取遠端電腦。"),
|
||||
("config_screen", "您需要授予 RustDesk「畫面錄製」權限才能存取遠端電腦。"),
|
||||
("Installing ...", "正在安裝 ..."),
|
||||
("Install", "安裝"),
|
||||
("Installation", "安裝"),
|
||||
("Installation Path", "安裝路徑"),
|
||||
("Create start menu shortcuts", "新增開始功能表捷徑"),
|
||||
("Create desktop icon", "新增桌面捷徑"),
|
||||
("agreement_tip", "開始安裝即表示接受許可協議"),
|
||||
("Accept and Install", "接受並安裝"),
|
||||
("End-user license agreement", "使用者授權合約"),
|
||||
("Generating ...", "正在產生 ..."),
|
||||
("Your installation is lower version.", "您安裝的版本過舊。"),
|
||||
("not_close_tcp_tip", "使用通道時請不要關閉此視窗"),
|
||||
("Listening ...", "正在等待通道連線 ..."),
|
||||
("Remote Host", "遠端主機"),
|
||||
("Remote Port", "遠端連線端口"),
|
||||
("Action", "操作"),
|
||||
("Add", "新增"),
|
||||
("Local Port", "本機連線端口"),
|
||||
("Local Address", "本機地址"),
|
||||
("Change Local Port", "修改本機連線端口"),
|
||||
("setup_server_tip", "若您需要更快的連線速度,可以選擇自行建立伺服器"),
|
||||
("Too short, at least 6 characters.", "過短,至少需要 6 個字元。"),
|
||||
("The confirmation is not identical.", "兩次輸入不相符"),
|
||||
("Permissions", "權限"),
|
||||
("Accept", "接受"),
|
||||
("Dismiss", "關閉"),
|
||||
("Disconnect", "中斷連線"),
|
||||
("Allow using keyboard and mouse", "允許使用鍵盤和滑鼠"),
|
||||
("Allow using clipboard", "允許使用剪貼簿"),
|
||||
("Allow hearing sound", "允許分享音訊"),
|
||||
("Allow file copy and paste", "允許檔案複製和貼上"),
|
||||
("Connected", "已連線"),
|
||||
("Direct and encrypted connection", "加密直接連線"),
|
||||
("Relayed and encrypted connection", "加密轉送連線"),
|
||||
("Direct and unencrypted connection", "未加密直接連線"),
|
||||
("Relayed and unencrypted connection", "未加密轉送連線"),
|
||||
("Enter Remote ID", "輸入遠端 ID"),
|
||||
("Enter your password", "輸入您的密碼"),
|
||||
("Logging in...", "正在登入 ..."),
|
||||
("Enable RDP session sharing", "啟用 RDP 工作階段共享"),
|
||||
("Auto Login", "自動登入 (鎖定將在設定關閉後套用)"),
|
||||
("Enable Direct IP Access", "允許 IP 直接存取"),
|
||||
("Rename", "重新命名"),
|
||||
("Space", "空白"),
|
||||
("Create Desktop Shortcut", "新增桌面捷徑"),
|
||||
("Change Path", "更改路徑"),
|
||||
("Create Folder", "新增資料夾"),
|
||||
("Please enter the folder name", "請輸入資料夾名稱"),
|
||||
("Fix it", "修復"),
|
||||
("Warning", "警告"),
|
||||
("Login screen using Wayland is not supported", "不支援使用 Wayland 的登入畫面"),
|
||||
("Reboot required", "需要重新啟動"),
|
||||
("Unsupported display server", "不支援顯示伺服器"),
|
||||
("x11 expected", "預期 x11"),
|
||||
("Port", "端口"),
|
||||
("Settings", "設定"),
|
||||
("Username", "使用者名稱"),
|
||||
("Invalid port", "連線端口無效"),
|
||||
("Closed manually by the peer", "遠端使用者關閉了工作階段"),
|
||||
("Enable remote configuration modification", "允許遠端使用者更改設定"),
|
||||
("Run without install", "跳過安裝直接執行"),
|
||||
("Connect via relay", "中繼連線"),
|
||||
("Always connect via relay", "一律透過轉送連線"),
|
||||
("whitelist_tip", "只有白名單中的 IP 可以存取"),
|
||||
("Login", "登入"),
|
||||
("Verify", "驗證"),
|
||||
("Remember me", "記住我"),
|
||||
("Trust this device", "信任此裝置"),
|
||||
("Verification code", "驗證碼"),
|
||||
("verification_tip", "檢測到新裝置登入,已向註冊電子信箱發送了登入驗證碼,請輸入驗證碼以繼續登入"),
|
||||
("Logout", "登出"),
|
||||
("Tags", "標籤"),
|
||||
("Search ID", "搜尋 ID"),
|
||||
("whitelist_sep", "使用逗號、分號、空白,或是換行來分隔"),
|
||||
("Add ID", "新增 ID"),
|
||||
("Add Tag", "新增標籤"),
|
||||
("Unselect all tags", "取消選取所有標籤"),
|
||||
("Network error", "網路錯誤"),
|
||||
("Username missed", "缺少使用者名稱"),
|
||||
("Password missed", "缺少密碼"),
|
||||
("Wrong credentials", "提供的登入資訊有誤"),
|
||||
("Edit Tag", "編輯標籤"),
|
||||
("Unremember Password", "忘掉密碼"),
|
||||
("Favorites", "我的最愛"),
|
||||
("Add to Favorites", "新增到我的最愛"),
|
||||
("Remove from Favorites", "從我的最愛中刪除"),
|
||||
("Empty", "空空如也"),
|
||||
("Invalid folder name", "資料夾名稱無效"),
|
||||
("Socks5 Proxy", "Socks5 代理"),
|
||||
("Hostname", "主機名稱"),
|
||||
("Discovered", "已探索"),
|
||||
("install_daemon_tip", "為了能夠開機時自動啟動,請先安裝系統服務。"),
|
||||
("Remote ID", "遠端 ID"),
|
||||
("Paste", "貼上"),
|
||||
("Paste here?", "貼上到這裡?"),
|
||||
("Are you sure to close the connection?", "您確定要關閉連線嗎?"),
|
||||
("Download new version", "下載新版本"),
|
||||
("Touch mode", "觸控模式"),
|
||||
("Mouse mode", "滑鼠模式"),
|
||||
("One-Finger Tap", "單指輕觸"),
|
||||
("Left Mouse", "滑鼠左鍵"),
|
||||
("One-Long Tap", "單指長按"),
|
||||
("Two-Finger Tap", "雙指輕觸"),
|
||||
("Right Mouse", "滑鼠右鍵"),
|
||||
("One-Finger Move", "單指移動"),
|
||||
("Double Tap & Move", "雙擊並移動"),
|
||||
("Mouse Drag", "滑鼠選中拖動"),
|
||||
("Three-Finger vertically", "三指垂直滑動"),
|
||||
("Mouse Wheel", "滑鼠滾輪"),
|
||||
("Two-Finger Move", "雙指移動"),
|
||||
("Canvas Move", "移動畫布"),
|
||||
("Pinch to Zoom", "雙指縮放"),
|
||||
("Canvas Zoom", "縮放畫布"),
|
||||
("Reset canvas", "重設畫布"),
|
||||
("No permission of file transfer", "沒有檔案傳輸權限"),
|
||||
("Note", "備註"),
|
||||
("Connection", "連線"),
|
||||
("Share Screen", "共享螢幕畫面"),
|
||||
("Chat", "聊天訊息"),
|
||||
("Total", "總計"),
|
||||
("items", "個項目"),
|
||||
("Selected", "已選擇"),
|
||||
("Screen Capture", "畫面錄製"),
|
||||
("Input Control", "輸入控制"),
|
||||
("Audio Capture", "音訊錄製"),
|
||||
("File Connection", "檔案連線"),
|
||||
("Screen Connection", "畫面連線"),
|
||||
("Do you accept?", "是否接受?"),
|
||||
("Open System Setting", "開啟系統設定"),
|
||||
("How to get Android input permission?", "如何獲取 Android 的輸入權限?"),
|
||||
("android_input_permission_tip1", "取得輸入權限後可以讓遠端裝置透過滑鼠控制此 Android 裝置"),
|
||||
("android_input_permission_tip2", "請在接下來的系統設定頁面中,找到並進入「已安裝的服務」頁面,並將「RustDesk Input」服務開啟"),
|
||||
("android_new_connection_tip", "收到新的連線控制請求,對方想要控制您目前的裝置"),
|
||||
("android_service_will_start_tip", "開啟畫面錄製權限將自動開啟服務,允許其他裝置向此裝置請求建立連線。"),
|
||||
("android_stop_service_tip", "關閉服務將自動關閉所有已建立的連線。"),
|
||||
("android_version_audio_tip", "目前的 Android 版本不支援音訊錄製,請升級到 Android 10 或以上版本。"),
|
||||
("android_start_service_tip", "點擊「啟動服務」或啟用「螢幕錄製」權限,以啟動螢幕共享服務。"),
|
||||
("android_permission_may_not_change_tip", "對於已經建立的連線,權限可能不會立即發生改變,除非重新建立連線。"),
|
||||
("Account", "帳號"),
|
||||
("Overwrite", "取代"),
|
||||
("This file exists, skip or overwrite this file?", "此檔案/資料夾已存在,要略過或是取代此檔案嗎?"),
|
||||
("Quit", "退出"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/zh-tw/manual/mac/#啟用權限"),
|
||||
("Help", "幫助"),
|
||||
("Failed", "失敗"),
|
||||
("Succeeded", "成功"),
|
||||
("Someone turns on privacy mode, exit", "其他使用者開啟隱私模式,退出"),
|
||||
("Unsupported", "不支援"),
|
||||
("Peer denied", "被控端拒絕"),
|
||||
("Please install plugins", "請安裝插件"),
|
||||
("Peer exit", "被控端退出"),
|
||||
("Failed to turn off", "退出失敗"),
|
||||
("Turned off", "退出"),
|
||||
("In privacy mode", "開啟隱私模式"),
|
||||
("Out privacy mode", "退出隱私模式"),
|
||||
("Language", "語言"),
|
||||
("Keep RustDesk background service", "保持 RustDesk 後台服務"),
|
||||
("Ignore Battery Optimizations", "忽略電池最佳化"),
|
||||
("android_open_battery_optimizations_tip", "如需關閉此功能,請在接下來的 RustDesk 應用設定頁面中,找到並進入 [電源] 頁面,取消勾選 [不受限制]"),
|
||||
("Start on Boot", "開機自動啟動"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "開機自動啟動螢幕共享服務,此功能需要一些特殊權限。"),
|
||||
("Connection not allowed", "對方不允許連線"),
|
||||
("Legacy mode", "傳統模式"),
|
||||
("Map mode", "1:1 傳輸模式"),
|
||||
("Translate mode", "翻譯模式"),
|
||||
("Use permanent password", "使用固定密碼"),
|
||||
("Use both passwords", "同時使用兩種密碼"),
|
||||
("Set permanent password", "設定固定密碼"),
|
||||
("Enable Remote Restart", "啟用遠端重新啟動"),
|
||||
("Allow remote restart", "允許遠端重新啟動"),
|
||||
("Restart Remote Device", "重新啟動遠端電腦"),
|
||||
("Are you sure you want to restart", "確定要重新啟動"),
|
||||
("Restarting Remote Device", "正在重新啟動遠端裝置"),
|
||||
("remote_restarting_tip", "遠端裝置正在重新啟動,請關閉當前提示框,並在一段時間後使用永久密碼重新連線"),
|
||||
("Copied", "已複製"),
|
||||
("Exit Fullscreen", "退出全螢幕"),
|
||||
("Fullscreen", "全螢幕"),
|
||||
("Mobile Actions", "手機操作"),
|
||||
("Select Monitor", "選擇顯示器"),
|
||||
("Control Actions", "控制操作"),
|
||||
("Display Settings", "顯示設定"),
|
||||
("Ratio", "比例"),
|
||||
("Image Quality", "畫質"),
|
||||
("Scroll Style", "滾動樣式"),
|
||||
("Show Menubar", "顯示選單欄"),
|
||||
("Hide Menubar", "隱藏選單欄"),
|
||||
("Direct Connection", "直接連線"),
|
||||
("Relay Connection", "中繼連線"),
|
||||
("Secure Connection", "安全連線"),
|
||||
("Insecure Connection", "非安全連線"),
|
||||
("Scale original", "原始尺寸"),
|
||||
("Scale adaptive", "適應視窗"),
|
||||
("General", "通用"),
|
||||
("Security", "安全"),
|
||||
("Theme", "主題"),
|
||||
("Dark Theme", "黑暗主題"),
|
||||
("Light Theme", "明亮主題"),
|
||||
("Dark", "黑暗"),
|
||||
("Light", "明亮"),
|
||||
("Follow System", "跟隨系統"),
|
||||
("Enable hardware codec", "使用硬體編解碼器"),
|
||||
("Unlock Security Settings", "解鎖安全設定"),
|
||||
("Enable Audio", "允許傳輸音訊"),
|
||||
("Unlock Network Settings", "解鎖網路設定"),
|
||||
("Server", "伺服器"),
|
||||
("Direct IP Access", "IP 直接連線"),
|
||||
("Proxy", "代理"),
|
||||
("Apply", "應用"),
|
||||
("Disconnect all devices?", "中斷所有遠端連線?"),
|
||||
("Clear", "清空"),
|
||||
("Audio Input Device", "音訊輸入裝置"),
|
||||
("Deny remote access", "拒絕遠端存取"),
|
||||
("Use IP Whitelisting", "只允許白名單上的 IP 進行連線"),
|
||||
("Network", "網路"),
|
||||
("Enable RDP", "允許 RDP 訪問"),
|
||||
("Pin menubar", "固定選單欄"),
|
||||
("Unpin menubar", "取消固定選單欄"),
|
||||
("Recording", "錄製"),
|
||||
("Directory", "路徑"),
|
||||
("Automatically record incoming sessions", "自動錄製連入的工作階段"),
|
||||
("Change", "變更"),
|
||||
("Start session recording", "開始錄影"),
|
||||
("Stop session recording", "停止錄影"),
|
||||
("Enable Recording Session", "啟用錄製工作階段"),
|
||||
("Allow recording session", "允許錄製工作階段"),
|
||||
("Enable LAN Discovery", "允許區域網路探索"),
|
||||
("Deny LAN Discovery", "拒絕區域網路探索"),
|
||||
("Write a message", "輸入聊天訊息"),
|
||||
("Prompt", "提示"),
|
||||
("Please wait for confirmation of UAC...", "請等待對方確認 UAC ..."),
|
||||
("elevated_foreground_window_tip", "目前的遠端桌面視窗需要更高的權限才能繼續操作,暫時無法使用滑鼠、鍵盤,可以請求對方最小化目前視窗,或者在連線管理視窗點擊提升權限。為了避免這個問題,建議在遠端裝置上安裝本軟體。"),
|
||||
("Disconnected", "斷開連線"),
|
||||
("Other", "其他"),
|
||||
("Confirm before closing multiple tabs", "關閉多個分頁前詢問我"),
|
||||
("Keyboard Settings", "鍵盤設定"),
|
||||
("Full Access", "完全訪問"),
|
||||
("Screen Share", "僅分享螢幕畫面"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland 需要 Ubuntu 21.04 或更高版本。"),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland 需要更高版本的 Linux 發行版。請嘗試使用 X11 桌面或更改您的作業系統。"),
|
||||
("JumpLink", "查看"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "請選擇要分享的螢幕畫面(在對端操作)。"),
|
||||
("Show RustDesk", "顯示 RustDesk"),
|
||||
("This PC", "此電腦"),
|
||||
("or", "或"),
|
||||
("Continue with", "繼續"),
|
||||
("Elevate", "提升權限"),
|
||||
("Zoom cursor", "縮放游標"),
|
||||
("Accept sessions via password", "只允許透過輸入密碼進行連線"),
|
||||
("Accept sessions via click", "只允許透過點擊接受進行連線"),
|
||||
("Accept sessions via both", "允許輸入密碼或點擊接受進行連線"),
|
||||
("Please wait for the remote side to accept your session request...", "請等待對方接受您的連線請求 ..."),
|
||||
("One-time Password", "一次性密碼"),
|
||||
("Use one-time password", "使用一次性密碼"),
|
||||
("One-time password length", "一次性密碼長度"),
|
||||
("Request access to your device", "請求訪問您的裝置"),
|
||||
("Hide connection management window", "隱藏連線管理視窗"),
|
||||
("hide_cm_tip", "在只允許密碼連線並且只用固定密碼的情況下才允許隱藏"),
|
||||
("wayland_experiment_tip", "Wayland 支援處於實驗階段,如果您需要使用無人值守訪問,請使用 X11。"),
|
||||
("Right click to select tabs", "右鍵選擇分頁"),
|
||||
("Skipped", "已略過"),
|
||||
("Add to Address Book", "新增到通訊錄"),
|
||||
("Group", "群組"),
|
||||
("Search", "搜尋"),
|
||||
("Closed manually by web console", "被 Web 控制台手動關閉"),
|
||||
("Local keyboard type", "本地鍵盤類型"),
|
||||
("Select local keyboard type", "請選擇本地鍵盤類型"),
|
||||
("software_render_tip", "如果您使用 NVIDIA 顯示卡,並且遠端視窗在建立連線後會立刻關閉,那麼請安裝 nouveau 顯示卡驅動程式並且選擇使用軟體渲染可能會有幫助。重新啟動軟體後生效。"),
|
||||
("Always use software rendering", "使用軟體渲染"),
|
||||
("config_input", "為了能夠透過鍵盤控制遠端桌面,請給予 RustDesk \"輸入監控\" 權限。"),
|
||||
("config_microphone", "為了支援透過麥克風進行音訊傳輸,請給予 RustDesk \"錄音\"權限。"),
|
||||
("request_elevation_tip", "如果遠端使用者可以操作電腦,也可以請求提升權限。"),
|
||||
("Wait", "等待"),
|
||||
("Elevation Error", "權限提升失敗"),
|
||||
("Ask the remote user for authentication", "請求遠端使用者進行身分驗證"),
|
||||
("Choose this if the remote account is administrator", "當遠端使用者帳戶是管理員時,請選擇此選項"),
|
||||
("Transmit the username and password of administrator", "發送管理員的使用者名稱和密碼"),
|
||||
("still_click_uac_tip", "依然需要遠端使用者在 UAC 視窗點擊確認。"),
|
||||
("Request Elevation", "請求權限提升"),
|
||||
("wait_accept_uac_tip", "請等待遠端使用者確認 UAC 對話框。"),
|
||||
("Elevate successfully", "權限提升成功"),
|
||||
("uppercase", "大寫字母"),
|
||||
("lowercase", "小寫字母"),
|
||||
("digit", "數字"),
|
||||
("special character", "特殊字元"),
|
||||
("length>=8", "長度不能小於 8"),
|
||||
("Weak", "弱"),
|
||||
("Medium", "中"),
|
||||
("Strong", "強"),
|
||||
("Switch Sides", "反轉存取方向"),
|
||||
("Please confirm if you want to share your desktop?", "請確認是否要讓對方存取您的桌面?"),
|
||||
("Display", "顯示"),
|
||||
("Default View Style", "預設顯示方式"),
|
||||
("Default Scroll Style", "預設滾動方式"),
|
||||
("Default Image Quality", "預設圖像質量"),
|
||||
("Default Codec", "預設編解碼器"),
|
||||
("Bitrate", "位元速率"),
|
||||
("FPS", "幀率"),
|
||||
("Auto", "自動"),
|
||||
("Other Default Options", "其他預設選項"),
|
||||
("Voice call", "語音通話"),
|
||||
("Text chat", "文字聊天"),
|
||||
("Stop voice call", "停止語音通話"),
|
||||
("relay_hint_tip", "可能無法直接連線,可以嘗試中繼連線。\n另外,如果想要直接使用中繼連線,可以在 ID 後面新增/r,或者在卡片選項裡選擇強制走中繼連線。"),
|
||||
("Reconnect", "重新連線"),
|
||||
("Codec", "編解碼器"),
|
||||
("Resolution", "解析度"),
|
||||
("No transfers in progress", "沒有正在進行的傳輸"),
|
||||
("Set one-time password length", "設定一次性密碼長度"),
|
||||
("idd_driver_tip", "安裝虛擬顯示器驅動程式,以便在沒有連接顯示器的情況下啟動虛擬顯示器進行控制。"),
|
||||
("confirm_idd_driver_tip", "安裝虛擬顯示器驅動程式的選項已勾選。請注意,測試證書將被安裝以信任虛擬顯示器驅動。測試證書僅會用於信任 RustDesk 的驅動程式。"),
|
||||
("RDP Settings", "RDP 設定"),
|
||||
("Sort by", "排序方式"),
|
||||
("New Connection", "新連線"),
|
||||
("Restore", "還原"),
|
||||
("Minimize", "最小化"),
|
||||
("Maximize", "最大化"),
|
||||
("Your Device", "您的裝置"),
|
||||
("empty_recent_tip", "空空如也"),
|
||||
("empty_favorite_tip", "空空如也"),
|
||||
("empty_lan_tip", "空空如也"),
|
||||
("empty_address_book_tip", "空空如也"),
|
||||
("eg: admin", "例如:admin"),
|
||||
("Empty Username", "空使用者帳號"),
|
||||
("Empty Password", "空密碼"),
|
||||
("Me", "我"),
|
||||
("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 介面。"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
[
|
||||
("Status", "狀態"),
|
||||
("Your Desktop", "您的桌面"),
|
||||
("desk_tip", "您可以透過此 ID 及密碼存取您的桌面"),
|
||||
("Password", "密碼"),
|
||||
("Ready", "就緒"),
|
||||
("Established", "已建立"),
|
||||
("connecting_status", "正在連線到 RustDesk 網路 ..."),
|
||||
("Enable Service", "啟用服務"),
|
||||
("Start Service", "啟動服務"),
|
||||
("Service is running", "服務正在執行"),
|
||||
("Service is not running", "服務尚未執行"),
|
||||
("not_ready_status", "尚未就緒,請檢查您的網路連線。"),
|
||||
("Control Remote Desktop", "控制遠端桌面"),
|
||||
("Transfer File", "傳輸檔案"),
|
||||
("Connect", "連線"),
|
||||
("Recent Sessions", "近期的工作階段"),
|
||||
("Address Book", "通訊錄"),
|
||||
("Confirmation", "確認"),
|
||||
("TCP Tunneling", "TCP 通道"),
|
||||
("Remove", "移除"),
|
||||
("Refresh random password", "重新產生隨機密碼"),
|
||||
("Set your own password", "自行設定密碼"),
|
||||
("Enable Keyboard/Mouse", "啟用鍵盤和滑鼠"),
|
||||
("Enable Clipboard", "啟用剪貼簿"),
|
||||
("Enable File Transfer", "啟用檔案傳輸"),
|
||||
("Enable TCP Tunneling", "啟用 TCP 通道"),
|
||||
("IP Whitelisting", "IP 白名單"),
|
||||
("ID/Relay Server", "ID / 轉送伺服器"),
|
||||
("Import Server Config", "匯入伺服器設定"),
|
||||
("Export Server Config", "匯出伺服器設定"),
|
||||
("Import server configuration successfully", "匯入伺服器設定成功"),
|
||||
("Export server configuration successfully", "匯出伺服器設定成功"),
|
||||
("Invalid server configuration", "無效的伺服器設定"),
|
||||
("Clipboard is empty", "剪貼簿是空的"),
|
||||
("Stop service", "停止服務"),
|
||||
("Change ID", "更改 ID"),
|
||||
("Your new ID", "您的新 ID"),
|
||||
("length %min% to %max%", "長度在 %min% 與 %max% 之間"),
|
||||
("starts with a letter", "以字母開頭"),
|
||||
("allowed characters", "使用允許的字元"),
|
||||
("id_change_tip", "僅能使用以下字元:a-z、A-Z、0-9、_ (底線)。首字元必須為 a-z 或 A-Z。長度介於 6 到 16 之間。"),
|
||||
("Website", "網站"),
|
||||
("About", "關於"),
|
||||
("Slogan_tip", ""),
|
||||
("Privacy Statement", "隱私權聲明"),
|
||||
("Mute", "靜音"),
|
||||
("Build Date", "構建日期"),
|
||||
("Version", "版本"),
|
||||
("Home", "首頁"),
|
||||
("Audio Input", "音訊輸入"),
|
||||
("Enhancements", "增強功能"),
|
||||
("Hardware Codec", "硬體編解碼器"),
|
||||
("Adaptive Bitrate", "自適應位元速率"),
|
||||
("ID Server", "ID 伺服器"),
|
||||
("Relay Server", "轉送伺服器"),
|
||||
("API Server", "API 伺服器"),
|
||||
("invalid_http", "開頭必須為 http:// 或 https://"),
|
||||
("Invalid IP", "IP 無效"),
|
||||
("Invalid format", "格式無效"),
|
||||
("server_not_support", "伺服器暫不支持"),
|
||||
("Not available", "無法使用"),
|
||||
("Too frequent", "修改過於頻繁,請稍後再試。"),
|
||||
("Cancel", "取消"),
|
||||
("Skip", "跳過"),
|
||||
("Close", "關閉"),
|
||||
("Retry", "重試"),
|
||||
("OK", "確定"),
|
||||
("Password Required", "需要密碼"),
|
||||
("Please enter your password", "請輸入您的密碼"),
|
||||
("Remember password", "記住密碼"),
|
||||
("Wrong Password", "密碼錯誤"),
|
||||
("Do you want to enter again?", "您要重新輸入嗎?"),
|
||||
("Connection Error", "連線錯誤"),
|
||||
("Error", "錯誤"),
|
||||
("Reset by the peer", "對方重設了連線"),
|
||||
("Connecting...", "正在連線 ..."),
|
||||
("Connection in progress. Please wait.", "正在連線,請稍候。"),
|
||||
("Please try 1 minute later", "請於 1 分鐘後再試"),
|
||||
("Login Error", "登入錯誤"),
|
||||
("Successful", "成功"),
|
||||
("Connected, waiting for image...", "已連線,等待畫面傳輸 ..."),
|
||||
("Name", "名稱"),
|
||||
("Type", "類型"),
|
||||
("Modified", "修改時間"),
|
||||
("Size", "大小"),
|
||||
("Show Hidden Files", "顯示隱藏檔案"),
|
||||
("Receive", "接收"),
|
||||
("Send", "傳送"),
|
||||
("Refresh File", "重新整理檔案"),
|
||||
("Local", "本地"),
|
||||
("Remote", "遠端"),
|
||||
("Remote Computer", "遠端電腦"),
|
||||
("Local Computer", "本地電腦"),
|
||||
("Confirm Delete", "確認刪除"),
|
||||
("Delete", "刪除"),
|
||||
("Properties", "屬性"),
|
||||
("Multi Select", "多選"),
|
||||
("Select All", "全選"),
|
||||
("Unselect All", "取消全選"),
|
||||
("Empty Directory", "空資料夾"),
|
||||
("Not an empty directory", "不是一個空資料夾"),
|
||||
("Are you sure you want to delete this file?", "您確定要刪除此檔案嗎?"),
|
||||
("Are you sure you want to delete this empty directory?", "您確定要刪除此空資料夾嗎?"),
|
||||
("Are you sure you want to delete the file of this directory?", "您確定要刪除此資料夾中的檔案嗎?"),
|
||||
("Do this for all conflicts", "套用到其他衝突"),
|
||||
("This is irreversible!", "此操作不可逆!"),
|
||||
("Deleting", "正在刪除 ..."),
|
||||
("files", "檔案"),
|
||||
("Waiting", "正在等候 ..."),
|
||||
("Finished", "已完成"),
|
||||
("Speed", "速度"),
|
||||
("Custom Image Quality", "自訂畫面品質"),
|
||||
("Privacy mode", "隱私模式"),
|
||||
("Block user input", "封鎖使用者輸入"),
|
||||
("Unblock user input", "取消封鎖使用者輸入"),
|
||||
("Adjust Window", "調整視窗"),
|
||||
("Original", "原始"),
|
||||
("Shrink", "縮減"),
|
||||
("Stretch", "延展"),
|
||||
("Scrollbar", "滾動條"),
|
||||
("ScrollAuto", "自動滾動"),
|
||||
("Good image quality", "最佳化畫面品質"),
|
||||
("Balanced", "平衡"),
|
||||
("Optimize reaction time", "最佳化反應時間"),
|
||||
("Custom", "自訂"),
|
||||
("Show remote cursor", "顯示遠端游標"),
|
||||
("Show quality monitor", "顯示質量監測"),
|
||||
("Disable clipboard", "停用剪貼簿"),
|
||||
("Lock after session end", "工作階段結束後鎖定電腦"),
|
||||
("Insert", "插入"),
|
||||
("Insert Lock", "鎖定遠端電腦"),
|
||||
("Refresh", "重新載入"),
|
||||
("ID does not exist", "ID 不存在"),
|
||||
("Failed to connect to rendezvous server", "無法連線到 rendezvous 伺服器"),
|
||||
("Please try later", "請稍候再試"),
|
||||
("Remote desktop is offline", "遠端桌面已離線"),
|
||||
("Key mismatch", "金鑰不符"),
|
||||
("Timeout", "逾時"),
|
||||
("Failed to connect to relay server", "無法連線到轉送伺服器"),
|
||||
("Failed to connect via rendezvous server", "無法透過 rendezvous 伺服器連線"),
|
||||
("Failed to connect via relay server", "無法透過轉送伺服器連線"),
|
||||
("Failed to make direct connection to remote desktop", "無法直接連線到遠端桌面"),
|
||||
("Set Password", "設定密碼"),
|
||||
("OS Password", "作業系統密碼"),
|
||||
("install_tip", "UAC 會導致 RustDesk 在某些情況下無法正常以遠端電腦執行。若要避開 UAC,請點擊下方按鈕將 RustDesk 安裝到系統中。"),
|
||||
("Click to upgrade", "點擊以升級"),
|
||||
("Click to download", "點擊以下載"),
|
||||
("Click to update", "點擊以更新"),
|
||||
("Configure", "設定"),
|
||||
("config_acc", "您需要授予 RustDesk「協助工具」權限才能存取遠端電腦。"),
|
||||
("config_screen", "您需要授予 RustDesk「畫面錄製」權限才能存取遠端電腦。"),
|
||||
("Installing ...", "正在安裝 ..."),
|
||||
("Install", "安裝"),
|
||||
("Installation", "安裝"),
|
||||
("Installation Path", "安裝路徑"),
|
||||
("Create start menu shortcuts", "新增開始功能表捷徑"),
|
||||
("Create desktop icon", "新增桌面捷徑"),
|
||||
("agreement_tip", "開始安裝即表示接受許可協議"),
|
||||
("Accept and Install", "接受並安裝"),
|
||||
("End-user license agreement", "使用者授權合約"),
|
||||
("Generating ...", "正在產生 ..."),
|
||||
("Your installation is lower version.", "您安裝的版本過舊。"),
|
||||
("not_close_tcp_tip", "使用通道時請不要關閉此視窗"),
|
||||
("Listening ...", "正在等待通道連線 ..."),
|
||||
("Remote Host", "遠端主機"),
|
||||
("Remote Port", "遠端連線端口"),
|
||||
("Action", "操作"),
|
||||
("Add", "新增"),
|
||||
("Local Port", "本機連線端口"),
|
||||
("Local Address", "本機地址"),
|
||||
("Change Local Port", "修改本機連線端口"),
|
||||
("setup_server_tip", "若您需要更快的連線速度,可以選擇自行建立伺服器"),
|
||||
("Too short, at least 6 characters.", "過短,至少需要 6 個字元。"),
|
||||
("The confirmation is not identical.", "兩次輸入不相符"),
|
||||
("Permissions", "權限"),
|
||||
("Accept", "接受"),
|
||||
("Dismiss", "關閉"),
|
||||
("Disconnect", "中斷連線"),
|
||||
("Allow using keyboard and mouse", "允許使用鍵盤和滑鼠"),
|
||||
("Allow using clipboard", "允許使用剪貼簿"),
|
||||
("Allow hearing sound", "允許分享音訊"),
|
||||
("Allow file copy and paste", "允許檔案複製和貼上"),
|
||||
("Connected", "已連線"),
|
||||
("Direct and encrypted connection", "加密直接連線"),
|
||||
("Relayed and encrypted connection", "加密轉送連線"),
|
||||
("Direct and unencrypted connection", "未加密直接連線"),
|
||||
("Relayed and unencrypted connection", "未加密轉送連線"),
|
||||
("Enter Remote ID", "輸入遠端 ID"),
|
||||
("Enter your password", "輸入您的密碼"),
|
||||
("Logging in...", "正在登入 ..."),
|
||||
("Enable RDP session sharing", "啟用 RDP 工作階段共享"),
|
||||
("Auto Login", "自動登入 (鎖定將在設定關閉後套用)"),
|
||||
("Enable Direct IP Access", "允許 IP 直接存取"),
|
||||
("Rename", "重新命名"),
|
||||
("Space", "空白"),
|
||||
("Create Desktop Shortcut", "新增桌面捷徑"),
|
||||
("Change Path", "更改路徑"),
|
||||
("Create Folder", "新增資料夾"),
|
||||
("Please enter the folder name", "請輸入資料夾名稱"),
|
||||
("Fix it", "修復"),
|
||||
("Warning", "警告"),
|
||||
("Login screen using Wayland is not supported", "不支援使用 Wayland 的登入畫面"),
|
||||
("Reboot required", "需要重新啟動"),
|
||||
("Unsupported display server", "不支援顯示伺服器"),
|
||||
("x11 expected", "預期 x11"),
|
||||
("Port", "端口"),
|
||||
("Settings", "設定"),
|
||||
("Username", "使用者名稱"),
|
||||
("Invalid port", "連線端口無效"),
|
||||
("Closed manually by the peer", "遠端使用者關閉了工作階段"),
|
||||
("Enable remote configuration modification", "允許遠端使用者更改設定"),
|
||||
("Run without install", "跳過安裝直接執行"),
|
||||
("Connect via relay", "中繼連線"),
|
||||
("Always connect via relay", "一律透過轉送連線"),
|
||||
("whitelist_tip", "只有白名單中的 IP 可以存取"),
|
||||
("Login", "登入"),
|
||||
("Verify", "驗證"),
|
||||
("Remember me", "記住我"),
|
||||
("Trust this device", "信任此裝置"),
|
||||
("Verification code", "驗證碼"),
|
||||
("verification_tip", "檢測到新裝置登入,已向註冊電子信箱發送了登入驗證碼,請輸入驗證碼以繼續登入"),
|
||||
("Logout", "登出"),
|
||||
("Tags", "標籤"),
|
||||
("Search ID", "搜尋 ID"),
|
||||
("whitelist_sep", "使用逗號、分號、空白,或是換行來分隔"),
|
||||
("Add ID", "新增 ID"),
|
||||
("Add Tag", "新增標籤"),
|
||||
("Unselect all tags", "取消選取所有標籤"),
|
||||
("Network error", "網路錯誤"),
|
||||
("Username missed", "缺少使用者名稱"),
|
||||
("Password missed", "缺少密碼"),
|
||||
("Wrong credentials", "提供的登入資訊有誤"),
|
||||
("Edit Tag", "編輯標籤"),
|
||||
("Unremember Password", "忘掉密碼"),
|
||||
("Favorites", "我的最愛"),
|
||||
("Add to Favorites", "新增到我的最愛"),
|
||||
("Remove from Favorites", "從我的最愛中刪除"),
|
||||
("Empty", "空空如也"),
|
||||
("Invalid folder name", "資料夾名稱無效"),
|
||||
("Socks5 Proxy", "Socks5 代理"),
|
||||
("Hostname", "主機名稱"),
|
||||
("Discovered", "已探索"),
|
||||
("install_daemon_tip", "為了能夠開機時自動啟動,請先安裝系統服務。"),
|
||||
("Remote ID", "遠端 ID"),
|
||||
("Paste", "貼上"),
|
||||
("Paste here?", "貼上到這裡?"),
|
||||
("Are you sure to close the connection?", "您確定要關閉連線嗎?"),
|
||||
("Download new version", "下載新版本"),
|
||||
("Touch mode", "觸控模式"),
|
||||
("Mouse mode", "滑鼠模式"),
|
||||
("One-Finger Tap", "單指輕觸"),
|
||||
("Left Mouse", "滑鼠左鍵"),
|
||||
("One-Long Tap", "單指長按"),
|
||||
("Two-Finger Tap", "雙指輕觸"),
|
||||
("Right Mouse", "滑鼠右鍵"),
|
||||
("One-Finger Move", "單指移動"),
|
||||
("Double Tap & Move", "雙擊並移動"),
|
||||
("Mouse Drag", "滑鼠選中拖動"),
|
||||
("Three-Finger vertically", "三指垂直滑動"),
|
||||
("Mouse Wheel", "滑鼠滾輪"),
|
||||
("Two-Finger Move", "雙指移動"),
|
||||
("Canvas Move", "移動畫布"),
|
||||
("Pinch to Zoom", "雙指縮放"),
|
||||
("Canvas Zoom", "縮放畫布"),
|
||||
("Reset canvas", "重設畫布"),
|
||||
("No permission of file transfer", "沒有檔案傳輸權限"),
|
||||
("Note", "備註"),
|
||||
("Connection", "連線"),
|
||||
("Share Screen", "共享螢幕畫面"),
|
||||
("Chat", "聊天訊息"),
|
||||
("Total", "總計"),
|
||||
("items", "個項目"),
|
||||
("Selected", "已選擇"),
|
||||
("Screen Capture", "畫面錄製"),
|
||||
("Input Control", "輸入控制"),
|
||||
("Audio Capture", "音訊錄製"),
|
||||
("File Connection", "檔案連線"),
|
||||
("Screen Connection", "畫面連線"),
|
||||
("Do you accept?", "是否接受?"),
|
||||
("Open System Setting", "開啟系統設定"),
|
||||
("How to get Android input permission?", "如何獲取 Android 的輸入權限?"),
|
||||
("android_input_permission_tip1", "取得輸入權限後可以讓遠端裝置透過滑鼠控制此 Android 裝置"),
|
||||
("android_input_permission_tip2", "請在接下來的系統設定頁面中,找到並進入「已安裝的服務」頁面,並將「RustDesk Input」服務開啟"),
|
||||
("android_new_connection_tip", "收到新的連線控制請求,對方想要控制您目前的裝置"),
|
||||
("android_service_will_start_tip", "開啟畫面錄製權限將自動開啟服務,允許其他裝置向此裝置請求建立連線。"),
|
||||
("android_stop_service_tip", "關閉服務將自動關閉所有已建立的連線。"),
|
||||
("android_version_audio_tip", "目前的 Android 版本不支援音訊錄製,請升級到 Android 10 或以上版本。"),
|
||||
("android_start_service_tip", "點擊「啟動服務」或啟用「螢幕錄製」權限,以啟動螢幕共享服務。"),
|
||||
("android_permission_may_not_change_tip", "對於已經建立的連線,權限可能不會立即發生改變,除非重新建立連線。"),
|
||||
("Account", "帳號"),
|
||||
("Overwrite", "取代"),
|
||||
("This file exists, skip or overwrite this file?", "此檔案/資料夾已存在,要略過或是取代此檔案嗎?"),
|
||||
("Quit", "退出"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/zh-tw/manual/mac/#啟用權限"),
|
||||
("Help", "幫助"),
|
||||
("Failed", "失敗"),
|
||||
("Succeeded", "成功"),
|
||||
("Someone turns on privacy mode, exit", "其他使用者開啟隱私模式,退出"),
|
||||
("Unsupported", "不支援"),
|
||||
("Peer denied", "被控端拒絕"),
|
||||
("Please install plugins", "請安裝插件"),
|
||||
("Peer exit", "被控端退出"),
|
||||
("Failed to turn off", "退出失敗"),
|
||||
("Turned off", "退出"),
|
||||
("In privacy mode", "開啟隱私模式"),
|
||||
("Out privacy mode", "退出隱私模式"),
|
||||
("Language", "語言"),
|
||||
("Keep RustDesk background service", "保持 RustDesk 後台服務"),
|
||||
("Ignore Battery Optimizations", "忽略電池最佳化"),
|
||||
("android_open_battery_optimizations_tip", "如需關閉此功能,請在接下來的 RustDesk 應用設定頁面中,找到並進入 [電源] 頁面,取消勾選 [不受限制]"),
|
||||
("Start on Boot", "開機自動啟動"),
|
||||
("Start the screen sharing service on boot, requires special permissions", "開機自動啟動螢幕共享服務,此功能需要一些特殊權限。"),
|
||||
("Connection not allowed", "對方不允許連線"),
|
||||
("Legacy mode", "傳統模式"),
|
||||
("Map mode", "1:1 傳輸模式"),
|
||||
("Translate mode", "翻譯模式"),
|
||||
("Use permanent password", "使用固定密碼"),
|
||||
("Use both passwords", "同時使用兩種密碼"),
|
||||
("Set permanent password", "設定固定密碼"),
|
||||
("Enable Remote Restart", "啟用遠端重新啟動"),
|
||||
("Allow remote restart", "允許遠端重新啟動"),
|
||||
("Restart Remote Device", "重新啟動遠端電腦"),
|
||||
("Are you sure you want to restart", "確定要重新啟動"),
|
||||
("Restarting Remote Device", "正在重新啟動遠端裝置"),
|
||||
("remote_restarting_tip", "遠端裝置正在重新啟動,請關閉當前提示框,並在一段時間後使用永久密碼重新連線"),
|
||||
("Copied", "已複製"),
|
||||
("Exit Fullscreen", "退出全螢幕"),
|
||||
("Fullscreen", "全螢幕"),
|
||||
("Mobile Actions", "手機操作"),
|
||||
("Select Monitor", "選擇顯示器"),
|
||||
("Control Actions", "控制操作"),
|
||||
("Display Settings", "顯示設定"),
|
||||
("Ratio", "比例"),
|
||||
("Image Quality", "畫質"),
|
||||
("Scroll Style", "滾動樣式"),
|
||||
("Show Menubar", "顯示選單欄"),
|
||||
("Hide Menubar", "隱藏選單欄"),
|
||||
("Direct Connection", "直接連線"),
|
||||
("Relay Connection", "中繼連線"),
|
||||
("Secure Connection", "安全連線"),
|
||||
("Insecure Connection", "非安全連線"),
|
||||
("Scale original", "原始尺寸"),
|
||||
("Scale adaptive", "適應視窗"),
|
||||
("General", "通用"),
|
||||
("Security", "安全"),
|
||||
("Theme", "主題"),
|
||||
("Dark Theme", "黑暗主題"),
|
||||
("Light Theme", "明亮主題"),
|
||||
("Dark", "黑暗"),
|
||||
("Light", "明亮"),
|
||||
("Follow System", "跟隨系統"),
|
||||
("Enable hardware codec", "使用硬體編解碼器"),
|
||||
("Unlock Security Settings", "解鎖安全設定"),
|
||||
("Enable Audio", "允許傳輸音訊"),
|
||||
("Unlock Network Settings", "解鎖網路設定"),
|
||||
("Server", "伺服器"),
|
||||
("Direct IP Access", "IP 直接連線"),
|
||||
("Proxy", "代理"),
|
||||
("Apply", "應用"),
|
||||
("Disconnect all devices?", "中斷所有遠端連線?"),
|
||||
("Clear", "清空"),
|
||||
("Audio Input Device", "音訊輸入裝置"),
|
||||
("Deny remote access", "拒絕遠端存取"),
|
||||
("Use IP Whitelisting", "只允許白名單上的 IP 進行連線"),
|
||||
("Network", "網路"),
|
||||
("Enable RDP", "允許 RDP 訪問"),
|
||||
("Pin menubar", "固定選單欄"),
|
||||
("Unpin menubar", "取消固定選單欄"),
|
||||
("Recording", "錄製"),
|
||||
("Directory", "路徑"),
|
||||
("Automatically record incoming sessions", "自動錄製連入的工作階段"),
|
||||
("Change", "變更"),
|
||||
("Start session recording", "開始錄影"),
|
||||
("Stop session recording", "停止錄影"),
|
||||
("Enable Recording Session", "啟用錄製工作階段"),
|
||||
("Allow recording session", "允許錄製工作階段"),
|
||||
("Enable LAN Discovery", "允許區域網路探索"),
|
||||
("Deny LAN Discovery", "拒絕區域網路探索"),
|
||||
("Write a message", "輸入聊天訊息"),
|
||||
("Prompt", "提示"),
|
||||
("Please wait for confirmation of UAC...", "請等待對方確認 UAC ..."),
|
||||
("elevated_foreground_window_tip", "目前的遠端桌面視窗需要更高的權限才能繼續操作,暫時無法使用滑鼠、鍵盤,可以請求對方最小化目前視窗,或者在連線管理視窗點擊提升權限。為了避免這個問題,建議在遠端裝置上安裝本軟體。"),
|
||||
("Disconnected", "斷開連線"),
|
||||
("Other", "其他"),
|
||||
("Confirm before closing multiple tabs", "關閉多個分頁前詢問我"),
|
||||
("Keyboard Settings", "鍵盤設定"),
|
||||
("Full Access", "完全訪問"),
|
||||
("Screen Share", "僅分享螢幕畫面"),
|
||||
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland 需要 Ubuntu 21.04 或更高版本。"),
|
||||
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland 需要更高版本的 Linux 發行版。請嘗試使用 X11 桌面或更改您的作業系統。"),
|
||||
("JumpLink", "查看"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "請選擇要分享的螢幕畫面(在對端操作)。"),
|
||||
("Show RustDesk", "顯示 RustDesk"),
|
||||
("This PC", "此電腦"),
|
||||
("or", "或"),
|
||||
("Continue with", "繼續"),
|
||||
("Elevate", "提升權限"),
|
||||
("Zoom cursor", "縮放游標"),
|
||||
("Accept sessions via password", "只允許透過輸入密碼進行連線"),
|
||||
("Accept sessions via click", "只允許透過點擊接受進行連線"),
|
||||
("Accept sessions via both", "允許輸入密碼或點擊接受進行連線"),
|
||||
("Please wait for the remote side to accept your session request...", "請等待對方接受您的連線請求 ..."),
|
||||
("One-time Password", "一次性密碼"),
|
||||
("Use one-time password", "使用一次性密碼"),
|
||||
("One-time password length", "一次性密碼長度"),
|
||||
("Request access to your device", "請求訪問您的裝置"),
|
||||
("Hide connection management window", "隱藏連線管理視窗"),
|
||||
("hide_cm_tip", "在只允許密碼連線並且只用固定密碼的情況下才允許隱藏"),
|
||||
("wayland_experiment_tip", "Wayland 支援處於實驗階段,如果您需要使用無人值守訪問,請使用 X11。"),
|
||||
("Right click to select tabs", "右鍵選擇分頁"),
|
||||
("Skipped", "已略過"),
|
||||
("Add to Address Book", "新增到通訊錄"),
|
||||
("Group", "群組"),
|
||||
("Search", "搜尋"),
|
||||
("Closed manually by web console", "被 Web 控制台手動關閉"),
|
||||
("Local keyboard type", "本地鍵盤類型"),
|
||||
("Select local keyboard type", "請選擇本地鍵盤類型"),
|
||||
("software_render_tip", "如果您使用 NVIDIA 顯示卡,並且遠端視窗在建立連線後會立刻關閉,那麼請安裝 nouveau 顯示卡驅動程式並且選擇使用軟體渲染可能會有幫助。重新啟動軟體後生效。"),
|
||||
("Always use software rendering", "使用軟體渲染"),
|
||||
("config_input", "為了能夠透過鍵盤控制遠端桌面,請給予 RustDesk \"輸入監控\" 權限。"),
|
||||
("config_microphone", "為了支援透過麥克風進行音訊傳輸,請給予 RustDesk \"錄音\"權限。"),
|
||||
("request_elevation_tip", "如果遠端使用者可以操作電腦,也可以請求提升權限。"),
|
||||
("Wait", "等待"),
|
||||
("Elevation Error", "權限提升失敗"),
|
||||
("Ask the remote user for authentication", "請求遠端使用者進行身分驗證"),
|
||||
("Choose this if the remote account is administrator", "當遠端使用者帳戶是管理員時,請選擇此選項"),
|
||||
("Transmit the username and password of administrator", "發送管理員的使用者名稱和密碼"),
|
||||
("still_click_uac_tip", "依然需要遠端使用者在 UAC 視窗點擊確認。"),
|
||||
("Request Elevation", "請求權限提升"),
|
||||
("wait_accept_uac_tip", "請等待遠端使用者確認 UAC 對話框。"),
|
||||
("Elevate successfully", "權限提升成功"),
|
||||
("uppercase", "大寫字母"),
|
||||
("lowercase", "小寫字母"),
|
||||
("digit", "數字"),
|
||||
("special character", "特殊字元"),
|
||||
("length>=8", "長度不能小於 8"),
|
||||
("Weak", "弱"),
|
||||
("Medium", "中"),
|
||||
("Strong", "強"),
|
||||
("Switch Sides", "反轉存取方向"),
|
||||
("Please confirm if you want to share your desktop?", "請確認是否要讓對方存取您的桌面?"),
|
||||
("Display", "顯示"),
|
||||
("Default View Style", "預設顯示方式"),
|
||||
("Default Scroll Style", "預設滾動方式"),
|
||||
("Default Image Quality", "預設圖像質量"),
|
||||
("Default Codec", "預設編解碼器"),
|
||||
("Bitrate", "位元速率"),
|
||||
("FPS", "幀率"),
|
||||
("Auto", "自動"),
|
||||
("Other Default Options", "其他預設選項"),
|
||||
("Voice call", "語音通話"),
|
||||
("Text chat", "文字聊天"),
|
||||
("Stop voice call", "停止語音通話"),
|
||||
("relay_hint_tip", "可能無法直接連線,可以嘗試中繼連線。\n另外,如果想要直接使用中繼連線,可以在 ID 後面新增/r,或者在卡片選項裡選擇強制走中繼連線。"),
|
||||
("Reconnect", "重新連線"),
|
||||
("Codec", "編解碼器"),
|
||||
("Resolution", "解析度"),
|
||||
("No transfers in progress", "沒有正在進行的傳輸"),
|
||||
("Set one-time password length", "設定一次性密碼長度"),
|
||||
("idd_driver_tip", "安裝虛擬顯示器驅動程式,以便在沒有連接顯示器的情況下啟動虛擬顯示器進行控制。"),
|
||||
("confirm_idd_driver_tip", "安裝虛擬顯示器驅動程式的選項已勾選。請注意,測試證書將被安裝以信任虛擬顯示器驅動。測試證書僅會用於信任 RustDesk 的驅動程式。"),
|
||||
("RDP Settings", "RDP 設定"),
|
||||
("Sort by", "排序方式"),
|
||||
("New Connection", "新連線"),
|
||||
("Restore", "還原"),
|
||||
("Minimize", "最小化"),
|
||||
("Maximize", "最大化"),
|
||||
("Your Device", "您的裝置"),
|
||||
("empty_recent_tip", "空空如也"),
|
||||
("empty_favorite_tip", "空空如也"),
|
||||
("empty_lan_tip", "空空如也"),
|
||||
("empty_address_book_tip", "空空如也"),
|
||||
("eg: admin", "例如:admin"),
|
||||
("Empty Username", "空使用者帳號"),
|
||||
("Empty Password", "空密碼"),
|
||||
("Me", "我"),
|
||||
("identical_file_tip", "此檔案與對方的檔案一致"),
|
||||
("show_monitors_tip", "在工具列中顯示顯示器"),
|
||||
("View Mode", "瀏覽模式"),
|
||||
("login_linux_tip", "需要登入到遠端 Linux 使用者帳戶才能啟用 X 介面。"),
|
||||
("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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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,6 +467,7 @@ pub fn get_env_var(k: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// Headless is enabled, always return true.
|
||||
pub fn is_prelogin() -> bool {
|
||||
let n = get_active_userid().len();
|
||||
n < 4 && n > 1
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
733
src/platform/linux_desktop_manager.rs
Normal file
733
src/platform/linux_desktop_manager.rs
Normal 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"
|
||||
}
|
||||
}
|
@ -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")))]
|
||||
|
@ -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;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -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<()> {
|
||||
|
@ -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,
|
||||
@ -57,6 +61,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,
|
||||
@ -134,6 +154,10 @@ pub struct Connection {
|
||||
audio_input_device_before_voice_call: Option<String>,
|
||||
options_in_login: Option<OptionMessage>,
|
||||
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 {
|
||||
@ -194,6 +218,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();
|
||||
@ -246,10 +274,16 @@ impl Connection {
|
||||
audio_input_device_before_voice_call: None,
|
||||
options_in_login: None,
|
||||
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);
|
||||
}
|
||||
});
|
||||
@ -856,6 +890,10 @@ 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());
|
||||
@ -874,7 +912,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
|
||||
@ -1216,8 +1256,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()
|
||||
{
|
||||
@ -1225,7 +1285,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
|
||||
@ -1234,12 +1294,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
|
||||
@ -1281,16 +1367,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2058,6 +2174,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() {
|
||||
@ -2073,6 +2191,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);
|
||||
@ -2085,7 +2231,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;
|
||||
@ -2117,6 +2263,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! {
|
||||
|
@ -221,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);
|
||||
}
|
||||
@ -256,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);
|
||||
}
|
||||
|
@ -142,6 +142,10 @@ div.password input {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
div.username input {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
svg {
|
||||
background: none;
|
||||
}
|
||||
|
@ -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') {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -404,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();
|
||||
|
@ -512,10 +512,11 @@ pub fn get_error() -> String {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let dtype = crate::platform::linux::get_display_server();
|
||||
if "wayland" == dtype {
|
||||
if crate::platform::linux::DISPLAY_SERVER_WAYLAND == dtype
|
||||
{
|
||||
return crate::server::wayland::common_get_error();
|
||||
}
|
||||
if dtype != "x11" {
|
||||
if dtype != crate::platform::linux::DISPLAY_SERVER_X11 {
|
||||
return format!(
|
||||
"{} {}, {}",
|
||||
crate::client::translate("Unsupported display server".to_owned()),
|
||||
|
@ -703,8 +703,14 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
fs::get_string(&path)
|
||||
}
|
||||
|
||||
pub fn login(&self, password: String, remember: bool) {
|
||||
self.send(Data::Login((password, remember)));
|
||||
pub fn login(
|
||||
&self,
|
||||
os_username: String,
|
||||
os_password: String,
|
||||
password: String,
|
||||
remember: bool,
|
||||
) {
|
||||
self.send(Data::Login((os_username, os_password, password, remember)));
|
||||
}
|
||||
|
||||
pub fn new_rdp(&self) {
|
||||
@ -997,8 +1003,23 @@ impl<T: InvokeUiSession> Interface for Session<T> {
|
||||
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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user