replace pkexec with gtk sudo (#9383)
* Fix https://github.com/rustdesk/rustdesk/issues/9286, replace pkexec with gtk sudo. Tested on gnome (ubuntu 22.04, debian 13), xfce (manjaro, suse), kde (kubuntu 23), lxqt (lubuntu 22), Cinnamon (mint 21.3), Mate (mint 21.2) * Fix incorrect config of the main window opened by the tray, replace xdg-open with run_me, replace with dbus + run_me * Fix `check_if_stop_service`, it causes the problem fixed in https://github.com/rustdesk/rustdesk/pull/8414, now revert that fix and fix itself. Signed-off-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
parent
d08c335fdf
commit
88a99211f3
23
Cargo.lock
generated
23
Cargo.lock
generated
@ -860,6 +860,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.38"
|
||||
@ -3967,11 +3973,23 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if 1.0.0",
|
||||
"cfg_aliases",
|
||||
"cfg_aliases 0.1.1",
|
||||
"libc",
|
||||
"memoffset 0.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if 1.0.0",
|
||||
"cfg_aliases 0.2.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@ -5494,6 +5512,7 @@ dependencies = [
|
||||
"flutter_rust_bridge",
|
||||
"fon",
|
||||
"fruitbasket",
|
||||
"gtk",
|
||||
"hbb_common",
|
||||
"hex",
|
||||
"hound",
|
||||
@ -5508,6 +5527,7 @@ dependencies = [
|
||||
"libpulse-simple-binding",
|
||||
"mac_address",
|
||||
"magnum-opus",
|
||||
"nix 0.29.0",
|
||||
"num_cpus",
|
||||
"objc",
|
||||
"objc_id",
|
||||
@ -5539,6 +5559,7 @@ dependencies = [
|
||||
"system_shutdown",
|
||||
"tao",
|
||||
"tauri-winrt-notification",
|
||||
"termios",
|
||||
"totp-rs",
|
||||
"tray-icon",
|
||||
"url",
|
||||
|
@ -161,6 +161,9 @@ x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/
|
||||
x11rb = {version = "0.12", features = ["all-extensions"], optional = true}
|
||||
percent-encoding = {version = "2.3", optional = true}
|
||||
once_cell = {version = "1.18", optional = true}
|
||||
nix = { version = "0.29", features = ["term", "process"]}
|
||||
gtk = "0.18"
|
||||
termios = "0.3"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.13"
|
||||
|
4
build.py
4
build.py
@ -331,8 +331,6 @@ def build_flutter_deb(version, features):
|
||||
'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 ../res/com.rustdesk.RustDesk.policy tmpdeb/usr/share/polkit-1/actions/')
|
||||
system2(
|
||||
'cp ../res/startwm.sh tmpdeb/etc/rustdesk/')
|
||||
system2(
|
||||
@ -376,8 +374,6 @@ def build_deb_from_folder(version, binary_folder):
|
||||
'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 ../res/com.rustdesk.RustDesk.policy tmpdeb/usr/share/polkit-1/actions/')
|
||||
system2(
|
||||
"echo \"#!/bin/sh\" >> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit")
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC
|
||||
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
|
||||
<policyconfig>
|
||||
<vendor>RustDesk</vendor>
|
||||
<vendor_url>https://rustdesk.com/</vendor_url>
|
||||
<icon_name>rustdesk</icon_name>
|
||||
<action id="com.rustdesk.RustDesk.options">
|
||||
<description>Change RustDesk options</description>
|
||||
<message>Authentication is required to change RustDesk options</message>
|
||||
<message xml:lang="zh_CN">要更改RustDesk选项, 需要您先通过身份验证</message>
|
||||
<message xml:lang="zh_TW">要變更RustDesk選項, 需要您先通過身份驗證</message>
|
||||
<message xml:lang="de">Authentifizierung zum Ändern der RustDesk-Optionen</message>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">/usr/share/rustdesk/files/polkit</annotate>
|
||||
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
||||
<defaults>
|
||||
<allow_any>auth_admin</allow_any>
|
||||
<allow_inactive>auth_admin</allow_inactive>
|
||||
<allow_active>auth_admin</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
</policyconfig>
|
@ -482,6 +482,13 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
crate::flutter::connection_manager::start_cm_no_ui();
|
||||
}
|
||||
return None;
|
||||
} else if args[0] == "-gtk-sudo" {
|
||||
// rustdesk service kill `rustdesk --` processes
|
||||
#[cfg(target_os = "linux")]
|
||||
if args.len() > 2 {
|
||||
crate::platform::gtk_sudo::exec();
|
||||
}
|
||||
return None;
|
||||
} else {
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", "Възобновяване"),
|
||||
("Invalid file name", "Невалидно име за файл"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", "继续"),
|
||||
("Invalid file name", "无效文件名"),
|
||||
("one-way-file-transfer-tip", "被控端启用了单项文件传输"),
|
||||
("Authentication Required", "需要身份验证"),
|
||||
("Authenticate", "认证"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", "Pokračovat"),
|
||||
("Invalid file name", "Nesprávný název souboru"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", "Fortsæt"),
|
||||
("Invalid file name", "Ugyldigt filnavn"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", "Fortsetzen"),
|
||||
("Invalid file name", "Ungültiger Dateiname"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", "Continuar"),
|
||||
("Invalid file name", "Nombre de archivo no válido"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", "Riprendi"),
|
||||
("Invalid file name", "Nome file non valido"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", "再開"),
|
||||
("Invalid file name", "無効なファイル名"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", "Atsākt"),
|
||||
("Invalid file name", "Nederīgs faila nosaukums"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", "Hervatten"),
|
||||
("Invalid file name", "Ongeldige bestandsnaam"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", "Wznów"),
|
||||
("Invalid file name", "Nieprawidłowa nazwa pliku"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", "Continuar"),
|
||||
("Invalid file name", "Nome de arquivo inválido"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", "Продолжить"),
|
||||
("Invalid file name", "Неверное имя файла"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", "Obnoviť"),
|
||||
("Invalid file name", "Nesprávny názov súboru"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", "繼續"),
|
||||
("Invalid file name", "無效文件名"),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Resume", ""),
|
||||
("Invalid file name", ""),
|
||||
("one-way-file-transfer-tip", ""),
|
||||
("Authentication Required", ""),
|
||||
("Authenticate", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
774
src/platform/gtk_sudo.rs
Normal file
774
src/platform/gtk_sudo.rs
Normal file
@ -0,0 +1,774 @@
|
||||
// https://github.com/aarnt/qt-sudo
|
||||
// Sometimes reboot is needed to refresh sudoers.
|
||||
|
||||
use crate::lang::translate;
|
||||
use gtk::{glib, prelude::*};
|
||||
use hbb_common::{
|
||||
anyhow::{bail, Error},
|
||||
log, ResultType,
|
||||
};
|
||||
use nix::{
|
||||
libc::{fcntl, kill},
|
||||
pty::{forkpty, ForkptyResult},
|
||||
sys::{
|
||||
signal::Signal,
|
||||
wait::{waitpid, WaitPidFlag},
|
||||
},
|
||||
unistd::{execvp, setsid, Pid},
|
||||
};
|
||||
use std::{
|
||||
ffi::CString,
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
|
||||
sync::{
|
||||
mpsc::{channel, Receiver, Sender},
|
||||
Arc, Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
const EXIT_CODE: i32 = -1;
|
||||
|
||||
enum Message {
|
||||
PasswordPrompt(String),
|
||||
Password((String, String)),
|
||||
ErrorDialog(String),
|
||||
Cancel,
|
||||
Exit(i32),
|
||||
}
|
||||
|
||||
pub fn run(cmds: Vec<&str>) -> ResultType<()> {
|
||||
// rustdesk service kill `rustdesk --` processes
|
||||
let second_arg = std::env::args().nth(1).unwrap_or_default();
|
||||
let cmd_mode =
|
||||
second_arg.starts_with("--") && second_arg != "--tray" && second_arg != "--no-server";
|
||||
let mod_arg = if cmd_mode { "cmd" } else { "gui" };
|
||||
let mut args = vec!["-gtk-sudo", mod_arg];
|
||||
args.append(&mut cmds.clone());
|
||||
let mut child = crate::run_me(args)?;
|
||||
let exit_status = child.wait()?;
|
||||
if exit_status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("child exited with status: {:?}", exit_status);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exec() {
|
||||
let mut args = vec![];
|
||||
for arg in std::env::args().skip(3) {
|
||||
args.push(arg);
|
||||
}
|
||||
let cmd_mode = std::env::args().nth(2) == Some("cmd".to_string());
|
||||
if cmd_mode {
|
||||
cmd(args);
|
||||
} else {
|
||||
ui(args);
|
||||
}
|
||||
}
|
||||
|
||||
fn cmd(args: Vec<String>) {
|
||||
match unsafe { forkpty(None, None) } {
|
||||
Ok(forkpty_result) => match forkpty_result {
|
||||
ForkptyResult::Parent { child, master } => {
|
||||
if let Err(e) = cmd_parent(child, master) {
|
||||
log::error!("Parent error: {:?}", e);
|
||||
kill_child(child);
|
||||
std::process::exit(EXIT_CODE);
|
||||
}
|
||||
}
|
||||
ForkptyResult::Child => {
|
||||
if let Err(e) = child(None, args) {
|
||||
log::error!("Child error: {:?}", e);
|
||||
std::process::exit(EXIT_CODE);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!("forkpty error: {:?}", err);
|
||||
std::process::exit(EXIT_CODE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ui(args: Vec<String>) {
|
||||
// https://docs.gtk.org/gtk4/ctor.Application.new.html
|
||||
// https://docs.gtk.org/gio/type_func.Application.id_is_valid.html
|
||||
let application = gtk::Application::new(None, Default::default());
|
||||
|
||||
let (tx_to_ui, rx_to_ui) = channel::<Message>();
|
||||
let (tx_from_ui, rx_from_ui) = channel::<Message>();
|
||||
|
||||
let rx_to_ui = Arc::new(Mutex::new(rx_to_ui));
|
||||
let tx_from_ui = Arc::new(Mutex::new(tx_from_ui));
|
||||
|
||||
let rx_to_ui_clone = rx_to_ui.clone();
|
||||
let tx_from_ui_clone = tx_from_ui.clone();
|
||||
|
||||
let username = Arc::new(Mutex::new(crate::platform::get_active_username()));
|
||||
let username_clone = username.clone();
|
||||
let first_prompt = Arc::new(Mutex::new(true));
|
||||
|
||||
application.connect_activate(glib::clone!(@weak application =>move |_| {
|
||||
let rx_to_ui = rx_to_ui_clone.clone();
|
||||
let tx_from_ui = tx_from_ui_clone.clone();
|
||||
let last_password = Arc::new(Mutex::new(String::new()));
|
||||
let username = username_clone.clone();
|
||||
let first_prompt = first_prompt.clone();
|
||||
|
||||
glib::timeout_add_local(std::time::Duration::from_millis(50), move || {
|
||||
if let Ok(msg) = rx_to_ui.lock().unwrap().try_recv() {
|
||||
match msg {
|
||||
Message::PasswordPrompt(err_msg) => {
|
||||
let last_pwd = last_password.lock().unwrap().clone();
|
||||
let username = username.lock().unwrap().clone();
|
||||
let first = first_prompt.lock().unwrap().clone();
|
||||
*first_prompt.lock().unwrap() = false;
|
||||
if let Some((username, password)) = password_prompt(&username, &last_pwd, &err_msg, first) {
|
||||
*last_password.lock().unwrap() = password.clone();
|
||||
if let Err(e) = tx_from_ui
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send(Message::Password((username, password))) {
|
||||
error_dialog_and_exit(&format!("Channel error: {e:?}"), EXIT_CODE);
|
||||
}
|
||||
} else {
|
||||
if let Err(e) = tx_from_ui.lock().unwrap().send(Message::Cancel) {
|
||||
error_dialog_and_exit(&format!("Channel error: {e:?}"), EXIT_CODE);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::ErrorDialog(err_msg) => {
|
||||
error_dialog_and_exit(&err_msg, EXIT_CODE);
|
||||
}
|
||||
Message::Exit(code) => {
|
||||
log::info!("Exit code: {}", code);
|
||||
std::process::exit(code);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
glib::ControlFlow::Continue
|
||||
});
|
||||
}));
|
||||
|
||||
let tx_to_ui_clone = tx_to_ui.clone();
|
||||
std::thread::spawn(move || {
|
||||
let acitve_user = crate::platform::get_active_username();
|
||||
let mut initial_password = None;
|
||||
if acitve_user != "root" {
|
||||
if let Err(e) = tx_to_ui_clone.send(Message::PasswordPrompt("".to_string())) {
|
||||
log::error!("Channel error: {e:?}");
|
||||
std::process::exit(EXIT_CODE);
|
||||
}
|
||||
match rx_from_ui.recv() {
|
||||
Ok(Message::Password((user, password))) => {
|
||||
*username.lock().unwrap() = user;
|
||||
initial_password = Some(password);
|
||||
}
|
||||
Ok(Message::Cancel) => {
|
||||
log::info!("User canceled");
|
||||
std::process::exit(EXIT_CODE);
|
||||
}
|
||||
_ => {
|
||||
log::error!("Unexpected message");
|
||||
std::process::exit(EXIT_CODE);
|
||||
}
|
||||
}
|
||||
}
|
||||
let username = username.lock().unwrap().clone();
|
||||
let su_user = if username == acitve_user {
|
||||
None
|
||||
} else {
|
||||
Some(username)
|
||||
};
|
||||
match unsafe { forkpty(None, None) } {
|
||||
Ok(forkpty_result) => match forkpty_result {
|
||||
ForkptyResult::Parent { child, master } => {
|
||||
if let Err(e) = ui_parent(
|
||||
child,
|
||||
master,
|
||||
tx_to_ui_clone,
|
||||
rx_from_ui,
|
||||
su_user.is_some(),
|
||||
initial_password,
|
||||
) {
|
||||
log::error!("Parent error: {:?}", e);
|
||||
kill_child(child);
|
||||
std::process::exit(EXIT_CODE);
|
||||
}
|
||||
}
|
||||
ForkptyResult::Child => {
|
||||
if let Err(e) = child(su_user, args) {
|
||||
log::error!("Child error: {:?}", e);
|
||||
std::process::exit(EXIT_CODE);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!("forkpty error: {:?}", err);
|
||||
if let Err(e) =
|
||||
tx_to_ui.send(Message::ErrorDialog(format!("Forkpty error: {:?}", err)))
|
||||
{
|
||||
log::error!("Channel error: {e:?}");
|
||||
std::process::exit(EXIT_CODE);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let _holder = application.hold();
|
||||
let args: Vec<&str> = vec![];
|
||||
application.run_with_args(&args);
|
||||
log::debug!("exit from gtk::Application::run_with_args");
|
||||
std::process::exit(EXIT_CODE);
|
||||
}
|
||||
|
||||
fn cmd_parent(child: Pid, master: OwnedFd) -> ResultType<()> {
|
||||
let raw_fd = master.as_raw_fd();
|
||||
if unsafe { fcntl(raw_fd, nix::libc::F_SETFL, nix::libc::O_NONBLOCK) } != 0 {
|
||||
let errno = std::io::Error::last_os_error();
|
||||
bail!("fcntl error: {errno:?}");
|
||||
}
|
||||
let mut file = unsafe { File::from_raw_fd(raw_fd) };
|
||||
let mut stdout = std::io::stdout();
|
||||
let stdin = std::io::stdin();
|
||||
let stdin_fd = stdin.as_raw_fd();
|
||||
let old_termios = termios::Termios::from_fd(stdin_fd)?;
|
||||
turn_off_echo(stdin_fd).ok();
|
||||
shutdown_hooks::add_shutdown_hook(turn_on_echo_shutdown_hook);
|
||||
let (tx, rx) = channel::<Vec<u8>>();
|
||||
std::thread::spawn(move || loop {
|
||||
let mut line = String::default();
|
||||
match stdin.read_line(&mut line) {
|
||||
Ok(0) => {
|
||||
kill_child(child);
|
||||
break;
|
||||
}
|
||||
Ok(_) => {
|
||||
if let Err(e) = tx.send(line.as_bytes().to_vec()) {
|
||||
log::error!("Channel error: {e:?}");
|
||||
kill_child(child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {}
|
||||
Err(e) => {
|
||||
log::info!("Failed to read stdin: {e:?}");
|
||||
kill_child(child);
|
||||
break;
|
||||
}
|
||||
};
|
||||
});
|
||||
loop {
|
||||
let mut buf = [0; 1024];
|
||||
match file.read(&mut buf) {
|
||||
Ok(0) => {
|
||||
log::info!("read from child: EOF");
|
||||
break;
|
||||
}
|
||||
Ok(n) => {
|
||||
let buf = String::from_utf8_lossy(&buf[..n]).to_string();
|
||||
print!("{}", buf);
|
||||
if let Err(e) = stdout.flush() {
|
||||
log::error!("flush failed: {e:?}");
|
||||
kill_child(child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
}
|
||||
Err(e) => {
|
||||
// Child process is dead
|
||||
log::info!("Read child error: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
match rx.try_recv() {
|
||||
Ok(v) => {
|
||||
if let Err(e) = file.write_all(&v) {
|
||||
log::error!("write error: {e:?}");
|
||||
kill_child(child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => match e {
|
||||
std::sync::mpsc::TryRecvError::Empty => {}
|
||||
std::sync::mpsc::TryRecvError::Disconnected => {
|
||||
log::error!("receive error: {e:?}");
|
||||
kill_child(child);
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for child process
|
||||
let status = waitpid(child, None);
|
||||
log::info!("waitpid status: {:?}", status);
|
||||
let mut code = EXIT_CODE;
|
||||
match status {
|
||||
Ok(s) => match s {
|
||||
nix::sys::wait::WaitStatus::Exited(_pid, status) => {
|
||||
code = status;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Err(_) => {}
|
||||
}
|
||||
termios::tcsetattr(stdin_fd, termios::TCSANOW, &old_termios).ok();
|
||||
std::process::exit(code);
|
||||
}
|
||||
|
||||
fn ui_parent(
|
||||
child: Pid,
|
||||
master: OwnedFd,
|
||||
tx_to_ui: Sender<Message>,
|
||||
rx_from_ui: Receiver<Message>,
|
||||
is_su: bool,
|
||||
initial_password: Option<String>,
|
||||
) -> ResultType<()> {
|
||||
let mut initial_password = initial_password;
|
||||
let raw_fd = master.as_raw_fd();
|
||||
if unsafe { fcntl(raw_fd, nix::libc::F_SETFL, nix::libc::O_NONBLOCK) } != 0 {
|
||||
let errno = std::io::Error::last_os_error();
|
||||
tx_to_ui.send(Message::ErrorDialog(format!("fcntl error: {errno:?}")))?;
|
||||
bail!("fcntl error: {errno:?}");
|
||||
}
|
||||
let mut file = unsafe { File::from_raw_fd(raw_fd) };
|
||||
|
||||
let mut first = initial_password.is_none();
|
||||
let mut su_password_sent = false;
|
||||
let mut saved_output = String::default();
|
||||
loop {
|
||||
let mut buf = [0; 1024];
|
||||
match file.read(&mut buf) {
|
||||
Ok(0) => {
|
||||
log::info!("read from child: EOF");
|
||||
break;
|
||||
}
|
||||
Ok(n) => {
|
||||
saved_output = String::default();
|
||||
let buf = String::from_utf8_lossy(&buf[..n]).trim().to_string();
|
||||
let last_line = buf.lines().last().unwrap_or(&buf).trim().to_string();
|
||||
log::info!("read from child: {}", buf);
|
||||
|
||||
if last_line.starts_with("sudo:") || last_line.starts_with("su:") {
|
||||
if let Err(e) = tx_to_ui.send(Message::ErrorDialog(last_line)) {
|
||||
log::error!("Channel error: {e:?}");
|
||||
kill_child(child);
|
||||
}
|
||||
break;
|
||||
} else if last_line.ends_with(":") {
|
||||
match get_echo_turn_off(raw_fd) {
|
||||
Ok(true) => {
|
||||
log::debug!("get_echo_turn_off ok");
|
||||
if let Some(password) = initial_password.clone() {
|
||||
let v = format!("{}\n", password);
|
||||
if let Err(e) = file.write_all(v.as_bytes()) {
|
||||
let e = format!("Failed to send password: {e:?}");
|
||||
if let Err(e) = tx_to_ui.send(Message::ErrorDialog(e)) {
|
||||
log::error!("Channel error: {e:?}");
|
||||
}
|
||||
kill_child(child);
|
||||
break;
|
||||
}
|
||||
if is_su && !su_password_sent {
|
||||
su_password_sent = true;
|
||||
continue;
|
||||
}
|
||||
initial_password = None;
|
||||
continue;
|
||||
}
|
||||
// In fact, su mode can only input password once
|
||||
let err_msg = if first { "" } else { "Sorry, try again." };
|
||||
first = false;
|
||||
if let Err(e) =
|
||||
tx_to_ui.send(Message::PasswordPrompt(err_msg.to_string()))
|
||||
{
|
||||
log::error!("Channel error: {e:?}");
|
||||
kill_child(child);
|
||||
break;
|
||||
}
|
||||
match rx_from_ui.recv() {
|
||||
Ok(Message::Password((_, password))) => {
|
||||
let v = format!("{}\n", password);
|
||||
if let Err(e) = file.write_all(v.as_bytes()) {
|
||||
let e = format!("Failed to send password: {e:?}");
|
||||
if let Err(e) = tx_to_ui.send(Message::ErrorDialog(e)) {
|
||||
log::error!("Channel error: {e:?}");
|
||||
}
|
||||
kill_child(child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(Message::Cancel) => {
|
||||
log::info!("User canceled");
|
||||
kill_child(child);
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
log::error!("Unexpected message");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false) => log::warn!("get_echo_turn_off timeout"),
|
||||
Err(e) => log::error!("get_echo_turn_off error: {:?}", e),
|
||||
}
|
||||
} else {
|
||||
saved_output = buf.clone();
|
||||
if !last_line.is_empty() && initial_password.is_some() {
|
||||
log::error!("received not empty line: {last_line}, clear initial password");
|
||||
initial_password = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
}
|
||||
Err(e) => {
|
||||
// Child process is dead
|
||||
log::debug!("Read error: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for child process
|
||||
let status = waitpid(child, None);
|
||||
log::info!("waitpid status: {:?}", status);
|
||||
let mut code = EXIT_CODE;
|
||||
match status {
|
||||
Ok(s) => match s {
|
||||
nix::sys::wait::WaitStatus::Exited(_pid, status) => {
|
||||
code = status;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
if code != 0 && !saved_output.is_empty() {
|
||||
if let Err(e) = tx_to_ui.send(Message::ErrorDialog(saved_output.clone())) {
|
||||
log::error!("Channel error: {e:?}");
|
||||
std::process::exit(code);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
if let Err(e) = tx_to_ui.send(Message::Exit(code)) {
|
||||
log::error!("Channel error: {e:?}");
|
||||
std::process::exit(code);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn child(su_user: Option<String>, args: Vec<String>) -> ResultType<()> {
|
||||
// https://doc.rust-lang.org/std/env/consts/constant.OS.html
|
||||
let os = std::env::consts::OS;
|
||||
let bsd = os == "freebsd" || os == "dragonfly" || os == "netbsd" || os == "openbad";
|
||||
let mut params = vec!["sudo".to_string()];
|
||||
if su_user.is_some() {
|
||||
params.push("-S".to_string());
|
||||
}
|
||||
params.push("/bin/sh".to_string());
|
||||
params.push("-c".to_string());
|
||||
|
||||
let command = args
|
||||
.iter()
|
||||
.map(|s| {
|
||||
if su_user.is_some() {
|
||||
s.to_string()
|
||||
} else {
|
||||
quote_shell_arg(s, true)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ");
|
||||
let mut command = if bsd {
|
||||
let lc = match std::env::var("LC_ALL") {
|
||||
Ok(lc_all) => {
|
||||
if lc_all.contains('\'') {
|
||||
eprintln!(
|
||||
"sudo: Detected attempt to inject privileged command via LC_ALL env({lc_all}). Exiting!\n",
|
||||
);
|
||||
std::process::exit(EXIT_CODE);
|
||||
}
|
||||
format!("LC_ALL='{lc_all}' ")
|
||||
}
|
||||
Err(_) => {
|
||||
format!("unset LC_ALL;")
|
||||
}
|
||||
};
|
||||
format!("{}exec {}", lc, command)
|
||||
} else {
|
||||
command.to_string()
|
||||
};
|
||||
if su_user.is_some() {
|
||||
command = format!("'{}'", quote_shell_arg(&command, false));
|
||||
}
|
||||
params.push(command);
|
||||
std::env::set_var("LC_ALL", "C.UTF-8");
|
||||
|
||||
if let Some(user) = &su_user {
|
||||
let su_subcommand = params
|
||||
.iter()
|
||||
.map(|p| p.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ");
|
||||
params = vec![
|
||||
"su".to_string(),
|
||||
"-".to_string(),
|
||||
user.to_string(),
|
||||
"-c".to_string(),
|
||||
su_subcommand,
|
||||
];
|
||||
}
|
||||
|
||||
// allow failure here
|
||||
let _ = setsid();
|
||||
let mut cparams = vec![];
|
||||
for param in ¶ms {
|
||||
cparams.push(CString::new(param.as_str())?);
|
||||
}
|
||||
let su_or_sudo = if su_user.is_some() { "su" } else { "sudo" };
|
||||
let res = execvp(CString::new(su_or_sudo)?.as_c_str(), &cparams);
|
||||
eprintln!("sudo: execvp error: {:?}", res);
|
||||
std::process::exit(EXIT_CODE);
|
||||
}
|
||||
|
||||
fn get_echo_turn_off(fd: RawFd) -> Result<bool, Error> {
|
||||
let tios = termios::Termios::from_fd(fd)?;
|
||||
for _ in 0..10 {
|
||||
if tios.c_lflag & termios::ECHO == 0 {
|
||||
return Ok(true);
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn turn_off_echo(fd: RawFd) -> Result<(), Error> {
|
||||
use termios::*;
|
||||
let mut termios = Termios::from_fd(fd)?;
|
||||
// termios.c_lflag &= !(ECHO | ECHONL | ICANON | IEXTEN);
|
||||
termios.c_lflag &= !ECHO;
|
||||
tcsetattr(fd, TCSANOW, &termios)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub extern "C" fn turn_on_echo_shutdown_hook() {
|
||||
let fd = std::io::stdin().as_raw_fd();
|
||||
if let Ok(mut termios) = termios::Termios::from_fd(fd) {
|
||||
termios.c_lflag |= termios::ECHO;
|
||||
termios::tcsetattr(fd, termios::TCSANOW, &termios).ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn kill_child(child: Pid) {
|
||||
unsafe { kill(child.as_raw(), Signal::SIGINT as _) };
|
||||
let mut res = 0;
|
||||
|
||||
for _ in 0..10 {
|
||||
match waitpid(child, Some(WaitPidFlag::WNOHANG)) {
|
||||
Ok(_) => {
|
||||
res = 1;
|
||||
break;
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
|
||||
if res == 0 {
|
||||
log::info!("Force killing child process");
|
||||
unsafe { kill(child.as_raw(), Signal::SIGKILL as _) };
|
||||
}
|
||||
}
|
||||
|
||||
fn password_prompt(
|
||||
username: &str,
|
||||
last_password: &str,
|
||||
err: &str,
|
||||
show_edit: bool,
|
||||
) -> Option<(String, String)> {
|
||||
let dialog = gtk::Dialog::builder()
|
||||
.title(crate::get_app_name())
|
||||
.modal(true)
|
||||
.build();
|
||||
// https://docs.gtk.org/gtk4/method.Dialog.set_default_response.html
|
||||
dialog.set_default_response(gtk::ResponseType::Ok);
|
||||
let content_area = dialog.content_area();
|
||||
|
||||
let label = gtk::Label::builder()
|
||||
.label(translate("Authentication Required".to_string()))
|
||||
.margin_top(10)
|
||||
.build();
|
||||
content_area.add(&label);
|
||||
|
||||
let image = gtk::Image::from_icon_name(Some("avatar-default-symbolic"), gtk::IconSize::Dialog);
|
||||
image.set_margin_top(10);
|
||||
content_area.add(&image);
|
||||
|
||||
let user_label = gtk::Label::new(Some(username));
|
||||
let edit_button = gtk::Button::new();
|
||||
edit_button.set_relief(gtk::ReliefStyle::None);
|
||||
let edit_icon =
|
||||
gtk::Image::from_icon_name(Some("document-edit-symbolic"), gtk::IconSize::Button.into());
|
||||
edit_button.set_image(Some(&edit_icon));
|
||||
edit_button.set_can_focus(false);
|
||||
let user_entry = gtk::Entry::new();
|
||||
user_entry.set_alignment(0.5);
|
||||
user_entry.set_width_request(100);
|
||||
let user_box = gtk::Box::new(gtk::Orientation::Horizontal, 5);
|
||||
user_box.add(&user_label);
|
||||
user_box.add(&edit_button);
|
||||
user_box.add(&user_entry);
|
||||
user_box.set_halign(gtk::Align::Center);
|
||||
user_box.set_valign(gtk::Align::Center);
|
||||
content_area.add(&user_box);
|
||||
|
||||
edit_button.connect_clicked(
|
||||
glib::clone!(@weak user_label, @weak edit_button, @weak user_entry=> move |_| {
|
||||
let username = user_label.text().to_string();
|
||||
user_entry.set_text(&username);
|
||||
user_label.hide();
|
||||
edit_button.hide();
|
||||
user_entry.show();
|
||||
user_entry.grab_focus();
|
||||
}),
|
||||
);
|
||||
|
||||
let password_input = gtk::Entry::builder()
|
||||
.visibility(false)
|
||||
.input_purpose(gtk::InputPurpose::Password)
|
||||
.placeholder_text(translate("Password".to_string()))
|
||||
.margin_top(20)
|
||||
.margin_start(30)
|
||||
.margin_end(30)
|
||||
.activates_default(true)
|
||||
.text(last_password)
|
||||
.build();
|
||||
password_input.set_alignment(0.5);
|
||||
// https://docs.gtk.org/gtk3/signal.Entry.activate.html
|
||||
password_input.connect_activate(glib::clone!(@weak dialog => move |_| {
|
||||
dialog.response(gtk::ResponseType::Ok);
|
||||
}));
|
||||
content_area.add(&password_input);
|
||||
|
||||
user_entry.connect_focus_out_event(
|
||||
glib::clone!(@weak user_label, @weak edit_button, @weak user_entry, @weak password_input => @default-return glib::Propagation::Proceed, move |_, _| {
|
||||
let username = user_entry.text().to_string();
|
||||
user_label.set_text(&username);
|
||||
user_entry.hide();
|
||||
user_label.show();
|
||||
edit_button.show();
|
||||
glib::Propagation::Proceed
|
||||
}),
|
||||
);
|
||||
user_entry.connect_activate(
|
||||
glib::clone!(@weak user_label, @weak edit_button, @weak user_entry, @weak password_input => move |_| {
|
||||
let username = user_entry.text().to_string();
|
||||
user_label.set_text(&username);
|
||||
user_entry.hide();
|
||||
user_label.show();
|
||||
edit_button.show();
|
||||
password_input.grab_focus();
|
||||
}),
|
||||
);
|
||||
|
||||
if !err.is_empty() {
|
||||
let err_label = gtk::Label::new(None);
|
||||
err_label.set_markup(&format!(
|
||||
"<span font='10' foreground='orange'>{}</span>",
|
||||
err
|
||||
));
|
||||
err_label.set_selectable(true);
|
||||
content_area.add(&err_label);
|
||||
}
|
||||
|
||||
let cancel_button = gtk::Button::builder()
|
||||
.label(translate("Cancel".to_string()))
|
||||
.expand(true)
|
||||
.build();
|
||||
cancel_button.connect_clicked(glib::clone!(@weak dialog => move |_| {
|
||||
dialog.response(gtk::ResponseType::Cancel);
|
||||
}));
|
||||
let authenticate_button = gtk::Button::builder()
|
||||
.label(translate("Authenticate".to_string()))
|
||||
.expand(true)
|
||||
.build();
|
||||
authenticate_button.connect_clicked(glib::clone!(@weak dialog => move |_| {
|
||||
dialog.response(gtk::ResponseType::Ok);
|
||||
}));
|
||||
let button_box = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Horizontal)
|
||||
.expand(true)
|
||||
.homogeneous(true)
|
||||
.spacing(10)
|
||||
.margin_top(10)
|
||||
.build();
|
||||
button_box.add(&cancel_button);
|
||||
button_box.add(&authenticate_button);
|
||||
content_area.add(&button_box);
|
||||
|
||||
content_area.set_spacing(10);
|
||||
content_area.set_border_width(10);
|
||||
|
||||
dialog.set_width_request(400);
|
||||
dialog.show_all();
|
||||
dialog.set_position(gtk::WindowPosition::Center);
|
||||
dialog.set_keep_above(true);
|
||||
password_input.grab_focus();
|
||||
user_entry.hide();
|
||||
if !show_edit {
|
||||
edit_button.hide();
|
||||
}
|
||||
dialog.check_resize();
|
||||
let response = dialog.run();
|
||||
dialog.hide();
|
||||
|
||||
if response == gtk::ResponseType::Ok {
|
||||
let username = if user_entry.get_visible() {
|
||||
user_entry.text().to_string()
|
||||
} else {
|
||||
user_label.text().to_string()
|
||||
};
|
||||
Some((username, password_input.text().to_string()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn error_dialog_and_exit(err_msg: &str, exit_code: i32) {
|
||||
log::error!("Error dialog: {err_msg}, exit code: {exit_code}");
|
||||
let dialog = gtk::MessageDialog::builder()
|
||||
.message_type(gtk::MessageType::Error)
|
||||
.title(crate::get_app_name())
|
||||
.text("Error")
|
||||
.secondary_text(err_msg)
|
||||
.modal(true)
|
||||
.buttons(gtk::ButtonsType::Ok)
|
||||
.build();
|
||||
dialog.set_position(gtk::WindowPosition::Center);
|
||||
dialog.set_keep_above(true);
|
||||
dialog.run();
|
||||
dialog.close();
|
||||
std::process::exit(exit_code);
|
||||
}
|
||||
|
||||
fn quote_shell_arg(arg: &str, add_splash_if_match: bool) -> String {
|
||||
let mut rv = arg.to_string();
|
||||
let re = hbb_common::regex::Regex::new("(\\s|[][!\"#$&'()*,;<=>?\\^`{}|~])");
|
||||
let Ok(re) = re else {
|
||||
return rv;
|
||||
};
|
||||
if re.is_match(arg) {
|
||||
rv = rv.replace("'", "'\\''");
|
||||
if add_splash_if_match {
|
||||
rv = format!("'{}'", rv);
|
||||
}
|
||||
}
|
||||
rv
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use super::{CursorData, ResultType};
|
||||
use super::{gtk_sudo, CursorData, ResultType};
|
||||
use desktop::Desktop;
|
||||
use hbb_common::config::keys::OPTION_ALLOW_LINUX_HEADLESS;
|
||||
pub use hbb_common::platform::linux::*;
|
||||
@ -15,8 +15,6 @@ use hbb_common::{
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
ffi::OsStr,
|
||||
fs::File,
|
||||
io::{BufRead, BufReader, Write},
|
||||
path::{Path, PathBuf},
|
||||
process::{Child, Command},
|
||||
string::String,
|
||||
@ -766,30 +764,18 @@ pub fn quit_gui() {
|
||||
unsafe { gtk_main_quit() };
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn exec_privileged(args: &[&str]) -> ResultType<Child> {
|
||||
Ok(Command::new("pkexec").args(args).spawn()?)
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn check_super_user_permission() -> ResultType<bool> {
|
||||
let file = format!(
|
||||
"/usr/share/{}/files/polkit",
|
||||
crate::get_app_name().to_lowercase()
|
||||
);
|
||||
let arg;
|
||||
if Path::new(&file).is_file() {
|
||||
arg = file.as_str();
|
||||
} else {
|
||||
arg = "echo";
|
||||
}
|
||||
// https://github.com/rustdesk/rustdesk/issues/2756
|
||||
if let Ok(status) = Command::new("pkexec").arg(arg).status() {
|
||||
// https://github.com/rustdesk/rustdesk/issues/5205#issuecomment-1658059657s
|
||||
Ok(status.code() != Some(126) && status.code() != Some(127))
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
gtk_sudo::run(vec!["echo"])?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn elevate(args: Vec<&str>) -> ResultType<bool> {
|
||||
let cmd = std::env::current_exe()?;
|
||||
match cmd.to_str() {
|
||||
@ -824,6 +810,7 @@ pub fn elevate(args: Vec<&str>) -> ResultType<bool> {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
type GtkSettingsPtr = *mut c_void;
|
||||
type GObjectPtr = *mut c_void;
|
||||
@ -1324,21 +1311,8 @@ fn has_cmd(cmd: &str) -> bool {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn run_cmds_pkexec(cmds: &str) -> bool {
|
||||
const DONE: &str = "RUN_CMDS_PKEXEC_DONE";
|
||||
if let Ok(output) = std::process::Command::new("pkexec")
|
||||
.arg("sh")
|
||||
.arg("-c")
|
||||
.arg(&format!("{cmds} echo {DONE}"))
|
||||
.output()
|
||||
{
|
||||
let out = String::from_utf8_lossy(&output.stdout);
|
||||
log::debug!("cmds: {cmds}");
|
||||
log::debug!("output: {out}");
|
||||
out.contains(DONE)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
pub fn run_cmds_privileged(cmds: &str) -> bool {
|
||||
crate::platform::gtk_sudo::run(vec![cmds]).is_ok()
|
||||
}
|
||||
|
||||
pub fn run_me_with(secs: u32) {
|
||||
@ -1367,13 +1341,15 @@ fn switch_service(stop: bool) -> String {
|
||||
|
||||
pub fn uninstall_service(show_new_window: bool, _: bool) -> bool {
|
||||
if !has_cmd("systemctl") {
|
||||
// Failed when installed + flutter run + started by `show_new_window`.
|
||||
return false;
|
||||
}
|
||||
log::info!("Uninstalling service...");
|
||||
let cp = switch_service(true);
|
||||
let app_name = crate::get_app_name().to_lowercase();
|
||||
if !run_cmds_pkexec(&format!(
|
||||
"systemctl disable {app_name}; systemctl stop {app_name}; {cp}"
|
||||
// systemctl kill rustdesk --tray, execute cp first
|
||||
if !run_cmds_privileged(&format!(
|
||||
"{cp} systemctl disable {app_name}; systemctl stop {app_name};"
|
||||
)) {
|
||||
Config::set_option("stop-service".into(), "".into());
|
||||
return true;
|
||||
@ -1393,8 +1369,8 @@ pub fn install_service() -> bool {
|
||||
log::info!("Installing service...");
|
||||
let cp = switch_service(false);
|
||||
let app_name = crate::get_app_name().to_lowercase();
|
||||
if !run_cmds_pkexec(&format!(
|
||||
"{cp} systemctl enable {app_name}; systemctl stop {app_name}; systemctl start {app_name};"
|
||||
if !run_cmds_privileged(&format!(
|
||||
"{cp} systemctl enable {app_name}; systemctl start {app_name};"
|
||||
)) {
|
||||
Config::set_option("stop-service".into(), "Y".into());
|
||||
}
|
||||
@ -1404,9 +1380,9 @@ pub fn install_service() -> bool {
|
||||
fn check_if_stop_service() {
|
||||
if Config::get_option("stop-service".into()) == "Y" {
|
||||
let app_name = crate::get_app_name().to_lowercase();
|
||||
allow_err!(run_cmds(
|
||||
allow_err!(run_cmds(&format!(
|
||||
"systemctl disable {app_name}; systemctl stop {app_name}"
|
||||
));
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,9 @@ pub mod linux;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod linux_desktop_manager;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod gtk_sudo;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::{message_proto::CursorData, ResultType};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
@ -98,12 +98,11 @@ fn make_tray() -> hbb_common::ResultType<()> {
|
||||
crate::run_me::<&str>(vec![]).ok();
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
if !std::process::Command::new("xdg-open")
|
||||
.arg(&crate::get_uri_prefix())
|
||||
.spawn()
|
||||
.is_ok()
|
||||
{
|
||||
crate::run_me::<&str>(vec![]).ok();
|
||||
// Do not use "xdg-open", it won't read config
|
||||
if crate::dbus::invoke_new_connection(crate::get_uri_prefix()).is_err() {
|
||||
crate::run_me::<&str>(vec![]).ok();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user