Compare commits

..

8 Commits

Author SHA1 Message Date
fufesou
3c574a4182 fix(wayland): clipboard, support ext-data-control (#15366)
* fix(wayland): clipboard, support ext-data-control

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(clipboard): restart stale listener and log join panics

Signed-off-by: fufesou <linlong1266@gmail.com>

* update clipboard-master

Signed-off-by: fufesou <linlong1266@gmail.com>

* refactor(clipboard): remove redundant stale listener cleanup

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-06-21 17:09:41 +08:00
Tigah
311d4708e5 fix(linux): reap a crashed headless session's leftovers on next start (#15348)
The teardown cleanup added for #15183 only runs on a clean disconnect.
If the service or its --server crashes before then, the headless logind
session scope and the /tmp/.X<n> lock files it created leak the same way
#15183 leaked them, with nothing to reclaim them afterwards.

Record the session scope and display when the headless session starts,
and on the next --server start reap exactly what the previous run
recorded, then drop the marker. It only ever touches the one scope and
display the previous run recorded, never a scan, so unrelated sessions
are untouched; the reap and X cleanup reuse the teardown path.

A logind session id is only unique within a boot: the counter lives in
/run and resets, so a recorded "session-N.scope" can name a different,
live session after a reboot. Tag the marker with the boot id and only
reap the scope when it matches the current boot. A leaked cgroup cannot
outlive a reboot, so nothing legitimate is lost cross-boot; the X lock
cleanup stays pid-guarded and runs either way.

Signed-off-by: TBX3D <88289044+TBX3D@users.noreply.github.com>
2026-06-21 16:56:41 +08:00
Maison da Silva
5cf4323d07 Fix Portuguese translations for consistency (#15354) 2026-06-21 16:52:28 +08:00
bovirus
3976701ac6 Update Italian translations (#15367) 2026-06-21 16:52:13 +08:00
fufesou
9ded8d6ab2 fix(keyboard): win, key, Pause (#15351)
Signed-off-by: fufesou <linlong1266@gmail.com>
2026-06-20 21:11:19 +08:00
rustdesk
cd2fff0655 fix hbb_common 2026-06-20 18:31:42 +08:00
rustdesk
10f61ffdc2 translate 2026-06-20 18:27:42 +08:00
Tigah
d72952bf93 fix(linux): clean up leftover session procs and X locks on headless teardown (#15337)
On headless login the desktop manager opens a PAM session, which makes
pam_systemd register a logind session and put the spawned Xorg + window
manager and their children (e.g. pipewire) in a "session-<id>.scope"
cgroup. Teardown only killed the Xorg and wm pids, so the rest of the
session kept running, holding the logind session in "closing" and leaking
runtime sockets and X display numbers on every reconnect.

Capture the session scope cgroup from a child pid and, on teardown, kill the
remaining processes in it and any descendant cgroups (cgroup.procs is not
recursive, and a desktop may move pipewire and apps into child scopes),
excluding our own service process and anything tracked in CHILD_PROCESS
together with its descendants. The connection manager is a sudo child, so the
tracked pid is the wrapper while the real --cm-no-ui worker may be a descendant
(sudo with use_pty runs it under a monitor); both can share the scope when
their PAM stack does not re-home them.

Xorg is killed with SIGKILL, so it also leaves its "/tmp/.X<n>-lock" and
"/tmp/.X11-unix/X<n>" behind; get_avail_display() treats either file as the
display being in use, so the number is never reused and climbs until the
range is exhausted. Remove those files for the session's display on
teardown, as a clean Xorg exit would.

Closes #15183

Signed-off-by: TBX3D <88289044+TBX3D@users.noreply.github.com>
2026-06-20 14:21:42 +08:00
54 changed files with 598 additions and 24 deletions

17
Cargo.lock generated
View File

@@ -1324,7 +1324,7 @@ dependencies = [
[[package]]
name = "clipboard-master"
version = "4.0.0-beta.6"
source = "git+https://github.com/rustdesk-org/clipboard-master#ddc39f00a6211959489ae683aa6ae6eedf03a809"
source = "git+https://github.com/rustdesk-org/clipboard-master#7762d74e38db37cfeb6ded88c964b9cdbddfb6db"
dependencies = [
"objc",
"objc-foundation",
@@ -6920,7 +6920,7 @@ dependencies = [
[[package]]
name = "rdev"
version = "0.5.0-2"
source = "git+https://github.com/rustdesk-org/rdev#f9b60b1dd0f3300a1b797d7a74c116683cd232c8"
source = "git+https://github.com/rustdesk-org/rdev#871bf1c856d6a30af2f56ab8848396a025140855"
dependencies = [
"cocoa 0.24.1",
"core-foundation 0.9.4",
@@ -9733,9 +9733,9 @@ dependencies = [
[[package]]
name = "wayland-protocols-wlr"
version = "0.3.3"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953"
checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec"
dependencies = [
"bitflags 2.9.1",
"wayland-backend",
@@ -10838,16 +10838,15 @@ dependencies = [
[[package]]
name = "wl-clipboard-rs"
version = "0.9.0"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4de22eebb1d1e2bad2d970086e96da0e12cde0b411321e5b0f7b2a1f876aa26f"
checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3"
dependencies = [
"libc",
"log",
"os_pipe",
"rustix 0.38.34",
"tempfile",
"thiserror 1.0.61",
"rustix 1.1.2",
"thiserror 2.0.17",
"tree_magic_mini",
"wayland-backend",
"wayland-client",

View File

@@ -868,6 +868,7 @@ pub mod clipboard_listener {
.unwrap()
.insert(name.clone(), tx);
cleanup_stale_listener(&mut listener_lock);
if listener_lock.handle.is_none() {
log::info!("Start clipboard listener thread");
let handler = Handler {
@@ -893,6 +894,24 @@ pub mod clipboard_listener {
Ok(())
}
fn cleanup_stale_listener(listener: &mut ClipboardListener) {
if !listener
.handle
.as_ref()
.map(|(_, h)| h.is_finished())
.unwrap_or(false)
{
return;
}
if let Some((shutdown, h)) = listener.handle.take() {
log::warn!("Cleaning up stale clipboard listener handle");
if let Err(e) = h.join() {
log::error!("Clipboard listener thread panicked during stale cleanup: {:?}", e);
}
drop(shutdown);
}
}
pub fn unsubscribe(name: &str) {
log::info!("Unsubscribe clipboard listener: {}", name);
let mut listener_lock = CLIPBOARD_LISTENER.lock().unwrap();

View File

@@ -1245,11 +1245,49 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec<KeyEv
#[inline]
pub fn map_keyboard_mode(_peer: &str, event: &Event, key_event: KeyEvent) -> Vec<KeyEvent> {
if let Some(evt) = windows_peer_special_key(_peer, event) {
return vec![evt];
}
_map_keyboard_mode(_peer, event, key_event)
.map(|e| vec![e])
.unwrap_or_default()
}
fn windows_peer_special_key(peer: &str, event: &Event) -> Option<KeyEvent> {
if peer != OS_LOWER_WINDOWS {
return None;
}
let (key, down) = match event.event_type {
EventType::KeyPress(key) => (key, true),
EventType::KeyRelease(key) => (key, false),
_ => return None,
};
// Handle only `Pause` for Windows peers for now.
// Windows has no normal scan code for `Pause`, so send it as a legacy control key.
#[cfg(target_os = "windows")]
let is_pause = {
// The Windows scan code can look like `NumLock`; VK_PAUSE distinguishes it.
let pause_vk_code = rdev::win_code_from_key(Key::Pause);
key == Key::Pause || pause_vk_code == Some(event.platform_code as _)
};
#[cfg(not(target_os = "windows"))]
let is_pause = key == Key::Pause;
if !is_pause {
return None;
}
let mut key_event = KeyEvent::new();
key_event.mode = KeyboardMode::Legacy.into();
key_event.down = down;
key_event.set_control_key(ControlKey::Pause);
let (alt, ctrl, shift, command) = client::get_modifiers_state(false, false, false, false);
client::legacy_modifiers(&mut key_event, alt, ctrl, shift, command);
Some(key_event)
}
fn _map_keyboard_mode(_peer: &str, event: &Event, mut key_event: KeyEvent) -> Option<KeyEvent> {
match event.event_type {
EventType::KeyPress(..) => {
@@ -1421,6 +1459,11 @@ fn is_press(event: &Event) -> bool {
pub fn translate_keyboard_mode(peer: &str, event: &Event, key_event: KeyEvent) -> Vec<KeyEvent> {
let mut events: Vec<KeyEvent> = Vec::new();
if let Some(evt) = windows_peer_special_key(peer, event) {
events.push(evt);
return events;
}
if let Some(unicode_info) = &event.unicode {
if unicode_info.is_dead {
#[cfg(target_os = "macos")]

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "إعادة تعيين اختيار إدخال لوحة المفاتيح"),
("remember-wayland-keyboard-choice-tip", "لا تسأل مرة أخرى لهذا الكمبيوتر البعيد"),
("Why this happens", "سبب حدوث ذلك"),
("Switch display", "تبديل الشاشة"),
("Show monitor switch button on the main toolbar", "إظهار زر تبديل الشاشة على شريط الأدوات الرئيسي"),
("Show on the minimized toolbar", "الإظهار على شريط الأدوات المُصغّر"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Скінуць выбар уводу з клавіятуры"),
("remember-wayland-keyboard-choice-tip", "Не пытацца зноў для гэтага аддаленага кампутара"),
("Why this happens", "Чаму гэта адбываецца"),
("Switch display", "Пераключыць дысплэй"),
("Show monitor switch button on the main toolbar", "Паказваць кнопку пераключэння манітора на галоўнай панэлі інструментаў"),
("Show on the minimized toolbar", "Паказваць на згорнутай панэлі інструментаў"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Нулиране на избора за въвеждане от клавиатура"),
("remember-wayland-keyboard-choice-tip", "Не питай отново за този отдалечен компютър"),
("Why this happens", "Защо се случва това"),
("Switch display", "Превключване на дисплея"),
("Show monitor switch button on the main toolbar", "Показване на бутона за превключване на монитора в главната лента с инструменти"),
("Show on the minimized toolbar", "Показване в минимизираната лента с инструменти"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Restableix l'opció d'entrada de teclat"),
("remember-wayland-keyboard-choice-tip", "No tornis a preguntar-ho per a aquest equip remot"),
("Why this happens", "Per què passa això"),
("Switch display", "Canvia de pantalla"),
("Show monitor switch button on the main toolbar", "Mostra el botó de canvi de monitor a la barra deines principal"),
("Show on the minimized toolbar", "Mostra a la barra deines minimitzada"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "重置键盘输入选择"),
("remember-wayland-keyboard-choice-tip", "以后对这台远程电脑不再询问"),
("Why this happens", "了解原因"),
("Switch display", "切换显示器"),
("Show monitor switch button on the main toolbar", "在主工具栏上显示显示器切换按钮"),
("Show on the minimized toolbar", "在最小化工具栏上显示"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Resetovat volbu vstupu z klávesnice"),
("remember-wayland-keyboard-choice-tip", "Pro tento vzdálený počítač se již neptat"),
("Why this happens", "Proč k tomu dochází"),
("Switch display", "Přepnout obrazovku"),
("Show monitor switch button on the main toolbar", "Zobrazit tlačítko přepnutí monitoru na hlavním panelu nástrojů"),
("Show on the minimized toolbar", "Zobrazit na minimalizovaném panelu nástrojů"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Nulstil valg for tastaturinput"),
("remember-wayland-keyboard-choice-tip", "Spørg ikke igen for denne fjerncomputer"),
("Why this happens", "Hvorfor dette sker"),
("Switch display", "Skift skærm"),
("Show monitor switch button on the main toolbar", "Vis knap til skærmskift på hovedværktøjslinjen"),
("Show on the minimized toolbar", "Vis på den minimerede værktøjslinje"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Auswahl der Tastatureingabe zurücksetzen"),
("remember-wayland-keyboard-choice-tip", "Für diesen entfernten Computer nicht erneut fragen"),
("Why this happens", "Warum dies passiert"),
("Switch display", "Anzeige wechseln"),
("Show monitor switch button on the main toolbar", "Schaltfläche zum Monitorwechsel in der Haupt-Symbolleiste anzeigen"),
("Show on the minimized toolbar", "In der minimierten Symbolleiste anzeigen"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Επαναφορά επιλογής εισαγωγής από πληκτρολόγιο"),
("remember-wayland-keyboard-choice-tip", "Να μην ερωτηθώ ξανά για αυτόν τον απομακρυσμένο υπολογιστή"),
("Why this happens", "Γιατί συμβαίνει αυτό"),
("Switch display", "Εναλλαγή οθόνης"),
("Show monitor switch button on the main toolbar", "Εμφάνιση κουμπιού εναλλαγής οθόνης στην κύρια γραμμή εργαλείων"),
("Show on the minimized toolbar", "Εμφάνιση στην ελαχιστοποιημένη γραμμή εργαλείων"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Restarigi la elekton de klavara enigo"),
("remember-wayland-keyboard-choice-tip", "Ne demandi denove por ĉi tiu fora komputilo"),
("Why this happens", "Kial ĉi tio okazas"),
("Switch display", "Ŝalti ekranon"),
("Show monitor switch button on the main toolbar", "Montri ekran-ŝaltan butonon en la ĉefa ilobreto"),
("Show on the minimized toolbar", "Montri en la minimumigita ilobreto"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Restablecer la opción de entrada del teclado"),
("remember-wayland-keyboard-choice-tip", "No volver a preguntar para este equipo remoto"),
("Why this happens", "Por qué ocurre esto"),
("Switch display", "Cambiar de pantalla"),
("Show monitor switch button on the main toolbar", "Mostrar el botón de cambio de monitor en la barra de herramientas principal"),
("Show on the minimized toolbar", "Mostrar en la barra de herramientas minimizada"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Lähtesta klaviatuurisisestuse valik"),
("remember-wayland-keyboard-choice-tip", "Ära küsi selle kaugarvuti puhul uuesti"),
("Why this happens", "Miks see juhtub"),
("Switch display", "Vaheta kuva"),
("Show monitor switch button on the main toolbar", "Näita monitori vahetamise nuppu peamisel tööriistaribal"),
("Show on the minimized toolbar", "Näita minimeeritud tööriistaribal"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Berrezarri teklatuko sarreraren aukera"),
("remember-wayland-keyboard-choice-tip", "Ez galdetu berriro urruneko ordenagailu honetarako"),
("Why this happens", "Zergatik gertatzen den hau"),
("Switch display", "Aldatu pantaila"),
("Show monitor switch button on the main toolbar", "Erakutsi monitorea aldatzeko botoia tresna-barra nagusian"),
("Show on the minimized toolbar", "Erakutsi minimizatutako tresna-barran"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "بازنشانی انتخاب ورودی صفحه کلید"),
("remember-wayland-keyboard-choice-tip", "برای این رایانه از راه دور دوباره نپرس"),
("Why this happens", "چرا این اتفاق می‌افتد"),
("Switch display", "تعویض نمایشگر"),
("Show monitor switch button on the main toolbar", "نمایش دکمه تعویض نمایشگر در نوار ابزار اصلی"),
("Show on the minimized toolbar", "نمایش در نوار ابزار کوچک‌شده"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Nollaa näppäimistösyötteen valinta"),
("remember-wayland-keyboard-choice-tip", "Älä kysy uudelleen tältä etätietokoneelta"),
("Why this happens", "Miksi näin tapahtuu"),
("Switch display", "Vaihda näyttöä"),
("Show monitor switch button on the main toolbar", "Näytä näytön vaihtopainike päätyökalurivillä"),
("Show on the minimized toolbar", "Näytä pienennetyssä työkalurivissä"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Réinitialiser le choix de la saisie au clavier"),
("remember-wayland-keyboard-choice-tip", "Ne plus demander pour cet appareil distant"),
("Why this happens", "Pourquoi cela se produit"),
("Switch display", "Changer décran"),
("Show monitor switch button on the main toolbar", "Afficher le bouton de changement décran dans la barre doutils principale"),
("Show on the minimized toolbar", "Afficher dans la barre doutils réduite"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "კლავიატურის შეყვანის არჩევანის ჩამოყრა"),
("remember-wayland-keyboard-choice-tip", "აღარ მკითხო ამ დისტანციური კომპიუტერისთვის"),
("Why this happens", "რატომ ხდება ეს"),
("Switch display", "ეკრანის გადართვა"),
("Show monitor switch button on the main toolbar", "მონიტორის გადართვის ღილაკის ჩვენება მთავარ ხელსაწყოთა ზოლზე"),
("Show on the minimized toolbar", "ჩვენება ჩაკეცილ ხელსაწყოთა ზოლზე"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "કીબોર્ડ ઇનપુટ પસંદગી રિસેટ કરો"),
("remember-wayland-keyboard-choice-tip", "આ રિમોટ કમ્પ્યુટર માટે ફરીથી પૂછશો નહીં"),
("Why this happens", "આવું શા માટે થાય છે"),
("Switch display", "ડિસ્પ્લે બદલો"),
("Show monitor switch button on the main toolbar", "મુખ્ય ટૂલબાર પર મોનિટર સ્વિચ બટન બતાવો"),
("Show on the minimized toolbar", "ન્યૂનતમ કરેલા ટૂલબાર પર બતાવો"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "אפס את בחירת קלט המקלדת"),
("remember-wayland-keyboard-choice-tip", "אל תשאל שוב עבור מחשב מרוחק זה"),
("Why this happens", "מדוע זה קורה"),
("Switch display", "החלפת צג"),
("Show monitor switch button on the main toolbar", "הצגת לחצן החלפת צג בסרגל הכלים הראשי"),
("Show on the minimized toolbar", "הצגה בסרגל הכלים הממוזער"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "कीबोर्ड इनपुट चयन रीसेट करें"),
("remember-wayland-keyboard-choice-tip", "इस रिमोट कंप्यूटर के लिए दोबारा न पूछें"),
("Why this happens", "ऐसा क्यों होता है"),
("Switch display", "डिस्प्ले बदलें"),
("Show monitor switch button on the main toolbar", "मुख्य टूलबार पर मॉनिटर स्विच बटन दिखाएं"),
("Show on the minimized toolbar", "न्यूनतम किए गए टूलबार पर दिखाएं"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Poništi izbor unosa tipkovnicom"),
("remember-wayland-keyboard-choice-tip", "Ne pitaj ponovno za ovo udaljeno računalo"),
("Why this happens", "Zašto se ovo događa"),
("Switch display", "Promijeni zaslon"),
("Show monitor switch button on the main toolbar", "Prikaži gumb za prebacivanje monitora na glavnoj alatnoj traci"),
("Show on the minimized toolbar", "Prikaži na minimiziranoj alatnoj traci"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Billentyűzetbevitel választásának visszaállítása"),
("remember-wayland-keyboard-choice-tip", "Ne kérdezze meg újra ennél a távoli számítógépnél"),
("Why this happens", "Miért történik ez"),
("Switch display", "Kijelző váltása"),
("Show monitor switch button on the main toolbar", "Monitorváltó gomb megjelenítése a fő eszköztáron"),
("Show on the minimized toolbar", "Megjelenítés a kis méretű eszköztáron"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Setel ulang pilihan masukan keyboard"),
("remember-wayland-keyboard-choice-tip", "Jangan tanya lagi untuk komputer jarak jauh ini"),
("Why this happens", "Mengapa ini terjadi"),
("Switch display", "Ganti tampilan"),
("Show monitor switch button on the main toolbar", "Tampilkan tombol pengalih monitor di bilah alat utama"),
("Show on the minimized toolbar", "Tampilkan di bilah alat yang diperkecil"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Ripristina scelta input da tastiera"),
("remember-wayland-keyboard-choice-tip", "Non chiedere più per questo computer remoto"),
("Why this happens", "Perché accade questo"),
("Switch display", "Cambia schermo"),
("Show monitor switch button on the main toolbar", "Visualizza nella barra strumenti principale il pulsante per il cambio schermo"),
("Show on the minimized toolbar", "Visualizza nella barra strumenti ridotta a icona"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "キーボード入力の選択をリセット"),
("remember-wayland-keyboard-choice-tip", "このリモートコンピューターでは今後確認しない"),
("Why this happens", "この問題が起こる理由"),
("Switch display", "ディスプレイを切り替え"),
("Show monitor switch button on the main toolbar", "メインツールバーにモニター切り替えボタンを表示"),
("Show on the minimized toolbar", "最小化したツールバーに表示"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "키보드 입력 선택 초기화"),
("remember-wayland-keyboard-choice-tip", "이 원격 컴퓨터에 대해 다시 묻지 않기"),
("Why this happens", "이런 현상이 발생하는 이유"),
("Switch display", "디스플레이 전환"),
("Show monitor switch button on the main toolbar", "기본 도구 모음에 모니터 전환 버튼 표시"),
("Show on the minimized toolbar", "최소화된 도구 모음에 표시"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Пернетақта еңгізу таңдауын қалпына келтіру"),
("remember-wayland-keyboard-choice-tip", "Осы қашықтағы компьютер үшін қайта сұрамау"),
("Why this happens", "Бұл неге болады"),
("Switch display", "Дисплейді ауыстыру"),
("Show monitor switch button on the main toolbar", "Негізгі құралдар тақтасында мониторды ауыстыру түймесін көрсету"),
("Show on the minimized toolbar", "Кішірейтілген құралдар тақтасында көрсету"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Atstatyti klaviatūros įvesties pasirinkimą"),
("remember-wayland-keyboard-choice-tip", "Daugiau neklausti dėl šio nuotolinio kompiuterio"),
("Why this happens", "Kodėl taip nutinka"),
("Switch display", "Perjungti ekraną"),
("Show monitor switch button on the main toolbar", "Rodyti monitoriaus perjungimo mygtuką pagrindinėje įrankių juostoje"),
("Show on the minimized toolbar", "Rodyti sumažintoje įrankių juostoje"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Atiestatīt tastatūras ievades izvēli"),
("remember-wayland-keyboard-choice-tip", "Vairs nejautāt par šo attālo datoru"),
("Why this happens", "Kāpēc tas notiek"),
("Switch display", "Pārslēgt displeju"),
("Show monitor switch button on the main toolbar", "Rādīt monitora pārslēgšanas pogu galvenajā rīkjoslā"),
("Show on the minimized toolbar", "Rādīt minimizētajā rīkjoslā"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "കീബോർഡ് ഇൻപുട്ട് തിരഞ്ഞെടുപ്പ് റീസെറ്റ് ചെയ്യുക"),
("remember-wayland-keyboard-choice-tip", "ഈ റിമോട്ട് കമ്പ്യൂട്ടറിനായി ഇനി ചോദിക്കരുത്"),
("Why this happens", "ഇത് എന്തുകൊണ്ട് സംഭവിക്കുന്നു"),
("Switch display", "ഡിസ്പ്ലേ മാറ്റുക"),
("Show monitor switch button on the main toolbar", "പ്രധാന ടൂൾബാറിൽ മോണിറ്റർ സ്വിച്ച് ബട്ടൺ കാണിക്കുക"),
("Show on the minimized toolbar", "ചെറുതാക്കിയ ടൂൾബാറിൽ കാണിക്കുക"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Tilbakestill valg for tastaturinndata"),
("remember-wayland-keyboard-choice-tip", "Ikke spør igjen for denne eksterne datamaskinen"),
("Why this happens", "Hvorfor dette skjer"),
("Switch display", "Bytt skjerm"),
("Show monitor switch button on the main toolbar", "Vis knapp for skjermbytte på hovedverktøylinjen"),
("Show on the minimized toolbar", "Vis på den minimerte verktøylinjen"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Keuze voor toetsenbordinvoer opnieuw instellen"),
("remember-wayland-keyboard-choice-tip", "Niet meer vragen voor deze externe computer"),
("Why this happens", "Waarom dit gebeurt"),
("Switch display", "Beeldscherm wisselen"),
("Show monitor switch button on the main toolbar", "Knop voor monitorwisseling weergeven op de hoofdwerkbalk"),
("Show on the minimized toolbar", "Weergeven op de geminimaliseerde werkbalk"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Zresetuj wybór dotyczący wprowadzania z klawiatury"),
("remember-wayland-keyboard-choice-tip", "Nie pytaj ponownie dla tego zdalnego komputera"),
("Why this happens", "Dlaczego tak się dzieje"),
("Switch display", "Przełącz ekran"),
("Show monitor switch button on the main toolbar", "Pokaż przycisk przełączania monitora na głównym pasku narzędzi"),
("Show on the minimized toolbar", "Pokaż na zminimalizowanym pasku narzędzi"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Repor escolha de entrada de teclado"),
("remember-wayland-keyboard-choice-tip", "Não voltar a perguntar para este computador remoto"),
("Why this happens", "Porque é que isto acontece"),
("Switch display", "Trocar de ecrã"),
("Show monitor switch button on the main toolbar", "Mostrar o botão de troca de monitor na barra de ferramentas principal"),
("Show on the minimized toolbar", "Mostrar na barra de ferramentas minimizada"),
].iter().cloned().collect();
}

View File

@@ -16,18 +16,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Control Remote Desktop", "Controle um Computador Remoto"),
("Transfer file", "Transferir arquivos"),
("Connect", "Conectar"),
("Recent sessions", "Sessões Recentes"),
("Address book", "Lista de Endereços"),
("Recent sessions", "Sessões recentes"),
("Address book", "Lista de endereços"),
("Confirmation", "Confirmação"),
("TCP tunneling", "Tunelamento TCP"),
("Remove", "Remover"),
("Refresh random password", "Atualizar senha aleatória"),
("Set your own password", "Configure sua própria senha"),
("Refresh random password", "Gerar nova senha aleatória"),
("Set your own password", "Definir sua própria senha"),
("Enable keyboard/mouse", "Habilitar teclado/mouse"),
("Enable clipboard", "Habilitar área de transferência"),
("Enable file transfer", "Habilitar transferência de arquivos"),
("Enable TCP tunneling", "Habilitar tunelamento TCP"),
("IP Whitelisting", "Lista de IPs Confiáveis"),
("IP Whitelisting", "Lista de IPs Permitidos"),
("ID/Relay Server", "Servidor ID/Relay"),
("Import server config", "Importar Configuração do Servidor"),
("Export Server Config", "Exportar Configuração do Servidor"),
@@ -320,12 +320,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Exit Fullscreen", "Sair da Tela Cheia"),
("Fullscreen", "Tela Cheia"),
("Mobile Actions", "Ações móveis"),
("Select Monitor", "Selecionar monitor"),
("Select Monitor", "Selecionar tela"),
("Control Actions", "Controlar ações"),
("Display Settings", "Configurações de exibição"),
("Ratio", "Proporção"),
("Image Quality", "Qualidade de imagem"),
("Scroll Style", "Estilo de Rolagem"),
("Scroll Style", "Estilo de rolagem"),
("Show Toolbar", "Mostrar barra de ferramentas"),
("Hide Toolbar", "Ocultar barra de ferramentas"),
("Direct Connection", "Conexão Direta"),
@@ -353,7 +353,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Disconnect all devices?", "Desconectar todos os dispositivos?"),
("Clear", "Limpar"),
("Audio Input Device", "Dispositivo de entrada de áudio"),
("Use IP Whitelisting", "Utilizar lista de IPs confiáveis"),
("Use IP Whitelisting", "Utilizar lista de IPs permitidos"),
("Network", "Rede"),
("Pin Toolbar", "Fixar barra de ferramentas"),
("Unpin Toolbar", "Desafixar barra de ferramentas"),
@@ -463,7 +463,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Password", "Senha Vazia"),
("Me", "Eu"),
("identical_file_tip", "Este arquivo é idêntico ao do parceiro."),
("show_monitors_tip", "Mostrar monitores na barra de ferramentas"),
("show_monitors_tip", "Mostrar telas na barra de ferramentas"),
("View Mode", "Modo de visualização"),
("login_linux_tip", "Você precisa fazer login na conta Linux remota para habilitar uma sessão de desktop X"),
("verify_rustdesk_password_tip", "Verifique a senha do RustDesk"),
@@ -674,7 +674,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("dont-show-again-tip", "Não mostrar novamente"),
("Take screenshot", "Capturar tela"),
("Taking screenshot", "Capturando tela"),
("screenshot-merged-screen-not-supported-tip", "Mesclar a captura de tela de múltiplos monitores não é suportada no momento. Por favor, alterne para um único monitor e tente novamente."),
("screenshot-merged-screen-not-supported-tip", "A captura de tela de múltiplas telas não é suportada no momento. Por favor, alterne para uma única tela e tente novamente."),
("screenshot-action-tip", "Por favor, selecione como deseja continuar com a captura de tela."),
("Save as", "Salvar como"),
("Copy to clipboard", "Copiar para área de transferência"),
@@ -694,7 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable UDP hole punching", "Habilitar UDP hole punching"),
("View camera", "Visualizar câmera"),
("Enable camera", "Habilitar câmera"),
("No cameras", "Nenhuma câmeras"),
("No cameras", "Nenhuma câmera"),
("view_camera_unsupported_tip", "O dispositivo remoto não suporta visualização da câmera."),
("Terminal", "Terminal"),
("Enable terminal", "Habilitar terminal"),
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Redefinir escolha de entrada do teclado"),
("remember-wayland-keyboard-choice-tip", "Não perguntar novamente para este computador remoto"),
("Why this happens", "Por que isso acontece"),
("Switch display", "Trocar de tela"),
("Show monitor switch button on the main toolbar", "Mostrar botão de troca de tela na barra de ferramentas"),
("Show on the minimized toolbar", "Mostrar na barra de ferramentas minimizada"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Resetează alegerea pentru introducerea de la tastatură"),
("remember-wayland-keyboard-choice-tip", "Nu mai întreba pentru acest computer la distanță"),
("Why this happens", "De ce se întâmplă acest lucru"),
("Switch display", "Comută afișajul"),
("Show monitor switch button on the main toolbar", "Afișează butonul de comutare a monitorului în bara de instrumente principală"),
("Show on the minimized toolbar", "Afișează în bara de instrumente minimizată"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Сбросить выбор для ввода с клавиатуры"),
("remember-wayland-keyboard-choice-tip", "Больше не спрашивать для этого удалённого компьютера"),
("Why this happens", "Почему это происходит"),
("Switch display", "Переключить дисплей"),
("Show monitor switch button on the main toolbar", "Показывать кнопку переключения монитора на главной панели инструментов"),
("Show on the minimized toolbar", "Показывать на свёрнутой панели инструментов"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Reseta s'isseberada de s'insertada cun su tecladu"),
("remember-wayland-keyboard-choice-tip", "No torres a preguntare pro custu elaboradore remotu"),
("Why this happens", "Pro ite custu càpitat"),
("Switch display", "Càmbia ischermu"),
("Show monitor switch button on the main toolbar", "Mustra su butone de càmbiu de monitor in sa barra de aina printzipale"),
("Show on the minimized toolbar", "Mustra in sa barra de aina minimizada"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Obnoviť voľbu vstupu z klávesnice"),
("remember-wayland-keyboard-choice-tip", "Nepýtať sa znova pre tento vzdialený počítač"),
("Why this happens", "Prečo sa to deje"),
("Switch display", "Prepnúť obrazovku"),
("Show monitor switch button on the main toolbar", "Zobraziť tlačidlo prepnutia monitora na hlavnom paneli nástrojov"),
("Show on the minimized toolbar", "Zobraziť na minimalizovanom paneli nástrojov"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Ponastavi izbiro vnosa s tipkovnice"),
("remember-wayland-keyboard-choice-tip", "Za ta oddaljeni računalnik ne vprašaj več"),
("Why this happens", "Zakaj se to dogaja"),
("Switch display", "Preklopi zaslon"),
("Show monitor switch button on the main toolbar", "Pokaži gumb za preklop monitorja v glavni orodni vrstici"),
("Show on the minimized toolbar", "Pokaži v pomanjšani orodni vrstici"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Rivendos zgjedhjen e hyrjes nga tastiera"),
("remember-wayland-keyboard-choice-tip", "Mos pyet më për këtë kompjuter në distancë"),
("Why this happens", "Pse ndodh kjo"),
("Switch display", "Ndërro ekranin"),
("Show monitor switch button on the main toolbar", "Shfaq butonin e ndërrimit të monitorit te shiriti kryesor i veglave"),
("Show on the minimized toolbar", "Shfaq te shiriti i minimizuar i veglave"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Resetuj izbor unosa sa tastature"),
("remember-wayland-keyboard-choice-tip", "Ne pitaj ponovo za ovaj udaljeni računar"),
("Why this happens", "Zašto se ovo dešava"),
("Switch display", "Промени екран"),
("Show monitor switch button on the main toolbar", "Прикажи дугме за пребацивање монитора на главној траци са алаткама"),
("Show on the minimized toolbar", "Прикажи на умањеној траци са алаткама"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Återställ val av tangentbordsinmatning"),
("remember-wayland-keyboard-choice-tip", "Fråga inte igen för den här fjärrdatorn"),
("Why this happens", "Varför detta händer"),
("Switch display", "Växla skärm"),
("Show monitor switch button on the main toolbar", "Visa knapp för skärmväxling i huvudverktygsfältet"),
("Show on the minimized toolbar", "Visa i det minimerade verktygsfältet"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "விசைப்பலகை உள்ளீட்டுத் தேர்வை மீட்டமை"),
("remember-wayland-keyboard-choice-tip", "இந்தத் தொலை கணினிக்கு மீண்டும் கேட்க வேண்டாம்"),
("Why this happens", "இது ஏன் நிகழ்கிறது"),
("Switch display", "திரையை மாற்று"),
("Show monitor switch button on the main toolbar", "முதன்மை கருவிப்பட்டையில் திரை மாற்று பொத்தானைக் காட்டு"),
("Show on the minimized toolbar", "சிறிதாக்கப்பட்ட கருவிப்பட்டையில் காட்டு"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", ""),
("remember-wayland-keyboard-choice-tip", ""),
("Why this happens", ""),
("Switch display", ""),
("Show monitor switch button on the main toolbar", ""),
("Show on the minimized toolbar", ""),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "รีเซ็ตตัวเลือกการป้อนข้อมูลจากคีย์บอร์ด"),
("remember-wayland-keyboard-choice-tip", "ไม่ต้องถามอีกสำหรับคอมพิวเตอร์ปลายทางนี้"),
("Why this happens", "เหตุใดจึงเกิดขึ้น"),
("Switch display", "สลับจอแสดงผล"),
("Show monitor switch button on the main toolbar", "แสดงปุ่มสลับจอภาพบนแถบเครื่องมือหลัก"),
("Show on the minimized toolbar", "แสดงบนแถบเครื่องมือที่ย่อเล็กสุด"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Klavye girişi seçimini sıfırla"),
("remember-wayland-keyboard-choice-tip", "Bu uzak bilgisayar için bir daha sorma"),
("Why this happens", "Bunun nedeni"),
("Switch display", "Ekranı değiştir"),
("Show monitor switch button on the main toolbar", "Ana araç çubuğunda monitör değiştirme düğmesini göster"),
("Show on the minimized toolbar", "Simge durumuna küçültülmüş araç çubuğunda göster"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "重設鍵盤輸入選擇"),
("remember-wayland-keyboard-choice-tip", "不要再為此遠端電腦詢問"),
("Why this happens", "發生原因"),
("Switch display", "切換螢幕"),
("Show monitor switch button on the main toolbar", "在主工具列上顯示螢幕切換按鈕"),
("Show on the minimized toolbar", "在最小化工具列上顯示"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Скинути вибір щодо введення з клавіатури"),
("remember-wayland-keyboard-choice-tip", "Більше не запитувати для цього віддаленого комп'ютера"),
("Why this happens", "Чому це відбувається"),
("Switch display", "Перемкнути дисплей"),
("Show monitor switch button on the main toolbar", "Показувати кнопку перемикання монітора на головній панелі інструментів"),
("Show on the minimized toolbar", "Показувати на згорнутій панелі інструментів"),
].iter().cloned().collect();
}

View File

@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-keyboard-input-reset-choice-tip", "Đặt lại lựa chọn nhập bàn phím"),
("remember-wayland-keyboard-choice-tip", "Không hỏi lại cho máy tính từ xa này"),
("Why this happens", "Tại sao điều này xảy ra"),
("Switch display", "Chuyển màn hình"),
("Show monitor switch button on the main toolbar", "Hiển thị nút chuyển đổi màn hình trên thanh công cụ chính"),
("Show on the minimized toolbar", "Hiển thị trên thanh công cụ thu nhỏ"),
].iter().cloned().collect();
}

View File

@@ -51,6 +51,7 @@ fn check_desktop_manager() {
pub fn start_xdesktop() {
debug_assert!(crate::is_server());
std::thread::spawn(|| {
DesktopManager::recover_orphaned_session();
*DESKTOP_MANAGER.lock().unwrap() = Some(DesktopManager::new());
let interval = time::Duration::from_millis(super::SERVICE_INTERVAL);
@@ -462,10 +463,15 @@ impl DesktopManager {
let (child_xorg, child_wm) = Self::start_x11(uid, gid, username, display_num, &envs)?;
is_child_running.store(true, Ordering::SeqCst);
// capture the logind session scope (from a live child) for teardown and crash
// recovery, see reap_session_scope and recover_orphaned_session.
let scope_dir = Self::session_scope_dir(child_xorg.id());
Self::save_orphaned_marker(&scope_dir, display_num);
log::info!("Start xorg and wm done, notify and wait xtop x11");
allow_err!(tx_res.send("".to_owned()));
Self::wait_stop_x11(child_xorg, child_wm);
Self::wait_stop_x11(child_xorg, child_wm, scope_dir, display_num);
log::info!("Wait x11 stop done");
Ok(())
}
@@ -665,7 +671,282 @@ impl DesktopManager {
}
}
fn try_wait_stop_x11(child_xorg: &mut Child, child_wm: &mut Child) -> bool {
// resolve the "session-<id>.scope" directory pam_systemd put the x session in, read
// from a live child pid. cgroup v2 mounts every cgroup under /sys/fs/cgroup, v1/hybrid
// keeps the scope under the systemd controller mount; pick by the controller field and
// confirm the cgroup is real. empty if there is no such scope (e.g. no logind).
fn session_scope_dir(pid: u32) -> String {
let path = format!("/proc/{}/cgroup", pid);
let content = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(e) => {
log::warn!("Failed to read {} to find session scope: {}", path, e);
return "".to_owned();
}
};
for line in content.lines() {
// "<hierarchy>:<controllers>:<path>"; v2 unified is "0::<path>", the v1
// systemd hierarchy is "<n>:name=systemd:<path>".
let mut fields = line.splitn(3, ':');
let (controllers, cgroup) = match (fields.next(), fields.next(), fields.next()) {
(Some(_), Some(c), Some(p)) => (c, p),
_ => continue,
};
let scope = match Self::session_scope(cgroup) {
Some(s) => s,
None => continue,
};
let mount = if controllers.is_empty() {
"/sys/fs/cgroup"
} else if controllers.split(',').any(|c| c == "name=systemd") {
"/sys/fs/cgroup/systemd"
} else {
continue;
};
let dir = format!("{}{}", mount, scope);
if Path::new(&format!("{}/cgroup.procs", dir)).exists() {
return dir;
}
}
"".to_owned()
}
// the "/.../session-<id>.scope" prefix of a cgroup path, dropping any nested child
// cgroup below it so a descendant scope does not get mistaken for the session.
fn session_scope(cgroup: &str) -> Option<String> {
let mut scope = String::new();
for comp in cgroup.split('/').filter(|c| !c.is_empty()) {
scope.push('/');
scope.push_str(comp);
if comp.starts_with("session-") && comp.ends_with(".scope") {
return Some(scope);
}
}
None
}
// on teardown reap the whole session scope subtree, not just the xorg + wm pids:
// the per-session pipewire and other desktop children otherwise outlive them and
// hold the logind session in "closing", leaking sockets + displays on reconnect
// (rustdesk/rustdesk#15183). SIGTERM first so pipewire unlinks its sockets, then
// SIGKILL stragglers; skip our own pid (pam put the service in the scope too).
fn reap_session_scope(scope_dir: &str) {
if scope_dir.is_empty() {
return;
}
let me = std::process::id();
// spare the --server's own children and any descendants of them sharing this scope
// (see pid_is_spared); only the desktop session's leftovers are reaped.
let spared: Vec<u32> = crate::server::CHILD_PROCESS
.lock()
.unwrap()
.iter()
.map(|c| c.id())
.collect();
for sig in [hbb_common::libc::SIGTERM, hbb_common::libc::SIGKILL] {
let mut pids = Vec::new();
Self::collect_scope_pids(Path::new(scope_dir), &mut pids);
let mut any = false;
for pid in pids {
if pid == me || Self::pid_is_spared(pid, &spared, me) {
continue;
}
any = true;
log::info!("Reaping leftover session process {} (signal {})", pid, sig);
unsafe {
if hbb_common::libc::kill(pid as hbb_common::libc::pid_t, sig) != 0 {
let err = std::io::Error::last_os_error();
// ESRCH = it already exited (or did between snapshot and now).
if err.raw_os_error() != Some(hbb_common::libc::ESRCH) {
log::warn!("Failed to signal session process {}: {}", pid, err);
}
}
}
}
if !any {
break;
}
if sig == hbb_common::libc::SIGTERM {
std::thread::sleep(Duration::from_millis(300));
}
}
}
// a tracked --server child (the sudo wrapper run_as_user spawns) or any descendant of
// one: with use_pty sudo runs --cm-no-ui under a monitor with its own pid, so walk the
// parent chain (stopping at the --server) to spare the worker, not just the wrapper.
fn pid_is_spared(pid: u32, spared: &[u32], me: u32) -> bool {
let mut cur = pid;
for _ in 0..32 {
if spared.contains(&cur) {
return true;
}
if cur <= 1 || cur == me {
return false;
}
match Self::parent_pid(cur) {
Some(ppid) => cur = ppid,
None => return false,
}
}
false
}
fn parent_pid(pid: u32) -> Option<u32> {
// /proc/<pid>/stat is "pid (comm) state ppid ..."; comm can contain spaces and ')',
// so read the fields after the last ')'.
let stat = std::fs::read_to_string(format!("/proc/{}/stat", pid)).ok()?;
stat.rsplit_once(')')?
.1
.split_whitespace()
.nth(1)?
.parse()
.ok()
}
// collect every pid in the cgroup subtree rooted at dir. "cgroup.procs" lists only
// the procs directly in a cgroup, so recurse into child cgroup directories to catch
// processes the desktop session moved into descendant scopes.
fn collect_scope_pids(dir: &Path, out: &mut Vec<u32>) {
let procs = dir.join("cgroup.procs");
match std::fs::read_to_string(&procs) {
Ok(content) => {
out.extend(content.lines().filter_map(|l| l.trim().parse::<u32>().ok()));
}
Err(e) if e.kind() != std::io::ErrorKind::NotFound => {
log::warn!("Failed to read {}: {}", procs.display(), e);
}
Err(_) => {}
}
let entries = match std::fs::read_dir(dir) {
Ok(e) => e,
Err(e) if e.kind() != std::io::ErrorKind::NotFound => {
log::warn!("Failed to list cgroup dir {}: {}", dir.display(), e);
return;
}
Err(_) => return,
};
for entry in entries {
let entry = match entry {
Ok(entry) => entry,
Err(e) => {
log::warn!("Failed to read entry under {}: {}", dir.display(), e);
continue;
}
};
match entry.file_type() {
Ok(t) if t.is_dir() => Self::collect_scope_pids(&entry.path(), out),
Ok(_) => {}
Err(e) if e.kind() != std::io::ErrorKind::NotFound => {
log::warn!("Failed to stat {}: {}", entry.path().display(), e);
}
Err(_) => {}
}
}
}
// a SIGKILL'd Xorg (how wait_x11_children_exit ends it) leaves "/tmp/.X<n>-lock" and
// "/tmp/.X11-unix/X<n>" behind, and get_avail_display() treats either file as "display
// in use", so the number is never reused and climbs until none are free
// (rustdesk/rustdesk#15183). a clean exit would remove them; do the same on teardown,
// but skip it if a live process still holds the lock: another server could have taken
// the number in the gap, and removing its files would break that display.
fn cleanup_x_display_files(display_num: u32) {
let lock = format!("/tmp/.X{}-lock", display_num);
if let Ok(content) = std::fs::read_to_string(&lock) {
if let Ok(pid) = content.trim().parse::<i32>() {
if Self::pid_alive(pid) {
log::info!("X display {} still held by pid {}, leaving its files", display_num, pid);
return;
}
}
}
for path in [lock, format!("/tmp/.X11-unix/X{}", display_num)] {
if let Err(e) = std::fs::remove_file(&path) {
if e.kind() != std::io::ErrorKind::NotFound {
log::warn!("Failed to remove stale X file {}: {}", path, e);
}
}
}
}
// signal-0 probe: the pid exists if kill succeeds or fails with EPERM (alive but not
// ours); only ESRCH means it is gone.
fn pid_alive(pid: i32) -> bool {
unsafe {
if hbb_common::libc::kill(pid as hbb_common::libc::pid_t, 0) == 0 {
return true;
}
}
std::io::Error::last_os_error().raw_os_error() == Some(hbb_common::libc::EPERM)
}
const ORPHANED_SESSION_KEY: &'static str = "headless-orphaned-session";
fn save_orphaned_marker(scope_dir: &str, display_num: u32) {
// tag the marker with this boot's id: a logind session id is only unique within a
// boot (the counter lives in /run and resets), so recovery must not reap a recorded
// scope path after a reboot, when it may name a different live session.
let boot_id = Self::current_boot_id().unwrap_or_default();
hbb_common::config::LocalConfig::set_option(
Self::ORPHANED_SESSION_KEY.to_owned(),
format!("{};{};{}", scope_dir, display_num, boot_id),
);
}
fn current_boot_id() -> Option<String> {
std::fs::read_to_string("/proc/sys/kernel/random/boot_id")
.ok()
.map(|s| s.trim().to_owned())
}
fn clear_orphaned_marker() {
hbb_common::config::LocalConfig::set_option(
Self::ORPHANED_SESSION_KEY.to_owned(),
String::new(),
);
}
fn parse_orphaned_marker(marker: &str) -> Option<(&str, u32, &str)> {
let (rest, boot_id) = marker.rsplit_once(';')?;
let (scope_dir, display) = rest.rsplit_once(';')?;
Some((scope_dir, display.trim().parse::<u32>().ok()?, boot_id))
}
// a run that dies before wait_stop_x11 (service or --server crash) leaks the headless
// session scope + X lock files, the same as a missed teardown (rustdesk/rustdesk#15183).
// reap exactly what the dead run recorded - never a scan, so unrelated sessions are safe.
fn recover_orphaned_session() {
let marker = hbb_common::config::LocalConfig::get_option(Self::ORPHANED_SESSION_KEY);
if marker.is_empty() {
return;
}
if let Some((scope_dir, display_num, boot_id)) = Self::parse_orphaned_marker(&marker) {
// only reap the recorded scope when the marker is from this same boot: a leaked
// cgroup cannot outlive a reboot, so cross-boot there is nothing legitimate to
// reap, and the recorded "session-N.scope" may by then name a different live
// session. the X lock cleanup is pid-guarded, so run it either way.
let same_boot = Self::current_boot_id().map_or(false, |b| b == boot_id);
log::info!(
"Recovering leaked headless session from a previous run: scope {}, display {} (same boot: {})",
scope_dir,
display_num,
same_boot
);
if same_boot {
Self::reap_session_scope(scope_dir);
}
Self::cleanup_x_display_files(display_num);
}
Self::clear_orphaned_marker();
}
fn try_wait_stop_x11(
child_xorg: &mut Child,
child_wm: &mut Child,
scope_dir: &str,
display_num: u32,
) -> bool {
let mut desktop_manager = DESKTOP_MANAGER.lock().unwrap();
let mut exited = true;
if let Some(desktop_manager) = &mut (*desktop_manager) {
@@ -677,6 +958,9 @@ impl DesktopManager {
if exited {
log::debug!("Wait x11 children exiting");
Self::wait_x11_children_exit(child_xorg, child_wm);
Self::reap_session_scope(scope_dir);
Self::cleanup_x_display_files(display_num);
Self::clear_orphaned_marker();
desktop_manager
.is_child_running
.store(false, Ordering::SeqCst);
@@ -686,9 +970,14 @@ impl DesktopManager {
exited
}
fn wait_stop_x11(mut child_xorg: Child, mut child_wm: Child) {
fn wait_stop_x11(
mut child_xorg: Child,
mut child_wm: Child,
scope_dir: String,
display_num: u32,
) {
loop {
if Self::try_wait_stop_x11(&mut child_xorg, &mut child_wm) {
if Self::try_wait_stop_x11(&mut child_xorg, &mut child_wm, &scope_dir, display_num) {
break;
}
std::thread::sleep(Duration::from_millis(super::SERVICE_INTERVAL));
@@ -806,3 +1095,77 @@ fn pam_get_service_name() -> String {
"gdm".to_owned()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn session_scope_truncates_at_first_scope() {
assert_eq!(
DesktopManager::session_scope("/user.slice/user-1000.slice/session-3.scope").as_deref(),
Some("/user.slice/user-1000.slice/session-3.scope")
);
// a nested child scope must not be mistaken for the session
assert_eq!(
DesktopManager::session_scope(
"/user.slice/user-1000.slice/session-3.scope/app-foo.scope"
)
.as_deref(),
Some("/user.slice/user-1000.slice/session-3.scope")
);
assert_eq!(
DesktopManager::session_scope(
"/user.slice/user-1000.slice/user@1000.service/app.slice/x.service"
),
None
);
assert_eq!(DesktopManager::session_scope("/"), None);
}
#[test]
fn collect_scope_pids_walks_descendant_cgroups() {
// regression for #15183: pids in descendant cgroups must be collected too
let base = std::env::temp_dir().join(format!("rustdesk-cgtest-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
let scope = base.join("session-3.scope");
let child = scope.join("app-foo.scope");
let nested = child.join("deeper.scope");
std::fs::create_dir_all(&nested).unwrap();
std::fs::create_dir_all(scope.join("empty.scope")).unwrap();
std::fs::write(scope.join("cgroup.procs"), "100\n101\n").unwrap();
std::fs::write(scope.join("cgroup.controllers"), "memory pids\n").unwrap();
std::fs::write(child.join("cgroup.procs"), "200\n").unwrap();
std::fs::write(nested.join("cgroup.procs"), "300\n").unwrap();
let mut pids = Vec::new();
DesktopManager::collect_scope_pids(&scope, &mut pids);
pids.sort();
let _ = std::fs::remove_dir_all(&base);
assert_eq!(pids, vec![100, 101, 200, 300]);
}
#[test]
fn parses_orphaned_session_marker() {
assert_eq!(
DesktopManager::parse_orphaned_marker(
"/sys/fs/cgroup/user.slice/user-1000.slice/session-3.scope;7;abc-123"
),
Some((
"/sys/fs/cgroup/user.slice/user-1000.slice/session-3.scope",
7,
"abc-123"
))
);
// an empty scope still carries the display so its stale X lock can be cleaned
assert_eq!(DesktopManager::parse_orphaned_marker(";5;abc-123"), Some(("", 5, "abc-123")));
// an empty boot id never matches the live one, so the scope reap is skipped
assert_eq!(DesktopManager::parse_orphaned_marker("/scope;5;"), Some(("/scope", 5, "")));
assert_eq!(DesktopManager::parse_orphaned_marker(""), None);
assert_eq!(DesktopManager::parse_orphaned_marker("garbage"), None);
// the pre-boot-id two-field format no longer parses, recovery just skips it
assert_eq!(DesktopManager::parse_orphaned_marker("/scope;7"), None);
assert_eq!(DesktopManager::parse_orphaned_marker("/scope;notnum;abc"), None);
}
}