Merge branch 'rustdesk:master' into master

This commit is contained in:
qinsheng13141981-afk 2026-06-21 17:04:35 +07:00 committed by GitHub
commit a49f558dd6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 128 additions and 24 deletions

15
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",
@ -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

@ -759,7 +759,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("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", "Mostra il pulsante di cambio monitor nella barra degli strumenti principale"),
("Show on the minimized toolbar", "Mostra nella barra degli strumenti ridotta a icona"),
("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

@ -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"),
@ -759,7 +759,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("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 o botão de troca de monitor na barra de ferramentas principal"),
("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

@ -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,9 +463,10 @@ 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, see
// reap_session_scope.
// 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()));
@ -879,6 +881,66 @@ impl DesktopManager {
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,
@ -898,6 +960,7 @@ impl DesktopManager {
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);
@ -1082,4 +1145,27 @@ mod tests {
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);
}
}