diff --git a/Cargo.lock b/Cargo.lock
index 3e793ddb7..486b7a6f9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index 28c2c363d..c71b2918b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/build.py b/build.py
index be13207ff..3ad206ab1 100755
--- a/build.py
+++ b/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")
diff --git a/res/com.rustdesk.RustDesk.policy b/res/com.rustdesk.RustDesk.policy
deleted file mode 100644
index 55f13629b..000000000
--- a/res/com.rustdesk.RustDesk.policy
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
- RustDesk
- https://rustdesk.com/
- rustdesk
-
- Change RustDesk options
- Authentication is required to change RustDesk options
- 要更改RustDesk选项, 需要您先通过身份验证
- 要變更RustDesk選項, 需要您先通過身份驗證
- Authentifizierung zum Ändern der RustDesk-Optionen
- /usr/share/rustdesk/files/polkit
- true
-
- auth_admin
- auth_admin
- auth_admin
-
-
-
diff --git a/src/core_main.rs b/src/core_main.rs
index 5d137516e..23d7706d4 100644
--- a/src/core_main.rs
+++ b/src/core_main.rs
@@ -482,6 +482,13 @@ pub fn core_main() -> Option> {
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")))]
diff --git a/src/lang/ar.rs b/src/lang/ar.rs
index a157d3d9d..be1a6b767 100644
--- a/src/lang/ar.rs
+++ b/src/lang/ar.rs
@@ -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();
}
diff --git a/src/lang/be.rs b/src/lang/be.rs
index 4fbfff131..fb8444bec 100644
--- a/src/lang/be.rs
+++ b/src/lang/be.rs
@@ -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();
}
diff --git a/src/lang/bg.rs b/src/lang/bg.rs
index 2992911a8..b683a5293 100644
--- a/src/lang/bg.rs
+++ b/src/lang/bg.rs
@@ -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();
}
diff --git a/src/lang/ca.rs b/src/lang/ca.rs
index 260b0b4c6..a52da9735 100644
--- a/src/lang/ca.rs
+++ b/src/lang/ca.rs
@@ -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();
}
diff --git a/src/lang/cn.rs b/src/lang/cn.rs
index dc986aaca..887a72971 100644
--- a/src/lang/cn.rs
+++ b/src/lang/cn.rs
@@ -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();
}
diff --git a/src/lang/cs.rs b/src/lang/cs.rs
index c3a1b9758..67588bfb8 100644
--- a/src/lang/cs.rs
+++ b/src/lang/cs.rs
@@ -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();
}
diff --git a/src/lang/da.rs b/src/lang/da.rs
index be02a7360..aea1514ae 100644
--- a/src/lang/da.rs
+++ b/src/lang/da.rs
@@ -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();
}
diff --git a/src/lang/de.rs b/src/lang/de.rs
index 3069ebe3c..38720f537 100644
--- a/src/lang/de.rs
+++ b/src/lang/de.rs
@@ -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();
}
diff --git a/src/lang/el.rs b/src/lang/el.rs
index e05aa9fb8..fecff2894 100644
--- a/src/lang/el.rs
+++ b/src/lang/el.rs
@@ -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();
}
diff --git a/src/lang/eo.rs b/src/lang/eo.rs
index d8cb8ec34..eb512922e 100644
--- a/src/lang/eo.rs
+++ b/src/lang/eo.rs
@@ -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();
}
diff --git a/src/lang/es.rs b/src/lang/es.rs
index c087b287c..e42a5abed 100644
--- a/src/lang/es.rs
+++ b/src/lang/es.rs
@@ -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();
}
diff --git a/src/lang/et.rs b/src/lang/et.rs
index 24901265f..2443faae9 100644
--- a/src/lang/et.rs
+++ b/src/lang/et.rs
@@ -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();
}
diff --git a/src/lang/eu.rs b/src/lang/eu.rs
index 9213c9689..7c953ebe4 100644
--- a/src/lang/eu.rs
+++ b/src/lang/eu.rs
@@ -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();
}
diff --git a/src/lang/fa.rs b/src/lang/fa.rs
index 47988a95d..a97566d99 100644
--- a/src/lang/fa.rs
+++ b/src/lang/fa.rs
@@ -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();
}
diff --git a/src/lang/fr.rs b/src/lang/fr.rs
index 688bcf25f..6a9ca8d8d 100644
--- a/src/lang/fr.rs
+++ b/src/lang/fr.rs
@@ -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();
}
diff --git a/src/lang/he.rs b/src/lang/he.rs
index 7fc4758e1..5a42c4257 100644
--- a/src/lang/he.rs
+++ b/src/lang/he.rs
@@ -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();
}
diff --git a/src/lang/hr.rs b/src/lang/hr.rs
index 4671a0d47..9a2b8e3a1 100644
--- a/src/lang/hr.rs
+++ b/src/lang/hr.rs
@@ -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();
}
diff --git a/src/lang/hu.rs b/src/lang/hu.rs
index 2ceb6bce4..fb748b6b8 100644
--- a/src/lang/hu.rs
+++ b/src/lang/hu.rs
@@ -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();
}
diff --git a/src/lang/id.rs b/src/lang/id.rs
index 168c78117..9ed642459 100644
--- a/src/lang/id.rs
+++ b/src/lang/id.rs
@@ -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();
}
diff --git a/src/lang/it.rs b/src/lang/it.rs
index 08c00ee1f..613dcea94 100644
--- a/src/lang/it.rs
+++ b/src/lang/it.rs
@@ -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();
}
diff --git a/src/lang/ja.rs b/src/lang/ja.rs
index e58ff25f7..a63687c68 100644
--- a/src/lang/ja.rs
+++ b/src/lang/ja.rs
@@ -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();
}
diff --git a/src/lang/ko.rs b/src/lang/ko.rs
index daf85e632..417073d05 100644
--- a/src/lang/ko.rs
+++ b/src/lang/ko.rs
@@ -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();
}
diff --git a/src/lang/kz.rs b/src/lang/kz.rs
index 85829e84a..a23d34448 100644
--- a/src/lang/kz.rs
+++ b/src/lang/kz.rs
@@ -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();
}
diff --git a/src/lang/lt.rs b/src/lang/lt.rs
index 7387d34e3..b818cb7e7 100644
--- a/src/lang/lt.rs
+++ b/src/lang/lt.rs
@@ -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();
}
diff --git a/src/lang/lv.rs b/src/lang/lv.rs
index 0d8b19387..a89590bf4 100644
--- a/src/lang/lv.rs
+++ b/src/lang/lv.rs
@@ -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();
}
diff --git a/src/lang/nb.rs b/src/lang/nb.rs
index f3c4ed410..eb2e21b68 100644
--- a/src/lang/nb.rs
+++ b/src/lang/nb.rs
@@ -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();
}
diff --git a/src/lang/nl.rs b/src/lang/nl.rs
index c6d7bcbd3..5b2eee79e 100644
--- a/src/lang/nl.rs
+++ b/src/lang/nl.rs
@@ -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();
}
diff --git a/src/lang/pl.rs b/src/lang/pl.rs
index 59e4cbad2..99bf57084 100644
--- a/src/lang/pl.rs
+++ b/src/lang/pl.rs
@@ -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();
}
diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs
index d172cb4f9..534efe12e 100644
--- a/src/lang/pt_PT.rs
+++ b/src/lang/pt_PT.rs
@@ -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();
}
diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs
index 6a5300028..4396dd40d 100644
--- a/src/lang/ptbr.rs
+++ b/src/lang/ptbr.rs
@@ -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();
}
diff --git a/src/lang/ro.rs b/src/lang/ro.rs
index 7b3ba2b28..ea1d98880 100644
--- a/src/lang/ro.rs
+++ b/src/lang/ro.rs
@@ -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();
}
diff --git a/src/lang/ru.rs b/src/lang/ru.rs
index 59a499595..f7db94bd2 100644
--- a/src/lang/ru.rs
+++ b/src/lang/ru.rs
@@ -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();
}
diff --git a/src/lang/sk.rs b/src/lang/sk.rs
index 7b4678423..e7b17c6ae 100644
--- a/src/lang/sk.rs
+++ b/src/lang/sk.rs
@@ -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();
}
diff --git a/src/lang/sl.rs b/src/lang/sl.rs
index 637859cd4..acbaf2ea9 100755
--- a/src/lang/sl.rs
+++ b/src/lang/sl.rs
@@ -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();
}
diff --git a/src/lang/sq.rs b/src/lang/sq.rs
index fe9a30c0c..aca5b6a7a 100644
--- a/src/lang/sq.rs
+++ b/src/lang/sq.rs
@@ -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();
}
diff --git a/src/lang/sr.rs b/src/lang/sr.rs
index 906dc0d3f..c12555c97 100644
--- a/src/lang/sr.rs
+++ b/src/lang/sr.rs
@@ -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();
}
diff --git a/src/lang/sv.rs b/src/lang/sv.rs
index e877aca0c..837f702f8 100644
--- a/src/lang/sv.rs
+++ b/src/lang/sv.rs
@@ -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();
}
diff --git a/src/lang/template.rs b/src/lang/template.rs
index 7b08bf3a3..43fbbcfa5 100644
--- a/src/lang/template.rs
+++ b/src/lang/template.rs
@@ -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();
}
diff --git a/src/lang/th.rs b/src/lang/th.rs
index 1b818f479..1c5b09333 100644
--- a/src/lang/th.rs
+++ b/src/lang/th.rs
@@ -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();
}
diff --git a/src/lang/tr.rs b/src/lang/tr.rs
index 26085f539..10fa46880 100644
--- a/src/lang/tr.rs
+++ b/src/lang/tr.rs
@@ -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();
}
diff --git a/src/lang/tw.rs b/src/lang/tw.rs
index 690792dbb..812d5f861 100644
--- a/src/lang/tw.rs
+++ b/src/lang/tw.rs
@@ -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();
}
diff --git a/src/lang/uk.rs b/src/lang/uk.rs
index ccc73b7b0..aa34887be 100644
--- a/src/lang/uk.rs
+++ b/src/lang/uk.rs
@@ -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();
}
diff --git a/src/lang/vn.rs b/src/lang/vn.rs
index 408060a09..4f7bcf7d1 100644
--- a/src/lang/vn.rs
+++ b/src/lang/vn.rs
@@ -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();
}
diff --git a/src/platform/gtk_sudo.rs b/src/platform/gtk_sudo.rs
new file mode 100644
index 000000000..a3727134e
--- /dev/null
+++ b/src/platform/gtk_sudo.rs
@@ -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) {
+ 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) {
+ // 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::();
+ let (tx_from_ui, rx_from_ui) = channel::();
+
+ 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::>();
+ 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,
+ rx_from_ui: Receiver,
+ is_su: bool,
+ initial_password: Option,
+) -> 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, args: Vec) -> 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::>()
+ .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::>()
+ .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 {
+ 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!(
+ "{}",
+ 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
+}
diff --git a/src/platform/linux.rs b/src/platform/linux.rs
index 4bb666fb9..9c5494230 100644
--- a/src/platform/linux.rs
+++ b/src/platform/linux.rs
@@ -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 {
Ok(Command::new("pkexec").args(args).spawn()?)
}
+*/
pub fn check_super_user_permission() -> ResultType {
- 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 {
let cmd = std::env::current_exe()?;
match cmd.to_str() {
@@ -824,6 +810,7 @@ pub fn elevate(args: Vec<&str>) -> ResultType {
}
}
}
+*/
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}"
- ));
+ )));
}
}
diff --git a/src/platform/mod.rs b/src/platform/mod.rs
index 169bdb199..d0ddd09bf 100644
--- a/src/platform/mod.rs
+++ b/src/platform/mod.rs
@@ -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};
diff --git a/src/tray.rs b/src/tray.rs
index 74c18bf7b..3a3ae92f3 100644
--- a/src/tray.rs
+++ b/src/tray.rs
@@ -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();
+ }
}
};