terminal works basically. (#12189)

* terminal works basically.
todo:
- persistent
- sessions restore
- web
- mobile

* missed terminal persistent option change

* android sdk 34 -> 35

* +#![cfg_attr(lt_1_77, feature(c_str_literals))]

* fixing ci

* fix ci

* fix ci for android

* try "Fix Android SDK Platform 35"

* fix android 34

* revert flutter_plugin_android_lifecycle to 2.0.17 which used in rustdesk 1.4.0

* refactor, but break something of desktop terminal (new tab showing loading)

* fix connecting...
This commit is contained in:
RustDesk
2025-07-01 13:12:55 +08:00
committed by GitHub
parent ee5cdc3155
commit 5faf0ad3cf
130 changed files with 4064 additions and 4247 deletions

View File

@@ -47,8 +47,8 @@ use hbb_common::{
anyhow::{anyhow, Context},
bail,
config::{
self, use_ws, Config, LocalConfig, PeerConfig, PeerInfoSerde, Resolution, CONNECT_TIMEOUT,
READ_TIMEOUT, RELAY_PORT, RENDEZVOUS_PORT, RENDEZVOUS_SERVERS,
self, keys, use_ws, Config, LocalConfig, PeerConfig, PeerInfoSerde, Resolution,
CONNECT_TIMEOUT, READ_TIMEOUT, RELAY_PORT, RENDEZVOUS_PORT, RENDEZVOUS_SERVERS,
},
fs::JobType,
futures::future::{select_ok, FutureExt},
@@ -274,7 +274,6 @@ impl Client {
}
};
if crate::get_ipv6_punch_enabled() {
crate::test_ipv6().await;
}
@@ -1784,6 +1783,9 @@ impl LoginConfigHandler {
/// * `v` - value of option
pub fn set_option(&mut self, k: String, v: String) {
let mut config = self.load_config();
if v == self.get_option(&k) {
return;
}
config.options.insert(k, v);
self.save_config(config);
}
@@ -1952,6 +1954,14 @@ impl LoginConfigHandler {
BoolOption::No
})
.into();
} else if name == keys::OPTION_TERMINAL_PERSISTENT {
config.terminal_persistent.v = !config.terminal_persistent.v;
option.terminal_persistent = (if config.terminal_persistent.v {
BoolOption::Yes
} else {
BoolOption::No
})
.into();
} else if name == "privacy-mode" {
// try toggle privacy mode
option.privacy_mode = (if config.privacy_mode.v {
@@ -2049,6 +2059,14 @@ impl LoginConfigHandler {
return None;
}
let mut msg = OptionMessage::new();
if self.conn_type.eq(&ConnType::TERMINAL) {
if self.get_toggle_option(keys::OPTION_TERMINAL_PERSISTENT) {
msg.terminal_persistent = BoolOption::Yes.into();
return Some(msg);
} else {
return None;
}
}
let q = self.image_quality.clone();
if let Some(q) = self.get_image_quality_enum(&q, ignore_default) {
msg.image_quality = q.into();
@@ -2094,7 +2112,7 @@ impl LoginConfigHandler {
if self.get_toggle_option("disable-audio") {
msg.disable_audio = BoolOption::Yes.into();
}
if !view_only && self.get_toggle_option(config::keys::OPTION_ENABLE_FILE_COPY_PASTE) {
if !view_only && self.get_toggle_option(keys::OPTION_ENABLE_FILE_COPY_PASTE) {
msg.enable_file_transfer = BoolOption::Yes.into();
}
if view_only || self.get_toggle_option("disable-clipboard") {
@@ -2150,9 +2168,11 @@ impl LoginConfigHandler {
self.config.show_remote_cursor.v
} else if name == "lock-after-session-end" {
self.config.lock_after_session_end.v
} else if name == keys::OPTION_TERMINAL_PERSISTENT {
self.config.terminal_persistent.v
} else if name == "privacy-mode" {
self.config.privacy_mode.v
} else if name == config::keys::OPTION_ENABLE_FILE_COPY_PASTE {
} else if name == keys::OPTION_ENABLE_FILE_COPY_PASTE {
self.config.enable_file_copy_paste.v
} else if name == "disable-audio" {
self.config.disable_audio.v
@@ -2452,7 +2472,7 @@ impl LoginConfigHandler {
} else {
(my_id, self.id.clone())
};
let mut display_name = get_builtin_option(config::keys::OPTION_DISPLAY_NAME);
let mut display_name = get_builtin_option(keys::OPTION_DISPLAY_NAME);
if display_name.is_empty() {
display_name =
serde_json::from_str::<serde_json::Value>(&LocalConfig::get_option("user_info"))
@@ -2522,6 +2542,11 @@ impl LoginConfigHandler {
port: self.port_forward.1,
..Default::default()
}),
ConnType::TERMINAL => {
let mut terminal = Terminal::new();
terminal.service_id = self.get_option("terminal-service-id");
lr.set_terminal(terminal);
}
_ => {}
}
@@ -3237,8 +3262,7 @@ pub async fn handle_hash(
}
if password.is_empty() {
let p =
crate::ui_interface::get_builtin_option(config::keys::OPTION_DEFAULT_CONNECT_PASSWORD);
let p = crate::ui_interface::get_builtin_option(keys::OPTION_DEFAULT_CONNECT_PASSWORD);
if !p.is_empty() {
let mut hasher = Sha256::new();
hasher.update(p.clone());
@@ -3789,11 +3813,9 @@ pub mod peer_online {
}
// Retry for 2 times to get the online response
for _ in 0..2 {
if let Some(msg_in) = crate::get_next_nonkeyexchange_msg(
&mut socket,
Some(timeout.as_millis() as _),
)
.await
if let Some(msg_in) =
crate::get_next_nonkeyexchange_msg(&mut socket, Some(timeout.as_millis() as _))
.await
{
match msg_in.union {
Some(rendezvous_message::Union::OnlineResponse(online_response)) => {

View File

@@ -85,6 +85,7 @@ struct ParsedPeerInfo {
is_installed: bool,
idd_impl: String,
support_view_camera: bool,
support_terminal: bool,
}
impl ParsedPeerInfo {
@@ -131,10 +132,7 @@ impl<T: InvokeUiSession> Remote<T> {
#[cfg(target_os = "windows")]
let _file_clip_context_holder = {
// `is_port_forward()` will not reach here, but we still check it for clarity.
if !self.handler.is_file_transfer()
&& !self.handler.is_port_forward()
&& !self.handler.is_view_camera()
{
if self.handler.is_default() {
// It is ok to call this function multiple times.
ContextSend::enable(true);
Some(crate::SimpleCallOnReturn {
@@ -159,6 +157,8 @@ impl<T: InvokeUiSession> Remote<T> {
ConnType::FILE_TRANSFER
} else if self.handler.is_view_camera() {
ConnType::VIEW_CAMERA
} else if self.handler.is_terminal() {
ConnType::TERMINAL
} else {
ConnType::default()
};
@@ -195,11 +195,7 @@ impl<T: InvokeUiSession> Remote<T> {
let mut rx_clip_client_holder = (Arc::new(TokioMutex::new(rx)), None);
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
{
let is_conn_not_default = self.handler.is_file_transfer()
|| self.handler.is_port_forward()
|| self.handler.is_rdp()
|| self.handler.is_view_camera();
if !is_conn_not_default {
if self.handler.is_default() {
(self.client_conn_id, rx_clip_client_holder.0) =
clipboard::get_rx_cliprdr_client(&self.handler.get_id());
log::debug!("get cliprdr client for conn_id {}", self.client_conn_id);
@@ -338,12 +334,12 @@ impl<T: InvokeUiSession> Remote<T> {
.set_disconnected(round);
#[cfg(not(target_os = "ios"))]
if !self.handler.is_view_camera() && _set_disconnected_ok {
if self.handler.is_default() && _set_disconnected_ok {
Client::try_stop_clipboard();
}
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
if !self.handler.is_view_camera() && _set_disconnected_ok {
if self.handler.is_default() && _set_disconnected_ok {
crate::clipboard::try_empty_clipboard_files(ClipboardSide::Client, self.client_conn_id);
}
}
@@ -437,7 +433,10 @@ impl<T: InvokeUiSession> Remote<T> {
// Start a voice call recorder, records audio and send to remote
fn start_voice_call(&mut self) -> Option<std::sync::mpsc::Sender<()>> {
if self.handler.is_file_transfer() || self.handler.is_port_forward() {
if self.handler.is_file_transfer()
|| self.handler.is_port_forward()
|| self.handler.is_terminal()
{
return None;
}
// iOS does not have this server.
@@ -1230,6 +1229,24 @@ impl<T: InvokeUiSession> Remote<T> {
return false;
}
fn check_terminal_support(&self, peer_version: &str) -> bool {
if self.peer_info.support_terminal {
return true;
}
if hbb_common::get_version_number(&peer_version) < hbb_common::get_version_number("1.4.1") {
self.handler.msgbox(
"error",
"Remote terminal not supported",
"Remote terminal is not supported by the remote side. Please upgrade to version 1.4.1 or higher.",
"",
);
} else {
self.handler
.on_error("Remote terminal is not supported by the remote side");
}
return false;
}
async fn handle_msg_from_peer(&mut self, data: &[u8], peer: &mut Stream) -> bool {
if let Ok(msg_in) = Message::parse_from_bytes(&data) {
match msg_in.union {
@@ -1290,13 +1307,16 @@ impl<T: InvokeUiSession> Remote<T> {
return false;
}
}
if self.handler.is_terminal() {
if !self.check_terminal_support(&peer_version) {
self.handler.lc.write().unwrap().handle_peer_info(&pi);
return false;
}
}
self.handler.handle_peer_info(pi);
#[cfg(all(target_os = "windows", not(feature = "flutter")))]
self.check_clipboard_file_context();
if !(self.handler.is_file_transfer()
|| self.handler.is_port_forward()
|| self.handler.is_view_camera())
{
if self.handler.is_default() {
#[cfg(feature = "flutter")]
#[cfg(not(target_os = "ios"))]
let rx = Client::try_start_clipboard(None);
@@ -1661,9 +1681,6 @@ impl<T: InvokeUiSession> Remote<T> {
);
}
}
Ok(Permission::Camera) => {
self.handler.set_permission("camera", p.enabled);
}
Ok(Permission::Restart) => {
self.handler.set_permission("restart", p.enabled);
}
@@ -1923,6 +1940,18 @@ impl<T: InvokeUiSession> Remote<T> {
self.handler
.handle_screenshot_resp(response.sid, response.msg);
}
Some(message::Union::TerminalResponse(response)) => {
use hbb_common::message_proto::terminal_response::Union;
if let Some(Union::Opened(opened)) = &response.union {
if opened.success && !opened.service_id.is_empty() {
self.handler.lc.write().unwrap().set_option(
"terminal-service-id".to_owned(),
opened.service_id.clone(),
);
}
}
self.handler.handle_terminal_response(response);
}
_ => {}
}
}
@@ -1931,6 +1960,12 @@ impl<T: InvokeUiSession> Remote<T> {
fn set_peer_info(&mut self, pi: &PeerInfo) {
self.peer_info.platform = pi.platform.clone();
// Check features field for terminal support
if let Some(features) = pi.features.as_ref() {
self.peer_info.support_terminal = features.terminal;
}
if let Ok(platform_additions) =
serde_json::from_str::<HashMap<String, serde_json::Value>>(&pi.platform_additions)
{

View File

@@ -1105,6 +1105,60 @@ impl InvokeUiSession for FlutterHandler {
}
}
}
fn handle_terminal_response(&self, response: TerminalResponse) {
use hbb_common::message_proto::terminal_response::Union;
match response.union {
Some(Union::Opened(opened)) => {
let mut event_data: Vec<(&str, serde_json::Value)> = vec![
("type", json!("opened")),
("terminal_id", json!(opened.terminal_id)),
("success", json!(opened.success)),
("message", json!(&opened.message)),
("pid", json!(opened.pid)),
("service_id", json!(&opened.service_id)),
];
self.push_event_("terminal_response", &event_data, &[], &[]);
}
Some(Union::Data(data)) => {
// Decompress data if needed
let output_data = if data.compressed {
hbb_common::compress::decompress(&data.data)
} else {
data.data.to_vec()
};
let encoded = crate::encode64(&output_data);
let event_data: Vec<(&str, serde_json::Value)> = vec![
("type", json!("data")),
("terminal_id", json!(data.terminal_id)),
("data", json!(&encoded)),
];
self.push_event_("terminal_response", &event_data, &[], &[]);
}
Some(Union::Closed(closed)) => {
let event_data: Vec<(&str, serde_json::Value)> = vec![
("type", json!("closed")),
("terminal_id", json!(closed.terminal_id)),
("exit_code", json!(closed.exit_code)),
];
self.push_event_("terminal_response", &event_data, &[], &[]);
}
Some(Union::Error(error)) => {
let event_data: Vec<(&str, serde_json::Value)> = vec![
("type", json!("error")),
("terminal_id", json!(error.terminal_id)),
("message", json!(&error.message)),
];
self.push_event_("terminal_response", &event_data, &[], &[]);
}
None => {}
Some(_) => {
log::warn!("Unhandled terminal response type");
}
}
}
}
impl FlutterHandler {
@@ -1221,6 +1275,7 @@ pub fn session_add(
is_view_camera: bool,
is_port_forward: bool,
is_rdp: bool,
is_terminal: bool,
switch_uuid: &str,
force_relay: bool,
password: String,
@@ -1231,6 +1286,8 @@ pub fn session_add(
ConnType::FILE_TRANSFER
} else if is_view_camera {
ConnType::VIEW_CAMERA
} else if is_terminal {
ConnType::TERMINAL
} else if is_port_forward {
if is_rdp {
ConnType::RDP

View File

@@ -103,6 +103,8 @@ pub fn peer_get_sessions_count(id: String, conn_type: i32) -> SyncReturn<usize>
ConnType::PORT_FORWARD
} else if conn_type == ConnType::RDP as i32 {
ConnType::RDP
} else if conn_type == ConnType::TERMINAL as i32 {
ConnType::TERMINAL
} else {
ConnType::DEFAULT_CONN
};
@@ -129,6 +131,7 @@ pub fn session_add_sync(
is_view_camera: bool,
is_port_forward: bool,
is_rdp: bool,
is_terminal: bool,
switch_uuid: String,
force_relay: bool,
password: String,
@@ -142,6 +145,7 @@ pub fn session_add_sync(
is_view_camera,
is_port_forward,
is_rdp,
is_terminal,
&switch_uuid,
force_relay,
password,
@@ -613,6 +617,33 @@ pub fn session_send_chat(session_id: SessionID, text: String) {
}
}
// Terminal functions
pub fn session_open_terminal(session_id: SessionID, terminal_id: i32, rows: u32, cols: u32) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.open_terminal(terminal_id, rows, cols);
} else {
log::error!("[flutter_ffi] Session not found for session_id: {}", session_id);
}
}
pub fn session_send_terminal_input(session_id: SessionID, terminal_id: i32, data: String) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.send_terminal_input(terminal_id, data);
}
}
pub fn session_resize_terminal(session_id: SessionID, terminal_id: i32, rows: u32, cols: u32) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.resize_terminal(terminal_id, rows, cols);
}
}
pub fn session_close_terminal(session_id: SessionID, terminal_id: i32) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.close_terminal(terminal_id);
}
}
pub fn session_peer_option(session_id: SessionID, name: String, value: String) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.set_option(name, value);

View File

@@ -190,6 +190,7 @@ pub enum Data {
id: i32,
is_file_transfer: bool,
is_view_camera: bool,
is_terminal: bool,
peer_id: String,
name: String,
authorized: bool,

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "لا يوجد اذن نقل الملف"),
("Note", "ملاحظة"),
("Connection", "الاتصال"),
("Share Screen", "مشاركة الشاشة"),
("Share screen", "مشاركة الشاشة"),
("Chat", "محادثة"),
("Total", "الاجمالي"),
("items", "عناصر"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "لقط الشاشة"),
("Input Control", "تحكم الادخال"),
("Audio Capture", "لقط الصوت"),
("File Connection", "اتصال الملف"),
("Screen Connection", "اتصال الشاشة"),
("Do you accept?", "هل تقبل؟"),
("Open System Setting", "فتح اعدادات النظام"),
("How to get Android input permission?", "كيف تحصل على اذن الادخال في اندرويد؟"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "غير موسوم"),
("new-version-of-{}-tip", "تحديث جديد متاح لـ {}"),
("Accessible devices", "الأجهزة القابلة للوصول"),
("View camera", "عرض الكاميرا"),
("upgrade_remote_rustdesk_client_to_{}_tip", "ترقية عميل RustDesk البعيد إلى {}"),
("view_camera_unsupported_tip", "عرض الكاميرا غير مدعوم في هذا الجهاز"),
("Enable camera", "تمكين الكاميرا"),
("No cameras", "لا توجد كاميرات"),
("d3d_render_tip", "تمكين العرض باستخدام D3D"),
("Use D3D rendering", "استخدام عرض D3D"),
("Printer", "الطابعة"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", "كلمة مرور رقمية لمرة واحدة"),
("Enable IPv6 P2P connection", "تمكين اتصال نظير إلى نظير عبر IPv6"),
("Enable UDP hole punching", "تمكين تقنية حفر الثغرات عبر UDP"),
("View camera", "عرض الكاميرا"),
("Enable camera", "تمكين الكاميرا"),
("No cameras", "لا توجد كاميرات"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Няма дазволу на перадачу файлаў"),
("Note", "Нататка"),
("Connection", "Падключэнне"),
("Share Screen", "Дзяліцца экранам"),
("Share screen", "Дзяліцца экранам"),
("Chat", "Чат"),
("Total", "Усяго"),
("items", "элементы"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Захоп экрана"),
("Input Control", "Кіраванне ўводам"),
("Audio Capture", "Захоп аўдыё"),
("File Connection", "Падлучэнне перадачы файлаў"),
("Screen Connection", "Падлучэнне прагляду/кіравання экранам"),
("Do you accept?", "Ці вы згодны?"),
("Open System Setting", "Адкрыць налады сістэмы"),
("How to get Android input permission?", "Як атрымаць дазвол на ўвод Android?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Прагляд камеры"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Калі ласка, абнавіце кліент RustDesk да версіі {} або навейшай на аддаленым баку!"),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Прагляд камеры"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Няма разрешение за прехвърляне на файлове"),
("Note", "Бележка"),
("Connection", "Връзка"),
("Share Screen", "Сподели екран"),
("Share screen", "Сподели екран"),
("Chat", "Чат"),
("Total", "Общо"),
("items", "неща"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Заснемане на екрана"),
("Input Control", "Управление на въвеждане"),
("Audio Capture", "Аудиозапис"),
("File Connection", "Файлова връзка"),
("Screen Connection", "Екранна връзка"),
("Do you accept?", "Приемате ли?"),
("Open System Setting", "Отворете системните настройки"),
("How to get Android input permission?", "Как да получим право за въвеждане при Андроид?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Преглед на камерата"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Моля, надстройте клиента RustDesk до версия {} или по-нова от отдалечената страна!"),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Преглед на камерата"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Cap permís per a transferència de fitxers"),
("Note", "Nota"),
("Connection", "Connexió"),
("Share Screen", "Compartició de pantalla"),
("Share screen", "Compartició de pantalla"),
("Chat", "Xat"),
("Total", "Total"),
("items", "elements"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Captura de pantalla"),
("Input Control", "Control d'entrada"),
("Audio Capture", "Captura d'àudio"),
("File Connection", "Connexió de fitxer"),
("Screen Connection", "Connexió de pantalla"),
("Do you accept?", "Voleu acceptar?"),
("Open System Setting", "Obre la configuració del sistema"),
("How to get Android input permission?", "Com modificar els permisos a Android?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Sense etiquetar"),
("new-version-of-{}-tip", ""),
("Accessible devices", "Dispositius accessibles"),
("View camera", "Mostra la càmera"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Veuillez mettre à niveau le client RustDesk vers la version {} ou plus récente du côté distant !"),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Mostra la càmera"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "没有文件传输权限"),
("Note", "备注"),
("Connection", "连接"),
("Share Screen", "共享屏幕"),
("Share screen", "共享屏幕"),
("Chat", "聊天消息"),
("Total", "总计"),
("items", "个项目"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "屏幕录制"),
("Input Control", "输入控制"),
("Audio Capture", "音频录制"),
("File Connection", "文件连接"),
("Screen Connection", "屏幕连接"),
("Do you accept?", "是否接受?"),
("Open System Setting", "打开系统设置"),
("How to get Android input permission?", "如何获取安卓的输入权限?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "无标签"),
("new-version-of-{}-tip", "{} 版本更新"),
("Accessible devices", "可访问的设备"),
("View camera", "查看摄像头"),
("upgrade_remote_rustdesk_client_to_{}_tip", "请在远程端将 RustDesk 客户端升级至版本 {} 或更新版本!"),
("view_camera_unsupported_tip", "您的远程端不支持查看摄像头。"),
("Enable camera", "允许查看摄像头"),
("No cameras", "没有摄像头"),
("d3d_render_tip", "当启用 D3D 渲染时,某些机器可能无法显示远程画面。"),
("Use D3D rendering", "使用 D3D 渲染"),
("Printer", "打印机"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", "一次性密码为数字"),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "查看摄像头"),
("Enable camera", "允许查看摄像头"),
("No cameras", "没有摄像头"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Žádné oprávnění k přenosu souborů"),
("Note", "Poznámka"),
("Connection", "Připojení"),
("Share Screen", "Sdílet obrazovku"),
("Share screen", "Sdílet obrazovku"),
("Chat", "Chat"),
("Total", "Celkem"),
("items", "Položek"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Zachytávání obrazovky"),
("Input Control", "Ovládání vstupních zařízení"),
("Audio Capture", "Zachytávání zvuku"),
("File Connection", "Souborové spojení"),
("Screen Connection", "Spojení obrazovky"),
("Do you accept?", "Přijímáte?"),
("Open System Setting", "Otevřít nastavení systému"),
("How to get Android input permission?", "Jak v systému Android získat oprávnění pro vstupní zařízení?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Zobrazit kameru"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Upgradujte prosím klienta RustDesk na verzi {} nebo novější na vzdálené straně!"),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Zobrazit kameru"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Ingen tilladelse til at overføre filen"),
("Note", "Note"),
("Connection", "Forbindelse"),
("Share Screen", "Del skærmen"),
("Share screen", "Del skærmen"),
("Chat", "Chat"),
("Total", "Total"),
("items", "artikel"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Skærmoptagelse"),
("Input Control", "Inputkontrol"),
("Audio Capture", "Lydoptagelse"),
("File Connection", "Filforbindelse"),
("Screen Connection", "Færdiggørelse"),
("Do you accept?", "Accepterer du?"),
("Open System Setting", "Åbn systemindstillingen"),
("How to get Android input permission?", "Hvordan får jeg en Android-input tilladelse?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Se kamera"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Opgrader venligst RustDesk-klienten til version {} eller nyere på fjernsiden!"),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Se kamera"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Keine Berechtigung für die Dateiübertragung"),
("Note", "Hinweis"),
("Connection", "Verbindung"),
("Share Screen", "Bildschirm freigeben"),
("Share screen", "Bildschirm freigeben"),
("Chat", "Chat"),
("Total", "Gesamt"),
("items", "Einträge"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Bildschirmaufnahme"),
("Input Control", "Eingabesteuerung"),
("Audio Capture", "Audioaufnahme"),
("File Connection", "Dateiverbindung"),
("Screen Connection", "Bildschirmverbindung"),
("Do you accept?", "Verbindung zulassen?"),
("Open System Setting", "Systemeinstellung öffnen"),
("How to get Android input permission?", "Wie erhalte ich eine Android-Eingabeberechtigung?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Unmarkiert"),
("new-version-of-{}-tip", "Es ist eine neue Version von {} verfügbar"),
("Accessible devices", "Erreichbare Geräte"),
("View camera", "Kamera anzeigen"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Bitte aktualisieren Sie den RustDesk-Client auf der Remote-Seite auf Version {} oder neuer!"),
("view_camera_unsupported_tip", "Das entfernte Gerät kann die Kamera nicht anzeigen."),
("Enable camera", "Kamera zulassen"),
("No cameras", "Keine Kameras"),
("d3d_render_tip", "Wenn das D3D-Rendering aktiviert ist, kann der entfernte Bildschirm auf manchen Rechnern schwarz sein."),
("Use D3D rendering", "D3D-Rendering verwenden"),
("Printer", "Drucker"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", "Numerisches Einmalpasswort"),
("Enable IPv6 P2P connection", "IPv6-P2P-Verbindung aktivieren"),
("Enable UDP hole punching", "UDP-Hole-Punching aktivieren"),
("View camera", "Kamera anzeigen"),
("Enable camera", "Kamera zulassen"),
("No cameras", "Keine Kameras"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Δεν υπάρχει άδεια για μεταφορά αρχείων"),
("Note", "Σημείωση"),
("Connection", "Σύνδεση"),
("Share Screen", "Κοινή χρήση οθόνης"),
("Share screen", "Κοινή χρήση οθόνης"),
("Chat", "Κουβέντα"),
("Total", "Σύνολο"),
("items", "στοιχεία"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Αποτύπωση οθόνης"),
("Input Control", "Έλεγχος εισόδου"),
("Audio Capture", "Εγγραφή ήχου"),
("File Connection", "Σύνδεση αρχείου"),
("Screen Connection", "Σύνδεση οθόνης"),
("Do you accept?", "Δέχεσαι;"),
("Open System Setting", "Άνοιγμα ρυθμίσεων συστήματος"),
("How to get Android input permission?", "Πώς να αποκτήσω άδεια εισαγωγής Android;"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Χωρίς ετικέτα"),
("new-version-of-{}-tip", "Υπάρχει διαθέσιμη νέα έκδοση του {}"),
("Accessible devices", "Προσβάσιμες συσκευές"),
("View camera", "Προβολή κάμερας"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Αναβαθμίστε τον πελάτη RustDesk στην έκδοση {} ή νεότερη στην απομακρυσμένη πλευρά!"),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Προβολή κάμερας"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -77,12 +77,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Canvas Move", "Canvas move"),
("Pinch to Zoom", "Pinch to zoom"),
("Canvas Zoom", "Canvas zoom"),
("Share Screen", "Share screen"),
("Screen Capture", "Screen capture"),
("Input Control", "Input control"),
("Audio Capture", "Audio capture"),
("File Connection", "File connection"),
("Screen Connection", "Screen connection"),
("Open System Setting", "Open system setting"),
("android_input_permission_tip1", "In order for a remote device to control your Android device via mouse or touch, you need to allow RustDesk to use the \"Accessibility\" service."),
("android_input_permission_tip2", "Please go to the next system settings page, find and enter [Installed Services], turn on [RustDesk Input] service."),

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Neniu permeso de dosiertransigo"),
("Note", "Notu"),
("Connection", "Konekto"),
("Share Screen", "Kunhavigi Ekranon"),
("Share screen", "Kunhavigi Ekranon"),
("Chat", "Babilo"),
("Total", "Sumo"),
("items", "eroj"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Ekrankapto"),
("Input Control", "Eniga Kontrolo"),
("Audio Capture", "Sonkontrolo"),
("File Connection", "Dosiero Konekto"),
("Screen Connection", "Ekrono konekto"),
("Do you accept?", "Ĉu vi akceptas?"),
("Open System Setting", "Malfermi Sistemajn Agordojn"),
("How to get Android input permission?", "Kiel akiri Android enigajn permesojn"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Rigardi kameron"),
("upgrade_remote_rustdesk_client_to_{}_tip", ""),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Rigardi kameron"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Sin permiso de transferencia de archivos"),
("Note", "Nota"),
("Connection", "Conexión"),
("Share Screen", "Compartir pantalla"),
("Share screen", "Compartir pantalla"),
("Chat", "Chat"),
("Total", "Total"),
("items", "items"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Captura de pantalla"),
("Input Control", "Control de entrada"),
("Audio Capture", "Captura de audio"),
("File Connection", "Conexión de archivos"),
("Screen Connection", "Conexión de pantalla"),
("Do you accept?", "¿Aceptas?"),
("Open System Setting", "Configuración del sistema abierto"),
("How to get Android input permission?", "¿Cómo obtener el permiso de entrada de Android?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Sin itiquetar"),
("new-version-of-{}-tip", "Hay una nueva versión de {} disponible"),
("Accessible devices", ""),
("View camera", "Ver cámara"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Por favor, actualiza el cliente RustDesk a la versión {} o superior en el lado remoto"),
("view_camera_unsupported_tip", "El dispositivo remoto no soporta la visualización de la cámara."),
("Enable camera", "Habilitar cámara"),
("No cameras", "No hay cámaras"),
("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"),
("Printer", "Impresora"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Ver cámara"),
("Enable camera", "Habilitar cámara"),
("No cameras", "No hay cámaras"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Failiülekande luba puudub"),
("Note", "Märkus"),
("Connection", "Ühendus"),
("Share Screen", "Jaga ekraani"),
("Share screen", "Jaga ekraani"),
("Chat", "Vestlus"),
("Total", "Kokku"),
("items", "üksust"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Ekraanisalvestus"),
("Input Control", "Sisendjuhtimine"),
("Audio Capture", "Helisalvestus"),
("File Connection", "Failiühendus"),
("Screen Connection", "Kuvaühendus"),
("Do you accept?", "Kas nõustud?"),
("Open System Setting", "Ava süsteemisätted"),
("How to get Android input permission?", "Kuidas saada Androidi sisendi luba?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Sildistamata"),
("new-version-of-{}-tip", "Saadaval on {} uus versioon"),
("Accessible devices", "Ligipääsetavad seadmed"),
("View camera", "Vaata kaamerat"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Täiendage RustDeski klient kaugküljel versioonile {} või uuemale!"),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Vaata kaamerat"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Ez duzu baimenik fitxategiak transferitzeko"),
("Note", "Nota"),
("Connection", "Konexioa"),
("Share Screen", "Partekatu pantaila"),
("Share screen", "Partekatu pantaila"),
("Chat", "Txata"),
("Total", "Guztira"),
("items", "elementuak"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Pantaila-grabazioa"),
("Input Control", "Sarrera-kontrola"),
("Audio Capture", "Audio-grabazioa"),
("File Connection", "Fitxategi-konexioa"),
("Screen Connection", "Pantaila-konexioa"),
("Do you accept?", "Onartzen al duzu?"),
("Open System Setting", "Ireki sistemaren ezarpenak"),
("How to get Android input permission?", "Nola lortu dezaket Android sarrera-baimena?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Ikusi kamera"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Mesedez, eguneratu RustDesk bezeroa {} bertsiora edo berriagoa urruneko aldean!"),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Ikusi kamera"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "مجوز انتقال فایل داده نشده"),
("Note", "یادداشت"),
("Connection", "ارتباط"),
("Share Screen", "اشتراک گذاری صفحه"),
("Share screen", "اشتراک گذاری صفحه"),
("Chat", "چت"),
("Total", "مجموع"),
("items", "آیتم ها"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "ضبط صفحه"),
("Input Control", "کنترل ورودی"),
("Audio Capture", "ضبط صدا"),
("File Connection", "ارتباط فایل"),
("Screen Connection", "ارتباط صفحه"),
("Do you accept?", "آیا می پذیرید؟"),
("Open System Setting", "باز کردن تنظیمات سیستم"),
("How to get Android input permission?", "چگونه مجوز ورود به سیستم اندروید را دریافت کنیم؟"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "بدون برچسب"),
("new-version-of-{}-tip", "نسخه جدید {} در دسترس است"),
("Accessible devices", "دستگاه‌های در دسترس"),
("View camera", "نمایش دوربین"),
("upgrade_remote_rustdesk_client_to_{}_tip", "لطفاً RustDesk را به نسخه {} یا جدیدتر در سمت راه دور ارتقا دهید"),
("view_camera_unsupported_tip", "دوربین در این دستگاه پشتیبانی نمی‌شود"),
("Enable camera", "فعال کردن دوربین"),
("No cameras", "هیچ دوربینی یافت نشد"),
("d3d_render_tip", "فعال کردن رندر D3D برای عملکرد بهتر"),
("Use D3D rendering", "استفاده از رندر D3D"),
("Printer", "چاپگر"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", "رمز عبور یک‌بار مصرف عددی"),
("Enable IPv6 P2P connection", "فعال‌سازی اتصال همتا‌به‌همتای IPv6"),
("Enable UDP hole punching", "فعال‌سازی تکنیک UDP hole punching"),
("View camera", "نمایش دوربین"),
("Enable camera", "فعال کردن دوربین"),
("No cameras", "هیچ دوربینی یافت نشد"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Absence de lautorisation de transfert de fichiers"),
("Note", "Note"),
("Connection", "Connexion"),
("Share Screen", "Partage décran"),
("Share screen", "Partage décran"),
("Chat", "Discussion"),
("Total", "Total"),
("items", "éléments"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Capture de lécran"),
("Input Control", "Contrôle de la saisie"),
("Audio Capture", "Capture de laudio"),
("File Connection", "Connexion aux fichiers"),
("Screen Connection", "Connexion à lécran"),
("Do you accept?", "Acceptez-vous ?"),
("Open System Setting", "Ouvrir les paramètres système"),
("How to get Android input permission?", "Comment obtenir lautorisation de contrôle de la saisie sur Android ?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Sans étiquette"),
("new-version-of-{}-tip", "Une nouvelle version de {} est disponible"),
("Accessible devices", "Appareils accessibles"),
("View camera", "Afficher la caméra"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Veuillez mettre le client RustDesk distant à jour vers la version {} ou ultérieure !"),
("view_camera_unsupported_tip", "Lappareil distant ne prend pas en charge laffichage de la caméra."),
("Enable camera", "Activer la caméra"),
("No cameras", "Aucune caméra"),
("d3d_render_tip", "Sur certaines machines, lécran du contrôle à distance peut rester noir lors de lutilisation du rendu D3D."),
("Use D3D rendering", "Utiliser le rendu D3D"),
("Printer", "Imprimante"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", "Mot de passe à usage unique numérique"),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Afficher la caméra"),
("Enable camera", "Activer la caméra"),
("No cameras", "Aucune caméra"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "ფაილების გადაცემის უფლება არ არის"),
("Note", "შენიშვნა"),
("Connection", "კავშირი"),
("Share Screen", "ეკრანის დემონსტრაცია"),
("Share screen", "ეკრანის დემონსტრაცია"),
("Chat", "ჩატი"),
("Total", "სულ"),
("items", "ელემენტები"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "ეკრანის ჩაწერა"),
("Input Control", "შეყვანის კონტროლი"),
("Audio Capture", "აუდიოს ჩაწერა"),
("File Connection", "ფაილების გადაცემის დაკავშირება"),
("Screen Connection", "ეკრანის ნახვის/მართვის დაკავშირება"),
("Do you accept?", "თანახმა ხართ?"),
("Open System Setting", "სისტემის პარამეტრების გახსნა"),
("How to get Android input permission?", "როგორ მივიღოთ Android-ის შეყვანის უფლება?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "უტეგო"),
("new-version-of-{}-tip", "ხელმისაწვდომია ახალი ვერსია {}"),
("Accessible devices", "ხელმისაწვდომი მოწყობილობები"),
("View camera", "კამერის ნახვა"),
("upgrade_remote_rustdesk_client_to_{}_tip", "განაახლეთ RustDesk კლიენტი ვერსიამდე {} ან უფრო ახალი დისტანციურ მხარეზე!"),
("view_camera_unsupported_tip", "დისტანციური მოწყობილობა არ უჭერს მხარს კამერის ნახვას."),
("Enable camera", "კამერის ჩართვა"),
("No cameras", "კამერა არ არის"),
("d3d_render_tip", "D3D ვიზუალიზაციის ჩართვისას ზოგიერთ მოწყობილობაზე დისტანციური ეკრანი შეიძლება იყოს შავი."),
("Use D3D rendering", "D3D ვიზუალიზაციის გამოყენება"),
("Printer", "პრინტერი"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "კამერის ნახვა"),
("Enable camera", "კამერის ჩართვა"),
("No cameras", "კამერა არ არის"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "אין הרשאת העברת קבצים"),
("Note", "הערה"),
("Connection", "התחברות"),
("Share Screen", "שיתוף מסך"),
("Share screen", "שיתוף מסך"),
("Chat", "צ'אט"),
("Total", "הכל"),
("items", "פריטים"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "לכידת מסך"),
("Input Control", "בקרת קלט"),
("Audio Capture", "לכידת שמע"),
("File Connection", "חיבור להעברת קבצים"),
("Screen Connection", "חיבור תצוגה"),
("Do you accept?", "האם אתה מקבל?"),
("Open System Setting", "פתח הגדרות מערכת"),
("How to get Android input permission?", "כיצד לקבל הרשאת קלט באנדרואיד?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "לא מתוייג"),
("new-version-of-{}-tip", "גרסה חדשה של {} זמינה"),
("Accessible devices", "מכשירים נגישים"),
("View camera", "הצג מצלמה"),
("upgrade_remote_rustdesk_client_to_{}_tip", "אנא שדרג את לקוח RustDesk לגרסה {} או חדשה יותר בצד המרוחק!"),
("view_camera_unsupported_tip", "הצגת מצלמה אינה נתמכת במכשיר המרוחק"),
("Enable camera", "הפעל מצלמה"),
("No cameras", "אין מצלמות"),
("d3d_render_tip", "שימוש בעיבוד Direct3D עשוי לשפר ביצועים בחלק מהמקרים"),
("Use D3D rendering", "השתמש בעיבוד D3D"),
("Printer", "מדפסת"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "הצג מצלמה"),
("Enable camera", "הפעל מצלמה"),
("No cameras", "אין מצלמות"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Nemate pravo prijenosa datoteka"),
("Note", "Bilješka"),
("Connection", "Povezivanje"),
("Share Screen", "Podijeli zaslon"),
("Share screen", "Podijeli zaslon"),
("Chat", "Dopisivanje"),
("Total", "Ukupno"),
("items", "stavki"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Snimanje zaslona"),
("Input Control", "Kontrola unosa"),
("Audio Capture", "Snimanje zvuka"),
("File Connection", "Spajanje preko datoteke"),
("Screen Connection", "Podijelite vezu"),
("Do you accept?", "Prihvaćate li?"),
("Open System Setting", "Postavke otvorenog sustava"),
("How to get Android input permission?", "Kako dobiti pristup za unos na Androidu?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Pregled kamere"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Molimo ažurirajte RustDesk klijent na verziju {} ili noviju na udaljenoj strani!"),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Pregled kamere"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Nincs engedély a fájlátvitelre"),
("Note", "Megjegyzés"),
("Connection", "Kapcsolat"),
("Share Screen", "Képernyőmegosztás"),
("Share screen", "Képernyőmegosztás"),
("Chat", "Csevegés"),
("Total", "Összes"),
("items", "elemek"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Képernyőrögzítés"),
("Input Control", "Távoli vezérlés"),
("Audio Capture", "Hangrögzítés"),
("File Connection", "Fájlátvitel"),
("Screen Connection", "Képátvitel"),
("Do you accept?", "Elfogadás?"),
("Open System Setting", "Rendszerbeállítások megnyitása"),
("How to get Android input permission?", "Hogyan állítható be az Androidos beviteli engedély?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Címkézetlen"),
("new-version-of-{}-tip", "A(z) {} új verziója"),
("Accessible devices", "Hozzáférhető eszközök"),
("View camera", "Kamera megtekintése"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Frissítse a RustDesk klienst {} vagy újabb verziójára a távoli oldalon!"),
("view_camera_unsupported_tip", "A kameranézet nem támogatott"),
("Enable camera", "Kamera engedélyezése"),
("No cameras", "Nincs kamera"),
("d3d_render_tip", "D3D renderelés"),
("Use D3D rendering", "D3D renderelés használata"),
("Printer", "Nyomtató"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Kamera megtekintése"),
("Enable camera", "Kamera engedélyezése"),
("No cameras", "Nincs kamera"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Tidak ada izin untuk mengirim file"),
("Note", "Catatan"),
("Connection", "Koneksi"),
("Share Screen", "Bagikan Layar"),
("Share screen", "Bagikan Layar"),
("Chat", "Obrolan"),
("Total", "Total"),
("items", "item"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Tangkapan Layar"),
("Input Control", "Kontrol input"),
("Audio Capture", "Rekam Suara"),
("File Connection", "Koneksi File"),
("Screen Connection", "Koneksi layar"),
("Do you accept?", "Apakah anda setuju?"),
("Open System Setting", "Buka Pengaturan Sistem"),
("How to get Android input permission?", "Bagaimana cara mendapatkan izin input dari Android?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", "Versi {} sudah tersedia."),
("Accessible devices", "Perangkat yang tersedia"),
("View camera", "Lihat Kamera"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Silahkan perbarui aplikasi RustDesk ke versi {} atau yang lebih baru pada komputer yang akan terhubung!"),
("view_camera_unsupported_tip", "Perangkat yang terhubung tidak mendukung tampilan kamera."),
("Enable camera", "Aktifkan kamera"),
("No cameras", "Tidak ada kamera"),
("d3d_render_tip", "Ketika rendering D3D diaktifkan, layar kontrol jarak jauh bisa tampak hitam di beberapa komputer"),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Lihat Kamera"),
("Enable camera", "Aktifkan kamera"),
("No cameras", "Tidak ada kamera"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Nessun permesso per il trasferimento file"),
("Note", "Nota"),
("Connection", "Connessione"),
("Share Screen", "Condividi schermo"),
("Share screen", "Condividi schermo"),
("Chat", "Chat"),
("Total", "Totale"),
("items", "Oggetti"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Cattura schermo"),
("Input Control", "Controllo input"),
("Audio Capture", "Acquisizione audio"),
("File Connection", "Connessione file"),
("Screen Connection", "Connessione schermo"),
("Do you accept?", "Accetti?"),
("Open System Setting", "Apri impostazioni di sistema"),
("How to get Android input permission?", "Come ottenere l'autorizzazione input in Android?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Senza tag"),
("new-version-of-{}-tip", "È disponibile una nuova versione di {}"),
("Accessible devices", "Dispositivi accessibili"),
("View camera", "Visualizza telecamera"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Aggiorna il client RustDesk remoto alla versione {} o successiva!"),
("view_camera_unsupported_tip", "Il dispositivo remoto non supporta la visualizzazione della camera."),
("Enable camera", "Abilita camera"),
("No cameras", "Nessuna camera"),
("d3d_render_tip", "Quando è abilitato il rendering D3D, in alcuni computer la schermata del telecomando potrebbe essere nera."),
("Use D3D rendering", "Usa rendering D3D"),
("Printer", "Stampante"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", "Password numerica monouso"),
("Enable IPv6 P2P connection", "Abilita connessione P2P IPv6"),
("Enable UDP hole punching", "Abilita hole punching UDP"),
("View camera", "Visualizza telecamera"),
("Enable camera", "Abilita camera"),
("No cameras", "Nessuna camera"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "ファイル転送の権限がありません"),
("Note", "ノート"),
("Connection", "接続"),
("Share Screen", "画面を共有"),
("Share screen", "画面を共有"),
("Chat", "チャット"),
("Total", ""),
("items", "個のアイテム"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "画面キャプチャ"),
("Input Control", "入力操作"),
("Audio Capture", "音声キャプチャ"),
("File Connection", "ファイルの接続"),
("Screen Connection", "画面の接続"),
("Do you accept?", "許可しますか?"),
("Open System Setting", "システム設定を開く"),
("How to get Android input permission?", "Androidの入力権限を取得するには"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "カメラを表示"),
("upgrade_remote_rustdesk_client_to_{}_tip", "リモート側のRustDeskクライアントをバージョン{}以上にアップグレードしてください!"),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "カメラを表示"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "파일 전송 권한이 없습니다."),
("Note", "메모"),
("Connection", "연결"),
("Share Screen", "화면 공유"),
("Share screen", "화면 공유"),
("Chat", "채팅"),
("Total", ""),
("items", ""),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "화면 캡처"),
("Input Control", "입력 제어"),
("Audio Capture", "오디오 캡처"),
("File Connection", "파일 전송"),
("Screen Connection", "화면 전송"),
("Do you accept?", "수락하시겠습니까?"),
("Open System Setting", "시스템 설정 열기"),
("How to get Android input permission?", "Android 입력 권한을 얻는 방법"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "태그 없음"),
("new-version-of-{}-tip", "{}의 새 버전이 출시되었습니다."),
("Accessible devices", "연결 가능한 기기"),
("View camera", "카메라 보기"),
("upgrade_remote_rustdesk_client_to_{}_tip", "원격 기기의 RustDesk 클라이언트를 {} 버전 이상으로 업그레이드하십시오!"),
("view_camera_unsupported_tip", "원격 기기에서 카메라 보기를 지원하지 않습니다."),
("Enable camera", "카메라 보기 허용"),
("No cameras", "카메라 없음"),
("d3d_render_tip", "D3D 렌더링을 활성화하면 일부 기기에서 원격 화면이 표시되지 않을 수 있습니다."),
("Use D3D rendering", "D3D 렌더링 활성화"),
("Printer", "프린터"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", "일회용 비밀번호"),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "카메라 보기"),
("Enable camera", "카메라 보기 허용"),
("No cameras", "카메라 없음"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Файыл алмасуға рұқсат берілмеген"),
("Note", "Нота"),
("Connection", "Қосылым"),
("Share Screen", "Екіренді Бөлісу"),
("Share screen", "Екіренді Бөлісу"),
("Chat", "Чат"),
("Total", "Барлығы"),
("items", "зат"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Екіренді Түсіру"),
("Input Control", "Еңгізуді Басқару/Қадағалау"),
("Audio Capture", "Аудио Түсіру"),
("File Connection", "Файыл Қосылымы"),
("Screen Connection", "Екірен Қосылымы"),
("Do you accept?", "Қабылдайсыз ба?"),
("Open System Setting", "Жүйе Орнатпаларын Ашу"),
("How to get Android input permission?", "Android еңгізу рұқсатын қалай алуға болады?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Камераны Көру"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Қашықтағы жақтағы RustDesk клиентін {} немесе одан жоғары нұсқаға жаңартуды өтінеміз!"),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Камераны Көру"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Nėra leidimo perkelti failus"),
("Note", "Pastaba"),
("Connection", "Ryšys"),
("Share Screen", "Bendrinti ekraną"),
("Share screen", "Bendrinti ekraną"),
("Chat", "Pokalbis"),
("Total", "Iš viso"),
("items", "elementai"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Ekrano nuotrauka"),
("Input Control", "Įvesties valdymas"),
("Audio Capture", "Garso fiksavimas"),
("File Connection", "Failo ryšys"),
("Screen Connection", "Ekrano jungtis"),
("Do you accept?", "Ar sutinki?"),
("Open System Setting", "Atviros sistemos nustatymas"),
("How to get Android input permission?", "Kaip gauti Android įvesties leidimą?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Peržiūrėti kamerą"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Prašome atnaujinti nuotolinės pusės RustDesk klientą į {} ar naujesnę versiją!"),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Peržiūrėti kamerą"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Nav atļaujas failu pārsūtīšanai"),
("Note", "Piezīme"),
("Connection", "Savienojums"),
("Share Screen", "Koplietot ekrānu"),
("Share screen", "Koplietot ekrānu"),
("Chat", "Tērzēšana"),
("Total", "Kopā"),
("items", "vienumi"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Ekrāna tveršana"),
("Input Control", "Ievades vadība"),
("Audio Capture", "Audio tveršana"),
("File Connection", "Failu savienojums"),
("Screen Connection", "Ekrāna savienojums"),
("Do you accept?", "Vai Jūs pieņemat?"),
("Open System Setting", "Atvērt sistēmas iestatījumus"),
("How to get Android input permission?", "Kā iegūt Android ievades atļauju?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Neatzīmēts"),
("new-version-of-{}-tip", "Ir pieejama jauna {} versija"),
("Accessible devices", "Pieejamas ierīces"),
("View camera", "Skatīt kameru"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Lūdzu, jauniniet attālās puses RustDesk klientu uz versiju {} vai jaunāku!"),
("view_camera_unsupported_tip", "Attālā ierīce neatbalsta kameras skatīšanos."),
("Enable camera", "Iespējot kameru"),
("No cameras", "Nav kameru"),
("d3d_render_tip", "Ja ir iespējota D3D renderēšana, dažās ierīcēs tālvadības pults ekrāns var būt melns."),
("Use D3D rendering", "Izmantot D3D renderēšanu"),
("Printer", "Printeris"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", "Vienreiz lietojama ciparu parole"),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Skatīt kameru"),
("Enable camera", "Iespējot kameru"),
("No cameras", "Nav kameru"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Ingen tillatelse til å overføre filen"),
("Note", "Notat"),
("Connection", "Tilkobling"),
("Share Screen", "Del skjermen"),
("Share screen", "Del skjermen"),
("Chat", "Chat"),
("Total", "Total"),
("items", "Objekter"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Skjermopptak"),
("Input Control", "Input kontroll"),
("Audio Capture", "Lydopptak"),
("File Connection", "Filtilkobling"),
("Screen Connection", "Skjermtilkobing"),
("Do you accept?", "Akepterer du?"),
("Open System Setting", "Åpne systeminnstillinger"),
("How to get Android input permission?", "Hvordan får jeg en Android-input tillatelse?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Vis kamera"),
("upgrade_remote_rustdesk_client_to_{}_tip", ""),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Vis kamera"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Geen toestemming voor bestandsoverdracht"),
("Note", "Opmerking"),
("Connection", "Verbinding"),
("Share Screen", "Scherm Delen"),
("Share screen", "Scherm Delen"),
("Chat", "Chat"),
("Total", "Totaal"),
("items", "items"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Schermopname"),
("Input Control", "Invoercontrole"),
("Audio Capture", "Audio Opnemen"),
("File Connection", "Bestandsverbinding"),
("Screen Connection", "Schermverbinding"),
("Do you accept?", "Geeft u toestemming?"),
("Open System Setting", "Systeeminstelling Openen"),
("How to get Android input permission?", "Hoe krijg ik Android invoer toestemming?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Ongemarkeerd"),
("new-version-of-{}-tip", "Er is een nieuwe versie van {} beschikbaar"),
("Accessible devices", "Toegankelijke apparaten"),
("View camera", "Camera bekijken"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Upgrade de RustDesk client naar versie {} of nieuwer op de externe computer!"),
("view_camera_unsupported_tip", "Het externe apparaat ondersteunt geen cameraweergave."),
("Enable camera", "Camera inschakelen"),
("No cameras", "Geen camera's"),
("d3d_render_tip", "Wanneer D3D-rendering is ingeschakeld kan het externe scherm op sommige apparaten, zwart zijn."),
("Use D3D rendering", "Gebruik D3D-rendering"),
("Printer", "Printer"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", "Eenmalig numeriek wachtwoord"),
("Enable IPv6 P2P connection", "IPv6 P2P-verbinding inschakelen"),
("Enable UDP hole punching", "UDP-hole punching inschakelen"),
("View camera", "Camera bekijken"),
("Enable camera", "Camera inschakelen"),
("No cameras", "Geen camera's"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Brak uprawnień na przesyłanie plików"),
("Note", "Notatka"),
("Connection", "Połączenie"),
("Share Screen", "Udostępnij ekran"),
("Share screen", "Udostępnij ekran"),
("Chat", "Czat"),
("Total", "Łącznie"),
("items", "elementów"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Przechwytywanie ekranu"),
("Input Control", "Kontrola wejścia"),
("Audio Capture", "Przechwytywanie dźwięku"),
("File Connection", "Przekazywanie plików"),
("Screen Connection", "Przekazywanie ekranu"),
("Do you accept?", "Akceptujesz?"),
("Open System Setting", "Otwórz ustawienia systemowe"),
("How to get Android input permission?", "Jak uzyskać uprawnienia do wprowadzania danych w systemie Android?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Bez etykiety"),
("new-version-of-{}-tip", "Dostępna jest nowa wersja {}"),
("Accessible devices", "Dostępne urządzenia"),
("View camera", "Podgląd kamery"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Proszę zaktualizować zdalny klient RustDesk do wersji {} lub nowszej!"),
("view_camera_unsupported_tip", "Zdalne urządzenie nie obsługuje podglądu kamery."),
("Enable camera", "Włącz kamerę"),
("No cameras", "Brak kamer"),
("d3d_render_tip", "Kiedy włączenie renderowania D3D jest włączone, ekran zdalnej kontroli może być czarny w niektórych przypadkach"),
("Use D3D rendering", "Użyj renderowania D3D"),
("Printer", "Drukarka"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", "Jednorazowe hasło cyfrowe"),
("Enable IPv6 P2P connection", "Włącz połączenie P2P IPv6"),
("Enable UDP hole punching", "Włącz tworzenie tunelu UDP"),
("View camera", "Podgląd kamery"),
("Enable camera", "Włącz kamerę"),
("No cameras", "Brak kamer"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Sem permissões de transferência de ficheiro"),
("Note", "Nota"),
("Connection", "Ligação"),
("Share Screen", "Partilhar ecrã"),
("Share screen", "Partilhar ecrã"),
("Chat", "Conversar"),
("Total", "Total"),
("items", "itens"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Captura de Ecran"),
("Input Control", "Controle de Entrada"),
("Audio Capture", "Captura de Áudio"),
("File Connection", "Ligação de Arquivo"),
("Screen Connection", "Ligação de Ecran"),
("Do you accept?", "Aceita?"),
("Open System Setting", "Abrir Configurações do Sistema"),
("How to get Android input permission?", "Como activar a permissão de entrada do Android?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Ver câmara"),
("upgrade_remote_rustdesk_client_to_{}_tip", ""),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Ver câmara"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Sem permissão para transferência de arquivo"),
("Note", "Nota"),
("Connection", "Conexão"),
("Share Screen", "Compartilhar Tela"),
("Share screen", "Compartilhar Tela"),
("Chat", "Chat"),
("Total", "Total"),
("items", "itens"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Captura de Tela"),
("Input Control", "Controle de Entrada"),
("Audio Capture", "Captura de Áudio"),
("File Connection", "Conexão de Arquivo"),
("Screen Connection", "Conexão de Tela"),
("Do you accept?", "Você aceita?"),
("Open System Setting", "Abrir Configurações do Sistema"),
("How to get Android input permission?", "Como habilitar a permissão de entrada do Android?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Sem etiqueta"),
("new-version-of-{}-tip", "Uma nova versão de {} está disponível"),
("Accessible devices", "Dispositivos acessíveis"),
("View camera", "Visualizar câmera"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Atualize o cliente RustDesk para a versão {} ou superior no lado remoto."),
("view_camera_unsupported_tip", "O dispositivo remoto não suporta visualização da câmera."),
("Enable camera", "Ativar câmera"),
("No cameras", "Sem câmeras"),
("d3d_render_tip", "Em algumas máquinas, a tela do controle remoto pode ficar preta ao usar a renderização D3D."),
("Use D3D rendering", "Usar renderização D3D"),
("Printer", "Impressora"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Visualizar câmera"),
("Enable camera", "Ativar câmera"),
("No cameras", "Sem câmeras"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Nicio permisiune pentru transferul de fișiere"),
("Note", "Reține"),
("Connection", "Conexiune"),
("Share Screen", "Partajează ecran"),
("Share screen", "Partajează ecran"),
("Chat", "Mesaje"),
("Total", "Total"),
("items", "elemente"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Capturare ecran"),
("Input Control", "Control intrări"),
("Audio Capture", "Capturare audio"),
("File Connection", "Conexiune fișier"),
("Screen Connection", "Conexiune ecran"),
("Do you accept?", "Accepți?"),
("Open System Setting", "Deschide setări sistem"),
("How to get Android input permission?", "Cum autorizez dispozitive de intrare pe Android?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Vezi camera"),
("upgrade_remote_rustdesk_client_to_{}_tip", ""),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Vezi camera"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Нет разрешения на передачу файлов"),
("Note", "Заметка"),
("Connection", "Подключение"),
("Share Screen", "Демонстрация экрана"),
("Share screen", "Демонстрация экрана"),
("Chat", "Чат"),
("Total", "Всего"),
("items", "элементы"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Захват экрана"),
("Input Control", "Управление вводом"),
("Audio Capture", "Захват аудио"),
("File Connection", "Подключение передачи файлов"),
("Screen Connection", "Подключение просмотра/управления экраном"),
("Do you accept?", "Вы согласны?"),
("Open System Setting", "Открыть настройки системы"),
("How to get Android input permission?", "Как получить разрешение на ввод Android?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Без метки"),
("new-version-of-{}-tip", "Доступна новая версия {}"),
("Accessible devices", "Доступные устройства"),
("View camera", "Просмотр камеры"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Обновите клиент RustDesk до версии {} или новее на удалённой стороне!"),
("view_camera_unsupported_tip", "Удалённое устройство не поддерживает просмотр камеры."),
("Enable camera", "Включить камеру"),
("No cameras", "Камера отсутствует"),
("d3d_render_tip", "При включении визуализации D3D на некоторых устройствах удалённый экран может быть чёрным."),
("Use D3D rendering", "Использовать визуализацию D3D"),
("Printer", "Принтер"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", "Цифровой одноразовый пароль"),
("Enable IPv6 P2P connection", "Использовать подключение IPv6 P2P"),
("Enable UDP hole punching", "Использовать UDP hole punching"),
("View camera", "Просмотр камеры"),
("Enable camera", "Включить камеру"),
("No cameras", "Камера отсутствует"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Perunu permissu pro sa tràmuda de documentos"),
("Note", "Nota"),
("Connection", "Connessione"),
("Share Screen", "Cumpartzi ischermu"),
("Share screen", "Cumpartzi ischermu"),
("Chat", "Tzarrada"),
("Total", "Totale"),
("items", "Elementos"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Catura de ischermu"),
("Input Control", "Controllu atziones"),
("Audio Capture", "Catura de s'àudio"),
("File Connection", "Connessione documentos"),
("Screen Connection", "Connessione ischermu"),
("Do you accept?", "Atzetas?"),
("Open System Setting", "Aberi sas impostatziones de sistema"),
("How to get Android input permission?", "Comente otènnere s'autorizatzione de intrada (input) in Android?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Chene tag"),
("new-version-of-{}-tip", "B'at una versione noa de {} a disponimentu"),
("Accessible devices", "Dispositivos atzessìbiles"),
("View camera", "Mustra sa càmera"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Atualiza su cliente RustDesk remotu a sa versione {} o prus noa!"),
("view_camera_unsupported_tip", "Su dispositivu remotu non suportat sa visualizatzione de sa càmera"),
("Enable camera", "Abìlita sa càmera"),
("No cameras", "Peruna càmera"),
("d3d_render_tip", "Cando sa renderizatzione D3D est abilitada, s'ischermu de controllu remotu diat pòdere èssere nieddu in unas cantas màchinas"),
("Use D3D rendering", "Imprea sa renderizatzione D3D"),
("Printer", "Imprentadora"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Mustra sa càmera"),
("Enable camera", "Abìlita sa càmera"),
("No cameras", "Peruna càmera"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Prenos súborov nie je povolený"),
("Note", "Poznámka"),
("Connection", "Pripojenie"),
("Share Screen", "Zdielať obrazovku"),
("Share screen", "Zdielať obrazovku"),
("Chat", "Chat"),
("Total", "Celkom"),
("items", "položiek"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Snímanie obrazovky"),
("Input Control", "Ovládanie vstupných zariadení"),
("Audio Capture", "Snímanie zvuku"),
("File Connection", "Pripojenie súborov"),
("Screen Connection", "Pripojenie obrazu"),
("Do you accept?", "Súhlasíte?"),
("Open System Setting", "Otvorenie nastavení systému"),
("How to get Android input permission?", "Ako v systéme Android povoliť oprávnenie písať zo vstupného zariadenia?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Zobraziť kameru"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Aktualizujte klienta RustDesk na verziu {} alebo novšiu na vzdialenej strane!"),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Zobraziť kameru"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Ni pravic za prenos datotek"),
("Note", "Opomba"),
("Connection", "Povezava"),
("Share Screen", "Deli zaslon"),
("Share screen", "Deli zaslon"),
("Chat", "Pogovor"),
("Total", "Skupaj"),
("items", "elementi"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Zajem zaslona"),
("Input Control", "Nadzor vnosa"),
("Audio Capture", "Zajem zvoka"),
("File Connection", "Datotečna povezava"),
("Screen Connection", "Zaslonska povezava"),
("Do you accept?", "Ali sprejmete?"),
("Open System Setting", "Odpri sistemske nastavitve"),
("How to get Android input permission?", "Kako pridobiti dovoljenje za vnos na Androidu?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Neoznačeno"),
("new-version-of-{}-tip", "Na voljo je nova različica {}"),
("Accessible devices", ""),
("View camera", "Pogled kamere"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Prosimo, nadgradite RustDesk odjemalec na različico {} ali novejšo na oddaljeni strani."),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Pogled kamere"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Nuk ka leje për transferimin e dosjesve"),
("Note", "Shënime"),
("Connection", "Lidhja"),
("Share Screen", "Ndaj ekranin"),
("Share screen", "Ndaj ekranin"),
("Chat", "Biseda"),
("Total", "Total"),
("items", "artikuj"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Kapja e ekranit"),
("Input Control", "Kontrollo inputin"),
("Audio Capture", "Kapja e zërit"),
("File Connection", "Lidhja e skedarëve"),
("Screen Connection", "Lidhja e ekranit"),
("Do you accept?", "E pranoni"),
("Open System Setting", "Hapni cilësimet e sistemit"),
("How to get Android input permission?", "Si të merrni leje e inputit të Android"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", ""),
("upgrade_remote_rustdesk_client_to_{}_tip", ""),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", ""),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Nemate pravo prenosa datoteka"),
("Note", "Primedba"),
("Connection", "Konekcija"),
("Share Screen", "Podeli ekran"),
("Share screen", "Podeli ekran"),
("Chat", "Dopisivanje"),
("Total", "Ukupno"),
("items", "stavki"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Snimanje ekrana"),
("Input Control", "Kontrola unosa"),
("Audio Capture", "Snimanje zvuka"),
("File Connection", "Spajanje preko datoteke"),
("Screen Connection", "Podeli konekciju"),
("Do you accept?", "Prihvatate?"),
("Open System Setting", "Postavke otvorenog sistema"),
("How to get Android input permission?", "Kako dobiti pristup za Android unos?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Pregled kamere"),
("upgrade_remote_rustdesk_client_to_{}_tip", ""),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Pregled kamere"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Rättigheter saknas"),
("Note", "Notering"),
("Connection", "Anslutning"),
("Share Screen", "Dela skärm"),
("Share screen", "Dela skärm"),
("Chat", "Chatt"),
("Total", "Totalt"),
("items", "föremål"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Skärminspelning"),
("Input Control", "Inputkontroll"),
("Audio Capture", "Ljudinspelning"),
("File Connection", "Fil anslutning"),
("Screen Connection", "Skärm anslutning"),
("Do you accept?", "Accepterar du?"),
("Open System Setting", "Öppna systeminställnig"),
("How to get Android input permission?", "Hur får man Android rättigheter?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Visa kamera"),
("upgrade_remote_rustdesk_client_to_{}_tip", ""),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Visa kamera"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", ""),
("Note", ""),
("Connection", ""),
("Share Screen", ""),
("Share screen", ""),
("Chat", ""),
("Total", ""),
("items", ""),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", ""),
("Input Control", ""),
("Audio Capture", ""),
("File Connection", ""),
("Screen Connection", ""),
("Do you accept?", ""),
("Open System Setting", ""),
("How to get Android input permission?", ""),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", ""),
("upgrade_remote_rustdesk_client_to_{}_tip", ""),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", ""),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", ""),
("Note", ""),
("Connection", ""),
("Share Screen", ""),
("Share screen", ""),
("Chat", ""),
("Total", ""),
("items", ""),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", ""),
("Input Control", ""),
("Audio Capture", ""),
("File Connection", ""),
("Screen Connection", ""),
("Do you accept?", ""),
("Open System Setting", ""),
("How to get Android input permission?", ""),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", ""),
("upgrade_remote_rustdesk_client_to_{}_tip", ""),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", ""),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "ไม่มีสิทธิ์ในการถ่ายโอนไฟล์"),
("Note", "บันทึกข้อความ"),
("Connection", "การเชื่อมต่อ"),
("Share Screen", "แชร์หน้าจอ"),
("Share screen", "แชร์หน้าจอ"),
("Chat", "แชท"),
("Total", "รวม"),
("items", "รายการ"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "บันทึกหน้าจอ"),
("Input Control", "ควบคุมอินพุท"),
("Audio Capture", "บันทึกเสียง"),
("File Connection", "การเชื่อมต่อไฟล์"),
("Screen Connection", "การเชื่อมต่อหน้าจอ"),
("Do you accept?", "ยอมรับหรือไม่?"),
("Open System Setting", "เปิดการตั้งค่าระบบ"),
("How to get Android input permission?", "เปิดสิทธิ์การใช้งานอินพุทของแอนดรอยด์ได้อย่างไร?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "ดูกล้อง"),
("upgrade_remote_rustdesk_client_to_{}_tip", "กรุณาอัปเดต RustDesk ไคลเอนต์ไปยังเวอร์ชัน {} หรือใหม่กว่าที่ฝั่งปลายทาง!"),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "ดูกล้อง"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Dosya aktarımı izni yok"),
("Note", "Not"),
("Connection", "Bağlantı"),
("Share Screen", "Ekranı Paylaş"),
("Share screen", "Ekranı Paylaş"),
("Chat", "Mesajlaş"),
("Total", "Toplam"),
("items", "öğeler"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Ekran görüntüsü"),
("Input Control", "Giriş Kontrolü"),
("Audio Capture", "Ses Yakalama"),
("File Connection", "Dosya Bağlantısı"),
("Screen Connection", "Ekran Bağlantısı"),
("Do you accept?", "Kabul ediyor musun?"),
("Open System Setting", "Sistem Ayarını"),
("How to get Android input permission?", "Android giriş izni nasıl alınır?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Kamerayı görüntüle"),
("upgrade_remote_rustdesk_client_to_{}_tip", ""),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Kamerayı görüntüle"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "沒有檔案傳輸權限"),
("Note", "備註"),
("Connection", "連線"),
("Share Screen", "螢幕分享"),
("Share screen", "螢幕分享"),
("Chat", "聊天"),
("Total", "總計"),
("items", "個項目"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "畫面錄製"),
("Input Control", "輸入控制"),
("Audio Capture", "音訊錄製"),
("File Connection", "檔案連線"),
("Screen Connection", "畫面連線"),
("Do you accept?", "是否接受?"),
("Open System Setting", "開啟系統設定"),
("How to get Android input permission?", "如何取得 Android 的輸入權限?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "無標籤"),
("new-version-of-{}-tip", "有新版本的 {} 可用"),
("Accessible devices", "可存取的裝置"),
("View camera", "檢視相機"),
("upgrade_remote_rustdesk_client_to_{}_tip", "請將遠端 RustDesk 客戶端升級到 {} 或更新版本!"),
("view_camera_unsupported_tip", "您的遠端設備不支援查看鏡頭"),
("Enable camera", "允許查看鏡頭"),
("No cameras", "沒有鏡頭"),
("d3d_render_tip", "當啟用 D3D 渲染時,某些機器可能會無法顯示遠端畫面。"),
("Use D3D rendering", "使用 D3D 渲染"),
("Printer", "印表機"),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", "數字一次性密碼"),
("Enable IPv6 P2P connection", "啟用 IPv6 P2P 連線"),
("Enable UDP hole punching", "啟用 UDP 打洞"),
("View camera", "檢視相機"),
("Enable camera", "允許查看鏡頭"),
("No cameras", "沒有鏡頭"),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Немає дозволу на передачу файлів"),
("Note", "Примітка"),
("Connection", "Підключення"),
("Share Screen", "Поділитися екраном"),
("Share screen", "Поділитися екраном"),
("Chat", "Чат"),
("Total", "Всього"),
("items", "елементи"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Захоплення екрана"),
("Input Control", "Керування введенням"),
("Audio Capture", "Захоплення аудіо"),
("File Connection", "Файлове підключення"),
("Screen Connection", "Підключення екрана"),
("Do you accept?", "Ви згодні?"),
("Open System Setting", "Відкрити налаштування системи"),
("How to get Android input permission?", "Як отримати дозвіл на введення в Android?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Без міток"),
("new-version-of-{}-tip", "Доступна нова версія {}"),
("Accessible devices", ""),
("View camera", "Перегляд камери"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Будь ласка, оновіть RustDesk клієнт на віддаленому пристрої до версії {} чи новіше!"),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Перегляд камери"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -267,7 +267,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "Không có quyền truyền tệp tin"),
("Note", "Ghi nhớ"),
("Connection", "Kết nối"),
("Share Screen", "Chia sẻ màn hình"),
("Share screen", "Chia sẻ màn hình"),
("Chat", "Chat"),
("Total", "Tổng"),
("items", "items"),
@@ -275,8 +275,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Capture", "Ghi màn hình"),
("Input Control", "Điều khiển đầu vào"),
("Audio Capture", "Ghi âm thanh"),
("File Connection", "Kết nối tệp tin"),
("Screen Connection", "Kết nối màn hình"),
("Do you accept?", "Bạn có chấp nhận không?"),
("Open System Setting", "Mở cài đặt hệ thống"),
("How to get Android input permission?", "Cách để có quyền nhập trên Android?"),
@@ -657,11 +655,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", ""),
("new-version-of-{}-tip", ""),
("Accessible devices", ""),
("View camera", "Xem camera"),
("upgrade_remote_rustdesk_client_to_{}_tip", ""),
("view_camera_unsupported_tip", ""),
("Enable camera", ""),
("No cameras", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
@@ -701,5 +696,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("View camera", "Xem camera"),
("Enable camera", ""),
("No cameras", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
].iter().cloned().collect();
}

View File

@@ -72,4 +72,4 @@ pub mod privacy_mode;
#[cfg(windows)]
pub mod virtual_display_manager;
mod kcp_stream;
mod kcp_stream;

View File

@@ -33,6 +33,8 @@ use video_service::VideoSource;
use crate::ipc::Data;
pub mod audio_service;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub mod terminal_service;
cfg_if::cfg_if! {
if #[cfg(not(target_os = "ios"))] {
mod clipboard_service;
@@ -146,6 +148,7 @@ pub fn new() -> ServerPtr {
}
}
}
// Terminal service is created per connection, not globally
Arc::new(RwLock::new(server))
}

View File

@@ -169,6 +169,7 @@ pub enum AuthConnType {
FileTransfer,
PortForward,
ViewCamera,
Terminal,
}
pub struct Connection {
@@ -182,6 +183,7 @@ pub struct Connection {
file_timer: crate::RustDeskInterval,
file_transfer: Option<(String, bool)>,
view_camera: bool,
terminal: bool,
port_forward_socket: Option<Framed<TcpStream, BytesCodec>>,
port_forward_address: String,
tx_to_cm: mpsc::UnboundedSender<ipc::Data>,
@@ -250,6 +252,9 @@ pub struct Connection {
// For post requests that need to be sent sequentially.
// eg. post_conn_audit
tx_post_seq: mpsc::UnboundedSender<(String, Value)>,
terminal_service_id: String,
terminal_persistent: bool,
terminal_generic_service: Option<Box<GenericService>>,
}
impl ConnInner {
@@ -347,6 +352,7 @@ impl Connection {
file_timer: crate::rustdesk_interval(time::interval(SEC30)),
file_transfer: None,
view_camera: false,
terminal: false,
port_forward_socket: None,
port_forward_address: "".to_owned(),
tx_to_cm,
@@ -410,6 +416,9 @@ impl Connection {
tx_from_authed,
printer_data: Vec::new(),
tx_post_seq,
terminal_service_id: "".to_owned(),
terminal_persistent: false,
terminal_generic_service: None,
};
let addr = hbb_common::try_into_v4(addr);
if !conn.on_open(addr).await {
@@ -450,7 +459,7 @@ impl Connection {
let mut last_recv_time = Instant::now();
conn.stream.set_send_timeout(
if conn.file_transfer.is_some() || conn.port_forward_socket.is_some() {
if conn.file_transfer.is_some() || conn.port_forward_socket.is_some() || conn.terminal {
SEND_TIMEOUT_OTHER
} else {
SEND_TIMEOUT_VIDEO
@@ -1253,6 +1262,8 @@ impl Connection {
(2, AuthConnType::PortForward)
} else if self.view_camera {
(3, AuthConnType::ViewCamera)
} else if self.terminal {
(4, AuthConnType::Terminal)
} else {
(0, AuthConnType::Remote)
};
@@ -1361,8 +1372,7 @@ impl Connection {
return;
}
#[cfg(target_os = "linux")]
if !self.file_transfer.is_some() && !self.port_forward_socket.is_some() && !self.view_camera
{
if self.is_remote() {
let mut msg = "".to_string();
if crate::platform::linux::is_login_screen_wayland() {
msg = crate::client::LOGIN_SCREEN_WAYLAND.to_owned()
@@ -1393,7 +1403,8 @@ impl Connection {
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if self.file_transfer.is_some() {
if crate::platform::is_prelogin() { // }|| self.tx_to_cm.send(ipc::Data::Test).is_err() {
if crate::platform::is_prelogin() {
// }|| self.tx_to_cm.send(ipc::Data::Test).is_err() {
username = "".to_owned();
}
}
@@ -1408,6 +1419,8 @@ impl Connection {
pi.sas_enabled = sas_enabled;
pi.features = Some(Features {
privacy_mode: privacy_mode::is_privacy_mode_supported(),
#[cfg(not(any(target_os = "android", target_os = "ios")))]
terminal: true, // Terminal feature is supported on desktop only
..Default::default()
})
.into();
@@ -1417,7 +1430,7 @@ impl Connection {
let mut wait_session_id_confirm = false;
#[cfg(windows)]
self.handle_windows_specific_session(&mut pi, &mut wait_session_id_confirm);
if self.file_transfer.is_some() {
if self.file_transfer.is_some() || self.terminal {
res.set_peer_info(pi);
} else if self.view_camera {
let supported_encoding = scrap::codec::Encoder::supported_encoding();
@@ -1509,6 +1522,10 @@ impl Connection {
} else {
self.delayed_read_dir = Some((dir.to_owned(), show_hidden));
}
} else if self.terminal {
self.keyboard = false;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
self.init_terminal_service().await;
} else if self.view_camera {
if !wait_session_id_confirm {
self.try_sub_camera_displays();
@@ -1531,9 +1548,16 @@ impl Connection {
}
}
#[inline]
fn is_remote(&self) -> bool {
self.file_transfer.is_none()
&& self.port_forward_socket.is_none()
&& !self.view_camera
&& !self.terminal
}
fn try_sub_monitor_services(&mut self) {
let is_remote =
self.file_transfer.is_none() && self.port_forward_socket.is_none() && !self.view_camera;
let is_remote = self.is_remote();
if is_remote && !self.services_subed {
self.services_subed = true;
if let Some(s) = self.server.upgrade() {
@@ -1651,6 +1675,7 @@ impl Connection {
id: self.inner.id(),
is_file_transfer: self.file_transfer.is_some(),
is_view_camera: self.view_camera,
is_terminal: self.terminal,
port_forward: self.port_forward_address.clone(),
peer_id,
name,
@@ -1902,6 +1927,19 @@ impl Connection {
}
self.view_camera = true;
}
Some(login_request::Union::Terminal(terminal)) => {
if !Connection::permission(keys::OPTION_ENABLE_TERMINAL) {
self.send_login_error("No permission of terminal").await;
sleep(1.).await;
return false;
}
self.terminal = true;
if let Some(o) = self.options_in_login.as_ref() {
self.terminal_persistent =
o.terminal_persistent.enum_value() == Ok(BoolOption::Yes);
}
self.terminal_service_id = terminal.service_id;
}
Some(login_request::Union::PortForward(mut pf)) => {
if !Connection::permission("enable-tunnel") {
self.send_login_error("No permission of IP tunneling").await;
@@ -2791,7 +2829,7 @@ impl Connection {
}
} else if self.view_camera {
self.try_sub_camera_displays();
} else {
} else if !self.terminal {
self.try_sub_monitor_services();
}
}
@@ -2843,6 +2881,12 @@ impl Connection {
self.refresh_video_display(Some(request.display as usize));
}
}
Some(message::Union::TerminalAction(action)) => {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
allow_err!(self.handle_terminal_action(action).await);
#[cfg(any(target_os = "android", target_os = "ios"))]
log::warn!("Terminal action received but not supported on this platform");
}
_ => {}
}
}
@@ -3371,6 +3415,12 @@ impl Connection {
}
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if let Ok(q) = o.terminal_persistent.enum_value() {
if q != BoolOption::NotSet {
self.update_terminal_persistence(q == BoolOption::Yes).await;
}
}
}
async fn turn_on_privacy(&mut self, impl_key: String) {
@@ -3562,12 +3612,7 @@ impl Connection {
#[cfg(windows)]
fn portable_check(&mut self) {
if self.portable.is_installed
|| self.file_transfer.is_some()
|| self.view_camera
|| self.port_forward_socket.is_some()
|| !self.keyboard
{
if self.portable.is_installed || !self.is_remote() || !self.keyboard {
return;
}
let running = portable_client::running();
@@ -3779,6 +3824,55 @@ impl Connection {
msg_out.set_message_box(res);
self.send(msg_out).await;
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
async fn update_terminal_persistence(&mut self, persistent: bool) {
self.terminal_persistent = persistent;
terminal_service::set_persistent(&self.terminal_service_id, persistent).ok();
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
async fn init_terminal_service(&mut self) {
if self.terminal_service_id.is_empty() {
self.terminal_service_id = terminal_service::generate_service_id();
}
let s = Box::new(terminal_service::new(
self.terminal_service_id.clone(),
self.terminal_persistent,
));
s.on_subscribe(self.inner.clone());
self.terminal_generic_service = Some(s);
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
async fn handle_terminal_action(&mut self, action: TerminalAction) -> ResultType<()> {
let mut proxy = terminal_service::TerminalServiceProxy::new(
self.terminal_service_id.clone(),
Some(self.terminal_persistent),
);
match proxy.handle_action(&action) {
Ok(Some(response)) => {
let mut msg_out = Message::new();
msg_out.set_terminal_response(response);
self.send(msg_out).await;
}
Ok(None) => {
// No response needed
}
Err(err) => {
let mut response = TerminalResponse::new();
let mut error = TerminalError::new();
error.message = format!("Failed to handle action: {}", err);
response.set_error(error);
let mut msg_out = Message::new();
msg_out.set_terminal_response(response);
self.send(msg_out).await;
}
}
Ok(())
}
}
pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
@@ -4151,6 +4245,10 @@ impl Drop for Connection {
fn drop(&mut self) {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
self.release_pressed_modifiers();
if let Some(s) = self.terminal_generic_service.as_ref() {
s.join();
}
}
}

View File

@@ -0,0 +1,968 @@
use super::*;
use hbb_common::{
anyhow::{anyhow, Context, Result},
compress,
};
use portable_pty::{Child, CommandBuilder, PtySize};
use std::{
collections::{HashMap, VecDeque},
io::{Read, Write},
sync::{
mpsc::{self, Receiver, SyncSender},
Arc, Mutex,
},
thread,
time::{Duration, Instant},
};
const MAX_OUTPUT_BUFFER_SIZE: usize = 1024 * 1024; // 1MB per terminal
const MAX_BUFFER_LINES: usize = 10000;
const MAX_SERVICES: usize = 100; // Maximum number of persistent terminal services
const SERVICE_IDLE_TIMEOUT: Duration = Duration::from_secs(3600); // 1 hour idle timeout
const CHANNEL_BUFFER_SIZE: usize = 100; // Number of messages to buffer in channel
const COMPRESS_THRESHOLD: usize = 512; // Compress terminal data larger than this
lazy_static::lazy_static! {
// Global registry of persistent terminal services indexed by service_id
static ref TERMINAL_SERVICES: Arc<Mutex<HashMap<String, Arc<Mutex<PersistentTerminalService>>>>> =
Arc::new(Mutex::new(HashMap::new()));
// Cleanup task handle
static ref CLEANUP_TASK: Arc<Mutex<Option<std::thread::JoinHandle<()>>>> = Arc::new(Mutex::new(None));
// List of terminal child processes to check for zombies
static ref TERMINAL_TASKS: Arc<Mutex<Vec<Box<dyn Child + Send + Sync>>>> = Arc::new(Mutex::new(Vec::new()));
}
/// Service metadata that is sent to clients
#[derive(Clone, Debug)]
pub struct ServiceMetadata {
pub service_id: String,
pub created_at: Instant,
pub terminal_count: usize,
pub is_persistent: bool,
}
/// Generate a new persistent service ID
pub fn generate_service_id() -> String {
format!("ts_{}", uuid::Uuid::new_v4())
}
fn get_default_shell() -> String {
#[cfg(target_os = "windows")]
{
// Try PowerShell Core first (cross-platform version)
// Common installation paths for PowerShell Core
let pwsh_paths = [
"pwsh.exe",
r"C:\Program Files\PowerShell\7\pwsh.exe",
r"C:\Program Files\PowerShell\6\pwsh.exe",
];
for path in &pwsh_paths {
if std::path::Path::new(path).exists() {
return path.to_string();
}
}
// Try Windows PowerShell (should be available on all Windows systems)
let powershell_path = r"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe";
if std::path::Path::new(powershell_path).exists() {
return powershell_path.to_string();
}
// Final fallback to cmd.exe
std::env::var("COMSPEC").unwrap_or_else(|_| "cmd.exe".to_string())
}
#[cfg(not(target_os = "windows"))]
{
// First try the SHELL environment variable
if let Ok(shell) = std::env::var("SHELL") {
if !shell.is_empty() {
return shell;
}
}
// Check for common shells in order of preference
let shells = ["/bin/bash", "/bin/zsh", "/bin/sh"];
for shell in &shells {
if std::path::Path::new(shell).exists() {
return shell.to_string();
}
}
// Final fallback to /bin/sh which should exist on all POSIX systems
"/bin/sh".to_string()
}
}
/// Get or create a persistent terminal service
fn get_or_create_service(
service_id: String,
is_persistent: bool,
) -> Result<Arc<Mutex<PersistentTerminalService>>> {
let mut services = TERMINAL_SERVICES.lock().unwrap();
// Check service limit
if !services.contains_key(&service_id) && services.len() >= MAX_SERVICES {
return Err(anyhow!(
"Maximum number of terminal services ({}) reached",
MAX_SERVICES
));
}
let service = services
.entry(service_id.clone())
.or_insert_with(|| {
log::info!(
"Creating new terminal service: {} (persistent: {})",
service_id,
is_persistent
);
Arc::new(Mutex::new(PersistentTerminalService::new(
service_id.clone(),
is_persistent,
)))
})
.clone();
// Ensure cleanup task is running
ensure_cleanup_task();
Ok(service)
}
/// Remove a service from the global registry
fn remove_service(service_id: &str) {
let mut services = TERMINAL_SERVICES.lock().unwrap();
if let Some(service) = services.remove(service_id) {
log::info!("Removed service: {}", service_id);
// Close all terminals in the service
let sessions = service.lock().unwrap().sessions.clone();
for (_, session) in sessions.iter() {
let mut session = session.lock().unwrap();
if let Some(mut child) = session.child.take() {
// Kill the process
let _ = child.kill();
add_to_reaper(child);
}
}
}
}
/// List all active terminal services
pub fn list_services() -> Vec<ServiceMetadata> {
let services = TERMINAL_SERVICES.lock().unwrap();
services
.iter()
.filter_map(|(id, service)| {
service.lock().ok().map(|svc| ServiceMetadata {
service_id: id.clone(),
created_at: svc.created_at,
terminal_count: svc.sessions.len(),
is_persistent: svc.is_persistent,
})
})
.collect()
}
/// Get service by ID
pub fn get_service(service_id: &str) -> Option<Arc<Mutex<PersistentTerminalService>>> {
let services = TERMINAL_SERVICES.lock().unwrap();
services.get(service_id).cloned()
}
/// Clean up inactive services
pub fn cleanup_inactive_services() {
let services = TERMINAL_SERVICES.lock().unwrap();
let now = Instant::now();
let mut to_remove = Vec::new();
for (service_id, service) in services.iter() {
if let Ok(svc) = service.lock() {
// Remove non-persistent services after idle timeout
if !svc.is_persistent && now.duration_since(svc.last_activity) > SERVICE_IDLE_TIMEOUT {
to_remove.push(service_id.clone());
log::info!("Cleaning up idle non-persistent service: {}", service_id);
}
// Remove persistent services with no active terminals after longer timeout
else if svc.is_persistent
&& svc.sessions.is_empty()
&& now.duration_since(svc.last_activity) > SERVICE_IDLE_TIMEOUT * 2
{
to_remove.push(service_id.clone());
log::info!("Cleaning up empty persistent service: {}", service_id);
}
}
}
// Remove outside of iteration to avoid deadlock
drop(services);
for id in to_remove {
remove_service(&id);
}
}
/// Add a child process to the zombie reaper
fn add_to_reaper(child: Box<dyn Child + Send + Sync>) {
if let Ok(mut tasks) = TERMINAL_TASKS.lock() {
tasks.push(child);
}
}
/// Check and reap zombie terminal processes
fn check_zombie_terminals() {
let mut tasks = match TERMINAL_TASKS.lock() {
Ok(t) => t,
Err(_) => return,
};
let mut i = 0;
while i < tasks.len() {
match tasks[i].try_wait() {
Ok(Some(_)) => {
// Process has exited, remove it
log::info!("Process exited: {:?}", tasks[i].process_id());
tasks.remove(i);
}
Ok(None) => {
// Still running
i += 1;
}
Err(err) => {
// Error checking status, remove it
log::info!(
"Process exited with error: {:?}, err: {err}",
tasks[i].process_id()
);
tasks.remove(i);
}
}
}
}
/// Ensure the cleanup task is running
fn ensure_cleanup_task() {
let mut task_handle = CLEANUP_TASK.lock().unwrap();
if task_handle.is_none() {
let handle = std::thread::spawn(|| {
log::info!("Started cleanup task");
let mut last_service_cleanup = Instant::now();
loop {
// Check for zombie processes every 100ms
check_zombie_terminals();
// Check for inactive services every 5 minutes
if last_service_cleanup.elapsed() > Duration::from_secs(300) {
cleanup_inactive_services();
last_service_cleanup = Instant::now();
}
std::thread::sleep(Duration::from_millis(100));
}
});
*task_handle = Some(handle);
}
}
pub fn new(service_id: String, is_persistent: bool) -> GenericService {
// Create the service with initial persistence setting
allow_err!(get_or_create_service(service_id.clone(), is_persistent));
let svc = EmptyExtraFieldService::new(service_id.clone(), false);
GenericService::run(&svc.clone(), move |sp| run(sp, service_id.clone()));
svc.sp
}
fn run(sp: EmptyExtraFieldService, service_id: String) -> ResultType<()> {
while sp.ok() {
let responses = TerminalServiceProxy::new(service_id.clone(), None).read_outputs();
for response in responses {
let mut msg_out = Message::new();
msg_out.set_terminal_response(response);
sp.send(msg_out);
}
thread::sleep(Duration::from_millis(30)); // Read at ~33fps for responsive terminal
}
// Clean up non-persistent service when loop exits
if let Some(service) = get_service(&service_id) {
let should_remove = !service.lock().unwrap().is_persistent;
if should_remove {
remove_service(&service_id);
}
}
Ok(())
}
/// Output buffer for terminal session
struct OutputBuffer {
lines: VecDeque<Vec<u8>>,
total_size: usize,
last_line_incomplete: bool,
}
impl OutputBuffer {
fn new() -> Self {
Self {
lines: VecDeque::new(),
total_size: 0,
last_line_incomplete: false,
}
}
fn append(&mut self, data: &[u8]) {
if data.is_empty() {
return;
}
// Handle incomplete lines
let mut start = 0;
if self.last_line_incomplete {
if let Some(last_line) = self.lines.back_mut() {
// Find first newline in new data
if let Some(newline_pos) = data.iter().position(|&b| b == b'\n') {
last_line.extend_from_slice(&data[..=newline_pos]);
start = newline_pos + 1;
self.last_line_incomplete = false;
} else {
// Still no newline, append all
last_line.extend_from_slice(data);
self.total_size += data.len();
return;
}
}
}
// Process remaining data
let remaining = &data[start..];
let ends_with_newline = remaining.last() == Some(&b'\n');
// Split by lines
let lines: Vec<&[u8]> = remaining.split(|&b| b == b'\n').collect();
for (i, line) in lines.iter().enumerate() {
if i == lines.len() - 1 && !ends_with_newline && !line.is_empty() {
// Last line without newline
self.last_line_incomplete = true;
}
if !line.is_empty() || i < lines.len() - 1 {
let mut line_data = line.to_vec();
if i < lines.len() - 1 || ends_with_newline {
line_data.push(b'\n');
}
self.total_size += line_data.len();
self.lines.push_back(line_data);
}
}
// Trim old data if buffer is too large
while self.total_size > MAX_OUTPUT_BUFFER_SIZE || self.lines.len() > MAX_BUFFER_LINES {
if let Some(removed) = self.lines.pop_front() {
self.total_size -= removed.len();
}
}
}
fn get_recent(&self, max_bytes: usize) -> Vec<u8> {
let mut result = Vec::new();
let mut size = 0;
// Get recent lines up to max_bytes
for line in self.lines.iter().rev() {
if size + line.len() > max_bytes {
break;
}
size += line.len();
result.splice(0..0, line.iter().cloned());
}
result
}
}
pub struct TerminalSession {
pub created_at: Instant,
last_activity: Instant,
pty_pair: Option<portable_pty::PtyPair>,
child: Option<Box<dyn Child + std::marker::Send + Sync>>,
// Channel for sending input to the writer thread
input_tx: Option<SyncSender<Vec<u8>>>,
// Channel for receiving output from the reader thread
output_rx: Option<Receiver<Vec<u8>>>,
// Thread handles
reader_thread: Option<thread::JoinHandle<()>>,
writer_thread: Option<thread::JoinHandle<()>>,
output_buffer: OutputBuffer,
title: String,
pid: u32,
rows: u16,
cols: u16,
// Track if we've already sent the closed message
closed_message_sent: bool,
}
impl TerminalSession {
fn new(terminal_id: i32, rows: u16, cols: u16) -> Self {
Self {
created_at: Instant::now(),
last_activity: Instant::now(),
pty_pair: None,
child: None,
input_tx: None,
output_rx: None,
reader_thread: None,
writer_thread: None,
output_buffer: OutputBuffer::new(),
title: format!("Terminal {}", terminal_id),
pid: 0,
rows,
cols,
closed_message_sent: false,
}
}
fn update_activity(&mut self) {
self.last_activity = Instant::now();
}
}
impl Drop for TerminalSession {
fn drop(&mut self) {
// Drop the input channel to signal writer thread to exit
drop(self.input_tx.take());
// Wait for threads to finish
if let Some(writer_thread) = self.writer_thread.take() {
let _ = writer_thread.join();
}
if let Some(reader_thread) = self.reader_thread.take() {
let _ = reader_thread.join();
}
// Ensure child process is properly handled when session is dropped
if let Some(mut child) = self.child.take() {
let _ = child.kill();
add_to_reaper(child);
}
}
}
/// Persistent terminal service that can survive connection drops
pub struct PersistentTerminalService {
service_id: String,
sessions: HashMap<i32, Arc<Mutex<TerminalSession>>>,
pub created_at: Instant,
last_activity: Instant,
pub is_persistent: bool,
}
impl PersistentTerminalService {
pub fn new(service_id: String, is_persistent: bool) -> Self {
Self {
service_id,
sessions: HashMap::new(),
created_at: Instant::now(),
last_activity: Instant::now(),
is_persistent,
}
}
fn update_activity(&mut self) {
self.last_activity = Instant::now();
}
/// Get list of terminal metadata
pub fn list_terminals(&self) -> Vec<(i32, String, u32, Instant)> {
self.sessions
.iter()
.map(|(id, session)| {
let s = session.lock().unwrap();
(*id, s.title.clone(), s.pid, s.created_at)
})
.collect()
}
/// Get buffered output for a terminal
pub fn get_terminal_buffer(&self, terminal_id: i32, max_bytes: usize) -> Option<Vec<u8>> {
self.sessions.get(&terminal_id).map(|session| {
let session = session.lock().unwrap();
session.output_buffer.get_recent(max_bytes)
})
}
/// Get terminal info for recovery
pub fn get_terminal_info(&self, terminal_id: i32) -> Option<(u16, u16, Vec<u8>)> {
self.sessions.get(&terminal_id).map(|session| {
let session = session.lock().unwrap();
(
session.rows,
session.cols,
session.output_buffer.get_recent(4096),
)
})
}
/// Check if service has active terminals
pub fn has_active_terminals(&self) -> bool {
!self.sessions.is_empty()
}
}
pub struct TerminalServiceProxy {
service_id: String,
is_persistent: bool,
}
pub fn set_persistent(service_id: &str, is_persistent: bool) -> Result<()> {
if let Some(service) = get_service(service_id) {
service.lock().unwrap().is_persistent = is_persistent;
Ok(())
} else {
Err(anyhow!("Service {} not found", service_id))
}
}
impl TerminalServiceProxy {
pub fn new(service_id: String, is_persistent: Option<bool>) -> Self {
// Get persistence from the service if it exists
let is_persistent =
is_persistent.unwrap_or(if let Some(service) = get_service(&service_id) {
service.lock().unwrap().is_persistent
} else {
false
});
TerminalServiceProxy {
service_id,
is_persistent,
}
}
pub fn get_service_id(&self) -> &str {
&self.service_id
}
pub fn handle_action(&mut self, action: &TerminalAction) -> Result<Option<TerminalResponse>> {
let service = match get_service(&self.service_id) {
Some(s) => s,
None => {
let mut response = TerminalResponse::new();
let mut error = TerminalError::new();
error.message = format!("Terminal service {} not found", self.service_id);
response.set_error(error);
return Ok(Some(response));
}
};
service.lock().unwrap().update_activity();
match &action.union {
Some(terminal_action::Union::Open(open)) => {
self.handle_open(&mut service.lock().unwrap(), open)
}
Some(terminal_action::Union::Resize(resize)) => {
let session = service
.lock()
.unwrap()
.sessions
.get(&resize.terminal_id)
.cloned();
self.handle_resize(session, resize)
}
Some(terminal_action::Union::Data(data)) => {
let session = service
.lock()
.unwrap()
.sessions
.get(&data.terminal_id)
.cloned();
self.handle_data(session, data)
}
Some(terminal_action::Union::Close(close)) => {
self.handle_close(&mut service.lock().unwrap(), close)
}
_ => Ok(None),
}
}
fn handle_open(
&self,
service: &mut PersistentTerminalService,
open: &OpenTerminal,
) -> Result<Option<TerminalResponse>> {
let mut response = TerminalResponse::new();
// Check if terminal already exists
if let Some(session_arc) = service.sessions.get(&open.terminal_id) {
// Reconnect to existing terminal
let session = session_arc.lock().unwrap();
let mut opened = TerminalOpened::new();
opened.terminal_id = open.terminal_id;
opened.success = true;
opened.message = "Reconnected to existing terminal".to_string();
opened.pid = session.pid;
// Return service_id for persistent sessions
if self.is_persistent {
opened.service_id = self.service_id.clone();
}
response.set_opened(opened);
// Send buffered output
let buffer = session.output_buffer.get_recent(4096);
if !buffer.is_empty() {
// We'll need to send this separately or extend the protocol
// For now, just acknowledge the reconnection
}
return Ok(Some(response));
}
// Create new terminal session
log::info!(
"Creating new terminal {} for service: {}",
open.terminal_id,
service.service_id
);
let mut session =
TerminalSession::new(open.terminal_id, open.rows as u16, open.cols as u16);
let pty_size = PtySize {
rows: open.rows as u16,
cols: open.cols as u16,
pixel_width: 0,
pixel_height: 0,
};
log::debug!("Opening PTY with size: {}x{}", open.rows, open.cols);
let pty_system = portable_pty::native_pty_system();
let pty_pair = pty_system.openpty(pty_size).context("Failed to open PTY")?;
// Use default shell for the platform
let shell = get_default_shell();
log::debug!("Using shell: {}", shell);
let cmd = CommandBuilder::new(&shell);
log::debug!("Spawning shell process...");
let child = pty_pair
.slave
.spawn_command(cmd)
.context("Failed to spawn command")?;
let writer = pty_pair
.master
.take_writer()
.context("Failed to get writer")?;
let reader = pty_pair
.master
.try_clone_reader()
.context("Failed to get reader")?;
session.pid = child.process_id().unwrap_or(0) as u32;
// Create channels for input/output
let (input_tx, input_rx) = mpsc::sync_channel::<Vec<u8>>(CHANNEL_BUFFER_SIZE);
let (output_tx, output_rx) = mpsc::sync_channel::<Vec<u8>>(CHANNEL_BUFFER_SIZE);
// Spawn writer thread
let terminal_id = open.terminal_id;
let writer_thread = thread::spawn(move || {
let mut writer = writer;
while let Ok(data) = input_rx.recv() {
if let Err(e) = writer.write_all(&data) {
log::error!("Terminal {} write error: {}", terminal_id, e);
break;
}
if let Err(e) = writer.flush() {
log::error!("Terminal {} flush error: {}", terminal_id, e);
}
}
log::debug!("Terminal {} writer thread exiting", terminal_id);
});
// Spawn reader thread
let terminal_id = open.terminal_id;
let reader_thread = thread::spawn(move || {
let mut reader = reader;
let mut buf = vec![0u8; 4096];
loop {
match reader.read(&mut buf) {
Ok(0) => {
// EOF
break;
}
Ok(n) => {
let data = buf[..n].to_vec();
// Try to send, if channel is full, drop the data
match output_tx.try_send(data) {
Ok(_) => {}
Err(mpsc::TrySendError::Full(_)) => {
log::debug!(
"Terminal {} output channel full, dropping data",
terminal_id
);
}
Err(mpsc::TrySendError::Disconnected(_)) => {
log::debug!("Terminal {} output channel disconnected", terminal_id);
break;
}
}
}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
// For non-blocking I/O, sleep briefly
thread::sleep(Duration::from_millis(10));
}
Err(e) => {
log::error!("Terminal {} read error: {}", terminal_id, e);
break;
}
}
}
log::debug!("Terminal {} reader thread exiting", terminal_id);
});
session.pty_pair = Some(pty_pair);
session.child = Some(child);
session.input_tx = Some(input_tx);
session.output_rx = Some(output_rx);
session.reader_thread = Some(reader_thread);
session.writer_thread = Some(writer_thread);
let mut opened = TerminalOpened::new();
opened.terminal_id = open.terminal_id;
opened.success = true;
opened.message = "Terminal opened".to_string();
opened.pid = session.pid;
// Return service_id for persistent sessions
if self.is_persistent {
opened.service_id = service.service_id.clone();
}
response.set_opened(opened);
log::info!(
"Terminal {} opened successfully with PID {}",
open.terminal_id,
session.pid
);
// Store the session
service
.sessions
.insert(open.terminal_id, Arc::new(Mutex::new(session)));
Ok(Some(response))
}
fn handle_resize(
&self,
session: Option<Arc<Mutex<TerminalSession>>>,
resize: &ResizeTerminal,
) -> Result<Option<TerminalResponse>> {
if let Some(session_arc) = session {
let mut session = session_arc.lock().unwrap();
session.update_activity();
session.rows = resize.rows as u16;
session.cols = resize.cols as u16;
if let Some(pty_pair) = &session.pty_pair {
pty_pair.master.resize(PtySize {
rows: resize.rows as u16,
cols: resize.cols as u16,
pixel_width: 0,
pixel_height: 0,
})?;
}
}
Ok(None)
}
fn handle_data(
&self,
session: Option<Arc<Mutex<TerminalSession>>>,
data: &TerminalData,
) -> Result<Option<TerminalResponse>> {
if let Some(session_arc) = session {
let mut session = session_arc.lock().unwrap();
session.update_activity();
if let Some(input_tx) = &session.input_tx {
// Send data to writer thread
if let Err(e) = input_tx.send(data.data.to_vec()) {
log::error!(
"Failed to send data to terminal {}: {}",
data.terminal_id,
e
);
}
}
}
Ok(None)
}
fn handle_close(
&self,
service: &mut PersistentTerminalService,
close: &CloseTerminal,
) -> Result<Option<TerminalResponse>> {
let mut response = TerminalResponse::new();
// Always close and remove the terminal
if let Some(session_arc) = service.sessions.remove(&close.terminal_id) {
let mut session = session_arc.lock().unwrap();
let exit_code = if let Some(mut child) = session.child.take() {
child.kill()?;
add_to_reaper(child);
-1 // -1 indicates forced termination
} else {
0
};
let mut closed = TerminalClosed::new();
closed.terminal_id = close.terminal_id;
closed.exit_code = exit_code;
response.set_closed(closed);
Ok(Some(response))
} else {
Ok(None)
}
}
pub fn read_outputs(&self) -> Vec<TerminalResponse> {
let service = match get_service(&self.service_id) {
Some(s) => s,
None => {
return vec![];
}
};
// Get session references with minimal service lock time
let sessions: Vec<(i32, Arc<Mutex<TerminalSession>>)> = {
let service = service.lock().unwrap();
service
.sessions
.iter()
.map(|(id, session)| (*id, session.clone()))
.collect()
};
let mut responses = Vec::new();
let mut closed_terminals = Vec::new();
// Process each session with its own lock
for (terminal_id, session_arc) in sessions {
if let Ok(mut session) = session_arc.try_lock() {
// Check if reader thread is still alive and we haven't sent closed message yet
let mut should_send_closed = false;
if !session.closed_message_sent {
if let Some(thread) = &session.reader_thread {
if thread.is_finished() {
should_send_closed = true;
session.closed_message_sent = true;
}
}
}
// Read from output channel
let mut has_activity = false;
let mut received_data = Vec::new();
if let Some(output_rx) = &session.output_rx {
// Try to read all available data
while let Ok(data) = output_rx.try_recv() {
has_activity = true;
received_data.push(data);
}
}
// Update buffer after reading
for data in &received_data {
session.output_buffer.append(data);
}
// Process received data for responses
for data in received_data {
let mut response = TerminalResponse::new();
let mut terminal_data = TerminalData::new();
terminal_data.terminal_id = terminal_id;
// Compress data if it exceeds threshold
if data.len() > COMPRESS_THRESHOLD {
let compressed = compress::compress(&data);
if compressed.len() < data.len() {
terminal_data.data = bytes::Bytes::from(compressed);
terminal_data.compressed = true;
} else {
// Compression didn't help, send uncompressed
terminal_data.data = bytes::Bytes::from(data);
}
} else {
terminal_data.data = bytes::Bytes::from(data);
}
response.set_data(terminal_data);
responses.push(response);
}
if has_activity {
session.update_activity();
}
if should_send_closed {
closed_terminals.push(terminal_id);
}
}
}
// Clean up closed terminals (requires service lock briefly)
if !closed_terminals.is_empty() {
let mut sessions = service.lock().unwrap().sessions.clone();
for terminal_id in closed_terminals {
let mut exit_code = 0;
if !self.is_persistent {
if let Some(session_arc) = sessions.remove(&terminal_id) {
service.lock().unwrap().sessions.remove(&terminal_id);
let mut session = session_arc.lock().unwrap();
// Take the child and add to zombie reaper
if let Some(mut child) = session.child.take() {
// Try to get exit code if available
if let Ok(Some(status)) = child.try_wait() {
exit_code = status.exit_code() as i32;
}
add_to_reaper(child);
}
}
} else {
// For persistent sessions, just clear the child reference
if let Some(session_arc) = sessions.get(&terminal_id) {
let mut session = session_arc.lock().unwrap();
if let Some(mut child) = session.child.take() {
// Try to get exit code if available
if let Ok(Some(status)) = child.try_wait() {
exit_code = status.exit_code() as i32;
}
add_to_reaper(child);
}
}
}
let mut response = TerminalResponse::new();
let mut closed = TerminalClosed::new();
closed.terminal_id = terminal_id;
closed.exit_code = exit_code;
response.set_closed(closed);
responses.push(response);
}
}
responses
}
/// Cleanup when connection drops
pub fn on_disconnect(&self) {
if !self.is_persistent {
// Remove non-persistent service
remove_service(&self.service_id);
}
}
}

View File

@@ -20,6 +20,7 @@ impl InvokeUiCM for SciterHandler {
client.id,
client.is_file_transfer,
client.is_view_camera,
client.is_terminal,
client.port_forward.clone(),
client.peer_id.clone(),
client.name.clone(),

View File

@@ -29,7 +29,7 @@ class Body: Reactor.Component
};
var right_style = show_chat ? "" : "display: none";
var disconnected = c.disconnected;
var show_elevation_btn = handler.can_elevate() && show_elevation && !c.is_file_transfer && c.port_forward.length == 0;
var show_elevation_btn = handler.can_elevate() && show_elevation && !c.is_file_transfer && !c.is_view_camera && !c.is_terminal && c.port_forward.length == 0;
var show_accept_btn = handler.get_option('approve-mode') != 'password';
// below size:* is a workaround for Linux, it already set in css, but not work, shit sciter
return <div .content style="size:*">
@@ -48,8 +48,8 @@ class Body: Reactor.Component
</div>
</div>
<div />
{c.is_file_transfer || c.port_forward || disconnected ? "" : <div>{translate('Permissions')}</div>}
{c.is_file_transfer || c.port_forward || disconnected ? "" : <div> <div .permissions>
{c.is_file_transfer || c.is_terminal || c.port_forward || disconnected ? "" : <div>{translate('Permissions')}</div>}
{c.is_file_transfer || c.is_terminal || c.port_forward || disconnected ? "" : <div> <div .permissions>
<div class={!c.keyboard ? "disabled" : ""} title={translate('Enable keyboard/mouse')}><icon .keyboard /></div>
<div class={!c.clipboard ? "disabled" : ""} title={translate('Enable clipboard')}><icon .clipboard /></div>
<div class={!c.audio ? "disabled" : ""} title={translate('Enable audio')}><icon .audio /></div>
@@ -60,6 +60,9 @@ class Body: Reactor.Component
<div class={!c.block_input ? "disabled" : ""} title={translate('Enable blocking user input')} style={is_win ? "" : "display:none;"}><icon .block_input /></div>
</div></div>
}
{c.is_file_transfer ? <div>{translate('Transfer file')}</div> : ""}
{c.is_view_camera ? <div>{translate('View camera')}</div> : ""}
{c.is_terminal ? <div>{translate('Terminal')}</div> : ""}
{c.port_forward ? <div>Port Forwarding: {c.port_forward}</div> : ""}
<div style="size:*"/>
<div .outer_buttons>
@@ -72,10 +75,10 @@ class Body: Reactor.Component
{auth && !disconnected ? <button #disconnect .control .button>{translate('Disconnect')}</button> : "" }
{auth && disconnected ? <button #close .control .button>{translate('Close')}</button> : "" }
</div>
{c.is_file_transfer || c.port_forward ? "" : <div .chaticon>{svg_chat}</div>}
{c.is_file_transfer || c.is_terminal || c.port_forward ? "" : <div .chaticon>{svg_chat}</div>}
</div>
<div .right-panel style={right_style}>
{c.is_file_transfer || c.port_forward ? "" : <ChatBox msgs={c.msgs} callback={callback} />}
{c.is_file_transfer || c.is_terminal || c.port_forward ? "" : <ChatBox msgs={c.msgs} callback={callback} />}
</div>
</div>;
}
@@ -356,7 +359,7 @@ function bring_to_top(idx=-1) {
}
}
handler.addConnection = function(id, is_file_transfer, is_view_camera, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart, recording, block_input) {
handler.addConnection = function(id, is_file_transfer, is_view_camera, is_terminal, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart, recording, block_input) {
stdout.println("new connection #" + id + ": " + peer_id);
var conn;
connections.map(function(c) {
@@ -373,7 +376,7 @@ handler.addConnection = function(id, is_file_transfer, is_view_camera, port_forw
});
if (!name) name = "NA";
conn = {
id: id, is_file_transfer: is_file_transfer, peer_id: peer_id,
id: id, is_file_transfer: is_file_transfer, is_view_camera: is_view_camera, is_terminal: is_terminal, peer_id: peer_id,
port_forward: port_forward,
name: name, authorized: authorized, time: new Date(), now: new Date(),
keyboard: keyboard, clipboard: clipboard, msgs: [], unreaded: 0,

View File

@@ -313,7 +313,9 @@ class MyIdMenu: Reactor.Component {
<menu.context #config-options>
<li #enable-keyboard><span>{svg_checkmark}</span>{translate('Enable keyboard/mouse')}</li>
<li #enable-clipboard><span>{svg_checkmark}</span>{translate('Enable clipboard')}</li>
<li #enable-file-transfer><span>{svg_checkmark}</span>{translate('Enable file transfer')}</li>
<li #enable-file-transfer><span>{svg_checkmark}</span>{translate('Enable file transfer')}</li>
<li #enable-camera><span>{svg_checkmark}</span>{translate('Enable camera')}</li>
<li #enable-terminal><span>{svg_checkmark}</span>{translate('Enable terminal')}</li>
<li #enable-remote-restart><span>{svg_checkmark}</span>{translate('Enable remote restart')}</li>
<li #enable-tunnel><span>{svg_checkmark}</span>{translate('Enable TCP tunneling')}</li>
{is_win ? <li #enable-block-input><span>{svg_checkmark}</span>{translate('Enable blocking user input')}</li> : ""}

View File

@@ -313,16 +313,10 @@ impl InvokeUiSession for SciterHandler {
fn on_connected(&self, conn_type: ConnType) {
match conn_type {
ConnType::RDP => {}
ConnType::PORT_FORWARD => {}
ConnType::FILE_TRANSFER => {}
ConnType::VIEW_CAMERA => {}
ConnType::DEFAULT_CONN => {
crate::keyboard::client::start_grab_loop();
}
// Left empty code from compilation.
// Please replace the code in the PR.
ConnType::VIEW_CAMERA => {}
_ => {}
}
}
@@ -387,6 +381,11 @@ impl InvokeUiSession for SciterHandler {
fn handle_screenshot_resp(&self, _sid: String, msg: String) {
self.call("screenshot", &make_args!(msg));
}
fn handle_terminal_response(&self, _response: TerminalResponse) {
// Terminal support is not implemented for Sciter UI
// This is a stub implementation to satisfy the trait requirements
}
}
pub struct SciterSession(Session<SciterHandler>);

View File

@@ -48,6 +48,7 @@ pub struct Client {
pub disconnected: bool,
pub is_file_transfer: bool,
pub is_view_camera: bool,
pub is_terminal: bool,
pub port_forward: String,
pub name: String,
pub peer_id: String,
@@ -130,6 +131,7 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
id: i32,
is_file_transfer: bool,
is_view_camera: bool,
is_terminal: bool,
port_forward: String,
peer_id: String,
name: String,
@@ -150,6 +152,7 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
disconnected: false,
is_file_transfer,
is_view_camera,
is_terminal,
port_forward,
name: name.clone(),
peer_id: peer_id.clone(),
@@ -206,7 +209,7 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
.read()
.unwrap()
.iter()
.filter(|(_k, v)| !v.is_file_transfer)
.filter(|(_k, v)| !v.is_file_transfer && !v.is_terminal)
.next()
.is_none()
{
@@ -405,9 +408,9 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
}
Ok(Some(data)) => {
match data {
Data::Login{id, is_file_transfer, is_view_camera, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, file_transfer_enabled: _file_transfer_enabled, restart, recording, block_input, from_switch} => {
Data::Login{id, is_file_transfer, is_view_camera, is_terminal, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, file_transfer_enabled: _file_transfer_enabled, restart, recording, block_input, from_switch} => {
log::debug!("conn_id: {}", id);
self.cm.add_connection(id, is_file_transfer, is_view_camera, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart, recording, block_input, from_switch, self.tx.clone());
self.cm.add_connection(id, is_file_transfer, is_view_camera, is_terminal, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart, recording, block_input, from_switch, self.tx.clone());
self.conn_id = id;
#[cfg(target_os = "windows")]
{
@@ -676,6 +679,7 @@ pub async fn start_listen<T: InvokeUiCM>(
id,
is_file_transfer,
is_view_camera,
is_terminal,
port_forward,
peer_id,
name,
@@ -695,6 +699,7 @@ pub async fn start_listen<T: InvokeUiCM>(
id,
is_file_transfer,
is_view_camera,
is_terminal,
port_forward,
peer_id,
name,

View File

@@ -20,7 +20,7 @@ use uuid::Uuid;
use hbb_common::fs;
use hbb_common::{
allow_err,
config::{Config, LocalConfig, PeerConfig},
config::{keys, Config, LocalConfig, PeerConfig},
get_version_number, log,
message_proto::*,
rendezvous_proto::ConnType,
@@ -191,10 +191,18 @@ impl<T: InvokeUiSession> Session<T> {
.eq(&ConnType::FILE_TRANSFER)
}
pub fn is_default(&self) -> bool {
self.lc.read().unwrap().conn_type.eq(&ConnType::DEFAULT_CONN)
}
pub fn is_view_camera(&self) -> bool {
self.lc.read().unwrap().conn_type.eq(&ConnType::VIEW_CAMERA)
}
pub fn is_terminal(&self) -> bool {
self.lc.read().unwrap().conn_type.eq(&ConnType::TERMINAL)
}
pub fn is_port_forward(&self) -> bool {
let conn_type = self.lc.read().unwrap().conn_type;
conn_type == ConnType::PORT_FORWARD || conn_type == ConnType::RDP
@@ -341,7 +349,7 @@ impl<T: InvokeUiSession> Session<T> {
pub fn toggle_option(&self, name: String) {
let msg = self.lc.write().unwrap().toggle_option(name.clone());
#[cfg(all(target_os = "windows", not(feature = "flutter")))]
if name == hbb_common::config::keys::OPTION_ENABLE_FILE_COPY_PASTE {
if name == keys::OPTION_ENABLE_FILE_COPY_PASTE {
self.send(Data::ToggleClipboardFile);
}
if let Some(msg) = msg {
@@ -746,6 +754,57 @@ impl<T: InvokeUiSession> Session<T> {
self.send(Data::Message(msg_out));
}
// Terminal methods
pub fn open_terminal(&self, terminal_id: i32, rows: u32, cols: u32) {
let mut action = TerminalAction::new();
action.set_open(OpenTerminal {
terminal_id,
rows,
cols,
..Default::default()
});
let mut msg_out = Message::new();
msg_out.set_terminal_action(action);
self.send(Data::Message(msg_out));
}
pub fn send_terminal_input(&self, terminal_id: i32, data: String) {
let mut action = TerminalAction::new();
action.set_data(TerminalData {
terminal_id,
data: bytes::Bytes::from(data.into_bytes()),
..Default::default()
});
let mut msg_out = Message::new();
msg_out.set_terminal_action(action);
self.send(Data::Message(msg_out));
}
pub fn resize_terminal(&self, terminal_id: i32, rows: u32, cols: u32) {
let mut action = TerminalAction::new();
action.set_resize(ResizeTerminal {
terminal_id,
rows,
cols,
..Default::default()
});
let mut msg_out = Message::new();
msg_out.set_terminal_action(action);
self.send(Data::Message(msg_out));
}
pub fn close_terminal(&self, terminal_id: i32) {
let mut action = TerminalAction::new();
action.set_close(CloseTerminal {
terminal_id,
..Default::default()
});
let mut msg_out = Message::new();
msg_out.set_terminal_action(action);
self.send(Data::Message(msg_out));
}
pub fn capture_displays(&self, add: Vec<i32>, sub: Vec<i32>, set: Vec<i32>) {
let mut misc = Misc::new();
misc.set_capture_displays(CaptureDisplays {
@@ -1488,7 +1547,7 @@ impl<T: InvokeUiSession> Session<T> {
self.read_remote_dir(remote_dir, show_hidden);
}
}
} else {
} else if !self.is_terminal() {
self.msgbox(
"success",
"Successful",
@@ -1603,6 +1662,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
fn update_empty_dirs(&self, _res: ReadEmptyDirsResponse) {}
fn printer_request(&self, id: i32, path: String);
fn handle_screenshot_resp(&self, sid: String, msg: String);
fn handle_terminal_response(&self, response: TerminalResponse);
}
impl<T: InvokeUiSession> Deref for Session<T> {
@@ -1663,7 +1723,7 @@ impl<T: InvokeUiSession> Interface for Session<T> {
self.on_error("No active console user logged on, please connect and logon first.");
return;
}
} else if !self.is_port_forward() {
} else if !self.is_port_forward() && !self.is_terminal() {
if pi.displays.is_empty() {
self.lc.write().unwrap().handle_peer_info(&pi);
self.update_privacy_mode();
@@ -1695,7 +1755,7 @@ impl<T: InvokeUiSession> Interface for Session<T> {
self.set_peer_info(&pi);
if self.is_file_transfer() {
self.close_success();
} else if !self.is_port_forward() {
} else if !self.is_port_forward() && !self.is_terminal() {
self.msgbox(
"success",
"Successful",