Compare commits

...

6 Commits

Author SHA1 Message Date
dependabot[bot]
2734d1baa7 Git submodule: Bump libs/hbb_common from c8cbb6b to 9043c15
Bumps [libs/hbb_common](https://github.com/rustdesk/hbb_common) from `c8cbb6b` to `9043c15`.
- [Release notes](https://github.com/rustdesk/hbb_common/releases)
- [Commits](c8cbb6be28...9043c15acc)

---
updated-dependencies:
- dependency-name: libs/hbb_common
  dependency-version: 9043c15acc6d5b42b6c12ad284c16c1ec172f1f0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-18 00:43:04 +00:00
IronCodeStudios
377547fa11 scrap/wayland: insert videoconvert to fix screencast on COSMIC / DMA-BUF portals (#15063)
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) <noreply@anthropic.com>
2026-05-17 16:02:23 +08:00
RustDesk
472c4fc03a --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 66e39abb74.

* remove too many logs

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-16 14:41:34 +08:00
rustdesk
9f8f726f12 fix compile 2026-05-15 17:30:59 +08:00
flusheDData
701a9c6cdc 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>
2026-05-15 15:31:25 +08:00
Alex Rijckaert
0d40cf2101 Update Dutch translations (#15024)
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2026-05-14 16:43:40 +08:00
7 changed files with 218 additions and 46 deletions

View File

@@ -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::<AppSink>()

View File

@@ -627,6 +627,98 @@ pub fn core_main() -> Option<Vec<String>> {
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();

View File

@@ -312,6 +312,7 @@ pub enum Data {
ClipboardNonFile(Option<(String, Vec<ClipboardNonFile>)>),
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")

View File

@@ -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();
}

View File

@@ -743,6 +743,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Display Name", "Naam Weergeven"),
("password-hidden-tip", "Er is een permanent wachtwoord ingesteld (verborgen)."),
("preset-password-in-use-tip", "Het basis wachtwoord is momenteel in gebruik."),
("Enable privacy mode", "Schakel privacymodus in"),
("Enable privacy mode", "Privacymodus inschakelen"),
].iter().cloned().collect();
}

View File

@@ -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<Option<Instant>> = 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 <api_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 <api_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 <api_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();