Compare commits

..

6 Commits

Author SHA1 Message Date
RustDesk
851d2df88c fix(audio): three 100% CPU busy loops on the _pa path (#16229)
The Linux audio service in `--server` ignored the `Err` from `next_raw()`, so
once the cm-side `_pa` peer closed, every iteration re-polled a dead socket:
tokio-util's paused `Framed` issues one 0-byte read per poll and returns ready
at once, never `Pending`. The thread never parked and burned a full core for
the life of the process. Propagate instead, so `ServiceTmpl::run`'s existing
backoff ends the inner loop and reconnects.

Two sibling loops on the same audio path have the same shape:

- `ipc::start_pa` (runs in `--cm`) ignored the `Err` from
  `psimple::Simple::read`, so a dead pulse handle spins there instead.
- `start_voice_call`'s forwarding thread polls two channels with `try_recv`
  and has no blocking primitive at all: measured 99.8% of a core for the whole
  call, against 1.0% with a 1 ms pause (audio packets arrive every 10 ms).

fix https://github.com/rustdesk/rustdesk/issues/16226

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 17:35:40 +08:00
solokot
be757de462 Update ru.rs (#16217) 2026-09-15 16:14:49 +08:00
fufesou
a437eb9bc6 fix(windows): recognize Hyper-V Enhanced Sessions by protocol (#16223)
Fixes #16182

Use WTSClientProtocolType to identify RDP sessions with nonstandard
names during session selection and enumeration.

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-09-15 15:21:19 +08:00
21pages
39d4f1854b chore(qos): remove verbose diagnostics while retaining trace logs (#16214) 2026-09-14 17:19:58 +08:00
VenusGirl❤
bd028ad350 Update Korean (#16212) 2026-09-14 16:15:55 +08:00
bovirus
b48fe17b52 Update Italian language (#16211)
* Update it.rs

* Update it.rs
2026-09-14 16:15:31 +08:00
7 changed files with 37 additions and 80 deletions

View File

@@ -780,6 +780,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("RustDesk could not load a GStreamer component needed for screen capture ({})", "RustDesk non è riuscito a caricare un componente GStreamer necessario per l'acquisizione dello schermo ({})"),
("Relay fallback delay in seconds", "Ritardo fallback relay (secondi)"),
("relay-fallback-delay-tip", "Quanto tempo una connessione relay già attiva attende la connessione WebRTC diretta prima di essere usata. Aumentalo per dare a una connessione diretta lenta più tempo per funzionare; diminuiscilo per passare prima al relay sulle reti in cui non è possibile effettuare una connessione diretta. Lascia vuoto per il valore predefinito di 2,5 secondi."),
("To start a voice call, enable \"Audio capture\" on the \"Screen share\" page.", "")
("To start a voice call, enable \"Audio capture\" on the \"Screen share\" page.", "Per avviare una chiamata vocale, attiva nella pagina 'Condivisione schermo' la voce 'Cattura audio'.")
].iter().cloned().collect();
}

View File

@@ -780,6 +780,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("RustDesk could not load a GStreamer component needed for screen capture ({})", "RustDesk가 화면 캡처에 필요한 GStreamer 구성 요소를 불러오지 못했습니다 ({})"),
("Relay fallback delay in seconds", "릴레이 대체 작동 지연 시간 (초)"),
("relay-fallback-delay-tip", "이미 연결된 중계 연결이 직접 WebRTC 연결을 얼마나 기다린 후 대신 사용되는지입니다. 값을 늘리면 느린 직접 연결에 더 많은 시간을 주고, 줄이면 직접 연결이 불가능한 네트워크에서 더 빨리 중계로 전환합니다. 비워 두면 기본값 2.5초가 사용됩니다."),
("To start a voice call, enable \"Audio capture\" on the \"Screen share\" page.", "")
("To start a voice call, enable \"Audio capture\" on the \"Screen share\" page.", "음성 통화를 시작하려면 '화면 공유' 페이지에서 '오디오 캡처'를 사용함으로 하세요.")
].iter().cloned().collect();
}

View File

@@ -730,7 +730,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("preset-password-in-use-tip", "Установленный пароль сейчас используется."),
("Enable privacy mode", "Использовать режим конфиденциальности"),
("allow-remote-toolbar-docking-any-edge", "Разрешать прикрепление удалённой панели инструментов к любому краю окна"),
("API Token", "API-токен"),
("API Token", "Токен API"),
("Deploy", "Развернуть"),
("Custom ID (optional)", "Пользовательский ID (необязательно)"),
("server_requires_deployment_tip", "Сервер требует явного развёртывания этого устройства. Развернуть сейчас?"),
@@ -748,23 +748,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Показывать на свёрнутой панели инструментов"),
("All monitors", "Все мониторы"),
("#{} monitor", "Монитор {}"),
("conn-e2ee-unavailable-tip", "Не удалось проверить сквозное шифрование.\nУдаленное устройство, возможно, еще настраивается. Повторите попытку позже.\nЕсли это повторяется, сервер может быть ненадежным.\nВсе равно продолжить?"),
("conn-e2ee-unavailable-tip", "Невозможно проверить сквозное шифрование.\nУдалённое устройство, возможно, ещё настраивается. Повторите попытку позже.\nЕсли это повторяется, сервер может быть ненадёжным.\nВсё равно продолжить?"),
("ID whitelisting", "Список разрешённых ID"),
("Use ID whitelisting", "Использовать белый список ID"),
("id_whitelist_tip", "Только ID из белого списка могут получить доступ к моему устройству."),
("id_whitelist_wildcard_tip", "Поддерживаются подстановочные знаки: '*' соответствует любому количеству символов, '?' — ровно одному символу"),
("id_whitelist_wildcard_tip", "Поддерживаются подстановочные знаки: \"*\" соответствует любому количеству символов, \"?\" — ровно одному символу"),
("Invalid ID", "Неправильный ID"),
("Your ID is blocked by the peer", "Ваш ID заблокирован удалённым устройством"),
("Your ip is blocked by the peer", "Ваш IP-адрес заблокирован удалённым устройством"),
("id_whitelist_caveat_tip", "ID сообщается подключающимся клиентом. Белый список уменьшает поверхность атаки и не заменяет пароль или 2FA"),
("whitelist_cidr_tip", "Поддерживается нотация CIDR, например 192.168.1.0/24"),
("whitelist_cidr_tip", "Поддерживается нотация CIDR, например: 192.168.1.0/24"),
("Continue", "Продолжить"),
("Browser didn't open? Use the url below to sign in.", "Браузер не открылся? Используйте ссылку ниже для входа."),
("Lock canvas", "Заблокировать холст"),
("Sync clipboard between sessions", "Синхронизировать буфер обмена между сеансами"),
("sync-clipboard-between-sessions-tip", "Текст или изображения, скопированные в одном удалённом сеансе, также отправляются в буфер обмена других подключённых сеансов."),
("terminal-clipboard-write-tip", ""),
("Allow terminal apps to copy to clipboard", ""),
("terminal-clipboard-write-tip", "Приложение в терминале хочет скопировать текст в буфер обмена этого устройства. Если соответствующее разрешение предоставлено, оно применяется к приложениям в терминале во всех соединениях, пока вы не отключите его в настройках. Ручные копирование и вставка не затрагиваются."),
("Allow terminal apps to copy to clipboard", "Разрешить приложениям в терминале копирование в буфер обмена"),
("Enable", "Включить"),
("Reuse one connection for port forwarding", "Использовать одно подключение для перенаправления портов"),
("port-forward-mux-tip", "Передавать все соединения одного перенаправления портов через одно подключение к удалённому устройству вместо повторного подключения и входа для каждого из них."),
@@ -780,6 +780,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("RustDesk could not load a GStreamer component needed for screen capture ({})", "RustDesk не удалось загрузить компонент GStreamer, необходимый для захвата экрана ({})"),
("Relay fallback delay in seconds", "Задержка перед переходом на ретранслятор в секундах"),
("relay-fallback-delay-tip", "Сколько времени уже установленное соединение через ретранслятор ждёт прямое соединение WebRTC, прежде чем будет использовано вместо него. Увеличьте, чтобы дать медленному прямому соединению больше времени; уменьшите, чтобы быстрее переходить на ретранслятор в сетях, где прямое соединение невозможно. Оставьте пустым для значения по умолчанию 2.5 секунды."),
("To start a voice call, enable \"Audio capture\" on the \"Screen share\" page.", "")
("To start a voice call, enable \"Audio capture\" on the \"Screen share\" page.", "Чтобы начать голосовой вызов, включите \"Захват аудио\" на странице \"Демонстрация экрана\" настроек.")
].iter().cloned().collect();
}

View File

@@ -542,6 +542,26 @@ extern "C"
SHAddToRecentDocs(SHARD_PATHW, path);
}
// Hyper-V Enhanced Session names do not have the usual "rdp" prefix.
static bool is_rdp_session_by_protocol(DWORD session_id)
{
LPSTR buffer = nullptr;
DWORD bytes = 0;
if (!WTSQuerySessionInformationA(
WTS_CURRENT_SERVER_HANDLE, session_id, WTSClientProtocolType, &buffer, &bytes)) {
flog("Failed to query protocol for session %lu: Windows error %lu\n",
session_id, GetLastError());
return false;
}
std::unique_ptr<char, decltype(&WTSFreeMemory)> protocol_info(buffer, WTSFreeMemory);
if (!buffer || bytes < sizeof(USHORT)) {
flog("Failed to query protocol for session %lu: Windows error %lu\n",
session_id, static_cast<DWORD>(ERROR_INVALID_DATA));
return false;
}
return *reinterpret_cast<const USHORT *>(buffer) == WTS_PROTOCOL_TYPE_RDP;
}
DWORD get_current_session(BOOL include_rdp)
{
auto rdp_or_console = WTSGetActiveConsoleSessionId();
@@ -573,6 +593,10 @@ extern "C"
{
rdp_or_console = info.SessionId;
}
else if (is_rdp_session_by_protocol(info.SessionId))
{
rdp_or_console = info.SessionId;
}
}
}
WTSFreeMemory(pInfos);
@@ -666,6 +690,9 @@ extern "C"
else if (include_rdp && !strnicmp(info.pWinStationName, ica, nica)) {
sessionIds.push_back(std::wstring(L"ICA:") + std::to_wstring(info.SessionId));
}
else if (include_rdp && is_rdp_session_by_protocol(info.SessionId)) {
sessionIds.push_back(std::wstring(L"RDP:") + std::to_wstring(info.SessionId));
}
}
}
WTSFreeMemory(pInfos);

View File

@@ -299,12 +299,6 @@ pub struct Connection {
tx_input: std_mpsc::Sender<MessageInput>,
// handle input messages
video_ack_required: bool,
// Diagnostics only, gated by `RUSTDESK_QOS_VERBOSE`: how long the shared
// write path blocked this second. The video send is inline in the message
// loop, so a slow write also delays the delay probe and its reply.
video_send_max_ms: u32,
video_send_sum_ms: u32,
video_send_count: u32,
server_audit_conn: String,
server_audit_file: String,
controlled_context: Option<ControlledContext>,
@@ -510,9 +504,6 @@ impl Connection {
show_my_cursor: false,
tx_input,
video_ack_required: false,
video_send_max_ms: 0,
video_send_sum_ms: 0,
video_send_count: 0,
server_audit_conn: "".to_owned(),
server_audit_file: "".to_owned(),
controlled_context,
@@ -959,17 +950,10 @@ impl Connection {
video_service::notify_video_frame_fetched(vf.display as usize, id, Some(instant.into()));
}
}
let send_begin = video_service::qos_diag_verbose().then(Instant::now);
if let Err(err) = conn.stream.send(&value as &Message).await {
conn.on_close(&err.to_string(), false).await;
break;
}
if let Some(begin) = send_begin {
let blocked = begin.elapsed().as_millis() as u32;
conn.video_send_max_ms = conn.video_send_max_ms.max(blocked);
conn.video_send_sum_ms = conn.video_send_sum_ms.saturating_add(blocked);
conn.video_send_count += 1;
}
},
Some((instant, value)) = rx.recv() => {
let latency = instant.elapsed().as_millis() as i64;
@@ -1056,21 +1040,6 @@ impl Connection {
break;
}
}
if video_service::qos_diag_verbose() && conn.video_send_count > 0 {
// Joined with `qos_trace` on `t`: a probe that waits behind a
// blocked write is not a slow network.
log::debug!(
"qos_send t={} id={id} frames={} send_max={} send_sum={} queued={}",
hbb_common::get_time(),
conn.video_send_count,
conn.video_send_max_ms,
conn.video_send_sum_ms,
rx_video.len()
);
conn.video_send_max_ms = 0;
conn.video_send_sum_ms = 0;
conn.video_send_count = 0;
}
conn.file_remove_log_control.on_timer().drain(..).map(|x| conn.send_to_cm(x)).count();
#[cfg(feature = "hwcodec")]
conn.update_supported_encoding();

View File

@@ -625,7 +625,7 @@ impl VideoQoS {
.clamp(MIN_AUTO_FPS.min(user_cap), user_cap)
.min(current);
user.delay.fps = Some(fps);
log::debug!(
log::trace!(
"qos_trace t={} id={id} timeout={elapsed} fps={fps}",
hbb_common::get_time()
);

View File

@@ -655,12 +655,6 @@ fn run(vs: VideoService) -> ResultType<()> {
let capture_width = c.width;
let capture_height = c.height;
let (mut second_instant, mut send_counter) = (Instant::now(), 0);
// Diagnostics only. `send_counter` counts capture rounds, which is not the
// number of frames that reached a connection: the encoder's own rate control
// drops frames when the bitrate cannot carry them. `wait_max_ms` is how long
// a round waited for the previous frame to be picked up, so a blocked write
// shows up here as capture stalling rather than as a slow network.
let (mut sent_counter, mut wait_max_ms) = (0usize, 0u32);
while sp.ok() {
#[cfg(windows)]
@@ -671,8 +665,6 @@ fn run(vs: VideoService) -> ResultType<()> {
&mut spf,
client_record,
&mut send_counter,
&mut sent_counter,
&mut wait_max_ms,
&mut second_instant,
&sp.name(),
)?;
@@ -793,9 +785,6 @@ fn run(vs: VideoService) -> ResultType<()> {
capture_width,
capture_height,
)?;
if !send_conn_ids.is_empty() {
sent_counter += 1;
}
frame_controller.set_send(now, send_conn_ids);
send_counter += 1;
}
@@ -855,9 +844,6 @@ fn run(vs: VideoService) -> ResultType<()> {
capture_width,
capture_height,
)?;
if !send_conn_ids.is_empty() {
sent_counter += 1;
}
frame_controller.set_send(now, send_conn_ids);
send_counter += 1;
}
@@ -899,7 +885,6 @@ fn run(vs: VideoService) -> ResultType<()> {
break;
}
}
wait_max_ms = wait_max_ms.max(wait_begin.elapsed().as_millis() as u32);
DISPLAY_CONN_IDS.lock().unwrap().remove(&display_idx);
let elapsed = now.elapsed();
@@ -1330,22 +1315,12 @@ pub fn make_display_changed_msg(
Some(msg_out)
}
/// Per-second pipeline diagnostics, off unless `RUSTDESK_QOS_VERBOSE` is set.
/// The default log level is `debug`, so an unconditional line here would land in
/// every user's log file once a second forever. Nothing enables it implicitly.
pub(crate) fn qos_diag_verbose() -> bool {
static VERBOSE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*VERBOSE.get_or_init(|| std::env::var("RUSTDESK_QOS_VERBOSE").is_ok())
}
fn check_qos(
encoder: &mut Encoder,
ratio: &mut f32,
spf: &mut Duration,
client_record: bool,
send_counter: &mut usize,
sent_counter: &mut usize,
wait_max_ms: &mut u32,
second_instant: &mut Instant,
name: &str,
) -> ResultType<()> {
@@ -1371,21 +1346,7 @@ fn check_qos(
if second_instant.elapsed() > Duration::from_secs(1) {
*second_instant = Instant::now();
video_qos.update_display_data(&name, *send_counter);
// Diagnostics only, joined with `qos_trace` on `t`: the controller's target
// is not the rate the encoder produced, and neither is the rate the send
// path accepted.
if qos_diag_verbose() {
log::debug!(
"qos_video t={} display={name} captured={} sent={} wait_max={}",
hbb_common::get_time(),
*send_counter,
*sent_counter,
*wait_max_ms
);
}
*send_counter = 0;
*sent_counter = 0;
*wait_max_ms = 0;
}
drop(video_qos);
Ok(())