From 701a9c6cdc1df2210d8c3f954efa8545388d97b1 Mon Sep 17 00:00:00 2001 From: flusheDData <116861809+flusheDData@users.noreply.github.com> Date: Fri, 15 May 2026 09:31:25 +0200 Subject: [PATCH 1/9] New terms added (#15036) * Update es.rs New terms added * Update es.rs New terms added * Update Spanish translations for various strings * Fix typo in Spanish translation for TLS fallback * Add Spanish translations for various UI elements * Update es.rs --------- Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com> --- src/lang/es.rs | 88 +++++++++++++++++++++++++------------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/src/lang/es.rs b/src/lang/es.rs index 2e543c25e..11c395f7d 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -74,7 +74,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Wrong Password", "Contraseña incorrecta"), ("Do you want to enter again?", "¿Quieres volver a entrar?"), ("Connection Error", "Error de conexión"), - ("Error", ""), + ("Error", ), ("Reset by the peer", "Restablecido por el par"), ("Connecting...", "Conectando..."), ("Connection in progress. Please wait.", "Conexión en curso. Espere por favor."), @@ -90,7 +90,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Receive", "Recibir"), ("Send", "Enviar"), ("Refresh File", "Actualizar archivo"), - ("Local", ""), + ("Local", ), ("Remote", "Remoto"), ("Remote Computer", "Computadora remota"), ("Local Computer", "Computadora local"), @@ -208,7 +208,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Closed manually by the peer", "Cerrado manualmente por el par"), ("Enable remote configuration modification", "Habilitar modificación remota de configuración"), ("Run without install", "Ejecutar sin instalar"), - ("Connect via relay", ""), + ("Connect via relay", "Conectar a través de relay"), ("Always connect via relay", "Conéctese siempre a través de relay"), ("whitelist_tip", "Solo las direcciones IP autorizadas pueden conectarse a este escritorio"), ("Login", "Iniciar sesión"), @@ -228,7 +228,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Username missed", "Olvidó su nombre de usuario"), ("Password missed", "Olvidó su contraseña"), ("Wrong credentials", "Credenciales incorrectas"), - ("The verification code is incorrect or has expired", ""), + ("The verification code is incorrect or has expired", "El código de verificación es incorrecto o ha caducado"), ("Edit Tag", "Editar tag"), ("Forget Password", "Olvidar contraseña"), ("Favorites", "Favoritos"), @@ -302,8 +302,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"), ("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"), ("android_open_battery_optimizations_tip", "Si deseas deshabilitar esta característica, por favor, ve a la página siguiente de ajustes, busca y entra en [Batería] y desmarca [Sin restricción]"), - ("Start on boot", ""), - ("Start the screen sharing service on boot, requires special permissions", ""), + ("Start on boot", "Iniciar al arrancar"), + ("Start the screen sharing service on boot, requires special permissions", "Iniciar el servicio de pantalla compartida al arrancar, requiere permisos especiales"), ("Connection not allowed", "Conexión no disponible"), ("Legacy mode", "Modo heredado"), ("Map mode", "Modo mapa"), @@ -326,8 +326,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Ratio", "Relación"), ("Image Quality", "Calidad de imagen"), ("Scroll Style", "Estilo de desplazamiento"), - ("Show Toolbar", ""), - ("Hide Toolbar", ""), + ("Show Toolbar", "Mostrar herramientas"), + ("Hide Toolbar", "Ocultar herramientas"), ("Direct Connection", "Conexión directa"), ("Relay Connection", "Conexión Relay"), ("Secure Connection", "Conexión segura"), @@ -338,7 +338,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Seguridad"), ("Theme", "Tema"), ("Dark Theme", "Tema Oscuro"), - ("Light Theme", ""), + ("Light Theme", "Tema claro"), ("Dark", "Oscuro"), ("Light", "Claro"), ("Follow System", "Tema del sistema"), @@ -355,12 +355,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Dispositivo de entrada de audio"), ("Use IP Whitelisting", "Usar lista de IPs admitidas"), ("Network", "Red"), - ("Pin Toolbar", ""), - ("Unpin Toolbar", ""), + ("Pin Toolbar", "Anclar herramientas"), + ("Unpin Toolbar", "Desanclar herramientas"), ("Recording", "Grabando"), ("Directory", "Directorio"), ("Automatically record incoming sessions", "Grabación automática de sesiones entrantes"), - ("Automatically record outgoing sessions", ""), + ("Automatically record outgoing sessions", "Grabación automática de sesiones salientes"), ("Change", "Cambiar"), ("Start session recording", "Comenzar grabación de sesión"), ("Stop session recording", "Detener grabación de sesión"), @@ -368,7 +368,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable LAN discovery", "Habilitar descubrimiento de LAN"), ("Deny LAN discovery", "Denegar descubrimiento de LAN"), ("Write a message", "Escribir un mensaje"), - ("Prompt", ""), + ("Prompt", "Solicitud"), ("Please wait for confirmation of UAC...", "Por favor, espera confirmación de UAC"), ("elevated_foreground_window_tip", "La ventana actual del escritorio remoto necesita privilegios elevados para funcionar, así que no puedes usar ratón y teclado temporalmente. Puedes solicitar al usuario remoto que minimize la ventana actual o hacer clic en el botón de elevación de la ventana de gestión de conexión. Para evitar este problema, se recomienda instalar el programa en el dispositivo remto."), ("Disconnected", "Desconectado"), @@ -616,9 +616,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("During service is on", "Mientras el servicio está activo"), ("Capture screen using DirectX", "Capturar pantalla con DirectX"), ("Back", "Atrás"), - ("Apps", ""), - ("Volume up", "Bajar volumen"), - ("Volume down", "Subir volumen"), + ("Apps", "Aplicaciones"), + ("Volume up", "Subir volumen"), + ("Volume down", "Bajar volumen"), ("Power", "Encendido"), ("Telegram bot", "Bot de Telegram"), ("enable-bot-tip", "Si activas esta característica puedes recibir código 2FA de tu bot. También puede funcionar como notificación de conexión."), @@ -651,7 +651,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Update client clipboard", "Actualizar portapapeles del cliente"), ("Untagged", "Sin itiquetar"), ("new-version-of-{}-tip", "Hay una nueva versión de {} disponible"), - ("Accessible devices", ""), + ("Accessible devices", "Dispositivos accesibles"), ("upgrade_remote_rustdesk_client_to_{}_tip", "Por favor, actualiza el cliente RustDesk a la versión {} o superior en el lado remoto"), ("d3d_render_tip", "Al activar el renderizado D3D, la pantalla de control remoto puede verse negra en algunos equipos."), ("Use D3D rendering", "Usar renderizado D3D"), @@ -689,9 +689,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Use WebSocket", "Usar WebSocket"), ("Trackpad speed", "Velocidad de trackpad"), ("Default trackpad speed", "Velocidad predeterminada de trackpad"), - ("Numeric one-time password", ""), - ("Enable IPv6 P2P connection", ""), - ("Enable UDP hole punching", ""), + ("Numeric one-time password", "Contraseña numérica de un solo uso"), + ("Enable IPv6 P2P connection", "Habilitar conexión IPv6 P2P"), + ("Enable UDP hole punching", "Habilitar perforación de agujero UDP"), ("View camera", "Ver cámara"), ("Enable camera", "Habilitar cámara"), ("No cameras", "No hay cámaras"), @@ -708,8 +708,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Failed to check if the user is an administrator.", "No se ha podido comprobar si el usuario es un administrador."), ("Supported only in the installed version.", "Soportado solo en la versión instalada."), ("elevation_username_tip", "Introduzca el nombre de usuario o dominio\\NombreDeUsuario"), - ("Preparing for installation ...", ""), - ("Show my cursor", ""), + ("Preparing for installation ...", "Preparando instlación..."), + ("Show my cursor", "Mostrar mi cursor"), ("Scale custom", "Escala personalizada"), ("Custom scale slider", "Control deslizante de escala personalizada"), ("Decrease", "Disminuir"), @@ -721,28 +721,28 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show virtual joystick", "Mostrar joystick virtual"), ("Edit note", "Editar nota"), ("Alias", ""), - ("ScrollEdge", ""), - ("Allow insecure TLS fallback", ""), - ("allow-insecure-tls-fallback-tip", ""), - ("Disable UDP", ""), - ("disable-udp-tip", ""), - ("server-oss-not-support-tip", ""), - ("input note here", ""), - ("note-at-conn-end-tip", ""), - ("Show terminal extra keys", ""), - ("Relative mouse mode", ""), - ("rel-mouse-not-supported-peer-tip", ""), - ("rel-mouse-not-ready-tip", ""), - ("rel-mouse-lock-failed-tip", ""), - ("rel-mouse-exit-{}-tip", ""), - ("rel-mouse-permission-lost-tip", ""), - ("Changelog", ""), - ("keep-awake-during-outgoing-sessions-label", ""), - ("keep-awake-during-incoming-sessions-label", ""), + ("ScrollEdge", "Desplazamiento de pantalla"), + ("Allow insecure TLS fallback", "Permitir conexión TLS insegura de respaldo"), + ("allow-insecure-tls-fallback-tip", "De forma predeterminada, RustDesk verifica el certificado de servidor para protocolos que usen TLS.\nCon esta opción habilitada, Rustdesk volverá al paso de omisión de verificación y procederá en caso de fallo de verificación."), + ("Disable UDP", "Inhabilitar UDP"), + ("disable-udp-tip", "Controla si se usa TCP solamente.\nCuando esta opción está activa, RustDesk no usará más el puerto UDP 21116, en su lugar se usará el TCP 21116."), + ("server-oss-not-support-tip", "NOTA: El servidor RustDesk OSS no incluye esta característica."), + ("input note here", "Introducir nota aquí"), + ("note-at-conn-end-tip", "Pedir nota al finalizar la conexión"), + ("Show terminal extra keys", "Mostrar teclas extra del terminal"), + ("Relative mouse mode", "Modo de ratón relativo"), + ("rel-mouse-not-supported-peer-tip", "El modo relativo de ratón no está soportado por el par."), + ("rel-mouse-not-ready-tip", "El modo relativo de ratón aún no está preparado. Por favor, inténtalo de nuevo."), + ("rel-mouse-lock-failed-tip", "Ha fallado el bloqueo del cursor. El modo relativo del ratón ha sido inhabilitado."), + ("rel-mouse-exit-{}-tip", "Pulsa {} para salir."), + ("rel-mouse-permission-lost-tip", "Permiso de teclado revocado. El modo relativo del ratón ha sido inhabilitado."), + ("Changelog", "Registro de cambios"), + ("keep-awake-during-outgoing-sessions-label", "Mantener la pantalla activa durante sesiones salientes"), + ("keep-awake-during-incoming-sessions-label", "Mantener la pantalla activa durante sesiones entrantes"), ("Continue with {}", "Continuar con {}"), - ("Display Name", ""), - ("password-hidden-tip", ""), - ("preset-password-in-use-tip", ""), - ("Enable privacy mode", ""), + ("Display Name", "Nombre de pantalla"), + ("password-hidden-tip", "La contraseña permanente está ajustada a (oculta)."), + ("preset-password-in-use-tip", "Se está usando la contraseña predeterminada."), + ("Enable privacy mode", "Habilitar modo privado"), ].iter().cloned().collect(); } From 9f8f726f12da733527cffafd8fa9657c4784c2af Mon Sep 17 00:00:00 2001 From: rustdesk Date: Fri, 15 May 2026 17:30:59 +0800 Subject: [PATCH 2/9] fix compile --- src/lang/es.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lang/es.rs b/src/lang/es.rs index 11c395f7d..b822432a0 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -74,7 +74,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Wrong Password", "Contraseña incorrecta"), ("Do you want to enter again?", "¿Quieres volver a entrar?"), ("Connection Error", "Error de conexión"), - ("Error", ), + ("Error", ""), ("Reset by the peer", "Restablecido por el par"), ("Connecting...", "Conectando..."), ("Connection in progress. Please wait.", "Conexión en curso. Espere por favor."), @@ -90,7 +90,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Receive", "Recibir"), ("Send", "Enviar"), ("Refresh File", "Actualizar archivo"), - ("Local", ), + ("Local", ""), ("Remote", "Remoto"), ("Remote Computer", "Computadora remota"), ("Local Computer", "Computadora local"), From 472c4fc03ab3e7e160bfe71d46d5481c8946f9bb Mon Sep 17 00:00:00 2001 From: RustDesk <71636191+rustdesk@users.noreply.github.com> Date: Sat, 16 May 2026 14:41:34 +0800 Subject: [PATCH 3/9] --deploy, reuse the device token (#15035) * --deploy, reuse the device token * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix review * no id validation in deploy, so to keep the same behavior in udp register pk * Fix collapsed toolbar drag preview sizing * Revert "Fix collapsed toolbar drag preview sizing" This reverts commit 66e39abb740b8cebbbf04e0441ce0c7433272d99. * remove too many logs --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/core_main.rs | 92 ++++++++++++++++++++++++++++++++++++++ src/ipc.rs | 12 +++++ src/rendezvous_mediator.rs | 59 ++++++++++++++++++++++++ 3 files changed, 163 insertions(+) diff --git a/src/core_main.rs b/src/core_main.rs index 67a83a37e..a0ca5eb95 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -627,6 +627,98 @@ pub fn core_main() -> Option> { println!("Installation and administrative privileges required!"); } return None; + } else if args[0] == "--deploy" { + if config::Config::no_register_device() { + println!("Cannot deploy an unregistrable device!"); + } else if crate::platform::is_installed() && is_root() { + let max = args.len() - 1; + let pos = args.iter().position(|x| x == "--token").unwrap_or(max); + if pos >= max { + println!("--token is required!"); + return None; + } + let token = args[pos + 1].to_owned(); + let get_value = |c: &str| { + let pos = args.iter().position(|x| x == c).unwrap_or(max); + if pos < max { + Some(args[pos + 1].to_owned()) + } else { + None + } + }; + let new_id = get_value("--id"); + let local_id = crate::ipc::get_id(); + let id_to_deploy = new_id.clone().unwrap_or_else(|| local_id.clone()); + let uuid = crate::encode64(hbb_common::get_uuid()); + let pk = crate::encode64( + hbb_common::config::Config::get_key_pair().1, + ); + let body = serde_json::json!({ + "id": id_to_deploy, + "uuid": uuid, + "pk": pk, + }); + let header = "Authorization: Bearer ".to_owned() + &token; + let url = crate::ui_interface::get_api_server() + "/api/devices/deploy"; + match crate::post_request_sync(url, body.to_string(), &header) { + Err(err) => { + println!("Request failed: {}", err); + std::process::exit(1); + } + Ok(text) => { + let parsed: serde_json::Value = + serde_json::from_str(&text).unwrap_or(serde_json::Value::Null); + let result = parsed["result"].as_str().unwrap_or(""); + match result { + "OK" => { + if let Some(ref new_id) = new_id { + if *new_id != local_id { + if let Err(err) = + crate::ipc::set_config("id", new_id.clone()) + { + println!( + "Failed to persist deployed id locally: {}", + err + ); + std::process::exit(1); + } + } + } + if let Err(err) = crate::ipc::notify_deployed() { + log::warn!("Failed to notify deployed state: {}", err); + } + println!("Device deployed."); + } + "NOT_ENABLED" => { + println!("Server does not require deployment."); + std::process::exit(3); + } + "INVALID_INPUT" => { + println!("Invalid input."); + std::process::exit(5); + } + "ID_TAKEN" => { + println!( + "Id `{}` is already used by another machine on the server.", + id_to_deploy + ); + std::process::exit(6); + } + _ => { + if text.is_empty() { + println!("Unknown response."); + } else { + println!("{}", text); + } + std::process::exit(1); + } + } + } + } + } else { + println!("Installation and administrative privileges required!"); + } + return None; } else if args[0] == "--check-hwcodec-config" { #[cfg(feature = "hwcodec")] crate::ipc::hwcodec_process(); diff --git a/src/ipc.rs b/src/ipc.rs index 0258a2816..0cd30634a 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -312,6 +312,7 @@ pub enum Data { ClipboardNonFile(Option<(String, Vec)>), PrivacyModeState((i32, PrivacyModeState, String)), TestRendezvousServer, + Deployed, #[cfg(not(any(target_os = "android", target_os = "ios")))] Keyboard(DataKeyboard), #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -929,6 +930,10 @@ async fn handle(data: Data, stream: &mut Connection) { Data::TestRendezvousServer => { crate::test_rendezvous_server(); } + Data::Deployed => { + crate::rendezvous_mediator::NEEDS_DEPLOY.store(false, Ordering::SeqCst); + crate::rendezvous_mediator::RendezvousMediator::restart(); + } #[cfg(feature = "flutter")] #[cfg(not(any(target_os = "android", target_os = "ios")))] Data::SwitchSidesRequest(id) => { @@ -1737,6 +1742,13 @@ pub async fn test_rendezvous_server() -> ResultType<()> { Ok(()) } +#[tokio::main(flavor = "current_thread")] +pub async fn notify_deployed() -> ResultType<()> { + let mut c = connect(1000, "").await?; + c.send(&Data::Deployed).await?; + Ok(()) +} + #[tokio::main(flavor = "current_thread")] pub async fn send_url_scheme(url: String) -> ResultType<()> { connect(1_000, "_url") diff --git a/src/rendezvous_mediator.rs b/src/rendezvous_mediator.rs index 3ef280a2a..89d7fa01e 100644 --- a/src/rendezvous_mediator.rs +++ b/src/rendezvous_mediator.rs @@ -41,6 +41,30 @@ lazy_static::lazy_static! { static SHOULD_EXIT: AtomicBool = AtomicBool::new(false); static MANUAL_RESTARTED: AtomicBool = AtomicBool::new(false); static SENT_REGISTER_PK: AtomicBool = AtomicBool::new(false); +pub(crate) static NEEDS_DEPLOY: AtomicBool = AtomicBool::new(false); +// register_pk retry interval (ms) when device is awaiting deployment +const DEPLOY_RETRY_INTERVAL: i64 = 30_000; +lazy_static::lazy_static! { + static ref LAST_NOT_DEPLOYED_REGISTER: Mutex> = Mutex::new(None); +} + +// Single source of truth for the "awaiting deployment" backoff. The server has +// already told us this device is not in its db; until the operator runs +// `rustdesk --deploy --token ` there is no point re-running the +// register path more often than DEPLOY_RETRY_INTERVAL. Gating in the timer +// loops (rather than only inside register_pk) also avoids the +// last_register_sent / fails / latency / UDP-rebind churn the loop would +// otherwise spin on while no response ever comes back. +async fn deploy_register_throttled() -> bool { + if !NEEDS_DEPLOY.load(Ordering::SeqCst) { + return false; + } + LAST_NOT_DEPLOYED_REGISTER + .lock() + .await + .map(|t| (t.elapsed().as_millis() as i64) < DEPLOY_RETRY_INTERVAL) + .unwrap_or(false) +} #[derive(Clone)] pub struct RendezvousMediator { @@ -226,6 +250,14 @@ impl RendezvousMediator { if SHOULD_EXIT.load(Ordering::SeqCst) { break; } + // The server already told us this device is not deployed. Skip + // the whole register / fails / latency / UDP-rebind path until + // DEPLOY_RETRY_INTERVAL elapses, otherwise the loop spins every + // few seconds (log spam + misapplied network-recovery rebind) + // until the operator runs `rustdesk --deploy`. + if deploy_register_throttled().await { + continue; + } let now = Some(Instant::now()); let expired = last_register_resp.map(|x| x.elapsed().as_millis() as i64 >= REG_INTERVAL).unwrap_or(true); let timeout = last_register_sent.map(|x| x.elapsed().as_millis() as i64 >= reg_timeout).unwrap_or(false); @@ -289,10 +321,22 @@ impl RendezvousMediator { Config::set_key_confirmed(true); Config::set_host_key_confirmed(&self.host_prefix, true); *SOLVING_PK_MISMATCH.lock().await = "".to_owned(); + NEEDS_DEPLOY.store(false, Ordering::SeqCst); } Ok(register_pk_response::Result::UUID_MISMATCH) => { self.handle_uuid_mismatch(sink).await?; } + Ok(register_pk_response::Result::NOT_DEPLOYED) => { + if !NEEDS_DEPLOY.load(Ordering::SeqCst) { + log::warn!("Server requires deployment. Run `rustdesk --deploy --token ` on this device."); + } + NEEDS_DEPLOY.store(true, Ordering::SeqCst); + // Clear key_confirmed so the UI reflects the truth: this device is + // not currently registered. Covers the case where an online device + // was deleted by an admin while running. + Config::set_key_confirmed(false); + Config::set_host_key_confirmed(&self.host_prefix, false); + } _ => { log::error!("unknown RegisterPkResponse"); } @@ -678,6 +722,21 @@ impl RendezvousMediator { } async fn register_pk(&mut self, socket: Sink<'_>) -> ResultType<()> { + // Throttle register_pk when the device is awaiting deployment: server + // already told us we're not in its db; sending more often than every + // DEPLOY_RETRY_INTERVAL ms is wasted traffic until the operator runs + // `rustdesk --deploy --token `. + if NEEDS_DEPLOY.load(Ordering::SeqCst) { + let mut last = LAST_NOT_DEPLOYED_REGISTER.lock().await; + if let Some(t) = *last { + if (t.elapsed().as_millis() as i64) < DEPLOY_RETRY_INTERVAL { + return Ok(()); + } + } + *last = Some(Instant::now()); + } else { + *LAST_NOT_DEPLOYED_REGISTER.lock().await = None; + } let mut msg_out = Message::new(); let pk = Config::get_key_pair().1; let uuid = hbb_common::get_uuid(); From 377547fa1128823d6c5a4ea17b1310640264713b Mon Sep 17 00:00:00 2001 From: IronCodeStudios Date: Sun, 17 May 2026 16:02:23 +0800 Subject: [PATCH 4/9] scrap/wayland: insert videoconvert to fix screencast on COSMIC / DMA-BUF portals (#15063) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Wayland compositors whose xdg-desktop-portal backend exposes screencast frames as DMA-BUF buffers — notably xdg-desktop-portal-cosmic 0.1.0 on Pop!_OS 24.04 / COSMIC — inbound screen capture fails. PipeWireRecorder links pipewiresrc directly to an appsink whose caps only accept video/x-raw BGRx/RGBx in system memory. That format set is too narrow for the portal's buffer-type / modifier negotiation, which collapses with: pw.link: negotiating -> error no more output formats (-22) gstpipewiresrc: stream error: no more output formats gstbasesrc: streaming stopped, reason not-negotiated (-4) ERROR src/server/wayland.rs: Failed scrap Element failed to change its state Inserting a videoconvert element between pipewiresrc and appsink widens the negotiable format set to any system-memory video/x-raw format, giving the portal room to settle on a format it can deliver via its SHM path. videoconvert then converts to the BGRx/RGBx the appsink expects. Verified on Pop!_OS 24.04 / COSMIC with gst-launch, before and after: # fails (current behaviour): gst-launch-1.0 pipewiresrc path=N ! video/x-raw,format=BGRx ! fakesink # works (with this change): gst-launch-1.0 pipewiresrc path=N ! videoconvert ! video/x-raw,format=BGRx ! fakesink After the change, inbound connections capture and stream the desktop normally and the "Failed scrap" error no longer occurs. Co-authored-by: Claude Opus 4.7 (1M context) --- libs/scrap/src/wayland/pipewire.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/libs/scrap/src/wayland/pipewire.rs b/libs/scrap/src/wayland/pipewire.rs index aedf786b7..8859d0d3b 100644 --- a/libs/scrap/src/wayland/pipewire.rs +++ b/libs/scrap/src/wayland/pipewire.rs @@ -276,12 +276,21 @@ impl PipeWireRecorder { // see: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/982 src.set_property("always-copy", &true)?; + // COSMIC/Wayland fix: insert videoconvert between pipewiresrc and appsink. + // xdg-desktop-portal-cosmic's modifier negotiation fails when the downstream + // format set is too narrow (appsink only accepts BGRx/RGBx), producing + // "no more output formats" / not-negotiated (-4). videoconvert accepts any + // system-memory video/x-raw format, widening negotiation so the portal can + // settle on a format it can deliver via its SHM path. + let convert = gst::ElementFactory::make("videoconvert", None)?; + let sink = gst::ElementFactory::make("appsink", None)?; sink.set_property("drop", &true)?; sink.set_property("max-buffers", &1u32)?; - pipeline.add_many(&[&src, &sink])?; - src.link(&sink)?; + pipeline.add_many(&[&src, &convert, &sink])?; + src.link(&convert)?; + convert.link(&sink)?; let appsink = sink .dynamic_cast::() From bc2c36215d15dd2ec223a5d470e38ebc87b9de7d Mon Sep 17 00:00:00 2001 From: fufesou Date: Mon, 18 May 2026 16:32:46 +0800 Subject: [PATCH 5/9] fix(ipc): scope active-user IPC routing to root CLI main requests (#15058) * fix(ipc): scope active-user IPC routing to root CLI main requests Signed-off-by: fufesou * fix(ipc): cmdline, comments fails close Signed-off-by: fufesou * fix(ipc): cmdline, better check Signed-off-by: fufesou * fix(ipc): cmdline, try active uid when no --server processes Signed-off-by: fufesou * fix(ipc): cmdline, select active uid Signed-off-by: fufesou * fix(ipc): remove unused import Signed-off-by: fufesou --------- Signed-off-by: fufesou --- libs/hbb_common | 2 +- src/core_main.rs | 63 ++++++++++++ src/ipc.rs | 210 +++++++++++++++++++++++++++++++++++++--- src/ipc/auth.rs | 71 +++++++++++--- src/platform/windows.rs | 1 + 5 files changed, 317 insertions(+), 30 deletions(-) diff --git a/libs/hbb_common b/libs/hbb_common index c8cbb6be2..9043c15ac 160000 --- a/libs/hbb_common +++ b/libs/hbb_common @@ -1 +1 @@ -Subproject commit c8cbb6be283e9215da87625016fe8838dda76c02 +Subproject commit 9043c15acc6d5b42b6c12ad284c16c1ec172f1f0 diff --git a/src/core_main.rs b/src/core_main.rs index a0ca5eb95..ee2a9d90d 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -199,6 +199,20 @@ pub fn core_main() -> Option> { } std::thread::spawn(move || crate::start_server(false, no_server)); } else { + #[cfg(any(target_os = "linux", target_os = "macos"))] + // Root CLI management commands must talk to the user `--server` main IPC. + // Example: `sudo rustdesk --option custom-rendezvous-server` should query the + // user's IPC instead of root's `/tmp/-0/ipc`; `connect()` still limits this + // routing to empty-postfix main IPC only. + let _user_main_ipc_scope = if crate::platform::is_installed() + && is_root() + && is_user_main_ipc_scope_cli_command(&args) + { + Some(crate::ipc::UserMainIpcScope::new()) + } else { + None + }; + #[cfg(windows)] { use crate::platform; @@ -938,6 +952,55 @@ fn is_root() -> bool { crate::platform::is_root() } +#[cfg(any(target_os = "linux", target_os = "macos", test))] +fn is_user_main_ipc_scope_cli_command(args: &[String]) -> bool { + matches!( + args.first().map(String::as_str), + Some("--password") + | Some("--set-unlock-pin") + | Some("--get-id") + | Some("--set-id") + | Some("--config") + | Some("--option") + | Some("--assign") + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn args(values: &[&str]) -> Vec { + values.iter().map(|value| value.to_string()).collect() + } + + #[test] + fn user_main_ipc_scope_cli_command_matches_management_commands_only() { + for command in [ + "--password", + "--set-unlock-pin", + "--get-id", + "--set-id", + "--config", + "--option", + "--assign", + ] { + assert!(is_user_main_ipc_scope_cli_command(&args(&[command]))); + } + + for command in [ + "--service", + "--server", + "--tray", + "--cm", + "--check-hwcodec-config", + "--connect", + ] { + assert!(!is_user_main_ipc_scope_cli_command(&args(&[command]))); + } + } +} + /// Check if the executable is a Quick Support version. /// Note: This function must be kept in sync with `libs/portable/src/main.rs`. #[cfg(windows)] diff --git a/src/ipc.rs b/src/ipc.rs index 0cd30634a..ffe1b08a5 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -33,25 +33,25 @@ use hbb_common::{ tokio_util::codec::Framed, ResultType, }; -#[cfg(any(target_os = "linux", target_os = "macos"))] -use ipc_auth::authorize_service_scoped_ipc_connection; #[cfg(windows)] pub(crate) use ipc_auth::authorize_windows_portable_service_ipc_connection; #[cfg(windows)] pub(crate) use ipc_auth::ensure_peer_executable_matches_current_by_pid_opt; #[cfg(windows)] pub(crate) use ipc_auth::log_rejected_windows_ipc_connection; -#[cfg(target_os = "linux")] -pub(crate) use ipc_auth::{ - active_uid, ensure_peer_executable_matches_current_by_fd, is_allowed_service_peer_uid, - log_rejected_uinput_connection, peer_uid_from_fd, -}; +#[cfg(any(target_os = "linux", target_os = "macos"))] +use ipc_auth::{active_uid, authorize_service_scoped_ipc_connection}; #[cfg(windows)] use ipc_auth::{ authorize_windows_main_ipc_connection, portable_service_listener_security_attributes, should_allow_everyone_create_on_windows, }; #[cfg(target_os = "linux")] +pub(crate) use ipc_auth::{ + ensure_peer_executable_matches_current_by_fd, is_allowed_service_peer_uid, + log_rejected_uinput_connection, peer_uid_from_fd, +}; +#[cfg(target_os = "linux")] use ipc_fs::terminal_count_candidate_uids; #[cfg(any(target_os = "linux", target_os = "macos"))] use ipc_fs::{ @@ -63,6 +63,8 @@ use parity_tokio_ipc::{ }; use serde_derive::{Deserialize, Serialize}; #[cfg(any(target_os = "linux", target_os = "macos"))] +use std::cell::Cell; +#[cfg(any(target_os = "linux", target_os = "macos"))] use std::os::unix::fs::PermissionsExt; use std::{ collections::HashMap, @@ -71,12 +73,47 @@ use std::{ // IPC actions here. pub const IPC_ACTION_CLOSE: &str = "close"; +#[cfg(target_os = "windows")] const PORTABLE_SERVICE_IPC_HANDSHAKE_TIMEOUT_MS: u64 = 3_000; +#[cfg(target_os = "windows")] pub(crate) const IPC_TOKEN_LEN: usize = 64; +#[cfg(target_os = "windows")] const IPC_TOKEN_RANDOM_BYTES: usize = IPC_TOKEN_LEN / 2; +#[cfg(target_os = "windows")] const _: () = assert!(IPC_TOKEN_LEN % 2 == 0); pub static EXIT_RECV_CLOSE: AtomicBool = AtomicBool::new(true); +#[cfg(any(target_os = "linux", target_os = "macos"))] +thread_local! { + static USE_USER_MAIN_IPC: Cell = Cell::new(false); +} + +#[must_use = "bind this guard to a local variable to keep the IPC scope active"] +/// Thread-local guard for routing root main IPC to the active user on Linux/macOS. +#[cfg(any(target_os = "linux", target_os = "macos"))] +pub(crate) struct UserMainIpcScope { + previous: bool, +} + +#[cfg(any(target_os = "linux", target_os = "macos"))] +impl UserMainIpcScope { + pub(crate) fn new() -> Self { + let previous = USE_USER_MAIN_IPC.with(|use_user_main| { + let previous = use_user_main.get(); + use_user_main.set(true); + previous + }); + Self { previous } + } +} + +#[cfg(any(target_os = "linux", target_os = "macos"))] +impl Drop for UserMainIpcScope { + fn drop(&mut self) { + USE_USER_MAIN_IPC.with(|use_user_main| use_user_main.set(self.previous)); + } +} + #[inline] pub async fn connect_service(ms_timeout: u64) -> ResultType> { connect(ms_timeout, crate::POSTFIX_SERVICE).await @@ -1112,11 +1149,7 @@ async fn handle(data: Data, stream: &mut Connection) { }; } -pub async fn connect(ms_timeout: u64, postfix: &str) -> ResultType> { - let path = Config::ipc_path(postfix); - connect_with_path(ms_timeout, &path).await -} - +#[cfg(target_os = "windows")] pub(crate) fn generate_one_time_ipc_token() -> ResultType { use hbb_common::rand::{rngs::OsRng, RngCore as _}; use std::fmt::Write as _; @@ -1137,6 +1170,7 @@ pub(crate) fn generate_one_time_ipc_token() -> ResultType { Ok(token) } +#[cfg(target_os = "windows")] pub(crate) fn constant_time_ipc_token_eq(expected: &str, candidate: &str) -> bool { if expected.len() != IPC_TOKEN_LEN || candidate.len() != IPC_TOKEN_LEN { return false; @@ -1149,6 +1183,7 @@ pub(crate) fn constant_time_ipc_token_eq(expected: &str, candidate: &str) -> boo == 0 } +#[cfg(target_os = "windows")] pub(crate) async fn portable_service_ipc_handshake_as_client( stream: &mut ConnectionTmpl, token: &str, @@ -1173,6 +1208,7 @@ where } } +#[cfg(target_os = "windows")] pub(crate) async fn portable_service_ipc_handshake_as_server( stream: &mut ConnectionTmpl, mut validate_token: F, @@ -1209,6 +1245,103 @@ async fn connect_with_path(ms_timeout: u64, path: &str) -> ResultType, + prefer_root: bool, +) -> ResultType { + let mut server_uids = server_uids.to_vec(); + server_uids.sort_unstable(); + server_uids.dedup(); + + match server_uids.as_slice() { + [] => { + if let Some(uid) = active_uid { + // If no `--server` processes are found but the active user is identifiable, + // try the active user anyway because the main process may also listen on "" IPC. + return Ok(uid); + } else { + bail!("No --server process found for user main IPC") + } + } + [uid] => return Ok(*uid), + _ => {} + } + + if prefer_root && server_uids.contains(&0) { + return Ok(0); + } + if let Some(active_uid) = active_uid.filter(|uid| server_uids.contains(uid)) { + return Ok(active_uid); + } + bail!("Multiple --server processes found for user main IPC"); +} + +#[cfg(any(target_os = "linux", target_os = "macos"))] +fn running_server_uids_for_current_exe() -> ResultType> { + let current_exe = std::env::current_exe()?; + let current_exe_path = std::fs::canonicalize(¤t_exe)?; + let current_pid = hbb_common::sysinfo::Pid::from_u32(std::process::id()); + let mut sys = hbb_common::sysinfo::System::new(); + sys.refresh_processes(); + let mut server_uids = Vec::new(); + for process in sys.processes().values() { + if process.pid() == current_pid { + continue; + } + if process.cmd().get(1).map_or(true, |arg| arg != "--server") { + continue; + } + let Ok(process_path) = std::fs::canonicalize(process.exe()) else { + continue; + }; + if process_path != current_exe_path { + continue; + } + let Some(uid) = process.user_id().map(|uid| **uid as u32) else { + // Root CLI management commands need a stable matching `--server` target. + // If this key process races during enumeration, failing the command is clearer + // than silently skipping it; `--server` is not expected to exit frequently. + bail!("Failed to read --server process uid"); + }; + server_uids.push(uid); + } + Ok(server_uids) +} + +#[cfg(any(target_os = "linux", target_os = "macos"))] +fn user_main_ipc_server_uid() -> ResultType { + let server_uids = running_server_uids_for_current_exe()?; + #[cfg(target_os = "linux")] + let prefer_root = crate::platform::linux::is_login_screen_wayland(); + #[cfg(target_os = "macos")] + let prefer_root = false; + select_server_uid_for_user_main_ipc(&server_uids, active_uid(), prefer_root) +} + +pub async fn connect(ms_timeout: u64, postfix: &str) -> ResultType> { + #[cfg(any(target_os = "linux", target_os = "macos"))] + { + let use_user_main_ipc = USE_USER_MAIN_IPC.with(|use_user_main| use_user_main.get()); + let is_root_main_ipc = + unsafe { hbb_common::libc::geteuid() == 0 } && postfix.is_empty() && use_user_main_ipc; + if is_root_main_ipc { + let uid = user_main_ipc_server_uid()?; + let path = Config::ipc_path_for_uid(uid, postfix); + return connect_with_path(ms_timeout, &path).await; + } + let path = Config::ipc_path(postfix); + return connect_with_path(ms_timeout, &path).await; + } + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + { + let path = Config::ipc_path(postfix); + connect_with_path(ms_timeout, &path).await + } +} + #[cfg(target_os = "linux")] pub async fn connect_for_uid( ms_timeout: u64, @@ -2002,7 +2135,16 @@ mod test { assert!(std::mem::size_of::() <= 120); } - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "linux", target_os = "macos"))] + #[test] + fn test_service_ipc_path_is_shared_across_uids() { + assert_eq!( + Config::ipc_path_for_uid(0, crate::POSTFIX_SERVICE), + Config::ipc_path_for_uid(501, crate::POSTFIX_SERVICE) + ); + } + + #[cfg(any(target_os = "linux", target_os = "macos"))] #[test] fn test_ipc_path_differs_by_uid_for_cm() { let effective_uid = unsafe { hbb_common::libc::geteuid() as u32 }; @@ -2021,4 +2163,46 @@ mod test { Config::ipc_path_for_uid(other_uid, postfix) ); } + + #[cfg(any(target_os = "linux", target_os = "macos"))] + #[test] + fn test_select_server_uid_uses_active_uid_when_no_server_found() { + assert_eq!( + select_server_uid_for_user_main_ipc(&[], Some(501), false).unwrap(), + 501 + ); + } + + #[cfg(any(target_os = "linux", target_os = "macos"))] + #[test] + fn test_select_server_uid_uses_single_server_uid() { + assert_eq!( + select_server_uid_for_user_main_ipc(&[501], None, false).unwrap(), + 501 + ); + } + + #[cfg(any(target_os = "linux", target_os = "macos"))] + #[test] + fn test_select_server_uid_prefers_active_uid_with_multiple_servers() { + assert_eq!( + select_server_uid_for_user_main_ipc(&[0, 501], Some(501), false).unwrap(), + 501 + ); + } + + #[cfg(any(target_os = "linux", target_os = "macos"))] + #[test] + fn test_select_server_uid_prefers_root_on_wayland_login_screen() { + assert_eq!( + select_server_uid_for_user_main_ipc(&[0, 501], Some(501), true).unwrap(), + 0 + ); + } + + #[cfg(any(target_os = "linux", target_os = "macos"))] + #[test] + fn test_select_server_uid_fails_when_multiple_servers_are_ambiguous() { + assert!(select_server_uid_for_user_main_ipc(&[501, 502], None, false).is_err()); + } } diff --git a/src/ipc/auth.rs b/src/ipc/auth.rs index 746a32eed..77fd148c6 100644 --- a/src/ipc/auth.rs +++ b/src/ipc/auth.rs @@ -607,27 +607,30 @@ pub(crate) fn log_rejected_windows_ipc_connection( peer_session_id: Option, expected_session_id: Option, peer_is_system: Option, + peer_is_elevated: Option, ) { static LOG_THROTTLE: OnceLock> = OnceLock::new(); throttled_unauthorized_ipc_log(&LOG_THROTTLE, |suppressed| { if suppressed > 0 { log::warn!( - "Rejected unauthorized connection on ipc channel: postfix={}, peer_pid={:?}, peer_session_id={:?}, expected_session_id={:?}, peer_is_system={:?} (suppressed {} similar events)", + "Rejected unauthorized connection on ipc channel: postfix={}, peer_pid={:?}, peer_session_id={:?}, expected_session_id={:?}, peer_is_system={:?}, peer_is_elevated={:?} (suppressed {} similar events)", postfix, peer_pid, peer_session_id, expected_session_id, peer_is_system, + peer_is_elevated, suppressed ); } else { log::warn!( - "Rejected unauthorized connection on ipc channel: postfix={}, peer_pid={:?}, peer_session_id={:?}, expected_session_id={:?}, peer_is_system={:?}", + "Rejected unauthorized connection on ipc channel: postfix={}, peer_pid={:?}, peer_session_id={:?}, expected_session_id={:?}, peer_is_system={:?}, peer_is_elevated={:?}", postfix, peer_pid, peer_session_id, expected_session_id, - peer_is_system + peer_is_system, + peer_is_elevated ); } }); @@ -655,8 +658,14 @@ pub(crate) fn authorize_service_scoped_ipc_connection(stream: &Connection, postf #[cfg(windows)] pub(crate) fn authorize_windows_main_ipc_connection(stream: &Connection, postfix: &str) -> bool { - let (authorized, peer_pid, peer_session_id, server_session_id, peer_is_system) = - stream.server_authorization_status(); + let ( + authorized, + peer_pid, + peer_session_id, + server_session_id, + peer_is_system, + peer_is_elevated, + ) = stream.server_authorization_status(); if !authorized { log_rejected_windows_ipc_connection( postfix, @@ -664,6 +673,7 @@ pub(crate) fn authorize_windows_main_ipc_connection(stream: &Connection, postfix peer_session_id, server_session_id, peer_is_system, + peer_is_elevated, ); return false; } @@ -776,7 +786,14 @@ impl ConnectionTmpl { fn server_authorization_status( &self, - ) -> (bool, Option, Option, Option, Option) { + ) -> ( + bool, + Option, + Option, + Option, + Option, + Option, + ) { let peer_pid = self.peer_pid(); let server_session_id = crate::platform::windows::get_current_process_session_id(); let peer_session_id = @@ -786,20 +803,34 @@ impl ConnectionTmpl { let peer_is_system = peer_is_system_result .as_ref() .and_then(|r| r.as_ref().ok().copied()); - if server_session_id.is_none() && !peer_is_system.unwrap_or(false) { - // When the server session id cannot be determined, the session-id allow-path is - // disabled and only SYSTEM peers can be authorized. - log::debug!( - "IPC authorization: server session id unavailable; rejecting non-SYSTEM peer, peer_pid={:?}, peer_session_id={:?}", - peer_pid, - peer_session_id - ); - } - let authorized = is_allowed_windows_session_scoped_peer( + let session_authorized = is_allowed_windows_session_scoped_peer( peer_is_system.unwrap_or(false), peer_session_id, server_session_id, ); + let peer_is_elevated_result = if session_authorized { + None + } else { + peer_pid.map(|pid| crate::platform::windows::is_elevated(Some(pid))) + }; + let peer_is_elevated = peer_is_elevated_result + .as_ref() + .and_then(|r| r.as_ref().ok().copied()); + if server_session_id.is_none() + && !peer_is_system.unwrap_or(false) + && !peer_is_elevated.unwrap_or(false) + { + // When the server session id cannot be determined, the session-id allow-path is + // disabled and only privileged peers can be authorized. + log::debug!( + "IPC authorization: server session id unavailable; rejecting non-privileged peer, peer_pid={:?}, peer_session_id={:?}", + peer_pid, + peer_session_id + ); + } + // Main IPC trusts same-session peers, LocalSystem, and elevated administrators. + // Service-scoped IPC channels keep their own stricter authorization paths. + let authorized = session_authorized || peer_is_elevated.unwrap_or(false); if !authorized { if let (Some(pid), Some(Err(err))) = (peer_pid, peer_is_system_result.as_ref()) { log::debug!( @@ -808,6 +839,13 @@ impl ConnectionTmpl { err ); } + if let (Some(pid), Some(Err(err))) = (peer_pid, peer_is_elevated_result.as_ref()) { + log::debug!( + "Failed to determine whether peer process is elevated, pid={}, err={}", + pid, + err + ); + } } ( authorized, @@ -815,6 +853,7 @@ impl ConnectionTmpl { peer_session_id, server_session_id, peer_is_system, + peer_is_elevated, ) } diff --git a/src/platform/windows.rs b/src/platform/windows.rs index a755714f9..1dc4a788a 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -614,6 +614,7 @@ fn authorize_service_scoped_ipc_connection( peer_session_id, expected_active_session_id, peer_is_system, + None, ); return false; } From 78e8134ad56094f58c53eaff2edae07a3845da55 Mon Sep 17 00:00:00 2001 From: fufesou Date: Mon, 18 May 2026 16:52:22 +0800 Subject: [PATCH 6/9] fix(ipc): cmdline, use scope, deploy (#15068) Signed-off-by: fufesou --- src/core_main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core_main.rs b/src/core_main.rs index ee2a9d90d..c9c1a658f 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -963,6 +963,7 @@ fn is_user_main_ipc_scope_cli_command(args: &[String]) -> bool { | Some("--config") | Some("--option") | Some("--assign") + | Some("--deploy") ) } From bb51c6aa4207b53904a2528615c5b94a37ecc053 Mon Sep 17 00:00:00 2001 From: fufesou Date: Mon, 18 May 2026 17:03:04 +0800 Subject: [PATCH 7/9] fix(ipc): cmdline, unit tests (#15069) Signed-off-by: fufesou --- src/core_main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core_main.rs b/src/core_main.rs index c9c1a658f..4515faa6b 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -985,6 +985,7 @@ mod tests { "--config", "--option", "--assign", + "--deploy", ] { assert!(is_user_main_ipc_scope_cli_command(&args(&[command]))); } From 546e9f1702572c4d5c9ce0d0f977cea17ba53c8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 09:07:43 +0800 Subject: [PATCH 8/9] Git submodule: Bump libs/hbb_common from `c8cbb6b` to `9043c15` (#15067) Bumps [libs/hbb_common](https://github.com/rustdesk/hbb_common) from `c8cbb6b` to `9043c15`. - [Release notes](https://github.com/rustdesk/hbb_common/releases) - [Commits](https://github.com/rustdesk/hbb_common/compare/c8cbb6be283e9215da87625016fe8838dda76c02...9043c15acc6d5b42b6c12ad284c16c1ec172f1f0) --- updated-dependencies: - dependency-name: libs/hbb_common dependency-version: 9043c15acc6d5b42b6c12ad284c16c1ec172f1f0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From b81ae6c8949a50f64bac175f32217b8897078a2d Mon Sep 17 00:00:00 2001 From: Maison da Silva Date: Fri, 22 May 2026 07:36:15 -0300 Subject: [PATCH 9/9] Translate various labels to Portuguese-BR (#15086) Update --- src/lang/ptbr.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 4eb2c1544..36581d4f1 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -740,9 +740,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("keep-awake-during-outgoing-sessions-label", "Manter tela ativa durante sessões de saída"), ("keep-awake-during-incoming-sessions-label", "Manter tela ativa durante sessões de entrada"), ("Continue with {}", "Continuar com {}"), - ("Display Name", ""), - ("password-hidden-tip", ""), - ("preset-password-in-use-tip", ""), - ("Enable privacy mode", ""), + ("Display Name", "Nome de Exibição"), + ("password-hidden-tip", "A senha permanente está definida como (oculta)."), + ("preset-password-in-use-tip", "A senha predefinida está sendo usada."), + ("Enable privacy mode", "Habilitar modo de privacidade"), ].iter().cloned().collect(); }