mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
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,7 +548,25 @@ class _RemotePageState extends State<RemotePage> {
|
||||
more.add(PopupMenuItem<String>(
|
||||
child: Text(translate('Refresh')), value: 'refresh'));
|
||||
}
|
||||
more.add(PopupMenuItem<String>(
|
||||
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')),
|
||||
@ -560,7 +578,9 @@ class _RemotePageState extends State<RemotePage> {
|
||||
child: Icon(Icons.edit, color: MyTheme.accent),
|
||||
)
|
||||
])),
|
||||
value: 'enter_os_password'));
|
||||
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) {
|
||||
|
||||
162
src/client.rs
162
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();
|
||||
} else if LOGIN_ERROR_MAP.contains_key(err) {
|
||||
if let Some(msgbox_info) = LOGIN_ERROR_MAP.get(err) {
|
||||
interface.msgbox(
|
||||
"wait-remote-accept-nook",
|
||||
"Prompt",
|
||||
"Please wait for the remote side to accept your session request...",
|
||||
"",
|
||||
msgbox_info.msgtype,
|
||||
msgbox_info.title,
|
||||
msgbox_info.text,
|
||||
msgbox_info.link,
|
||||
);
|
||||
true
|
||||
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 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 {},
|
||||
};
|
||||
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();
|
||||
}
|
||||
|
||||
@ -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", ""),
|
||||
("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() {
|
||||
#[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,17 +1367,47 @@ impl Connection {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(self.ip.clone(), failure);
|
||||
self.send_login_error("Wrong Password").await;
|
||||
#[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);
|
||||
#[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(message::Union::TestDelay(t)) = msg.union {
|
||||
@ -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