Merge remote-tracking branch 'origin/master' into feat/x11/clipboard-file/init

Signed-off-by: 蔡略 <cailue@bupt.edu.cn>
This commit is contained in:
蔡略
2023-09-08 20:09:57 +08:00
302 changed files with 14257 additions and 6606 deletions

View File

@@ -56,9 +56,15 @@ impl Interface for Session {
}
"re-input-password" => {
log::error!("{}: {}", title, text);
let password = rpassword::prompt_password("Enter password: ").unwrap();
let login_data = Data::Login((password, true));
self.sender.send(login_data).ok();
match rpassword::prompt_password("Enter password: ") {
Ok(password) => {
let login_data = Data::Login((password, true));
self.sender.send(login_data).ok();
}
Err(e) => {
log::error!("reinput password failed, {:?}", e);
}
}
}
msg if msg.contains("error") => {
log::error!("{}: {}: {}", msgtype, title, text);
@@ -85,8 +91,23 @@ impl Interface for Session {
handle_hash(self.lc.clone(), &pass, hash, self, peer).await;
}
async fn handle_login_from_ui(&mut self, os_username: String, os_password: String, password: String, remember: bool, peer: &mut Stream) {
handle_login_from_ui(self.lc.clone(), os_username, os_password, password, remember, peer).await;
async fn handle_login_from_ui(
&mut self,
os_username: String,
os_password: String,
password: String,
remember: bool,
peer: &mut Stream,
) {
handle_login_from_ui(
self.lc.clone(),
os_username,
os_password,
password,
remember,
peer,
)
.await;
}
async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream) {
@@ -117,13 +138,14 @@ pub async fn connect_test(id: &str, key: String, token: String) {
break;
}
Ok(Some(Ok(bytes))) => {
let msg_in = Message::parse_from_bytes(&bytes).unwrap();
match msg_in.union {
Some(message::Union::Hash(hash)) => {
log::info!("Got hash");
break;
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
match msg_in.union {
Some(message::Union::Hash(hash)) => {
log::info!("Got hash");
break;
}
_ => {}
}
_ => {}
}
}
_ => {}

View File

@@ -24,6 +24,8 @@ use sha2::{Digest, Sha256};
use uuid::Uuid;
pub use file_trait::FileManager;
#[cfg(windows)]
use hbb_common::tokio;
#[cfg(not(feature = "flutter"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::tokio::sync::mpsc::UnboundedSender;
@@ -32,7 +34,8 @@ use hbb_common::{
anyhow::{anyhow, Context},
bail,
config::{
Config, PeerConfig, PeerInfoSerde, Resolution, CONNECT_TIMEOUT, READ_TIMEOUT, RELAY_PORT,
Config, LocalConfig, PeerConfig, PeerInfoSerde, Resolution, CONNECT_TIMEOUT, READ_TIMEOUT,
RELAY_PORT,
},
get_version_number, log,
message_proto::{option_message::BoolOption, *},
@@ -40,6 +43,7 @@ use hbb_common::{
rand,
rendezvous_proto::*,
socket_client,
sodiumoxide::base64,
sodiumoxide::crypto::{box_, secretbox, sign},
tcp::FramedStream,
timeout,
@@ -53,13 +57,16 @@ use scrap::{
ImageFormat, ImageRgb,
};
use crate::is_keyboard_mode_supported;
use crate::{
common::input::{MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT, MOUSE_TYPE_DOWN, MOUSE_TYPE_UP},
is_keyboard_mode_supported,
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::{check_clipboard, ClipboardContext, CLIPBOARD_INTERVAL};
#[cfg(not(feature = "flutter"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::ui_session_interface::SessionPermissionConfig;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::{check_clipboard, ClipboardContext, CLIPBOARD_INTERVAL};
pub use super::lang::*;
@@ -88,6 +95,7 @@ pub const LOGIN_MSG_PASSWORD_EMPTY: &str = "Empty Password";
pub const LOGIN_MSG_PASSWORD_WRONG: &str = "Wrong Password";
pub const LOGIN_MSG_NO_PASSWORD_ACCESS: &str = "No Password Access";
pub const LOGIN_MSG_OFFLINE: &str = "Offline";
pub const LOGIN_SCREEN_WAYLAND: &str = "Wayland login screen is not supported";
#[cfg(target_os = "linux")]
pub const SCRAP_UBUNTU_HIGHER_REQUIRED: &str = "Wayland requires Ubuntu 21.04 or higher version.";
#[cfg(target_os = "linux")]
@@ -213,6 +221,8 @@ impl Client {
conn_type: ConnType,
interface: impl Interface,
) -> ResultType<(Stream, bool, Option<Vec<u8>>)> {
interface.update_direct(None);
interface.update_received(false);
match Self::_start(peer, key, token, conn_type, interface).await {
Err(err) => {
let err_str = err.to_string();
@@ -313,19 +323,20 @@ impl Client {
if !ph.other_failure.is_empty() {
bail!(ph.other_failure);
}
match ph.failure.enum_value_or_default() {
punch_hole_response::Failure::ID_NOT_EXIST => {
match ph.failure.enum_value() {
Ok(punch_hole_response::Failure::ID_NOT_EXIST) => {
bail!("ID does not exist");
}
punch_hole_response::Failure::OFFLINE => {
Ok(punch_hole_response::Failure::OFFLINE) => {
bail!("Remote desktop is offline");
}
punch_hole_response::Failure::LICENSE_MISMATCH => {
Ok(punch_hole_response::Failure::LICENSE_MISMATCH) => {
bail!("Key mismatch");
}
punch_hole_response::Failure::LICENSE_OVERUSE => {
Ok(punch_hole_response::Failure::LICENSE_OVERUSE) => {
bail!("Key overuse");
}
_ => bail!("other punch hole failure"),
}
} else {
peer_nat_type = ph.nat_type();
@@ -353,15 +364,8 @@ impl Client {
my_addr.is_ipv4(),
)
.await?;
let pk = Self::secure_connection(
peer,
signed_id_pk,
key,
&mut conn,
false,
interface,
)
.await?;
let pk =
Self::secure_connection(peer, signed_id_pk, key, &mut conn).await?;
return Ok((conn, false, pk));
}
_ => {
@@ -459,6 +463,7 @@ impl Client {
let mut conn =
socket_client::connect_tcp_local(peer, Some(local_addr), connect_timeout).await;
let mut direct = !conn.is_err();
interface.update_direct(Some(direct));
if interface.is_force_relay() || conn.is_err() {
if !relay_server.is_empty() {
conn = Self::request_relay(
@@ -471,11 +476,9 @@ impl Client {
conn_type,
)
.await;
if conn.is_err() {
bail!(
"Failed to connect via relay server: {}",
conn.err().unwrap()
);
interface.update_direct(Some(false));
if let Err(e) = conn {
bail!("Failed to connect via relay server: {}", e);
}
direct = false;
} else {
@@ -490,8 +493,7 @@ impl Client {
}
let mut conn = conn?;
log::info!("{:?} used to establish connection", start.elapsed());
let pk = Self::secure_connection(peer_id, signed_id_pk, key, &mut conn, direct, interface)
.await?;
let pk = Self::secure_connection(peer_id, signed_id_pk, key, &mut conn).await?;
Ok((conn, direct, pk))
}
@@ -501,8 +503,6 @@ impl Client {
signed_id_pk: Vec<u8>,
key: &str,
conn: &mut Stream,
direct: bool,
interface: impl Interface,
) -> ResultType<Option<Vec<u8>>> {
let rs_pk = get_rs_pk(if key.is_empty() {
hbb_common::config::RS_PUB_KEY
@@ -511,11 +511,13 @@ impl Client {
});
let mut sign_pk = None;
let mut option_pk = None;
if !signed_id_pk.is_empty() && rs_pk.is_some() {
if let Ok((id, pk)) = decode_id_pk(&signed_id_pk, &rs_pk.unwrap()) {
if id == peer_id {
sign_pk = Some(sign::PublicKey(pk));
option_pk = Some(pk.to_vec());
if !signed_id_pk.is_empty() {
if let Some(rs_pk) = rs_pk {
if let Ok((id, pk)) = decode_id_pk(&signed_id_pk, &rs_pk) {
if id == peer_id {
sign_pk = Some(sign::PublicKey(pk));
option_pk = Some(pk.to_vec());
}
}
}
if sign_pk.is_none() {
@@ -532,13 +534,7 @@ impl Client {
};
match timeout(READ_TIMEOUT, conn.next()).await? {
Some(res) => {
let bytes = match res {
Ok(bytes) => bytes,
Err(err) => {
interface.set_force_relay(direct, false);
bail!("{}", err);
}
};
let bytes = res?;
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
if let Some(message::Union::SignedId(si)) = msg_in.union {
if let Ok((id, their_pk_b)) = decode_id_pk(&si.id, &sign_pk) {
@@ -1073,17 +1069,15 @@ pub struct LoginConfigHandler {
config: PeerConfig,
pub port_forward: (String, i32),
pub version: i64,
pub conn_id: i32,
features: Option<Features>,
session_id: u64,
pub session_id: u64, // used for local <-> server communication
pub supported_encoding: SupportedEncoding,
pub restarting_remote_device: bool,
pub force_relay: bool,
pub direct: Option<bool>,
pub received: bool,
switch_uuid: Option<String>,
pub success_time: Option<hbb_common::tokio::time::Instant>,
pub direct_error_counter: usize,
pub save_ab_password_to_recent: bool, // true: connected with ab password
}
impl Deref for LoginConfigHandler {
@@ -1123,15 +1117,18 @@ impl LoginConfigHandler {
let config = self.load_config();
self.remember = !config.password.is_empty();
self.config = config;
self.session_id = rand::random();
let mut sid = rand::random();
if sid == 0 {
// you won the lottery
sid = 1;
}
self.session_id = sid;
self.supported_encoding = Default::default();
self.restarting_remote_device = false;
self.force_relay = !self.get_option("force-always-relay").is_empty() || force_relay;
self.direct = None;
self.received = false;
self.switch_uuid = switch_uuid;
self.success_time = None;
self.direct_error_counter = 0;
}
/// Check if the client should auto login.
@@ -1217,7 +1214,11 @@ impl LoginConfigHandler {
/// * `v` - value of option
pub fn save_ui_flutter(&mut self, k: String, v: String) {
let mut config = self.load_config();
config.ui_flutter.insert(k, v);
if v.is_empty() {
config.ui_flutter.remove(&k);
} else {
config.ui_flutter.insert(k, v);
}
self.save_config(config);
}
@@ -1650,11 +1651,26 @@ impl LoginConfigHandler {
log::debug!("remember password of {}", self.id);
}
} else {
if !password0.is_empty() {
if self.save_ab_password_to_recent {
config.password = password;
log::debug!("save ab password of {} to recent", self.id);
} else if !password0.is_empty() {
config.password = Default::default();
log::debug!("remove password of {}", self.id);
}
}
#[cfg(feature = "flutter")]
{
// sync ab password with PeerConfig password
let password = base64::encode(config.password.clone(), base64::Variant::Original);
let evt: HashMap<&str, String> = HashMap::from([
("name", "sync_peer_password_to_ab".to_string()),
("id", self.id.clone()),
("password", password),
]);
let evt = serde_json::ser::to_string(&evt).unwrap_or("".to_owned());
crate::flutter::push_global_event(crate::flutter::APP_TYPE_MAIN, evt);
}
if config.keyboard_mode.is_empty() {
if is_keyboard_mode_supported(&KeyboardMode::Map, get_version_number(&pi.version)) {
config.keyboard_mode = KeyboardMode::Map.to_string();
@@ -1669,7 +1685,6 @@ impl LoginConfigHandler {
config.keyboard_mode = KeyboardMode::Legacy.to_string();
}
}
self.conn_id = pi.conn_id;
// no matter if change, for update file time
self.save_config(config);
self.supported_encoding = pi.encoding.clone().unwrap_or_default();
@@ -1759,18 +1774,6 @@ impl LoginConfigHandler {
msg_out.set_misc(misc);
msg_out
}
pub fn set_force_relay(&mut self, direct: bool, received: bool) {
self.force_relay = false;
if direct && !received {
let errno = errno::errno().0;
// TODO: check mac and ios
if cfg!(windows) && errno == 10054 || !cfg!(windows) && errno == 104 {
self.force_relay = true;
self.set_option("force-always-relay".to_owned(), "Y".to_owned());
}
}
}
}
/// Media data.
@@ -1813,6 +1816,8 @@ where
let mut skip_beginning = 0;
std::thread::spawn(move || {
#[cfg(windows)]
sync_cpu_usage();
let mut video_handler = VideoHandler::new();
loop {
if let Ok(data) = video_receiver.recv() {
@@ -1845,7 +1850,7 @@ where
);
}
// Clear to get real-time fps
if count > 300 {
if count > 150 {
count = 0;
duration = Duration::ZERO;
}
@@ -1896,6 +1901,39 @@ pub fn start_audio_thread() -> MediaSender {
audio_sender
}
#[cfg(windows)]
fn sync_cpu_usage() {
use std::sync::Once;
static ONCE: Once = Once::new();
ONCE.call_once(|| {
let t = std::thread::spawn(do_sync_cpu_usage);
t.join().ok();
});
}
#[cfg(windows)]
#[tokio::main(flavor = "current_thread")]
async fn do_sync_cpu_usage() {
use crate::ipc::{connect, Data};
let start = std::time::Instant::now();
match connect(50, "").await {
Ok(mut conn) => {
if conn.send(&&Data::SyncWinCpuUsage(None)).await.is_ok() {
if let Ok(Some(data)) = conn.next_timeout(50).await {
match data {
Data::SyncWinCpuUsage(cpu_usage) => {
hbb_common::platform::windows::sync_cpu_usage(cpu_usage);
}
_ => {}
}
}
}
}
_ => {}
}
log::info!("{:?} used to sync cpu usage", start.elapsed());
}
/// Handle latency test.
///
/// # Arguments
@@ -1991,18 +2029,56 @@ pub fn send_mouse(
interface.send(Data::Message(msg_out));
}
#[inline]
pub fn send_pointer_device_event(
mut evt: PointerDeviceEvent,
alt: bool,
ctrl: bool,
shift: bool,
command: bool,
interface: &impl Interface,
) {
let mut msg_out = Message::new();
if alt {
evt.modifiers.push(ControlKey::Alt.into());
}
if shift {
evt.modifiers.push(ControlKey::Shift.into());
}
if ctrl {
evt.modifiers.push(ControlKey::Control.into());
}
if command {
evt.modifiers.push(ControlKey::Meta.into());
}
msg_out.set_pointer_device_event(evt);
interface.send(Data::Message(msg_out));
}
/// Activate OS by sending mouse movement.
///
/// # Arguments
///
/// * `interface` - The interface for sending data.
fn activate_os(interface: &impl Interface) {
/// * `send_left_click` - Whether to send a click event.
fn activate_os(interface: &impl Interface, send_left_click: bool) {
let left_down = MOUSE_BUTTON_LEFT << 3 | MOUSE_TYPE_DOWN;
let left_up = MOUSE_BUTTON_LEFT << 3 | MOUSE_TYPE_UP;
let right_down = MOUSE_BUTTON_RIGHT << 3 | MOUSE_TYPE_DOWN;
let right_up = MOUSE_BUTTON_RIGHT << 3 | MOUSE_TYPE_UP;
send_mouse(left_up, 0, 0, false, false, false, false, interface);
std::thread::sleep(Duration::from_millis(50));
send_mouse(0, 0, 0, false, false, false, false, interface);
std::thread::sleep(Duration::from_millis(50));
send_mouse(0, 3, 3, false, false, false, false, interface);
let (click_down, click_up) = if send_left_click {
(left_down, left_up)
} else {
(right_down, right_up)
};
std::thread::sleep(Duration::from_millis(50));
send_mouse(1 | 1 << 3, 0, 0, false, false, false, false, interface);
send_mouse(2 | 1 << 3, 0, 0, false, false, false, false, interface);
send_mouse(click_down, 0, 0, false, false, false, false, interface);
send_mouse(click_up, 0, 0, false, false, false, false, interface);
/*
let mut key_event = KeyEvent::new();
// do not use Esc, which has problem with Linux
@@ -2035,10 +2111,15 @@ pub fn input_os_password(p: String, activate: bool, interface: impl Interface) {
/// * `activate` - Whether to activate OS.
/// * `interface` - The interface for sending data.
fn _input_os_password(p: String, activate: bool, interface: impl Interface) {
let input_password = !p.is_empty();
if activate {
activate_os(&interface);
// Click event is used to bring up the password input box.
activate_os(&interface, input_password);
std::thread::sleep(Duration::from_millis(1200));
}
if !input_password {
return;
}
let mut key_event = KeyEvent::new();
key_event.press = true;
let mut msg_out = Message::new();
@@ -2062,7 +2143,13 @@ struct LoginErrorMsgBox {
lazy_static::lazy_static! {
static ref LOGIN_ERROR_MAP: Arc<HashMap<&'static str, LoginErrorMsgBox>> = {
use hbb_common::config::LINK_HEADLESS_LINUX_SUPPORT;
let map = HashMap::from([(LOGIN_MSG_DESKTOP_SESSION_NOT_READY, LoginErrorMsgBox{
let map = HashMap::from([(LOGIN_SCREEN_WAYLAND, LoginErrorMsgBox{
msgtype: "error",
title: "Login Error",
text: "Login screen using Wayland is not supported",
link: "https://rustdesk.com/docs/en/manual/linux/#login-screen",
try_again: true,
}), (LOGIN_MSG_DESKTOP_SESSION_NOT_READY, LoginErrorMsgBox{
msgtype: "session-login",
title: "",
text: "",
@@ -2122,6 +2209,7 @@ pub fn handle_login_error(
err: &str,
interface: &impl Interface,
) -> bool {
lc.write().unwrap().save_ab_password_to_recent = false;
if err == LOGIN_MSG_PASSWORD_EMPTY {
lc.write().unwrap().password = Default::default();
interface.msgbox("input-password", "Password Required", "", "");
@@ -2190,6 +2278,26 @@ pub async fn handle_hash(
if password.is_empty() {
password = lc.read().unwrap().config.password.clone();
}
if password.is_empty() {
let access_token = LocalConfig::get_option("access_token");
let ab = hbb_common::config::Ab::load();
if !access_token.is_empty() && access_token == ab.access_token {
let id = lc.read().unwrap().id.clone();
if let Some(p) = ab
.peers
.iter()
.find_map(|p| if p.id == id { Some(p) } else { None })
{
if let Ok(hash) = base64::decode(p.hash.clone(), base64::Variant::Original) {
if !hash.is_empty() {
password = hash;
lc.write().unwrap().save_ab_password_to_recent = true;
}
}
}
}
}
lc.write().unwrap().password = password.clone();
let password = if password.is_empty() {
// login without password, the remote side can click accept
interface.msgbox("input-password", "Password Required", "", "");
@@ -2261,9 +2369,9 @@ pub async fn handle_login_from_ui(
hasher.update(&lc.read().unwrap().hash.salt);
let res = hasher.finalize();
lc.write().unwrap().remember = remember;
lc.write().unwrap().password = res[..].into();
res[..].into()
};
lc.write().unwrap().password = hash_password.clone();
let mut hasher2 = Sha256::new();
hasher2.update(&hash_password[..]);
hasher2.update(&lc.read().unwrap().hash.challenge);
@@ -2315,16 +2423,48 @@ pub trait Interface: Send + Clone + 'static + Sized {
async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream);
fn get_login_config_handler(&self) -> Arc<RwLock<LoginConfigHandler>>;
fn set_force_relay(&self, direct: bool, received: bool) {
self.get_login_config_handler()
.write()
.unwrap()
.set_force_relay(direct, received);
}
fn is_force_relay(&self) -> bool {
self.get_login_config_handler().read().unwrap().force_relay
}
fn swap_modifier_mouse(&self, _msg: &mut hbb_common::protos::message::MouseEvent) {}
fn update_direct(&self, direct: Option<bool>) {
self.get_login_config_handler().write().unwrap().direct = direct;
}
fn update_received(&self, received: bool) {
self.get_login_config_handler().write().unwrap().received = received;
}
fn on_establish_connection_error(&self, err: String) {
log::error!("Connection closed: {}", err);
let title = "Connection Error";
let text = err.to_string();
let lc = self.get_login_config_handler();
let direct = lc.read().unwrap().direct;
let received = lc.read().unwrap().received;
let relay_condition = direct == Some(true) && !received;
// force relay
let errno = errno::errno().0;
if relay_condition
&& (cfg!(windows) && (errno == 10054 || err.contains("10054"))
|| !cfg!(windows) && (errno == 104 || err.contains("104")))
{
lc.write().unwrap().force_relay = true;
lc.write()
.unwrap()
.set_option("force-always-relay".to_owned(), "Y".to_owned());
}
// relay-hint
if cfg!(feature = "flutter") && relay_condition {
self.msgbox("relay-hint", title, &text, "");
} else {
self.msgbox("error", title, &text, "");
}
}
}
/// Data used by the client interface.

View File

@@ -56,6 +56,7 @@ pub struct Remote<T: InvokeUiSession> {
remove_jobs: HashMap<i32, RemoveJob>,
timer: Interval,
last_update_jobs_status: (Instant, HashMap<i32, u64>),
is_connected: bool,
first_frame: bool,
#[cfg(any(target_os = "windows", target_os = "linux"))]
client_conn_id: i32, // used for file clipboard
@@ -90,6 +91,7 @@ impl<T: InvokeUiSession> Remote<T> {
remove_jobs: Default::default(),
timer: time::interval(SEC30),
last_update_jobs_status: (Instant::now(), Default::default()),
is_connected: false,
first_frame: false,
#[cfg(any(target_os = "windows", target_os = "linux"))]
client_conn_id: 0,
@@ -124,7 +126,7 @@ impl<T: InvokeUiSession> Remote<T> {
{
Ok((mut peer, direct, pk)) => {
self.handler.set_connection_type(peer.is_secured(), direct); // flutter -> connection_ready
self.handler.set_connection_info(direct, false);
self.handler.update_direct(Some(direct));
if conn_type == ConnType::DEFAULT_CONN {
self.handler
.set_fingerprint(crate::common::pk_to_fingerprint(pk.unwrap_or_default()));
@@ -160,24 +162,14 @@ impl<T: InvokeUiSession> Remote<T> {
if let Some(res) = res {
match res {
Err(err) => {
log::error!("Connection closed: {}", err);
self.handler.set_force_relay(direct, received);
let msgtype = "error";
let title = "Connection Error";
let text = err.to_string();
let show_relay_hint = self.handler.show_relay_hint(last_recv_time, msgtype, title, &text);
if show_relay_hint{
self.handler.msgbox("relay-hint", title, &text, "");
} else {
self.handler.msgbox(msgtype, title, &text, "");
}
self.handler.on_establish_connection_error(err.to_string());
break;
}
Ok(ref bytes) => {
last_recv_time = Instant::now();
if !received {
received = true;
self.handler.set_connection_info(direct, true);
self.handler.update_received(true);
}
self.data_count.fetch_add(bytes.len(), Ordering::Relaxed);
if !self.handle_msg_from_peer(bytes, &mut peer).await {
@@ -205,28 +197,7 @@ impl<T: InvokeUiSession> Remote<T> {
}
_msg = rx_clip_client.recv() => {
#[cfg(any(target_os="windows", target_os="linux"))]
match _msg {
Some(clip) => match clip {
clipboard::ClipboardFile::NotifyCallback{r#type, title, text} => {
self.handler.msgbox(&r#type, &title, &text, "");
}
_ => {
let is_stopping_allowed = clip.is_stopping_allowed();
let server_file_transfer_enabled = *self.handler.server_file_transfer_enabled.read().unwrap();
let file_transfer_enabled = self.handler.lc.read().unwrap().enable_file_transfer.v;
let stop = is_stopping_allowed && !(server_file_transfer_enabled && file_transfer_enabled);
log::debug!("Process clipboard message from system, stop: {}, is_stopping_allowed: {}, server_file_transfer_enabled: {}, file_transfer_enabled: {}", stop, is_stopping_allowed, server_file_transfer_enabled, file_transfer_enabled);
if stop {
ContextSend::set_is_stopped();
} else {
allow_err!(peer.send(&crate::clipboard_file::clip_2_msg(clip)).await);
}
}
}
None => {
// unreachable!()
}
}
self.handle_local_clipboard_msg(&mut peer, _msg).await;
}
_ = self.timer.tick() => {
if last_recv_time.elapsed() >= SEC30 {
@@ -244,7 +215,7 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
_ = status_timer.tick() => {
self.fps_control();
self.fps_control(direct);
let elapsed = fps_instant.elapsed().as_millis();
if elapsed < 1000 {
continue;
@@ -271,8 +242,7 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
Err(err) => {
self.handler
.msgbox("error", "Connection Error", &err.to_string(), "");
self.handler.on_establish_connection_error(err.to_string());
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -288,6 +258,44 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
#[cfg(any(target_os = "windows", target_os = "linux"))]
async fn handle_local_clipboard_msg(
&self,
peer: &mut crate::client::FramedStream,
msg: Option<clipboard::ClipboardFile>,
) {
match msg {
Some(clip) => match clip {
clipboard::ClipboardFile::NotifyCallback {
r#type,
title,
text,
} => {
self.handler.msgbox(&r#type, &title, &text, "");
}
_ => {
let is_stopping_allowed = clip.is_stopping_allowed();
let server_file_transfer_enabled =
*self.handler.server_file_transfer_enabled.read().unwrap();
let file_transfer_enabled =
self.handler.lc.read().unwrap().enable_file_transfer.v;
let stop = is_stopping_allowed
&& (!self.is_connected
|| !(server_file_transfer_enabled && file_transfer_enabled));
log::debug!("Process clipboard message from system, stop: {}, is_stopping_allowed: {}, server_file_transfer_enabled: {}, file_transfer_enabled: {}", stop, is_stopping_allowed, server_file_transfer_enabled, file_transfer_enabled);
if stop {
ContextSend::set_is_stopped();
} else {
allow_err!(peer.send(&crate::clipboard_file::clip_2_msg(clip)).await);
}
}
},
None => {
// unreachable!()
}
}
}
fn handle_job_status(&mut self, id: i32, file_num: i32, err: Option<String>) {
if let Some(job) = self.remove_jobs.get_mut(&id) {
if job.no_confirm {
@@ -475,9 +483,13 @@ impl<T: InvokeUiSession> Remote<T> {
// peer is not windows, need transform \ to /
fs::transform_windows_path(&mut files);
}
let total_size = job.total_size();
self.read_jobs.push(job);
self.timer = time::interval(MILLI1);
allow_err!(peer.send(&fs::new_receive(id, to, file_num, files)).await);
allow_err!(
peer.send(&fs::new_receive(id, to, file_num, files, total_size))
.await
);
}
}
}
@@ -560,7 +572,8 @@ impl<T: InvokeUiSession> Remote<T> {
id,
job.path.to_string_lossy().to_string(),
job.file_num,
job.files.clone()
job.files.clone(),
job.total_size(),
))
.await
);
@@ -843,8 +856,10 @@ impl<T: InvokeUiSession> Remote<T> {
transfer_metas.write_jobs.push(json_str);
}
log::info!("meta: {:?}", transfer_metas);
config.transfer = transfer_metas;
self.handler.save_config(config);
if config.transfer != transfer_metas {
config.transfer = transfer_metas;
self.handler.save_config(config);
}
true
}
@@ -875,38 +890,77 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
#[inline]
fn fps_control(&mut self) {
fn fps_control(&mut self, direct: bool) {
let len = self.video_queue.len();
let ctl = &mut self.fps_control;
// Current full speed decoding fps
let decode_fps = self.decode_fps.load(std::sync::atomic::Ordering::Relaxed);
// 500ms
let debounce = if decode_fps > 10 { decode_fps / 2 } else { 5 };
if len < debounce || decode_fps == 0 {
if decode_fps == 0 {
return;
}
// First setting , or the length of the queue still increases after setting, or exceed the size of the last setting again
if ctl.set_times < 10 // enough
&& (ctl.set_times == 0
|| (len > ctl.last_queue_size && ctl.last_set_instant.elapsed().as_secs() > 30))
let limited_fps = if direct {
decode_fps * 9 / 10 // 30 got 27
} else {
decode_fps * 4 / 5 // 30 got 24
};
// send full speed fps
let version = self.handler.lc.read().unwrap().version;
let max_encode_speed = 144 * 10 / 9;
if version >= hbb_common::get_version_number("1.2.1")
&& (ctl.last_full_speed_fps.is_none() // First time
|| ((ctl.last_full_speed_fps.unwrap_or_default() - decode_fps as i32).abs() >= 5 // diff 5
&& !(decode_fps > max_encode_speed // already exceed max encoding speed
&& ctl.last_full_speed_fps.unwrap_or_default() > max_encode_speed as i32)))
{
// 80% fps to ensure decoding is faster than encoding
let mut custom_fps = decode_fps as i32 * 4 / 5;
let mut misc = Misc::new();
misc.set_full_speed_fps(decode_fps as _);
let mut msg = Message::new();
msg.set_misc(misc);
self.sender.send(Data::Message(msg)).ok();
ctl.last_full_speed_fps = Some(decode_fps as _);
}
// decrease judgement
let debounce = if decode_fps > 10 { decode_fps / 2 } else { 5 }; // 500ms
let should_decrease = len >= debounce // exceed debounce
&& len > ctl.last_queue_size + 5 // still caching
&& !ctl.last_custom_fps.unwrap_or(i32::MAX) < limited_fps as i32; // NOT already set a smaller one
// increase judgement
if len <= 1 {
ctl.idle_counter += 1;
} else {
ctl.idle_counter = 0;
}
let mut should_increase = false;
if let Some(last_custom_fps) = ctl.last_custom_fps {
// ever set
if last_custom_fps + 5 < limited_fps as i32 && ctl.idle_counter > 3 {
// limited_fps is 5 larger than last set, and idle time is more than 3 seconds
should_increase = true;
}
}
if should_decrease || should_increase {
// limited_fps to ensure decoding is faster than encoding
let mut custom_fps = limited_fps as i32;
if custom_fps < 1 {
custom_fps = 1;
}
// send custom fps
let mut misc = Misc::new();
misc.set_option(OptionMessage {
custom_fps,
..Default::default()
});
if version > hbb_common::get_version_number("1.2.1") {
// avoid confusion with custom image quality fps
misc.set_auto_adjust_fps(custom_fps as _);
} else {
misc.set_option(OptionMessage {
custom_fps,
..Default::default()
});
}
let mut msg = Message::new();
msg.set_misc(misc);
self.sender.send(Data::Message(msg)).ok();
ctl.last_queue_size = len;
ctl.set_times += 1;
ctl.last_set_instant = Instant::now();
ctl.last_custom_fps = Some(custom_fps);
}
// send refresh
if ctl.refresh_times < 10 // enough
@@ -1002,6 +1056,8 @@ impl<T: InvokeUiSession> Remote<T> {
if self.handler.is_file_transfer() {
self.handler.load_last_jobs();
}
self.is_connected = true;
}
_ => {}
},
@@ -1415,12 +1471,9 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
}
Some(message::Union::PeerInfo(pi)) => match pi.conn_id {
crate::SYNC_PEER_INFO_DISPLAYS => {
self.handler.set_displays(&pi.displays);
}
_ => {}
},
Some(message::Union::PeerInfo(pi)) => {
self.handler.set_displays(&pi.displays);
}
_ => {}
}
}
@@ -1620,20 +1673,22 @@ impl RemoveJob {
struct FpsControl {
last_queue_size: usize,
set_times: usize,
refresh_times: usize,
last_set_instant: Instant,
last_refresh_instant: Instant,
last_full_speed_fps: Option<i32>,
last_custom_fps: Option<i32>,
idle_counter: usize,
}
impl Default for FpsControl {
fn default() -> Self {
Self {
last_queue_size: Default::default(),
set_times: Default::default(),
refresh_times: Default::default(),
last_set_instant: Instant::now(),
last_refresh_instant: Instant::now(),
last_full_speed_fps: None,
last_custom_fps: None,
idle_counter: 0,
}
}
}

View File

@@ -25,7 +25,7 @@ use hbb_common::{
protobuf::Enum,
protobuf::Message as _,
rendezvous_proto::*,
sleep, socket_client,
socket_client,
tcp::FramedStream,
tokio, ResultType,
};
@@ -39,8 +39,6 @@ pub type NotifyMessageBox = fn(String, String, String, String) -> dyn Future<Out
pub const CLIPBOARD_NAME: &'static str = "clipboard";
pub const CLIPBOARD_INTERVAL: u64 = 333;
pub const SYNC_PEER_INFO_DISPLAYS: i32 = 1;
#[cfg(all(target_os = "macos", feature = "flutter_texture_render"))]
// https://developer.apple.com/forums/thread/712709
// Memory alignment should be multiple of 64.
@@ -51,6 +49,11 @@ pub const DST_STRIDE_RGBA: usize = 1;
// the executable name of the portable version
pub const PORTABLE_APPNAME_RUNTIME_ENV_KEY: &str = "RUSTDESK_APPNAME";
pub const PLATFORM_WINDOWS: &str = "Windows";
pub const PLATFORM_LINUX: &str = "Linux";
pub const PLATFORM_MACOS: &str = "Mac OS";
pub const PLATFORM_ANDROID: &str = "Android";
pub mod input {
pub const MOUSE_TYPE_MOVE: i32 = 0;
pub const MOUSE_TYPE_DOWN: i32 = 1;
@@ -627,6 +630,12 @@ pub async fn get_rendezvous_server(ms_timeout: u64) -> (String, Vec<String>, boo
let (mut a, mut b) = get_rendezvous_server_(ms_timeout);
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let (mut a, mut b) = get_rendezvous_server_(ms_timeout).await;
#[cfg(windows)]
if let Ok(lic) = crate::platform::get_license_from_exe_name() {
if !lic.host.is_empty() {
a = lic.host;
}
}
let mut b: Vec<String> = b
.drain(..)
.map(|x| socket_client::check_port(x, config::RENDEZVOUS_PORT))
@@ -721,7 +730,7 @@ pub fn run_me<T: AsRef<std::ffi::OsStr>>(args: Vec<T>) -> std::io::Result<std::p
}
#[cfg(feature = "appimage")]
{
let appdir = std::env::var("APPDIR").unwrap();
let appdir = std::env::var("APPDIR").map_err(|_| std::io::ErrorKind::Other)?;
let appimage_cmd = std::path::Path::new(&appdir).join("AppRun");
log::info!("path: {:?}", appimage_cmd);
return std::process::Command::new(appimage_cmd).args(&args).spawn();
@@ -745,6 +754,45 @@ pub fn hostname() -> String {
return DEVICE_NAME.lock().unwrap().clone();
}
#[inline]
pub fn get_sysinfo() -> serde_json::Value {
use hbb_common::sysinfo::{CpuExt, System, SystemExt};
let system = System::new_all();
let memory = system.total_memory();
let memory = (memory as f64 / 1024. / 1024. / 1024. * 100.).round() / 100.;
let cpus = system.cpus();
let cpu_name = cpus.first().map(|x| x.brand()).unwrap_or_default();
let cpu_name = cpu_name.trim_end();
let cpu_freq = cpus.first().map(|x| x.frequency()).unwrap_or_default();
let cpu_freq = (cpu_freq as f64 / 1024. * 100.).round() / 100.;
let cpu = if cpu_freq > 0. {
format!("{}, {}GHz, ", cpu_name, cpu_freq)
} else {
"".to_owned() // android
};
let num_cpus = num_cpus::get();
let num_pcpus = num_cpus::get_physical();
let mut os = system.distribution_id();
os = format!("{} / {}", os, system.long_os_version().unwrap_or_default());
#[cfg(windows)]
{
os = format!("{os} - {}", system.os_version().unwrap_or_default());
}
let hostname = hostname(); // sys.hostname() return localhost on android in my test
use serde_json::json;
let mut out = json!({
"cpu": format!("{cpu}{num_cpus}/{num_pcpus} cores"),
"memory": format!("{memory}GB"),
"os": os,
"hostname": hostname,
});
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
out["username"] = json!(crate::platform::get_active_username());
}
out
}
#[inline]
pub fn check_port<T: std::string::ToString>(host: T, port: i32) -> String {
hbb_common::socket_client::check_port(host, port)
@@ -789,30 +837,19 @@ pub fn check_software_update() {
#[tokio::main(flavor = "current_thread")]
async fn check_software_update_() -> hbb_common::ResultType<()> {
sleep(3.).await;
let url = "https://github.com/rustdesk/rustdesk/releases/latest";
let latest_release_response = reqwest::get(url).await?;
let latest_release_version = latest_release_response
.url()
.path()
.rsplit('/')
.next()
.unwrap();
let rendezvous_server = format!("rs-sg.rustdesk.com:{}", config::RENDEZVOUS_PORT);
let (mut socket, rendezvous_server) =
socket_client::new_udp_for(&rendezvous_server, CONNECT_TIMEOUT).await?;
let response_url = latest_release_response.url().to_string();
let mut msg_out = RendezvousMessage::new();
msg_out.set_software_update(SoftwareUpdate {
url: crate::VERSION.to_owned(),
..Default::default()
});
socket.send(&msg_out, rendezvous_server).await?;
use hbb_common::protobuf::Message;
for _ in 0..2 {
if let Some(Ok((bytes, _))) = socket.next_timeout(READ_TIMEOUT).await {
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
if let Some(rendezvous_message::Union::SoftwareUpdate(su)) = msg_in.union {
let version = hbb_common::get_version_from_url(&su.url);
if get_version_number(&version) > get_version_number(crate::VERSION) {
*SOFTWARE_UPDATE_URL.lock().unwrap() = su.url;
}
}
}
}
if get_version_number(&latest_release_version) > get_version_number(crate::VERSION) {
*SOFTWARE_UPDATE_URL.lock().unwrap() = response_url;
}
Ok(())
}
@@ -835,15 +872,15 @@ pub fn is_setup(name: &str) -> bool {
}
pub fn get_custom_rendezvous_server(custom: String) -> String {
if !custom.is_empty() {
return custom;
}
#[cfg(windows)]
if let Some(lic) = crate::platform::windows::get_license() {
if let Ok(lic) = crate::platform::windows::get_license_from_exe_name() {
if !lic.host.is_empty() {
return lic.host.clone();
}
}
if !custom.is_empty() {
return custom;
}
if !config::PROD_RENDEZVOUS_SERVER.read().unwrap().is_empty() {
return config::PROD_RENDEZVOUS_SERVER.read().unwrap().clone();
}
@@ -851,15 +888,15 @@ pub fn get_custom_rendezvous_server(custom: String) -> String {
}
pub fn get_api_server(api: String, custom: String) -> String {
if !api.is_empty() {
return api.to_owned();
}
#[cfg(windows)]
if let Some(lic) = crate::platform::windows::get_license() {
if let Ok(lic) = crate::platform::windows::get_license_from_exe_name() {
if !lic.api.is_empty() {
return lic.api.clone();
}
}
if !api.is_empty() {
return api.to_owned();
}
let api = option_env!("API_SERVER").unwrap_or_default();
if !api.is_empty() {
return api.into();
@@ -984,6 +1021,12 @@ pub fn decode64<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, base64::DecodeError
}
pub async fn get_key(sync: bool) -> String {
#[cfg(windows)]
if let Ok(lic) = crate::platform::windows::get_license_from_exe_name() {
if !lic.key.is_empty() {
return lic.key;
}
}
#[cfg(target_os = "ios")]
let mut key = Config::get_option("key");
#[cfg(not(target_os = "ios"))]
@@ -993,12 +1036,6 @@ pub async fn get_key(sync: bool) -> String {
let mut options = crate::ipc::get_options_async().await;
options.remove("key").unwrap_or_default()
};
if key.is_empty() {
#[cfg(windows)]
if let Some(lic) = crate::platform::windows::get_license() {
return lic.key;
}
}
if key.is_empty() && !option_env!("RENDEZVOUS_SERVER").unwrap_or("").is_empty() {
key = config::RS_PUB_KEY.to_owned();
}
@@ -1070,6 +1107,7 @@ pub fn check_process(arg: &str, same_uid: bool) -> bool {
if let Ok(linked) = path.read_link() {
path = linked;
}
let path = path.to_string_lossy().to_lowercase();
let my_uid = sys
.process((std::process::id() as usize).into())
.map(|x| x.user_id())
@@ -1079,7 +1117,7 @@ pub fn check_process(arg: &str, same_uid: bool) -> bool {
if let Ok(linked) = cur_path.read_link() {
cur_path = linked;
}
if cur_path != path {
if cur_path.to_string_lossy().to_lowercase() != path {
continue;
}
if p.pid().to_string() == std::process::id().to_string() {

View File

@@ -1,3 +1,5 @@
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::client::translate;
#[cfg(not(debug_assertions))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::platform::breakdown_callback;
@@ -5,6 +7,28 @@ use hbb_common::log;
#[cfg(not(debug_assertions))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::platform::register_breakdown_handler;
#[cfg(windows)]
use tauri_winrt_notification::{Duration, Sound, Toast};
#[macro_export]
macro_rules! my_println{
($($arg:tt)*) => {
#[cfg(not(windows))]
println!("{}", format_args!($($arg)*));
#[cfg(windows)]
crate::platform::message_box(
&format!("{}", format_args!($($arg)*))
);
};
}
#[inline]
fn is_empty_uni_link(arg: &str) -> bool {
if !arg.starts_with("rustdesk://") {
return false;
}
arg["rustdesk://".len()..].chars().all(|c| c == '/')
}
/// shared by flutter and sciter main function
///
@@ -13,21 +37,31 @@ use hbb_common::platform::register_breakdown_handler;
/// If it returns [`Some`], then the process will continue, and flutter gui will be started.
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn core_main() -> Option<Vec<String>> {
#[cfg(windows)]
crate::platform::windows::bootstrap();
let mut args = Vec::new();
let mut flutter_args = Vec::new();
let mut i = 0;
let mut _is_elevate = false;
let mut _is_run_as_system = false;
let mut _is_quick_support = false;
let mut _is_flutter_connect = false;
let mut _is_flutter_invoke_new_connection = false;
let mut arg_exe = Default::default();
for arg in std::env::args() {
if i == 0 {
arg_exe = arg;
} else if i > 0 {
#[cfg(feature = "flutter")]
if arg == "--connect" || arg == "--play" {
_is_flutter_connect = true;
if [
"--connect",
"--play",
"--file-transfer",
"--port-forward",
"--rdp",
]
.contains(&arg.as_str())
{
_is_flutter_invoke_new_connection = true;
}
if arg == "--elevate" {
_is_elevate = true;
@@ -43,9 +77,9 @@ pub fn core_main() -> Option<Vec<String>> {
}
#[cfg(any(target_os = "linux", target_os = "windows"))]
if args.is_empty() {
#[cfg(target_os = "linux")]
hbb_common::allow_err!(crate::platform::check_autostart_config());
if crate::check_process("--server", false) && !crate::check_process("--tray", true) {
#[cfg(target_os = "linux")]
hbb_common::allow_err!(crate::platform::check_autostart_config());
hbb_common::allow_err!(crate::run_me(vec!["--tray"]));
}
}
@@ -55,15 +89,19 @@ pub fn core_main() -> Option<Vec<String>> {
#[cfg(target_os = "linux")]
#[cfg(feature = "flutter")]
{
let (k, v) = ("LIBGL_ALWAYS_SOFTWARE", "true");
let (k, v) = ("LIBGL_ALWAYS_SOFTWARE", "1");
if !hbb_common::config::Config::get_option("allow-always-software-render").is_empty() {
std::env::set_var(k, v);
} else {
std::env::remove_var(k);
}
}
#[cfg(windows)]
if args.contains(&"--connect".to_string()) {
hbb_common::platform::windows::start_cpu_performance_monitor();
}
#[cfg(feature = "flutter")]
if _is_flutter_connect {
if _is_flutter_invoke_new_connection {
return core_main_invoke_new_connection(std::env::args());
}
let click_setup = cfg!(windows) && args.is_empty() && crate::common::is_setup(&arg_exe);
@@ -82,7 +120,7 @@ pub fn core_main() -> Option<Vec<String>> {
{
_is_quick_support |= !crate::platform::is_installed()
&& args.is_empty()
&& (arg_exe.to_lowercase().ends_with("qs.exe")
&& (arg_exe.to_lowercase().contains("-qs-")
|| (!click_setup && crate::platform::is_elevated(None).unwrap_or(false)));
crate::portable_service::client::set_quick_support(_is_quick_support);
}
@@ -121,9 +159,8 @@ pub fn core_main() -> Option<Vec<String>> {
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
init_plugins(&args);
if args.is_empty() {
#[cfg(windows)]
clipboard::ContextSend::enable(true);
log::info!("main start args:{:?}", args);
if args.is_empty() || is_empty_uni_link(&args[0]) {
std::thread::spawn(move || crate::start_server(false));
} else {
#[cfg(windows)]
@@ -145,12 +182,23 @@ pub fn core_main() -> Option<Vec<String>> {
}
return None;
} else if args[0] == "--silent-install" {
hbb_common::allow_err!(platform::install_me(
let res = platform::install_me(
"desktopicon startmenu driverCert",
"".to_owned(),
true,
args.len() > 1,
));
);
let text = match res {
Ok(_) => translate("Installation Successful!".to_string()),
Err(_) => translate("Installation failed!".to_string()),
};
Toast::new(Toast::POWERSHELL_APP_ID)
.title(&hbb_common::config::APP_NAME.read().unwrap())
.text1(&text)
.sound(Some(Sound::Default))
.duration(Duration::Short)
.show()
.ok();
return None;
} else if args[0] == "--install-cert" {
#[cfg(windows)]
@@ -181,7 +229,16 @@ pub fn core_main() -> Option<Vec<String>> {
crate::tray::start_tray();
}
return None;
} else if args[0] == "--install-service" {
log::info!("start --install-service");
crate::platform::install_service();
return None;
} else if args[0] == "--uninstall-service" {
log::info!("start --uninstall-service");
crate::platform::uninstall_service(false);
} else if args[0] == "--service" {
#[cfg(target_os = "macos")]
crate::platform::macos::hide_dock();
log::info!("start --service");
crate::start_os_service();
return None;
@@ -215,18 +272,126 @@ pub fn core_main() -> Option<Vec<String>> {
return None;
} else if args[0] == "--password" {
if args.len() == 2 {
if crate::platform::is_root() {
crate::ipc::set_permanent_password(args[1].to_owned()).unwrap();
if crate::platform::is_installed() && is_root() {
if let Err(err) = crate::ipc::set_permanent_password(args[1].to_owned()) {
println!("{err}");
} else {
println!("Done!");
}
} else {
println!("Administrative privileges required!");
println!("Installation and administrative privileges required!");
}
}
return None;
} else if args[0] == "--get-id" {
if crate::platform::is_root() {
if crate::platform::is_installed() && is_root() {
println!("{}", crate::ipc::get_id());
} else {
println!("Permission denied!");
println!("Installation and administrative privileges required!");
}
return None;
} else if args[0] == "--set-id" {
if args.len() == 2 {
if crate::platform::is_installed() && is_root() {
let old_id = crate::ipc::get_id();
let mut res = crate::ui_interface::change_id_shared(args[1].to_owned(), old_id);
if res.is_empty() {
res = "Done!".to_owned();
}
println!("{}", res);
} else {
println!("Installation and administrative privileges required!");
}
}
return None;
} else if args[0] == "--config" {
if args.len() == 2 && !args[0].contains("host=") {
if crate::platform::is_installed() && is_root() {
// encrypted string used in renaming exe.
let name = if args[1].ends_with(".exe") {
args[1].to_owned()
} else {
format!("{}.exe", args[1])
};
if let Ok(lic) = crate::license::get_license_from_string(&name) {
if !lic.host.is_empty() {
crate::ui_interface::set_option("key".into(), lic.key);
crate::ui_interface::set_option(
"custom-rendezvous-server".into(),
lic.host,
);
crate::ui_interface::set_option("api-server".into(), lic.api);
}
}
} else {
println!("Installation and administrative privileges required!");
}
}
return None;
} else if args[0] == "--option" {
if crate::platform::is_installed() && is_root() {
if args.len() == 2 {
let options = crate::ipc::get_options();
println!("{}", options.get(&args[1]).unwrap_or(&"".to_owned()));
} else if args.len() == 3 {
crate::ipc::set_option(&args[1], &args[2]);
}
} else {
println!("Installation and administrative privileges required!");
}
return None;
} else if args[0] == "--assign" {
if crate::platform::is_installed() && is_root() {
let max = args.len() - 1;
let pos = args.iter().position(|x| x == "--token").unwrap_or(max);
if pos < max {
let token = args[pos + 1].to_owned();
let id = crate::ipc::get_id();
let uuid = crate::encode64(hbb_common::get_uuid());
let mut user_name = None;
let pos = args.iter().position(|x| x == "--user_name").unwrap_or(max);
if pos < max {
user_name = Some(args[pos + 1].to_owned());
}
let mut strategy_name = None;
let pos = args
.iter()
.position(|x| x == "--strategy_name")
.unwrap_or(max);
if pos < max {
strategy_name = Some(args[pos + 1].to_owned());
}
let mut body = serde_json::json!({
"id": id,
"uuid": uuid,
});
let header = "Authorization: Bearer ".to_owned() + &token;
if user_name.is_none() && strategy_name.is_none() {
println!("--user_name or --strategy_name is required!");
} else {
if let Some(name) = user_name {
body["user_name"] = serde_json::json!(name);
}
if let Some(name) = strategy_name {
body["strategy_name"] = serde_json::json!(name);
}
let url = crate::ui_interface::get_api_server() + "/api/devices/cli";
match crate::post_request_sync(url, body.to_string(), &header) {
Err(err) => println!("{}", err),
Ok(text) => {
if text.is_empty() {
println!("Done!");
} else {
println!("{}", text);
}
}
}
}
} else {
println!("--token is required!");
}
} else {
println!("Installation and administrative privileges required!");
}
return None;
} else if args[0] == "--check-hwcodec-config" {
@@ -318,38 +483,48 @@ fn import_config(path: &str) {
/// If it returns [`Some`], then the process will continue, and flutter gui will be started.
#[cfg(feature = "flutter")]
fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<String>> {
args.position(|element| {
return element == "--connect" || element == "--play";
})?;
let mut peer_id = args.next().unwrap_or("".to_string());
if peer_id.is_empty() {
eprintln!("please provide a valid peer id");
return None;
}
let app_name = crate::get_app_name();
let ext = format!(".{}", app_name.to_lowercase());
if peer_id.ends_with(&ext) {
peer_id = peer_id.replace(&ext, "");
}
let mut switch_uuid = None;
while let Some(item) = args.next() {
if item == "--switch_uuid" {
switch_uuid = args.next();
let mut authority = None;
let mut id = None;
let mut param_array = vec![];
while let Some(arg) = args.next() {
match arg.as_str() {
"--connect" | "--play" | "--file-transfer" | "--port-forward" | "--rdp" => {
authority = Some((&arg.to_string()[2..]).to_owned());
id = args.next();
}
"--password" => {
if let Some(password) = args.next() {
param_array.push(format!("password={password}"));
}
}
"--relay" => {
param_array.push(format!("relay=true"));
}
// inner
"--switch_uuid" => {
if let Some(switch_uuid) = args.next() {
param_array.push(format!("switch_uuid={switch_uuid}"));
}
}
_ => {}
}
}
let mut param_array = vec![];
if switch_uuid.is_some() {
let switch_uuid = switch_uuid.map_or("".to_string(), |p| format!("switch_uuid={}", p));
param_array.push(switch_uuid);
let mut uni_links = Default::default();
if let Some(authority) = authority {
if let Some(mut id) = id {
let app_name = crate::get_app_name();
let ext = format!(".{}", app_name.to_lowercase());
if id.ends_with(&ext) {
id = id.replace(&ext, "");
}
let params = param_array.join("&");
let params_flag = if params.is_empty() { "" } else { "?" };
uni_links = format!("rustdesk://{}/{}{}{}", authority, id, params_flag, params);
}
}
if uni_links.is_empty() {
return None;
}
let params = param_array.join("&");
let params_flag = if params.is_empty() { "" } else { "?" };
#[allow(unused)]
let uni_links = format!(
"rustdesk://connection/new/{}{}{}",
peer_id, params_flag, params
);
#[cfg(target_os = "linux")]
return try_send_by_dbus(uni_links);
@@ -391,3 +566,14 @@ fn try_send_by_dbus(uni_links: String) -> Option<Vec<String>> {
}
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn is_root() -> bool {
#[cfg(windows)]
{
return crate::platform::is_elevated(None).unwrap_or_default()
|| crate::platform::is_root();
}
#[allow(unreachable_code)]
crate::platform::is_root()
}

View File

@@ -36,9 +36,11 @@ pub(crate) const APP_TYPE_CM: &str = "cm";
#[cfg(any(target_os = "android", target_os = "ios"))]
pub(crate) const APP_TYPE_CM: &str = "main";
pub(crate) const APP_TYPE_DESKTOP_REMOTE: &str = "remote";
pub(crate) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer";
pub(crate) const APP_TYPE_DESKTOP_PORT_FORWARD: &str = "port forward";
// Do not remove the following constants.
// Uncomment them when they are used.
// pub(crate) const APP_TYPE_DESKTOP_REMOTE: &str = "remote";
// pub(crate) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer";
// pub(crate) const APP_TYPE_DESKTOP_PORT_FORWARD: &str = "port forward";
lazy_static::lazy_static! {
pub(crate) static ref CUR_SESSION_ID: RwLock<SessionID> = Default::default();
@@ -104,7 +106,10 @@ fn rust_args_to_c_args(args: Vec<String>, outlen: *mut c_int) -> *mut *mut c_cha
// Let's fill a vector with null-terminated strings
for s in args {
v.push(CString::new(s).unwrap());
match CString::new(s) {
Ok(s) => v.push(s),
Err(_) => return std::ptr::null_mut() as _,
}
}
// Turning each null-terminated string into a pointer.
@@ -183,7 +188,7 @@ pub type FlutterRgbaRendererPluginOnRgba = unsafe extern "C" fn(
#[derive(Clone)]
struct VideoRenderer {
// TextureRgba pointer in flutter native.
ptr: usize,
ptr: Arc<RwLock<usize>>,
width: usize,
height: usize,
on_rgba_func: Option<Symbol<'static, FlutterRgbaRendererPluginOnRgba>>,
@@ -211,7 +216,7 @@ impl Default for VideoRenderer {
}
};
Self {
ptr: 0,
ptr: Default::default(),
width: 0,
height: 0,
on_rgba_func,
@@ -228,19 +233,27 @@ impl VideoRenderer {
}
pub fn on_rgba(&self, rgba: &mut scrap::ImageRgb) {
if self.ptr == usize::default() {
let ptr = self.ptr.read().unwrap();
if *ptr == usize::default() {
return;
}
// It is also Ok to skip this check.
if self.width != rgba.w || self.height != rgba.h {
log::error!(
"width/height mismatch: ({},{}) != ({},{})",
self.width,
self.height,
rgba.w,
rgba.h
);
return;
}
if let Some(func) = &self.on_rgba_func {
unsafe {
func(
self.ptr as _,
*ptr as _,
rgba.raw.as_ptr() as _,
rgba.raw.len() as _,
rgba.w as _,
@@ -325,7 +338,7 @@ impl FlutterHandler {
#[inline]
#[cfg(feature = "flutter_texture_render")]
pub fn register_texture(&mut self, ptr: usize) {
self.renderer.write().unwrap().ptr = ptr;
*self.renderer.read().unwrap().ptr.write().unwrap() = ptr;
}
#[inline]
@@ -334,6 +347,14 @@ impl FlutterHandler {
*self.notify_rendered.write().unwrap() = false;
self.renderer.write().unwrap().set_size(width, height);
}
pub fn on_waiting_for_image_dialog_show(&self) {
#[cfg(any(feature = "flutter_texture_render"))]
{
*self.notify_rendered.write().unwrap() = false;
}
// rgba array render will notify every frame
}
}
impl InvokeUiSession for FlutterHandler {
@@ -778,11 +799,15 @@ pub fn session_start_(
);
#[cfg(not(feature = "flutter_texture_render"))]
log::info!("Session {} start, render by flutter paint widget", id);
let is_pre_added = session.event_stream.read().unwrap().is_some();
session.close_event_stream();
*session.event_stream.write().unwrap() = Some(event_stream);
let session = session.clone();
std::thread::spawn(move || {
io_loop(session);
});
if !is_pre_added {
let session = session.clone();
std::thread::spawn(move || {
io_loop(session);
});
}
Ok(())
} else {
bail!("No session with peer id {}", id)
@@ -882,6 +907,10 @@ pub mod connection_manager {
let client_json = serde_json::to_string(&client).unwrap_or("".into());
self.push_event("update_voice_call_state", vec![("client", &client_json)]);
}
fn file_transfer_log(&self, log: String) {
self.push_event("cm_file_transfer_log", vec![("log", &log.to_string())]);
}
}
impl FlutterHandler {
@@ -910,7 +939,7 @@ pub mod connection_manager {
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn start_listen_ipc_thread() {
fn start_listen_ipc_thread() {
start_listen_ipc(true);
}
@@ -931,6 +960,12 @@ pub mod connection_manager {
}
}
#[inline]
pub fn cm_init() {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
start_listen_ipc_thread();
}
#[cfg(target_os = "android")]
use hbb_common::tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
@@ -1006,24 +1041,24 @@ fn serialize_resolutions(resolutions: &Vec<Resolution>) -> String {
}
fn char_to_session_id(c: *const char) -> ResultType<SessionID> {
if c.is_null() {
bail!("Session id ptr is null");
}
let cstr = unsafe { std::ffi::CStr::from_ptr(c as _) };
let str = cstr.to_str()?;
SessionID::from_str(str).map_err(|e| anyhow!("{:?}", e))
}
#[no_mangle]
pub fn session_get_rgba_size(_session_uuid_str: *const char) -> usize {
pub fn session_get_rgba_size(_session_id: SessionID) -> usize {
#[cfg(not(feature = "flutter_texture_render"))]
if let Ok(session_id) = char_to_session_id(_session_uuid_str) {
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
return session.rgba.read().unwrap().len();
}
if let Some(session) = SESSIONS.read().unwrap().get(&_session_id) {
return session.rgba.read().unwrap().len();
}
0
}
#[no_mangle]
pub fn session_get_rgba(session_uuid_str: *const char) -> *const u8 {
pub extern "C" fn session_get_rgba(session_uuid_str: *const char) -> *const u8 {
if let Ok(session_id) = char_to_session_id(session_uuid_str) {
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
return session.get_rgba();
@@ -1033,23 +1068,17 @@ pub fn session_get_rgba(session_uuid_str: *const char) -> *const u8 {
std::ptr::null()
}
#[no_mangle]
pub fn session_next_rgba(session_uuid_str: *const char) {
if let Ok(session_id) = char_to_session_id(session_uuid_str) {
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
return session.next_rgba();
}
pub fn session_next_rgba(session_id: SessionID) {
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
return session.next_rgba();
}
}
#[inline]
#[no_mangle]
pub fn session_register_texture(_session_uuid_str: *const char, _ptr: usize) {
pub fn session_register_texture(_session_id: SessionID, _ptr: usize) {
#[cfg(feature = "flutter_texture_render")]
if let Ok(session_id) = char_to_session_id(_session_uuid_str) {
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
return session.register_texture(_ptr);
}
if let Some(session) = SESSIONS.write().unwrap().get_mut(&_session_id) {
return session.register_texture(_ptr);
}
}
@@ -1071,16 +1100,28 @@ pub fn push_global_event(channel: &str, event: String) -> Option<bool> {
Some(GLOBAL_EVENT_STREAM.read().unwrap().get(channel)?.add(event))
}
pub fn start_global_event_stream(s: StreamSink<String>, app_type: String) -> ResultType<()> {
if let Some(_) = GLOBAL_EVENT_STREAM
.write()
#[inline]
pub fn get_global_event_channels() -> Vec<String> {
GLOBAL_EVENT_STREAM
.read()
.unwrap()
.insert(app_type.clone(), s)
{
log::warn!(
"Global event stream of type {} is started before, but now removed",
app_type
);
.keys()
.cloned()
.collect()
}
pub fn start_global_event_stream(s: StreamSink<String>, app_type: String) -> ResultType<()> {
let app_type_values = app_type.split(",").collect::<Vec<&str>>();
let mut lock = GLOBAL_EVENT_STREAM.write().unwrap();
if !lock.contains_key(app_type_values[0]) {
lock.insert(app_type_values[0].to_string(), s);
} else {
if let Some(_) = lock.insert(app_type.clone(), s) {
log::warn!(
"Global event stream of type {} is started before, but now removed",
app_type
);
}
}
Ok(())
}
@@ -1089,8 +1130,84 @@ pub fn stop_global_event_stream(app_type: String) {
let _ = GLOBAL_EVENT_STREAM.write().unwrap().remove(&app_type);
}
#[no_mangle]
unsafe extern "C" fn get_rgba() {}
#[inline]
fn session_send_touch_scale(
session_id: SessionID,
v: &serde_json::Value,
alt: bool,
ctrl: bool,
shift: bool,
command: bool,
) {
match v.get("v").and_then(|s| s.as_i64()) {
Some(scale) => {
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
session.send_touch_scale(scale as _, alt, ctrl, shift, command);
}
}
None => {}
}
}
#[inline]
fn session_send_touch_pan(
session_id: SessionID,
v: &serde_json::Value,
pan_event: &str,
alt: bool,
ctrl: bool,
shift: bool,
command: bool,
) {
match v.get("v") {
Some(v) => match (
v.get("x").and_then(|x| x.as_i64()),
v.get("y").and_then(|y| y.as_i64()),
) {
(Some(x), Some(y)) => {
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
session
.send_touch_pan_event(pan_event, x as _, y as _, alt, ctrl, shift, command);
}
}
_ => {}
},
_ => {}
}
}
fn session_send_touch_event(
session_id: SessionID,
v: &serde_json::Value,
alt: bool,
ctrl: bool,
shift: bool,
command: bool,
) {
match v.get("t").and_then(|t| t.as_str()) {
Some("scale") => session_send_touch_scale(session_id, v, alt, ctrl, shift, command),
Some(pan_event) => {
session_send_touch_pan(session_id, v, pan_event, alt, ctrl, shift, command)
}
_ => {}
}
}
pub fn session_send_pointer(session_id: SessionID, msg: String) {
if let Ok(m) = serde_json::from_str::<HashMap<String, serde_json::Value>>(&msg) {
let alt = m.get("alt").is_some();
let ctrl = m.get("ctrl").is_some();
let shift = m.get("shift").is_some();
let command = m.get("command").is_some();
match (m.get("k"), m.get("v")) {
(Some(k), Some(v)) => match k.as_str() {
Some("touch") => session_send_touch_event(session_id, v, alt, ctrl, shift, command),
_ => {}
},
_ => {}
}
}
}
/// Hooks for session.
#[derive(Clone)]

View File

@@ -15,7 +15,7 @@ use flutter_rust_bridge::{StreamSink, SyncReturn};
use hbb_common::allow_err;
use hbb_common::{
config::{self, LocalConfig, PeerConfig, PeerInfoSerde},
fs, log,
fs, lazy_static, log,
message_proto::KeyboardMode,
ResultType,
};
@@ -24,11 +24,19 @@ use std::{
ffi::{CStr, CString},
os::raw::c_char,
str::FromStr,
sync::{
atomic::{AtomicI32, Ordering},
Arc,
},
time::SystemTime,
};
pub type SessionID = uuid::Uuid;
lazy_static::lazy_static! {
static ref TEXTURE_RENDER_KEY: Arc<AtomicI32> = Arc::new(AtomicI32::new(0));
}
fn initialize(app_dir: &str) {
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
#[cfg(target_os = "android")]
@@ -166,6 +174,12 @@ pub fn session_record_screen(session_id: SessionID, start: bool, width: usize, h
}
}
pub fn session_record_status(session_id: SessionID, status: bool) {
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
session.record_status(status);
}
}
pub fn session_reconnect(session_id: SessionID, force_relay: bool) {
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
session.reconnect(force_relay);
@@ -183,26 +197,39 @@ pub fn session_toggle_option(session_id: SessionID, value: String) {
}
}
pub fn session_get_flutter_config(session_id: SessionID, k: String) -> Option<String> {
pub fn session_get_flutter_option(session_id: SessionID, k: String) -> Option<String> {
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
Some(session.get_flutter_config(k))
Some(session.get_flutter_option(k))
} else {
None
}
}
pub fn session_set_flutter_config(session_id: SessionID, k: String, v: String) {
pub fn session_set_flutter_option(session_id: SessionID, k: String, v: String) {
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
session.save_flutter_config(k, v);
session.save_flutter_option(k, v);
}
}
pub fn get_local_flutter_config(k: String) -> SyncReturn<String> {
SyncReturn(ui_interface::get_local_flutter_config(k))
pub fn session_get_flutter_option_by_peer_id(id: String, k: String) -> Option<String> {
if let Some((_, session)) = SESSIONS.read().unwrap().iter().find(|(_, s)| s.id == id) {
Some(session.get_flutter_option(k))
} else {
None
}
}
pub fn set_local_flutter_config(k: String, v: String) {
ui_interface::set_local_flutter_config(k, v);
pub fn get_next_texture_key() -> SyncReturn<i32> {
let k = TEXTURE_RENDER_KEY.fetch_add(1, Ordering::SeqCst) + 1;
SyncReturn(k)
}
pub fn get_local_flutter_option(k: String) -> SyncReturn<String> {
SyncReturn(ui_interface::get_local_flutter_option(k))
}
pub fn set_local_flutter_option(k: String, v: String) {
ui_interface::set_local_flutter_option(k, v);
}
pub fn get_local_kb_layout_type() -> SyncReturn<String> {
@@ -357,6 +384,7 @@ pub fn session_enter_or_leave(_session_id: SessionID, _enter: bool) -> SyncRetur
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if let Some(session) = SESSIONS.read().unwrap().get(&_session_id) {
if _enter {
set_cur_session_id(_session_id);
session.enter();
} else {
session.leave();
@@ -590,8 +618,8 @@ pub fn main_get_default_sound_input() -> Option<String> {
None
}
pub fn main_get_hostname() -> SyncReturn<String> {
SyncReturn(get_hostname())
pub fn main_get_login_device_info() -> SyncReturn<String> {
SyncReturn(get_login_device_info_json())
}
pub fn main_change_id(new_id: String) {
@@ -606,10 +634,23 @@ pub fn main_get_option(key: String) -> String {
get_option(key)
}
pub fn main_get_option_sync(key: String) -> SyncReturn<String> {
SyncReturn(get_option(key))
}
pub fn main_get_error() -> String {
get_error()
}
pub fn main_show_option(_key: String) -> SyncReturn<bool> {
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
if _key.eq(config::CONFIG_OPTION_ALLOW_LINUX_HEADLESS) {
return SyncReturn(true);
}
SyncReturn(false)
}
pub fn main_set_option(key: String, value: String) {
if key.eq("custom-rendezvous-server") {
set_option(key, value);
@@ -626,6 +667,10 @@ pub fn main_get_options() -> String {
get_options()
}
pub fn main_get_options_sync() -> SyncReturn<String> {
SyncReturn(get_options())
}
pub fn main_set_options(json: String) {
let map: HashMap<String, String> = serde_json::from_str(&json).unwrap_or(HashMap::new());
if !map.is_empty() {
@@ -685,7 +730,7 @@ pub fn main_get_connect_status() -> String {
}
#[cfg(any(target_os = "android", target_os = "ios"))]
{
let mut state = hbb_common::config::get_online_statue();
let mut state = hbb_common::config::get_online_state();
if state > 0 {
state = 1;
}
@@ -742,6 +787,17 @@ pub fn main_get_peer_option_sync(id: String, key: String) -> SyncReturn<String>
SyncReturn(get_peer_option(id, key))
}
// Sometimes we need to get the flutter option of a peer by reading the file.
// Because the session may not be established yet.
pub fn main_get_peer_flutter_option_sync(id: String, k: String) -> SyncReturn<String> {
SyncReturn(get_peer_flutter_option(id, k))
}
pub fn main_set_peer_flutter_option_sync(id: String, k: String, v: String) -> SyncReturn<()> {
set_peer_flutter_option(id, k, v);
SyncReturn(())
}
pub fn main_set_peer_option(id: String, key: String, value: String) {
set_peer_option(id, key, value)
}
@@ -760,6 +816,15 @@ pub fn main_set_peer_alias(id: String, alias: String) {
set_peer_option(id, "alias".to_owned(), alias)
}
pub fn main_get_new_stored_peers() -> String {
let peers: Vec<String> = config::NEW_STORED_PEER_CONFIG
.lock()
.unwrap()
.drain()
.collect();
serde_json::to_string(&peers).unwrap_or_default()
}
pub fn main_forget_password(id: String) {
forget_password(id)
}
@@ -768,9 +833,13 @@ pub fn main_peer_has_password(id: String) -> bool {
peer_has_password(id)
}
pub fn main_peer_exists(id: String) -> bool {
peer_exists(&id)
}
pub fn main_load_recent_peers() {
if !config::APP_DIR.read().unwrap().is_empty() {
let peers: Vec<HashMap<&str, String>> = PeerConfig::peers()
let peers: Vec<HashMap<&str, String>> = PeerConfig::peers(None)
.drain(..)
.map(|(id, _, p)| peer_to_map(id, p))
.collect();
@@ -791,7 +860,7 @@ pub fn main_load_recent_peers() {
pub fn main_load_recent_peers_sync() -> SyncReturn<String> {
if !config::APP_DIR.read().unwrap().is_empty() {
let peers: Vec<HashMap<&str, String>> = PeerConfig::peers()
let peers: Vec<HashMap<&str, String>> = PeerConfig::peers(None)
.drain(..)
.map(|(id, _, p)| peer_to_map(id, p))
.collect();
@@ -808,10 +877,27 @@ pub fn main_load_recent_peers_sync() -> SyncReturn<String> {
SyncReturn("".to_string())
}
pub fn main_load_recent_peers_for_ab(filter: String) -> String {
let id_filters = serde_json::from_str::<Vec<String>>(&filter).unwrap_or_default();
let id_filters = if id_filters.is_empty() {
None
} else {
Some(id_filters)
};
if !config::APP_DIR.read().unwrap().is_empty() {
let peers: Vec<HashMap<&str, String>> = PeerConfig::peers(id_filters)
.drain(..)
.map(|(id, _, p)| peer_to_map(id, p))
.collect();
return serde_json::ser::to_string(&peers).unwrap_or("".to_owned());
}
"".to_string()
}
pub fn main_load_fav_peers() {
if !config::APP_DIR.read().unwrap().is_empty() {
let favs = get_fav();
let mut recent = PeerConfig::peers();
let mut recent = PeerConfig::peers(None);
let mut lan = config::LanPeers::load()
.peers
.iter()
@@ -876,15 +962,12 @@ pub fn main_remove_discovered(id: String) {
}
fn main_broadcast_message(data: &HashMap<&str, &str>) {
let apps = vec![
flutter::APP_TYPE_DESKTOP_REMOTE,
flutter::APP_TYPE_DESKTOP_FILE_TRANSFER,
flutter::APP_TYPE_DESKTOP_PORT_FORWARD,
];
let event = serde_json::ser::to_string(&data).unwrap_or("".to_owned());
for app in apps {
let _res = flutter::push_global_event(app, event.clone());
for app in flutter::get_global_event_channels() {
if app == flutter::APP_TYPE_MAIN || app == flutter::APP_TYPE_CM {
continue;
}
let _res = flutter::push_global_event(&app, event.clone());
}
}
@@ -1069,6 +1152,28 @@ pub fn main_start_dbus_server() {
}
}
pub fn main_save_ab(json: String) {
if json.len() > 1024 {
std::thread::spawn(|| {
config::Ab::store(json);
});
} else {
config::Ab::store(json);
}
}
pub fn main_clear_ab() {
config::Ab::remove();
}
pub fn main_load_ab() -> String {
serde_json::to_string(&config::Ab::load()).unwrap_or_default()
}
pub fn session_send_pointer(session_id: SessionID, msg: String) {
super::flutter::session_send_pointer(session_id, msg);
}
pub fn session_send_mouse(session_id: SessionID, msg: String) {
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(&msg) {
let alt = m.get("alt").is_some();
@@ -1146,6 +1251,12 @@ pub fn session_change_prefer_codec(session_id: SessionID) {
}
}
pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) {
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
session.ui_handler.on_waiting_for_image_dialog_show();
}
}
pub fn main_set_home_dir(_home: String) {
#[cfg(any(target_os = "android", target_os = "ios"))]
{
@@ -1277,18 +1388,6 @@ pub fn main_get_build_date() -> String {
crate::BUILD_DATE.to_string()
}
#[no_mangle]
unsafe extern "C" fn translate(name: *const c_char, locale: *const c_char) -> *const c_char {
let name = CStr::from_ptr(name);
let locale = CStr::from_ptr(locale);
let res = if let (Ok(name), Ok(locale)) = (name.to_str(), locale.to_str()) {
crate::client::translate_locale(name.to_owned(), locale)
} else {
String::new()
};
CString::from_vec_unchecked(res.into_bytes()).into_raw()
}
fn handle_query_onlines(onlines: Vec<String>, offlines: Vec<String>) {
let data = HashMap::from([
("name", "callback_query_onlines".to_owned()),
@@ -1301,6 +1400,22 @@ fn handle_query_onlines(onlines: Vec<String>, offlines: Vec<String>) {
);
}
pub fn translate(name: String, locale: String) -> SyncReturn<String> {
SyncReturn(crate::client::translate_locale(name, &locale))
}
pub fn session_get_rgba_size(session_id: SessionID) -> SyncReturn<usize> {
SyncReturn(super::flutter::session_get_rgba_size(session_id))
}
pub fn session_next_rgba(session_id: SessionID) -> SyncReturn<()> {
SyncReturn(super::flutter::session_next_rgba(session_id))
}
pub fn session_register_texture(session_id: SessionID, ptr: usize) -> SyncReturn<()> {
SyncReturn(super::flutter::session_register_texture(session_id, ptr))
}
pub fn query_onlines(ids: Vec<String>) {
#[cfg(not(any(target_os = "ios")))]
crate::rendezvous_mediator::query_online_states(ids, handle_query_onlines)
@@ -1448,9 +1563,9 @@ pub fn main_use_texture_render() -> SyncReturn<bool> {
}
}
pub fn cm_start_listen_ipc_thread() {
pub fn cm_init() {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
crate::flutter::connection_manager::start_listen_ipc_thread();
crate::flutter::connection_manager::cm_init();
}
/// Start an ipc server for receiving the url scheme.

View File

@@ -1,8 +1,5 @@
use super::HbbHttpResponse;
use hbb_common::{
config::{Config, LocalConfig},
log, ResultType,
};
use hbb_common::{config::LocalConfig, log, ResultType};
use reqwest::blocking::Client;
use serde_derive::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
@@ -14,8 +11,6 @@ use std::{
use url::Url;
lazy_static::lazy_static! {
static ref API_SERVER: String = crate::get_api_server(
Config::get_option("api-server"), Config::get_option("custom-rendezvous-server"));
static ref OIDC_SESSION: Arc<RwLock<OidcSession>> = Arc::new(RwLock::new(OidcSession::new()));
}
@@ -142,20 +137,35 @@ impl OidcSession {
}
}
fn auth(op: &str, id: &str, uuid: &str) -> ResultType<HbbHttpResponse<OidcAuthUrl>> {
fn auth(
api_server: &str,
op: &str,
id: &str,
uuid: &str,
) -> ResultType<HbbHttpResponse<OidcAuthUrl>> {
Ok(OIDC_SESSION
.read()
.unwrap()
.client
.post(format!("{}/api/oidc/auth", *API_SERVER))
.json(&HashMap::from([("op", op), ("id", id), ("uuid", uuid)]))
.post(format!("{}/api/oidc/auth", api_server))
.json(&serde_json::json!({
"op": op,
"id": id,
"uuid": uuid,
"deviceInfo": crate::ui_interface::get_login_device_info(),
}))
.send()?
.try_into()?)
}
fn query(code: &str, id: &str, uuid: &str) -> ResultType<HbbHttpResponse<AuthBody>> {
fn query(
api_server: &str,
code: &str,
id: &str,
uuid: &str,
) -> ResultType<HbbHttpResponse<AuthBody>> {
let url = reqwest::Url::parse_with_params(
&format!("{}/api/oidc/auth-query", *API_SERVER),
&format!("{}/api/oidc/auth-query", api_server),
&[("code", code), ("id", id), ("uuid", uuid)],
)?;
Ok(OIDC_SESSION
@@ -189,8 +199,8 @@ impl OidcSession {
std::thread::sleep(std::time::Duration::from_secs_f32(secs));
}
fn auth_task(op: String, id: String, uuid: String, remember_me: bool) {
let auth_request_res = Self::auth(&op, &id, &uuid);
fn auth_task(api_server: String, op: String, id: String, uuid: String, remember_me: bool) {
let auth_request_res = Self::auth(&api_server, &op, &id, &uuid);
log::info!("Request oidc auth result: {:?}", &auth_request_res);
let code_url = match auth_request_res {
Ok(HbbHttpResponse::<_>::Data(code_url)) => code_url,
@@ -226,7 +236,7 @@ impl OidcSession {
let begin = Instant::now();
let query_timeout = OIDC_SESSION.read().unwrap().query_timeout;
while OIDC_SESSION.read().unwrap().keep_querying && begin.elapsed() < query_timeout {
match Self::query(&code_url.code, &id, &uuid) {
match Self::query(&api_server, &code_url.code, &id, &uuid) {
Ok(HbbHttpResponse::<_>::Data(auth_body)) => {
if remember_me {
LocalConfig::set_option(
@@ -289,12 +299,18 @@ impl OidcSession {
}
}
pub fn account_auth(op: String, id: String, uuid: String, remember_me: bool) {
pub fn account_auth(
api_server: String,
op: String,
id: String,
uuid: String,
remember_me: bool,
) {
Self::auth_cancel();
Self::wait_stop_querying();
OIDC_SESSION.write().unwrap().before_task();
std::thread::spawn(move || {
Self::auth_task(op, id, uuid, remember_me);
Self::auth_task(api_server, op, id, uuid, remember_me);
OIDC_SESSION.write().unwrap().after_task();
});
}

View File

@@ -1,4 +1,8 @@
use std::{collections::HashMap, sync::Mutex, time::Duration};
use std::{
collections::HashMap,
sync::{Arc, Mutex},
time::Duration,
};
#[cfg(not(any(target_os = "ios")))]
use crate::Connection;
@@ -9,15 +13,17 @@ use hbb_common::{
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
const TIME_HEARTBEAT: Duration = Duration::from_secs(30);
const TIME_HEARTBEAT: Duration = Duration::from_secs(15);
const UPLOAD_SYSINFO_TIMEOUT: Duration = Duration::from_secs(120);
const TIME_CONN: Duration = Duration::from_secs(3);
#[cfg(not(any(target_os = "ios")))]
lazy_static::lazy_static! {
static ref SENDER : Mutex<broadcast::Sender<Vec<i32>>> = Mutex::new(start_hbbs_sync());
static ref PRO: Arc<Mutex<bool>> = Default::default();
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[cfg(not(any(target_os = "ios")))]
pub fn start() {
let _sender = SENDER.lock().unwrap();
}
@@ -45,55 +51,84 @@ pub struct StrategyOptions {
#[cfg(not(any(target_os = "ios")))]
#[tokio::main(flavor = "current_thread")]
async fn start_hbbs_sync_async() {
tokio::spawn(async move {
let mut interval = tokio::time::interval_at(Instant::now() + TIME_CONN, TIME_CONN);
let mut last_send = Instant::now();
loop {
tokio::select! {
_ = interval.tick() => {
let url = heartbeat_url();
let modified_at = LocalConfig::get_option("strategy_timestamp").parse::<i64>().unwrap_or(0);
if !url.is_empty() {
let conns = Connection::alive_conns();
if conns.is_empty() && last_send.elapsed() < TIME_HEARTBEAT {
continue;
let mut interval = tokio::time::interval_at(Instant::now() + TIME_CONN, TIME_CONN);
let mut last_sent: Option<Instant> = None;
let mut info_uploaded: (bool, String, Option<Instant>) = (false, "".to_owned(), None);
loop {
tokio::select! {
_ = interval.tick() => {
let url = heartbeat_url();
if url.is_empty() {
*PRO.lock().unwrap() = false;
continue;
}
if !Config::get_option("stop-service").is_empty() {
continue;
}
let conns = Connection::alive_conns();
if info_uploaded.0 && url != info_uploaded.1 {
info_uploaded.0 = false;
*PRO.lock().unwrap() = false;
}
if !info_uploaded.0 && info_uploaded.2.map(|x| x.elapsed() >= UPLOAD_SYSINFO_TIMEOUT).unwrap_or(true){
let mut v = crate::get_sysinfo();
v["version"] = json!(crate::VERSION);
v["id"] = json!(Config::get_id());
v["uuid"] = json!(crate::encode64(hbb_common::get_uuid()));
match crate::post_request(url.replace("heartbeat", "sysinfo"), v.to_string(), "").await {
Ok(x) => {
if x == "SYSINFO_UPDATED" {
info_uploaded = (true, url.clone(), None);
hbb_common::log::info!("sysinfo updated");
*PRO.lock().unwrap() = true;
} else if x == "ID_NOT_FOUND" {
info_uploaded.2 = None; // next heartbeat will upload sysinfo again
} else {
info_uploaded.2 = Some(Instant::now());
}
}
last_send = Instant::now();
let mut v = Value::default();
v["id"] = json!(Config::get_id());
v["ver"] = json!(hbb_common::get_version_number(crate::VERSION));
if !conns.is_empty() {
v["conns"] = json!(conns);
_ => {
info_uploaded.2 = Some(Instant::now());
}
v["modified_at"] = json!(modified_at);
if let Ok(s) = crate::post_request(url.clone(), v.to_string(), "").await {
if let Ok(mut rsp) = serde_json::from_str::<HashMap::<&str, Value>>(&s) {
if let Some(conns) = rsp.remove("disconnect") {
if let Ok(conns) = serde_json::from_value::<Vec<i32>>(conns) {
SENDER.lock().unwrap().send(conns).ok();
}
}
}
if conns.is_empty() && last_sent.map(|x| x.elapsed() < TIME_HEARTBEAT).unwrap_or(false){
continue;
}
last_sent = Some(Instant::now());
let mut v = Value::default();
v["id"] = json!(Config::get_id());
v["uuid"] = json!(crate::encode64(hbb_common::get_uuid()));
v["ver"] = json!(hbb_common::get_version_number(crate::VERSION));
if !conns.is_empty() {
v["conns"] = json!(conns);
}
let modified_at = LocalConfig::get_option("strategy_timestamp").parse::<i64>().unwrap_or(0);
v["modified_at"] = json!(modified_at);
if let Ok(s) = crate::post_request(url.clone(), v.to_string(), "").await {
if let Ok(mut rsp) = serde_json::from_str::<HashMap::<&str, Value>>(&s) {
if let Some(conns) = rsp.remove("disconnect") {
if let Ok(conns) = serde_json::from_value::<Vec<i32>>(conns) {
SENDER.lock().unwrap().send(conns).ok();
}
if let Some(rsp_modified_at) = rsp.remove("modified_at") {
if let Ok(rsp_modified_at) = serde_json::from_value::<i64>(rsp_modified_at) {
if rsp_modified_at != modified_at {
LocalConfig::set_option("strategy_timestamp".to_string(), rsp_modified_at.to_string());
}
}
}
if let Some(strategy) = rsp.remove("strategy") {
if let Ok(strategy) = serde_json::from_value::<StrategyOptions>(strategy) {
handle_config_options(strategy.config_options);
}
}
if let Some(rsp_modified_at) = rsp.remove("modified_at") {
if let Ok(rsp_modified_at) = serde_json::from_value::<i64>(rsp_modified_at) {
if rsp_modified_at != modified_at {
LocalConfig::set_option("strategy_timestamp".to_string(), rsp_modified_at.to_string());
}
}
}
if let Some(strategy) = rsp.remove("strategy") {
if let Ok(strategy) = serde_json::from_value::<StrategyOptions>(strategy) {
handle_config_options(strategy.config_options);
}
}
}
}
}
}
})
.await
.ok();
}
}
fn heartbeat_url() -> String {
@@ -121,3 +156,8 @@ fn handle_config_options(config_options: HashMap<String, String>) {
.count();
Config::set_options(options);
}
#[cfg(not(any(target_os = "ios")))]
pub fn is_pro() -> bool {
PRO.lock().unwrap().clone()
}

View File

@@ -70,6 +70,8 @@ pub enum FS {
file_num: i32,
files: Vec<(String, u64)>,
overwrite_detection: bool,
total_size: u64,
conn_id: i32,
},
CancelWrite {
id: i32,
@@ -152,6 +154,7 @@ pub enum DataPortableService {
Pong,
ConnCount(Option<usize>),
Mouse((Vec<u8>, i32)),
Pointer((Vec<u8>, i32)),
Key(Vec<u8>),
RequestStart,
WillClose,
@@ -228,6 +231,9 @@ pub enum Data {
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Plugin(Plugin),
#[cfg(windows)]
SyncWinCpuUsage(Option<f64>),
FileTransferLog(String),
}
#[tokio::main(flavor = "current_thread")]
@@ -347,7 +353,7 @@ async fn handle(data: Data, stream: &mut Connection) {
}
}
Data::OnlineStatus(_) => {
let x = config::get_online_statue();
let x = config::get_online_state();
let confirmed = Config::get_key_confirmed();
allow_err!(stream.send(&Data::OnlineStatus(Some((x, confirmed)))).await);
}
@@ -451,6 +457,16 @@ async fn handle(data: Data, stream: &mut Connection) {
.await
);
}
#[cfg(windows)]
Data::SyncWinCpuUsage(None) => {
allow_err!(
stream
.send(&Data::SyncWinCpuUsage(
hbb_common::platform::windows::cpu_uage_one_minute()
))
.await
);
}
Data::TestRendezvousServer => {
crate::test_rendezvous_server();
}

View File

@@ -52,6 +52,9 @@ lazy_static::lazy_static! {
pub mod client {
use super::*;
lazy_static::lazy_static! {
static ref IS_GRAB_STARTED: Arc<Mutex<bool>> = Arc::new(Mutex::new(false));
}
pub fn get_keyboard_mode() -> String {
#[cfg(not(any(feature = "flutter", feature = "cli")))]
@@ -70,7 +73,12 @@ pub mod client {
}
pub fn start_grab_loop() {
let mut lock = IS_GRAB_STARTED.lock().unwrap();
if *lock {
return;
}
super::start_grab_loop();
*lock = true;
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -708,7 +716,7 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec<KeyEv
Key::Final => Some(ControlKey::Final),
Key::Hanja => Some(ControlKey::Hanja),
Key::Hanji => Some(ControlKey::Hanja),
Key::Convert => Some(ControlKey::Convert),
Key::Lang2 => Some(ControlKey::Convert),
Key::Print => Some(ControlKey::Print),
Key::Select => Some(ControlKey::Select),
Key::Execute => Some(ControlKey::Execute),

View File

@@ -33,6 +33,7 @@ mod tw;
mod ua;
mod vn;
mod lt;
mod ar;
pub const LANGS: &[(&str, &str)] = &[
("en", "English"),
@@ -68,6 +69,7 @@ pub const LANGS: &[(&str, &str)] = &[
("sl", "Slovenščina"),
("ro", "Română"),
("lt", "Lietuvių"),
("ar", "العربية"),
];
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -132,6 +134,7 @@ pub fn translate_locale(name: String, locale: &str) -> String {
"sl" => sl::T.deref(),
"ro" => ro::T.deref(),
"lt" => lt::T.deref(),
"ar" => ar::T.deref(),
_ => en::T.deref(),
};
if let Some(v) = m.get(&name as &str) {

547
src/lang/ar.rs Normal file
View File

@@ -0,0 +1,547 @@
lazy_static::lazy_static! {
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[
("Status", "الحالة"),
("Your Desktop", "سطح مكتبك"),
("desk_tip", "يمكن الوصول لسطح مكتبك بهذا المعرف والرقم السري."),
("Password", "كلمة المرور"),
("Ready", "جاهز"),
("Established", "تم الانشاء"),
("connecting_status", "جاري الاتصال بشبكة RustDesk..."),
("Enable Service", "تفعيل الخدمة"),
("Start Service", "بدء الخدمة"),
("Service is running", "الخدمة تعمل"),
("Service is not running", "الخدمة لا تعمل"),
("not_ready_status", "غير جاهز. الرجاء التأكد من الاتصال"),
("Control Remote Desktop", "التحكم بسطح المكتب البعيد"),
("Transfer File", "نقل ملف"),
("Connect", "اتصال"),
("Recent Sessions", "الجلسات الحديثة"),
("Address Book", "كتاب العناوين"),
("Confirmation", "التأكيد"),
("TCP Tunneling", "نفق TCP"),
("Remove", "ازالة"),
("Refresh random password", "تحديث كلمة مرور عشوائية"),
("Set your own password", "تعيين كلمة مرور خاصة بك"),
("Enable Keyboard/Mouse", "تفعيل لوحة المفاتيح/الفأرة"),
("Enable Clipboard", "تفعيل الحافظة"),
("Enable File Transfer", "تفعيل نقل الملفات"),
("Enable TCP Tunneling", "تفعيل نفق TCP"),
("IP Whitelisting", "القائمة البيضاء للـ IP"),
("ID/Relay Server", "معرف خادم الوسيط"),
("Import Server Config", "استيراد إعدادات الخادم"),
("Export Server Config", "تصدير إعدادات الخادم"),
("Import server configuration successfully", "تم استيراد إعدادات الخادم بنجاح"),
("Export server configuration successfully", "تم تصدير إعدادات الخادم بنجاح"),
("Invalid server configuration", "إعدادات الخادم غير صحيحة"),
("Clipboard is empty", "الحافظة فارغة"),
("Stop service", "إيقاف الخدمة"),
("Change ID", "تغيير المعرف"),
("Your new ID", "معرفك الجديد"),
("length %min% to %max%", "الطول من %min% الى %max%"),
("starts with a letter", "يبدأ بحرف"),
("allowed characters", "الحروف المسموح بها"),
("id_change_tip", "فقط a-z, A-Z, 0-9 و _ مسموح بها. اول حرف يجب ان يكون a-z او A-Z. الطول بين 6 و 16."),
("Website", "الموقع"),
("About", "عن"),
("Slogan_tip", "صنع بحب في هذا العالم الفوضوي!"),
("Privacy Statement", "بيان الخصوصية"),
("Mute", "كتم"),
("Build Date", "تاريخ البناء"),
("Version", "النسخة"),
("Home", "المنزل"),
("Audio Input", "مصدر الصوت"),
("Enhancements", "التحسينات"),
("Hardware Codec", "ترميز العتاد"),
("Adaptive bitrate", "معدل بت متكيف"),
("ID Server", "معرف الخادم"),
("Relay Server", "خادم الوسيط"),
("API Server", "خادم API"),
("invalid_http", "يجب ان يبدأ بـ http:// او https://"),
("Invalid IP", "عنوان IP غير صحيح"),
("Invalid format", "صيغة غير صحيحة"),
("server_not_support", "الخادم غير مدعوم"),
("Not available", "غير متوفر"),
("Too frequent", "متكرر جدا"),
("Cancel", "إلغاء الأمر"),
("Skip", "تجاوز"),
("Close", "إغلاق"),
("Retry", "إعادة المحاولة"),
("OK", "موافق"),
("Password Required", "كلمة المرور اجبارية"),
("Please enter your password", "الرجاء كتابة كلمة المرور"),
("Remember password", "تذكر كلمة المرور"),
("Wrong Password", "كلمة مرور خاطئة"),
("Do you want to enter again?", "هل تريد الادخال مرة اخرى؟"),
("Connection Error", "خطأ غي الاتصال"),
("Error", "خطأ"),
("Reset by the peer", "تمت اعادة التعيين بواسطة القرين"),
("Connecting...", "جاري الاتصال..."),
("Connection in progress. Please wait.", "جاري الاتصال, الرجاء الانتظار..."),
("Please try 1 minute later", "الرجاء المحاولة بعد دقيقة واحدة"),
("Login Error", "خطأ في تسجيل الدخول"),
("Successful", "نجاح"),
("Connected, waiting for image...", "متصل, جاري انتظار الصورة..."),
("Name", "الاسم"),
("Type", "النوع"),
("Modified", "آخر تعديل"),
("Size", "الحجم"),
("Show Hidden Files", "عرض الملفات المخفية"),
("Receive", "استقبال"),
("Send", "ارسال"),
("Refresh File", "تحديث الملف"),
("Local", "المحلي"),
("Remote", "البعيد"),
("Remote Computer", "الحاسب البعيد"),
("Local Computer", "الحاسب المحلي"),
("Confirm Delete", "تأكيد الحذف"),
("Delete", "حذف"),
("Properties", "الخصائص"),
("Multi Select", "اختيار متعدد"),
("Select All", "تحديد الكل"),
("Unselect All", "الغاء تحديد الكل"),
("Empty Directory", "مجلد فارغ"),
("Not an empty directory", "مجلد غير فارغ"),
("Are you sure you want to delete this file?", "هل انت متأكد من أنك تريد حذف هذا الملف؟"),
("Are you sure you want to delete this empty directory?", "هل انت متأكد من أنك تريد حذف هذا المجلد؟"),
("Are you sure you want to delete the file of this directory?", "هل انت متأكد من أنك تريد حذف ملفات هذا المجلد؟"),
("Do this for all conflicts", "فعل هذا لكل التعارضات"),
("This is irreversible!", "لا يمكن التراجع عن هذا!"),
("Deleting", "جاري الحذف"),
("files", "ملفات"),
("Waiting", "انتظار"),
("Finished", "انتهى"),
("Speed", "السرعة"),
("Custom Image Quality", "جودة صورة مخصصة"),
("Privacy mode", "وضع الخصوصية"),
("Block user input", "حجم ادخال المستخدم"),
("Unblock user input", "الغاء حجب ادخال المستخدم"),
("Adjust Window", "ضبط النافذة"),
("Original", "الاصلي"),
("Shrink", "تقليص"),
("Stretch", "تمديد"),
("Scrollbar", "شريط التمرير"),
("ScrollAuto", "التمرير التلقائي"),
("Good image quality", "دقة صورة جيدة"),
("Balanced", "متوازن"),
("Optimize reaction time", "تحسين وقت رد الفعل"),
("Custom", "مخصص"),
("Show remote cursor", "عرض مؤشر الجهاز البعيد"),
("Show quality monitor", "عرض مراقب الجودة"),
("Disable clipboard", "تعطيل الحافظة"),
("Lock after session end", "القفل بعد نهاية هذه الجلسة"),
("Insert", "ادخال"),
("Insert Lock", "قفل الادخال"),
("Refresh", "تحديث"),
("ID does not exist", "المعرف غير موجود"),
("Failed to connect to rendezvous server", "فشل الاتصال بخادم rendezvous"),
("Please try later", "الرجاء المحاولة لاحقا"),
("Remote desktop is offline", "سطح المكتب البعيد غير متصل"),
("Key mismatch", "المفتاح غير متطابق"),
("Timeout", "نفذ الوقت"),
("Failed to connect to relay server", "فشل الاتصال بالخادم الوسيط"),
("Failed to connect via rendezvous server", "فشل الاتصال عير خادم rendezvous"),
("Failed to connect via relay server", "فشل الاتصال عبر الخادم الوسيط"),
("Failed to make direct connection to remote desktop", "فشل في اجراء اتصال مباشر لسطح المكتب البعيد"),
("Set Password", "ضبط كلمة المرور"),
("OS Password", "كلمة مرور نظام التشغيل"),
("install_tip", "بسبب صلاحيات تحكم حساب المستخدم. RustDesk قد لا يعمل بشكل صحيح في جهة البعيد في بعض الحالات. لتفادي ذلك. الرجاء الضغط على الزر ادناه لتثبيت RustDesk في جهازك."),
("Click to upgrade", "اضغط للارتقاء"),
("Click to download", "اضغط للتنزيل"),
("Click to update", "ضغط للتحديث"),
("Configure", "تهيئة"),
("config_acc", "لتتمكن من التحكم بسطح مكتبك البعيد, تحتاج الى منح RustDesk اذونات \"امكانية الوصول\"."),
("config_screen", "لتتمكن من الوصول الى سطح مكتبك البعيد, تحتاج الى منح RustDesk اذونات \"تسجيل الشاشة\"."),
("Installing ...", "جاري التثبيت..."),
("Install", "تثبيت"),
("Installation", "التثبيت"),
("Installation Path", "مسار التثبيت"),
("Create start menu shortcuts", "انشاء اختصارات قائمة ابداء"),
("Create desktop icon", "انشاء اختصار سطح المكتب"),
("agreement_tip", "بمجرد البدء بالتثبيت, فانت قد قبلت اتفاقية الترخيص."),
("Accept and Install", "الموافقة والتثبيت"),
("End-user license agreement", "اتفاقية ترخيص المستخدم النهائي"),
("Generating ...", "جاري الانشاء..."),
("Your installation is lower version.", "انت تحاول تثبيت نسخة قديمة."),
("not_close_tcp_tip", "لا تغلق هذه النافذة اثناء استخدامك للنفق"),
("Listening ...", "جاري الاستماع..."),
("Remote Host", "المستضيف البعيد"),
("Remote Port", "منفذ المستضيف البعيد"),
("Action", "فعل"),
("Add", "اضافة"),
("Local Port", "المنفذ المحلي"),
("Local Address", "العنوان المحلي"),
("Change Local Port", "تغيير المنفذ المحلي"),
("setup_server_tip", "لاتصال اسرع, الرجاء اعداد خادم خاص بك"),
("Too short, at least 6 characters.", "قصير جدا. يجب ان يكون على الاقل 6 خانات"),
("The confirmation is not identical.", "التأكيد غير متطابق"),
("Permissions", "الاذونات"),
("Accept", "قبول"),
("Dismiss", "صرف"),
("Disconnect", "قطع الاتصال"),
("Allow using keyboard and mouse", "السماح باستخدام لوحة المفاتيح والفأرة"),
("Allow using clipboard", "السماح باستخدام الحافظة"),
("Allow hearing sound", "السماح بسماع الصوت"),
("Allow file copy and paste", "السماح بالنسخ واللصق"),
("Connected", "متصل"),
("Direct and encrypted connection", "اتصال مباشر مشفر"),
("Relayed and encrypted connection", "اتصال غير مباشر مشفر"),
("Direct and unencrypted connection", "اتصال مباشر غير مشفر"),
("Relayed and unencrypted connection", "اتصال غير مباشر غير مشفر"),
("Enter Remote ID", "ادخل المعرف البعيد"),
("Enter your password", "ادخل كلمة المرور"),
("Logging in...", "جاري تسجيل الدخول..."),
("Enable RDP session sharing", "تفعيل مشاركة الجلسة باستخدام RDP"),
("Auto Login", "تسجيل دخول تلقائي"),
("Enable Direct IP Access", "تفعيل الوصول المباشر لعنوان IP"),
("Rename", "اعادة تسمية"),
("Space", "مساحة"),
("Create Desktop Shortcut", "انشاء اختصار سطح مكتب"),
("Change Path", "تغيير المسار"),
("Create Folder", "انشاء مجلد"),
("Please enter the folder name", "الرجاء ادخال اسم المجلد"),
("Fix it", "اصلحه"),
("Warning", "تحذير"),
("Login screen using Wayland is not supported", "تسجيل الدخول باستخدام Wayland غير مدعوم"),
("Reboot required", "يجب اعادة التشغيل"),
("Unsupported display server", "خادم العرض غير مدعوم"),
("x11 expected", "x11 متوقع"),
("Port", "المنفذ"),
("Settings", "الاعدادات"),
("Username", "اسم المستخدم"),
("Invalid port", "منفذ خاطئ"),
("Closed manually by the peer", "اغلاق يدويا بواسطة القرين"),
("Enable remote configuration modification", "تفعيل تعديل اعدادات البعيد"),
("Run without install", "تشغيل بدون تثبيت"),
("Connect via relay", "الاتصال عبر وسيط"),
("Always connect via relay", "الاتصال باستخدام وسيط دائما"),
("whitelist_tip", "فقط قائمة الـ IP البيضاء تستطيع الوصول لي"),
("Login", "تسجيل الدخول"),
("Verify", "تأكيد"),
("Remember me", "تذكرني"),
("Trust this device", "الوثوق بهذا الجهاز"),
("Verification code", "رمز التحقق"),
("verification_tip", "رمز التحقق ارسل الى بريدك الالكتروني المسجل. ادخل رمز التحقق للاستمرار بتسجيل الدخول."),
("Logout", "تسجيل الخروج"),
("Tags", "العلامات"),
("Search ID", "البحث المعرف"),
("whitelist_sep", "مفصولة بفاصلة او فاصلة منقوطة او سطر جديد"),
("Add ID", "اضافة معرف"),
("Add Tag", "اضافة علامة"),
("Unselect all tags", "عدم تحديد كل العلامات"),
("Network error", "خطأ شبكة"),
("Username missed", "اسم المستخدم مفقود"),
("Password missed", "كلمة المرور مفقودة"),
("Wrong credentials", "اسم مستخدم او كلمة مرور خاطئة"),
("The verification code is incorrect or has expired", "رمز التحقق غير صحيح او منتهي"),
("Edit Tag", "تحرير علامة"),
("Unremember Password", "عدم تذكر كلمة المرور"),
("Favorites", "المفضلة"),
("Add to Favorites", "اضافة للمفضلة"),
("Remove from Favorites", "ازالة من المفضلة"),
("Empty", "فارغ"),
("Invalid folder name", "اسم المجلد غير صحيح"),
("Socks5 Proxy", "وكيل Socks5"),
("Hostname", "اسم المستضيف"),
("Discovered", "المكتشفة"),
("install_daemon_tip", "للبدء مع بدء تشغيل النظام. تحتاج الى تثبيت خدمة النظام."),
("Remote ID", "المعرف البعيد"),
("Paste", "لصق"),
("Paste here?", "لصق هنا؟"),
("Are you sure to close the connection?", "هل انت متاكد من انك تريد اغلاق هذا الاتصال؟"),
("Download new version", "تنويل نسخة جديدة"),
("Touch mode", "وضع اللمس"),
("Mouse mode", "وضع الفأرة"),
("One-Finger Tap", "لم اصبع واحد"),
("Left Mouse", "الفأرة اليسرى"),
("One-Long Tap", "لمسة واحدة طويلة"),
("Two-Finger Tap", "لمس اصبعين"),
("Right Mouse", "الفأرة اليمنى"),
("One-Finger Move", "نقل الاصبع الواحد"),
("Double Tap & Move", "لمستان ونقل"),
("Mouse Drag", "سحب الفأرة"),
("Three-Finger vertically", "ثلاث اصابع افقيا"),
("Mouse Wheel", "عجلة الفارة"),
("Two-Finger Move", "نقل الاصبعين"),
("Canvas Move", ""),
("Pinch to Zoom", "قرصة للتكبير"),
("Canvas Zoom", ""),
("Reset canvas", ""),
("No permission of file transfer", "لا يوجد اذن نقل الملف"),
("Note", "ملاحظة"),
("Connection", "الاتصال"),
("Share Screen", "مشاركة الشاشة"),
("Chat", "محادثة"),
("Total", "الاجمالي"),
("items", "عناصر"),
("Selected", "محدد"),
("Screen Capture", "لقط الشاشة"),
("Input Control", "تحكم الادخال"),
("Audio Capture", "لقط الصوت"),
("File Connection", "اتصال الملف"),
("Screen Connection", "اتصال الشاشة"),
("Do you accept?", "هل تقبل؟"),
("Open System Setting", "فتح اعدادات النظام"),
("How to get Android input permission?", "كيف تحصل على اذن الادخال في اندرويد؟"),
("android_input_permission_tip1", "لكي يتمكن جهاز بعيد من التحكم بجهازك الـ Android عن طريق الفارة أو اللمس، يلزمك السماح لـ RustDesk باستخدام خدمة \"إمكانية الوصول\"."),
("android_input_permission_tip2", "يرجى الانتقال إلى صفحة إعدادات النظام التالية، والعثور على [الخدمات المثبتة]، وتشغيل خدمة [RustDesk Input]."),
("android_new_connection_tip", "تم استلام طلب تحكم جديد، حيث يريد التحكم بجهازك الحالي."),
("android_service_will_start_tip", "تشغيل \"لقط الشاشة\" سيبدأ الخدمة تلقائيا، حيث سيسمح للاجهزة الاخرى بطلب الاتصال مع جهازك."),
("android_stop_service_tip", "اغلاق الخدمة سيغلق جميع الاتصالات القائمة."),
("android_version_audio_tip", "نسخة اندرويد الحالية لا تدعم خدمة لقط الصوت، الرجاء الترقية الى نسخة 10 او أحدث."),
("android_start_service_tip", "اضغط تشغيل الخدمة او فعل صلاحية لقط الشاشة لبدء خدمة مشاركة الشاشة."),
("android_permission_may_not_change_tip", "الاذونات الاتصالات القائمة قد لا تتغير مباشرة الا بعد اعادة الاتصال."),
("Account", "الحساب"),
("Overwrite", "استبدال"),
("This file exists, skip or overwrite this file?", "الملف موجود, هل تريد التجاوز او الاستبدال؟"),
("Quit", "خروج"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("Help", "مساعدة"),
("Failed", "فشل"),
("Succeeded", "نجاح"),
("Someone turns on privacy mode, exit", "شخص ما فعل وضع الخصوصية, خروج"),
("Unsupported", "غير مدعوم"),
("Peer denied", "القرين رفض"),
("Please install plugins", "الرجاء تثبيت الاضافات"),
("Peer exit", "خروج القرين"),
("Failed to turn off", "فشل ايقاف التشغيل"),
("Turned off", "مطفئ"),
("In privacy mode", "في وضع الخصوصية"),
("Out privacy mode", "الخروج من وضع الخصوصية"),
("Language", "اللغة"),
("Keep RustDesk background service", "ابق خدمة RustDesk تعمل في الخلفية"),
("Ignore Battery Optimizations", "تجاهل تحسينات البطارية"),
("android_open_battery_optimizations_tip", "اذا اردت تعطيل هذه الميزة, الرجاء الذهاب الى صفحة اعدادات تطبيق RustDesk, ابحث عن البطارية, الغ تحديد غير مقيد"),
("Start on Boot", "البدء عند تشغيل النظام"),
("Start the screen sharing service on boot, requires special permissions", "تشغيل خدمة مشاركة الشاشة عند بدء تشغيل النظام, يحتاج الى اذونات خاصة"),
("Connection not allowed", "الاتصال غير مسموح"),
("Legacy mode", "الوضع التقليدي"),
("Map mode", "وضع الخريطة"),
("Translate mode", "وضع الترجمة"),
("Use permanent password", "استخدام كلمة مرور دائمة"),
("Use both passwords", "استخدام طريقتي كلمة المرور"),
("Set permanent password", "تعيين كلمة مرور دائمة"),
("Enable Remote Restart", "تفعيل اعداة تشغيل البعيد"),
("Allow remote restart", "السماح باعادة تشغيل البعيد"),
("Restart Remote Device", "اعادة تشغيل الجهاز البعيد"),
("Are you sure you want to restart", "هل انت متاكد من انك تريد اعادة التشغيل؟"),
("Restarting Remote Device", "جاري اعادة تشغيل البعيد"),
("remote_restarting_tip", "الجهاز البعيد يعيد التشغيل. الرجاء اغلاق هذه الرسالة واعادة الاتصال باستخدام كلمة المرور الدائمة بعد فترة بسيطة."),
("Copied", "منسوخ"),
("Exit Fullscreen", "الخروج من ملئ الشاشة"),
("Fullscreen", "ملئ الشاشة"),
("Mobile Actions", "اجراءات الهاتف"),
("Select Monitor", "اختر شاشة"),
("Control Actions", "اجراءات التحكم"),
("Display Settings", "اعدادات العرض"),
("Ratio", "النسبة"),
("Image Quality", "جودة الصورة"),
("Scroll Style", "شكل التمرير"),
("Show Toolbar", "عرض شريط الادوات"),
("Hide Toolbar", "اخفاء شريط الادوات"),
("Direct Connection", "اتصال مباشر"),
("Relay Connection", "اتصال الوسيط"),
("Secure Connection", "اتصال آمن"),
("Insecure Connection", "اتصال غير آمن"),
("Scale original", "المقياس الأصلي"),
("Scale adaptive", "مقياس التكيف"),
("General", "عام"),
("Security", "الأمان"),
("Theme", "السمة"),
("Dark Theme", "سمة غامقة"),
("Light Theme", "سمة فاتحة"),
("Dark", "غامق"),
("Light", "فاتح"),
("Follow System", "نفس نظام التشغيل"),
("Enable hardware codec", "تفعيل ترميز العتاد"),
("Unlock Security Settings", "فتح اعدادات الامان"),
("Enable Audio", "تفعيل الصوت"),
("Unlock Network Settings", "فتح اعدادات الشبكة"),
("Server", "الخادم"),
("Direct IP Access", "وصول مباشر للـ IP"),
("Proxy", "الوكيل"),
("Apply", "تطبيق"),
("Disconnect all devices?", "قطع اتصال كل الاجهزة؟"),
("Clear", "مسح"),
("Audio Input Device", "جهاز ادخال الصوت"),
("Use IP Whitelisting", "استخدام قائمة الـ IP البيضاء"),
("Network", "الشبكة"),
("Enable RDP", "تفعيل RDP"),
("Pin Toolbar", "تثبيت شريط الادوات"),
("Unpin Toolbar", "الغاء تثبيت شريط الادوات"),
("Recording", "التسجيل"),
("Directory", "المسار"),
("Automatically record incoming sessions", "تسجيل الجلسات القادمة تلقائيا"),
("Change", "تغيير"),
("Start session recording", "بدء تسجيل الجلسة"),
("Stop session recording", "ايقاف تسجيل الجلسة"),
("Enable Recording Session", "تفعيل تسجيل الجلسة"),
("Allow recording session", "السماح بتسجيل الجلسة"),
("Enable LAN Discovery", "تفعيل اكتشاف الشبكة المحلية"),
("Deny LAN Discovery", "رفض اكتشاف الشبكة المحلية"),
("Write a message", "اكتب رسالة"),
("Prompt", ""),
("Please wait for confirmation of UAC...", "الرجاء انتظار تاكيد تحكم حساب المستخدم..."),
("elevated_foreground_window_tip", "النافذة الحالية لسطح المكتب البعيد تحتاج صلاحية اعلى لتعمل, لذلك لن تستطيع استخدام الفارة ولوحة المفاتيح مؤقتا. تستطيع انت تطلب من المستخدم البعيد تصغير النافذة الحالية, او ضفط زر الارتقاء في نافذة ادارة الاتصال. لتفادي هذة المشكلة من المستحسن تثبيت البرنامج في الجهاز البعيد."),
("Disconnected", "مفصول"),
("Other", "اخرى"),
("Confirm before closing multiple tabs", "التاكيد قبل اغلاق السنة عديدة"),
("Keyboard Settings", "اعدادات لوحة المفاتيح"),
("Full Access", "وصول كامل"),
("Screen Share", "مشاركة الشاشة"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland يتطلب نسخة ابونتو 21.04 او اعلى."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland يتطلب نسخة اعلى من توزيعة لينكس. الرجاء تجربة سطح مكتب X11 او غير نظام تشغيلك."),
("JumpLink", "رابط القفز"),
("Please Select the screen to be shared(Operate on the peer side).", "الرجاء اختيار شاشة لمشاركتها (تعمل على جانب القرين)."),
("Show RustDesk", "عرض RustDesk"),
("This PC", "هذا الحاسب"),
("or", "او"),
("Continue with", "متابعة مع"),
("Elevate", "ارتقاء"),
("Zoom cursor", "تكبير المؤشر"),
("Accept sessions via password", "قبول الجلسات عبر كلمة المرور"),
("Accept sessions via click", "قبول الجلسات عبر الضغط"),
("Accept sessions via both", "قبول الجلسات عبر الاثنين"),
("Please wait for the remote side to accept your session request...", "الرجاء الانتظار حتى يقبل الطرف البعيد طلب الجلسة..."),
("One-time Password", "كلمة مرور لمرة واحدة"),
("Use one-time password", "استخدام كلمة مرور لمرة واحدة"),
("One-time password length", "طول كلمة مرور لمرة واحدة"),
("Request access to your device", "طلب الوصول إلى جهازك"),
("Hide connection management window", "اخفاء نافذة ادارة الاتصال"),
("hide_cm_tip", "السماح بالاخفاء فقط في حالة قبول الجلسات عبر كلمة المرور واستخدام كلمة المرور الدائمة"),
("wayland_experiment_tip", "دعم Wayland لازال في المرحلة التجريبية. الرجاء استخدام X11 اذا اردت وصول كامل."),
("Right click to select tabs", "الضغط بالزر الايمن لتحديد الالسنة"),
("Skipped", "متجاوز"),
("Add to Address Book", "اضافة الى كتاب العناوين"),
("Group", "مجموعة"),
("Search", "بحث"),
("Closed manually by web console", "اغلق يدويا عبر طرفية الويب"),
("Local keyboard type", "توع لوحة المفاتيح المحلية"),
("Select local keyboard type", "اختر نوع لوحة المفاتيح الملحية"),
("software_render_tip", "اذا كنت تستخدم بطاقة رسوميات Nvidia تحت لينكس والشاشة البعيد تغلق مباشرة بعد الاتصال, قم بالتبديل الى تعريفات Nouveau مفتوحة المصدر واختيار الترميز البرمجي قد يساعد. اعادة تشغيل البرناج مطلوبة."),
("Always use software rendering", "استخدام الترميز البرمجي دائما"),
("config_input", "لتتمكن من التحكم بسطح المكتب البعيد. يجب من RustDesk اذونات \"مراقبة المدخلات\"."),
("config_microphone", "لتتمكن من التحدث. يجب منح RustDesk اذونات \"تسجيل الصوت\"."),
("request_elevation_tip", "اطلب الارتقاء في حالة وجود شخص في الطرف الاخر."),
("Wait", "انتظر"),
("Elevation Error", "خطأ الارتقاء"),
("Ask the remote user for authentication", "اسأل المستخدم البعيد التوثيق"),
("Choose this if the remote account is administrator", "اختر اذا كان الحساب البعيد مدير للنظام"),
("Transmit the username and password of administrator", "انقل اسم المستخدم وكلمة مرور مدير النظام"),
("still_click_uac_tip", "لازال يحتاج المستخدم البعيد للضغط على موافق في نافذة تحكم حساب المستخدم في RustDesk الذي يعمل."),
("Request Elevation", "طلب ارتقاء"),
("wait_accept_uac_tip", "الرجاء انتظار المستخدم البعيد حتى يوافق على طلب تحكم حساب المستخدم."),
("Elevate successfully", "الارتقاء بنجاح"),
("uppercase", "حرف كبير"),
("lowercase", "حرف صغير"),
("digit", "رقم"),
("special character", "رمز"),
("length>=8", "الطول 8 على الاقل"),
("Weak", "ضعيف"),
("Medium", "متوسط"),
("Strong", "قوي"),
("Switch Sides", "تبديل الاماكن"),
("Please confirm if you want to share your desktop?", "الرجاء التاكيد على انك تريد مشاركة سطح مكتبك؟"),
("Display", "العرض"),
("Default View Style", "شكل العرض الافتراضي"),
("Default Scroll Style", "شكل التمرير الافتراضي"),
("Default Image Quality", "دقة الصورة الافتراضية"),
("Default Codec", "الترميز الاقتراضي"),
("Bitrate", "معدل البت"),
("FPS", "مشهد في الثانية"),
("Auto", "تلقائي"),
("Other Default Options", "الخيارات الافتراضية الاخرى"),
("Voice call", "مكالمة صوتية"),
("Text chat", "دردشة نصية"),
("Stop voice call", "ايقاف المكالمة الصوتية"),
("relay_hint_tip", "قد لا يكون ممكن الاتصال مباشرة. يمكنك محاولة الاتصال عبر وسيط. ايضا اذا اردت استخدام وسيط لمحاولتك الاولى اضف لاحقة \"/r\" الى المعرف او اختر \"الاتصال باستخدام وسيط دائما\" في بطاقة الجلسات الحديثة ان وجدت."),
("Reconnect", "اعادة الاتصال"),
("Codec", "الترميز"),
("Resolution", "الدقة"),
("No transfers in progress", "لا توجد عمليات نقل حاليا"),
("Set one-time password length", "تعيين طول كلمة مرور المرة الواحدة"),
("install_cert_tip", "تثبيت شهادة RustDesk"),
("confirm_install_cert_tip", "هذه شهادة RustDesk الاختبارية, يمكنك الوثوق بها. هذه الشهادة ستستخدم للوثوق وتثبيت تعاريف RustDesk عند الحاجة."),
("RDP Settings", "اعدادات RDP"),
("Sort by", "ترتيب حسب"),
("New Connection", "اتصال جديد"),
("Restore", "استعادة"),
("Minimize", "تصغير"),
("Maximize", "تكبير"),
("Your Device", "جهازك"),
("empty_recent_tip", "للاسف. لا توجد جلسات حديثة\nحان الوقت للتخطيط لواحدة جديدة."),
("empty_favorite_tip", "لا يوجد اقران مفضلين حتى الان؟\nحسنا لنبحث عن شخص للاتصال معه ومن ثم اضافته للمفضلة."),
("empty_lan_tip", "اه لا, يبدو انك لم تكتشف اي قرين بعد."),
("empty_address_book_tip", "يا عزيزي, يبدو انه لايوجد حاليا اي اقران في كتاب العناوين."),
("eg: admin", "مثلا: admin"),
("Empty Username", "اسم مستخدم فارغ"),
("Empty Password", "كلمة مرور فارغة"),
("Me", "انا"),
("identical_file_tip", "هذا الملف مطابق لملف موجود عن القرين."),
("show_monitors_tip", "عرض الشاشات في شريط الادوات"),
("View Mode", "وضع العرض"),
("login_linux_tip", "تحتاج الى تسجيل الدخول حساب لينكس البعيد وتفعيل جلسة سطح مكتب X"),
("verify_rustdesk_password_tip", "تحقق من كلمة مرور RustDesk"),
("remember_account_tip", "تذكر هذا الحساب"),
("os_account_desk_tip", "هذا الحساب مستخدم لتسجيل الدخول الى سطح المكتب البعيد وتفعيل الجلسة"),
("OS Account", "حساب نظام التشغيل"),
("another_user_login_title_tip", "مستخدم اخر مسجل دخول حاليا"),
("another_user_login_text_tip", "قطع الاتصال"),
("xorg_not_found_title_tip", "Xorg غير موجود"),
("xorg_not_found_text_tip", "الرجاء تثبيت Xorg"),
("no_desktop_title_tip", "لا يتوفر سطح مكتب"),
("no_desktop_text_tip", "الرجاء تثبيت سطح مكتب GNOME"),
("No need to elevate", "لا حاجة للارتقاء"),
("System Sound", "صوت النظام"),
("Default", "الافتراضي"),
("New RDP", "RDP جديد"),
("Fingerprint", "البصمة"),
("Copy Fingerprint", "نسخ البصمة"),
("no fingerprints", "لا توجد بصمات اصابع"),
("Select a peer", "اختر قرين"),
("Select peers", "اختر الاقران"),
("Plugins", "الاضافات"),
("Uninstall", "الغاء التثبيت"),
("Update", "تحديث"),
("Enable", "تفعيل"),
("Disable", "تعطيل"),
("Options", "الخيارات"),
("resolution_original_tip", "الدقة الأصلية"),
("resolution_fit_local_tip", "تناسب الدقة المحلية"),
("resolution_custom_tip", "دقة مخصصة"),
("Collapse toolbar", "طي شريط الادوات"),
("Accept and Elevate", "قبول وارتقاء"),
("accept_and_elevate_btn_tooltip", "قبول الاتصال وارتقاء صلاحيات التحكم بصلاحيات المستخدم."),
("clipboard_wait_response_timeout_tip", "انتهى وقت الانتظار لنسخ الرد."),
("Incoming connection", "اتصال قادم"),
("Outgoing connection", "اتصال مغادر"),
("Exit", "خروج"),
("Open", "فتح"),
("logout_tip", "هل انت متاكد من انك تريد تسجيل الخروج"),
("Service", "الخدمة"),
("Start", "تشغيل"),
("Stop", "ايقاف"),
("exceed_max_devices", "لقد وصلت الحد الأقصى لعدد الاجهزة التي يمكن دارتها."),
("Sync with recent sessions", "المزامنة مع الجلسات الحديثة"),
("Sort tags", "ترتيب العلامات"),
("Open connection in new tab", "فتح اتصال في لسان جديد"),
("Move tab to new window", "نقل اللسان الى نافذة جديدة"),
("Can not be empty", "لا يمكن ان يكون فارغ"),
("Already exists", "موجود مسبقا"),
("Change Password", "تغيير كلمة المرور"),
("Refresh Password", "تحديث كلمة المرور"),
("ID", "المعرف"),
("Grid View", "عرض شبكي"),
("List View", "رعض قائمة"),
("Select", "اختيار"),
("Toggle Tags", "تفعيل/تعطيل العلامات"),
("pull_ab_failed_tip", "فشل تحديث كتاب العناوين"),
("push_ab_failed_tip", "فشل مزامنة كتاب العناوين مع الخادم"),
("synced_peer_readded_tip", "الاجهزة الموجودة في الجلسات الحديثة سيتم مزامنتها مع كتاب العناوين"),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Entrada d'àudio"),
("Enhancements", "Millores"),
("Hardware Codec", "Còdec de hardware"),
("Adaptive Bitrate", "Tasa de bits adaptativa"),
("Adaptive bitrate", "Tasa de bits adaptativa"),
("ID Server", "Servidor de IDs"),
("Relay Server", "Servidor Relay"),
("API Server", "Servidor API"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Nom d'usuari oblidat"),
("Password missed", "Contrasenya oblidada"),
("Wrong credentials", "Credencials incorrectes"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Editar tag"),
("Unremember Password", "Contrasenya oblidada"),
("Favorites", "Preferits"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "音频输入"),
("Enhancements", "增强功能"),
("Hardware Codec", "硬件编解码"),
("Adaptive Bitrate", "自适应码率"),
("Adaptive bitrate", "自适应码率"),
("ID Server", "ID 服务器"),
("Relay Server", "中继服务器"),
("API Server", "API 服务器"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "用户名没有填写"),
("Password missed", "密码没有填写"),
("Wrong credentials", "提供的登录信息错误"),
("The verification code is incorrect or has expired", "验证码错误或已超时"),
("Edit Tag", "修改标签"),
("Unremember Password", "忘记密码"),
("Favorites", "收藏"),
@@ -453,14 +454,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", "语音通话"),
("Text chat", "文字聊天"),
("Stop voice call", "停止语音通话"),
("relay_hint_tip", "可能无法直连,可以尝试中继连接。\n另外,如果想直接使用中继连接,可以在 ID 后面添加/r或者在卡片选项里选择强制走中继连接。"),
("relay_hint_tip", "可能无法直连,可以尝试中继连接。\n另外,如果想直接使用中继连接,可以在 ID 后面添加/r如果最近访问里存在该卡片,也可以在卡片选项里选择强制走中继连接。"),
("Reconnect", "重连"),
("Codec", "编解码"),
("Resolution", "分辨率"),
("No transfers in progress", "无进行中的传输"),
("Set one-time password length", "设置一次性密码长度"),
("idd_driver_tip", "安装虚拟显示器驱动,以便在没有连接显示器的情况下启动虚拟显示器进行控制。"),
("confirm_idd_driver_tip", "安装虚拟显示器驱动的选项已勾选。请注意,测试证书将被安装以信任虚拟显示器驱动。测试证书仅会用于信任Rustdesk驱动。"),
("install_cert_tip", "安装 RustDesk 证书"),
("confirm_install_cert_tip", "此证书为 RustDesk 测试证书,您可以信任此证书。证书将被用于信任和安装 RustDesk 驱动。"),
("RDP Settings", "RDP 设置"),
("Sort by", "排序方式"),
("New Connection", "新连接"),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", "接受并提权"),
("accept_and_elevate_btn_tooltip", "接受连接并提升 UAC 权限"),
("clipboard_wait_response_timeout_tip", "等待拷贝响应超时"),
("Incoming connection", "收到的连接"),
("Outgoing connection", "发起的连接"),
("Exit", "退出"),
("Open", "打开"),
("logout_tip", "确定要退出登录吗?"),
("Service", "服务"),
("Start", "启动"),
("Stop", "停止"),
("exceed_max_devices", "管理的设备数已达到最大值"),
("Sync with recent sessions", "同步最近会话"),
("Sort tags", "对标签进行排序"),
("Open connection in new tab", "在选项卡中打开新连接"),
("Move tab to new window", "将标签页移至新窗口"),
("Can not be empty", "不能为空"),
("Already exists", "已经存在"),
("Change Password", "更改密码"),
("Refresh Password", "刷新密码"),
("ID", "ID"),
("Grid View", "网格视图"),
("List View", "列表视图"),
("Select", "选择"),
("Toggle Tags", "切换标签"),
("pull_ab_failed_tip", "未成功获取地址簿"),
("push_ab_failed_tip", "未成功上传地址簿"),
("synced_peer_readded_tip", "最近会话中存在的设备将会被重新同步到地址簿。"),
("Change Color", "更改颜色"),
("Primary Color", "基本色"),
("HSV Color", "HSV 色"),
("Installation Successful!", "安装成功!"),
("Installation failed!", "安装失败!"),
].iter().cloned().collect();
}

View File

@@ -3,18 +3,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[
("Status", "Stav"),
("Your Desktop", "Vaše plocha"),
("desk_tip", "Pomocí tohoto identifikátoru a hesla můžete přistupovat ke své ploše."),
("desk_tip", "Pomocí tohoto ID a hesla lze přistupovat k pracovní ploše."),
("Password", "Heslo"),
("Ready", "Připraveno"),
("Established", "Navázáno"),
("connecting_status", "Připojování se k Rusdesk síti"),
("connecting_status", "Připojování k RustDesk síti..."),
("Enable Service", "Povolit službu"),
("Start Service", "Spustit službu"),
("Service is running", "Služba je spuštěná"),
("Service is not running", "Služba není spuštěná"),
("not_ready_status", "Nepřipraveno. Zkontrolujte své připojení."),
("Control Remote Desktop", "Ovládat vzdálenou plochu"),
("Transfer File", "Přenést soubor"),
("Transfer File", "Přenos souborů"),
("Connect", "Připojit"),
("Recent Sessions", "Nedávné relace"),
("Address Book", "Adresář kontaktů"),
@@ -27,38 +27,38 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable Clipboard", "Povolit schránku"),
("Enable File Transfer", "Povolit přenos souborů"),
("Enable TCP Tunneling", "Povolit TCP tunelování"),
("IP Whitelisting", "Povolování pouze z daných IP adres)"),
("ID/Relay Server", "Identifikátor / předávací (relay) server"),
("IP Whitelisting", "Povolování pouze z daných IP adres"),
("ID/Relay Server", "ID/předávací server"),
("Import Server Config", "Importovat konfiguraci serveru"),
("Export Server Config", ""),
("Export Server Config", "Exportovat konfiguraci serveru"),
("Import server configuration successfully", "Konfigurace serveru úspěšně importována"),
("Export server configuration successfully", ""),
("Export server configuration successfully", "Konfigurace serveru úspěšně exportována"),
("Invalid server configuration", "Neplatná konfigurace serveru"),
("Clipboard is empty", "Schránka je prázdná"),
("Stop service", "Zastavit službu"),
("Change ID", "Změnit identifikátor"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Použít je mozné pouze znaky a-z, A-Z, 0-9 a _ (podtržítko). Dále je třeba aby začínalo na písmeno a-z, A-Z. Délka mezi 6 a 16 znaky."),
("Change ID", "Změnit ID"),
("Your new ID", "Váše nové ID"),
("length %min% to %max%", "délka mezi %min% a %max%"),
("starts with a letter", "začíná písmenem"),
("allowed characters", "povolené znaky"),
("id_change_tip", "Použít je možné pouze znaky a-z, A-Z, 0-9 a _ (podtržítko). Dále je třeba aby začínalo písmenem a-z, A-Z. Délka mezi 6 a 16 znaky."),
("Website", "Webové stránky"),
("About", "O aplikaci"),
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Ztlumit"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Slogan_tip", "Vytvořeno srdcem v tomto chaotickém světě!"),
("Privacy Statement", "Prohlášení o ochraně osobních údajů"),
("Mute", "Ztlumit zvuk"),
("Build Date", "Datum sestavení"),
("Version", "Verze"),
("Home", "Domů"),
("Audio Input", "Vstup zvuku"),
("Enhancements", ""),
("Hardware Codec", ""),
("Adaptive Bitrate", ""),
("ID Server", "Server pro identif."),
("Relay Server", "Předávací (relay) server"),
("API Server", "Server s API rozhraním"),
("Enhancements", "Vylepšení"),
("Hardware Codec", "Hardwarový kodek"),
("Adaptive bitrate", "Adaptivní datový tok"),
("ID Server", "ID Server"),
("Relay Server", "Předávací server"),
("API Server", "API Server"),
("invalid_http", "Je třeba, aby začínalo na http:// nebo https://"),
("Invalid IP", "Neplatná IP adresa"),
("Invalid IP", "Neplatná IP"),
("Invalid format", "Neplatný formát"),
("server_not_support", "Server zatím nepodporuje"),
("Not available", "Není k dispozici"),
@@ -75,13 +75,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Do you want to enter again?", "Chcete se znovu připojit?"),
("Connection Error", "Chyba spojení"),
("Error", "Chyba"),
("Reset by the peer", "Resetováno protějškem"),
("Connecting...", "Připojování"),
("Connection in progress. Please wait.", "Probíhá připojování vyčkejte."),
("Please try 1 minute later", "Zkuste to až za minutu či déle"),
("Reset by the peer", "Resetováno protistranou"),
("Connecting...", "Připojování..."),
("Connection in progress. Please wait.", "Probíhá připojování, vyčkejte prosím."),
("Please try 1 minute later", "Zkuste to prosím o 1 minutu později"),
("Login Error", "Chyba přihlášení se"),
("Successful", "Úspěšné"),
("Connected, waiting for image...", "Připojeno, čeká se na obraz"),
("Connected, waiting for image...", "Připojeno, čeká se na obraz..."),
("Name", "Název"),
("Type", "Typ"),
("Modified", "Změněno"),
@@ -98,13 +98,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Delete", "Smazat"),
("Properties", "Vlastnosti"),
("Multi Select", "Vícenásobný výběr"),
("Select All", ""),
("Unselect All", ""),
("Select All", "Vybrat vše"),
("Unselect All", "Zrušit výběr všech"),
("Empty Directory", "Prázdná složka"),
("Not an empty directory", "Neprázdná složka"),
("Are you sure you want to delete this file?", "Opravdu chcete tento soubor vymazat?"),
("Are you sure you want to delete this empty directory?", "Opravdu chcete tuto prázdnou složku smazat?"),
("Are you sure you want to delete the file of this directory?", "Opravdu chcete vymazat soubor, pocházející z této složky?"),
("Are you sure you want to delete the file of this directory?", "Opravdu chcete vymazat soubor z této složky?"),
("Do this for all conflicts", "Naložit takto se všemi konflikty"),
("This is irreversible!", "Toto nelze vzít zpět"),
("Deleting", "Mazání"),
@@ -113,7 +113,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Finished", "Dokončeno"),
("Speed", "Rychlost"),
("Custom Image Quality", "Uživatelsky určená kvalita obrazu"),
("Privacy mode", "Režim soukromí"),
("Privacy mode", "Režim ochrany soukromí"),
("Block user input", "Blokovat vstupní zařízení uživatele"),
("Unblock user input", "Odblokovat vstupní zařízení uživatele"),
("Adjust Window", "Přizpůsobit velikost okna"),
@@ -121,79 +121,79 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Shrink", "Oříznout"),
("Stretch", "Roztáhnout"),
("Scrollbar", "Posuvník"),
("ScrollAuto", "Rolovať Auto"),
("ScrollAuto", "Automatické rolování"),
("Good image quality", "Dobrá kvalita obrazu"),
("Balanced", "Vyvážené"),
("Optimize reaction time", "Optimalizovat pro co nejnižší prodlevu odezvy"),
("Custom", ""),
("Show remote cursor", "Zobrazovat ukazatel myši z protějšku"),
("Show quality monitor", ""),
("Balanced", "Vyvážená"),
("Optimize reaction time", "Optimalizovat reakční dobu"),
("Custom", "Vlastní"),
("Show remote cursor", "Zobrazit vzdálený kurzor"),
("Show quality monitor", "Zobrazit monitor kvality"),
("Disable clipboard", "Vypnout schránku"),
("Lock after session end", "Po ukončení relace zamknout plochu"),
("Insert", "Vložit"),
("Insert Lock", "Vložit zámek"),
("Insert Lock", "Zamknout"),
("Refresh", "Načíst znovu"),
("ID does not exist", "Takový identifikátor neexistuje"),
("Failed to connect to rendezvous server", "Nepodařil se připojit ke zprostředkovávajícímu serveru"),
("ID does not exist", "Toto ID neexistuje"),
("Failed to connect to rendezvous server", "Nepodařilo se připojit ke zprostředkovávajícímu serveru"),
("Please try later", "Zkuste to později"),
("Remote desktop is offline", "Vzdálená plocha není připojená ke službě"),
("Key mismatch", "Neshoda klíčů"),
("Timeout", "Překročen časový limit pro navázání spojení"),
("Failed to connect to relay server", "Nepodařilo se připojit k předávacímu (relay) serveru"),
("Failed to connect to relay server", "Nepodařilo se připojit k předávacímu serveru"),
("Failed to connect via rendezvous server", "Nepodařilo se připojit prostřednictvím zprostředkovávajícího serveru"),
("Failed to connect via relay server", "Nepodařilo se připojit prostřednictvím předávacímu (relay) serveru"),
("Failed to connect via relay server", "Nepodařilo se připojit prostřednictvím předávacího serveru"),
("Failed to make direct connection to remote desktop", "Nepodařilo s navázat přímé připojení ke vzdálené ploše"),
("Set Password", "Nastavit heslo"),
("OS Password", "Heslo do operačního systému"),
("install_tip", "Kvůli řízení oprávnění v systému (UAC), RustDesk v některých případech na protějšku nefunguje správně. Abyste se UAC vyhnuli, klikněte na níže uvedené tlačítko a nainstalujte tak RustDesk do systému."),
("Click to upgrade", "Aktualizaci nainstalujete kliknutím"),
("Click to download", "Stáhnete si kliknutím"),
("Click to update", "Znovu načtete kliknutím"),
("install_tip", "Kvůli řízení oprávnění v systému (UAC), RustDesk v některých případech na protistraně nefunguje správně. Abyste se UAC vyhnuli, klikněte na níže uvedené tlačítko a nainstalujte tak RustDesk do systému."),
("Click to upgrade", "Aktualizovat"),
("Click to download", "Stáhnout"),
("Click to update", "Aktualizovat"),
("Configure", "Nastavit"),
("config_acc", "Aby bylo možné na dálku ovládat vaši plochu, je třeba aplikaci RustDesk udělit oprávnění pro Zpřístupnění pro hendikepované."),
("config_screen", "Aby bylo možné přistupovat k vaší ploše na dálku, je třeba aplikaci RustDesk udělit oprávněí pro Nahrávání obsahu obrazovky."),
("Installing ...", "Instaluje se"),
("config_acc", "Aby bylo možné na dálku ovládat vaši plochu, je třeba aplikaci RustDesk udělit oprávnění pro \"Zpřístupnění pro hendikepované\"."),
("config_screen", "Aby bylo možné přistupovat k vaší ploše na dálku, je třeba aplikaci RustDesk udělit oprávnění pro \"Nahrávání obsahu obrazovky\"."),
("Installing ...", "Instaluje se ..."),
("Install", "Nainstalovat"),
("Installation", "Instalace"),
("Installation Path", "Popis umístění instalace"),
("Installation Path", "Umístění instalace"),
("Create start menu shortcuts", "Vytvořit zástupce v nabídce Start"),
("Create desktop icon", "Vytvořit ikonu na ploše"),
("agreement_tip", "Spuštěním instalace přijímáte licenční ujednání."),
("Accept and Install", "Přijmout a nainstalovat"),
("End-user license agreement", "Licencenční ujednání s koncovým uživatelem"),
("Generating ...", "Vytváření"),
("Generating ...", "Vytváření ..."),
("Your installation is lower version.", "Máte nainstalovanou starší verzi"),
("not_close_tcp_tip", "Po dobu, po kterou tunel potřebujete, nezavírejte toto okno"),
("Listening ...", "Očekávní spojení"),
("Remote Host", "Vzdálený stroj"),
("Remote Port", "Port na protějšku"),
("Listening ...", "Očekávaní spojení ..."),
("Remote Host", "Vzdálený hostitel"),
("Remote Port", "Vzdálený port"),
("Action", "Akce"),
("Add", "Přidat"),
("Local Port", "Místní port"),
("Local Address", ""),
("Change Local Port", ""),
("Local Address", "Místní adresa"),
("Change Local Port", "Změnit místní port"),
("setup_server_tip", "Rychlejší připojení získáte vytvořením si svého vlastního serveru"),
("Too short, at least 6 characters.", "Příliš krátké alespoň 6 znaků."),
("Too short, at least 6 characters.", "Příliš krátké, alespoň 6 znaků."),
("The confirmation is not identical.", "Kontrolní zadání se neshoduje."),
("Permissions", "Oprávnění"),
("Accept", "Přijmout"),
("Dismiss", "Zahodit"),
("Disconnect", "Odpojit"),
("Allow using keyboard and mouse", "Umožnit ovládání klávesnice a myši"),
("Allow using clipboard", "Umožnit používání schránky"),
("Allow hearing sound", "Umožnit slyšet můj zvuk"),
("Allow using keyboard and mouse", "Povolit ovládání klávesnice a myši"),
("Allow using clipboard", "Povolit použití schránky"),
("Allow hearing sound", "Povolit slyšet zvuk"),
("Allow file copy and paste", "Povolit kopírování a vkládání souborů"),
("Connected", "Připojeno"),
("Direct and encrypted connection", "Přímé a šifrované spojení"),
("Relayed and encrypted connection", "Předávané (relay) a šifrované spojení"),
("Relayed and encrypted connection", "Předávané a šifrované spojení"),
("Direct and unencrypted connection", "Přímé a nešifrované spojení"),
("Relayed and unencrypted connection", "Předávané (relay) a nešifrované spojení"),
("Enter Remote ID", "Zadejte identifikátor protějšku"),
("Relayed and unencrypted connection", "Předávané a nešifrované spojení"),
("Enter Remote ID", "Zadejte ID protistrany"),
("Enter your password", "Zadejte své heslo"),
("Logging in...", "Přihlašování se…"),
("Enable RDP session sharing", "Zapnout sdílení relace RDP protokolu"),
("Logging in...", "Přihlašování..."),
("Enable RDP session sharing", "Povolit sdílení relace RDP"),
("Auto Login", "Automatické přihlášení"),
("Enable Direct IP Access", "Zapnout přímý přístup na IP adresu"),
("Enable Direct IP Access", "Povolit přímý přístup k IP"),
("Rename", "Přejmenovat"),
("Space", "Mezera"),
("Create Desktop Shortcut", "Vytvořit zástupce na ploše"),
@@ -201,54 +201,55 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Create Folder", "Vytvořit složku"),
("Please enter the folder name", "Zadejte název pro složku"),
("Fix it", "Opravit to"),
("Warning", "Upozorne"),
("Login screen using Wayland is not supported", "Přihlašovací obrazovka prostřednictvím Wayland není podporována"),
("Reboot required", "Je třeba restartovat"),
("Warning", "Upozorně"),
("Login screen using Wayland is not supported", "Přihlašovací obrazovka pomocí systému Wayland není podporována"),
("Reboot required", "Je vyžadován restart"),
("Unsupported display server", "Nepodporovaný zobrazovací server"),
("x11 expected", "očekávány x11"),
("Port", ""),
("x11 expected", "očekávaný x11"),
("Port", "Port"),
("Settings", "Nastavení"),
("Username", "Uživatelské jméno"),
("Invalid port", "Neplatné číslo portu"),
("Closed manually by the peer", "Ručně ukončeno protějškem"),
("Enable remote configuration modification", "Umožnit upravování nastavení vzdáleného"),
("Run without install", "Spustit bez instalování"),
("Connect via relay", ""),
("Always connect via relay", "Vždy se spojovat prostřednictvím brány pro předávání (relay)"),
("Closed manually by the peer", "Ručně ukončeno protistranou"),
("Enable remote configuration modification", "Povolit vzdálenou úpravu konfigurace"),
("Run without install", "Spustit bez instalace"),
("Connect via relay", "Připojení přes předávací server"),
("Always connect via relay", "Vždy se připojovat prostřednictvím předávacího serveru"),
("whitelist_tip", "Přístup je umožněn pouze z IP adres, nacházejících se na seznamu povolených"),
("Login", "Přihlásit se"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Verify", "Ověřit"),
("Remember me", "Zapamatovat si"),
("Trust this device", "Důvěřovat tomuto zařízení"),
("Verification code", "Ověřovací kód"),
("verification_tip", "Na registrovanou e-mailovou adresu byl zaslán ověřovací kód, zadejte jej a pokračujte v přihlašování."),
("Logout", "Odhlásit se"),
("Tags", "Štítky"),
("Search ID", "Hledat identifikátor"),
("whitelist_sep", "Odělováno čárkou, středníkem, mezerou nebo koncem řádku"),
("Add ID", "Přidat identifikátor"),
("Search ID", "Hledat ID"),
("whitelist_sep", "Oddělené čárkou, středníkem, mezerami, nebo novým řádkem."),
("Add ID", "Přidat ID"),
("Add Tag", "Přidat štítek"),
("Unselect all tags", "Zrušit výběr všech štítků"),
("Network error", "Chyba sítě"),
("Username missed", "Chybí uživatelské jméno"),
("Password missed", "Chybí heslo"),
("Wrong credentials", "Nesprávné přihlašovací údaje"),
("The verification code is incorrect or has expired", "Ověřovací kód je nesprávný, nebo jeho platnost vypršela"),
("Edit Tag", "Upravit štítek"),
("Unremember Password", "Přestat si heslo pamatovat"),
("Unremember Password", "Přestat si pamatovat heslo"),
("Favorites", "Oblíbené"),
("Add to Favorites", "Přidat do oblíbených"),
("Remove from Favorites", "Odebrat z oblíbených"),
("Empty", "Prázdné"),
("Invalid folder name", "Neplatný název složky"),
("Socks5 Proxy", "Socks5 proxy"),
("Hostname", "Název stroje"),
("Hostname", "Název hostitele"),
("Discovered", "Objeveno"),
("install_daemon_tip", "Pokud má být spouštěno při startu systému, je třeba nainstalovat systémovou službu."),
("Remote ID", "Identif. protějšku"),
("Remote ID", "Vzdálené ID"),
("Paste", "Vložit"),
("Paste here?", "Vložit sem?"),
("Are you sure to close the connection?", "Opravdu chcete spojení ukončit?"),
("Download new version", "Stáhnout si novou verzi"),
("Paste here?", "Vložit zde?"),
("Are you sure to close the connection?", "Opravdu chcete spojení uzavřít?"),
("Download new version", "Stáhnout novou verzi"),
("Touch mode", "Režim dotyku"),
("Mouse mode", "Režim myši"),
("One-Finger Tap", "Klepnutí jedním prstem"),
@@ -266,10 +267,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Pinch to Zoom", "Přiblížíte roztažením dvěma prsty"),
("Canvas Zoom", "Přiblížení zobrazení"),
("Reset canvas", "Vrátit měřtko zobrazení na výchozí"),
("No permission of file transfer", "Žádné oprávnění přenosu souboru"),
("No permission of file transfer", "Žádné oprávnění k přenosu souborů"),
("Note", "Poznámka"),
("Connection", "Připojení"),
("Share Screen", "Nasdílet obrazovku"),
("Share Screen", "Sdílet obrazovku"),
("Chat", "Chat"),
("Total", "Celkem"),
("items", "Položek"),
@@ -287,230 +288,260 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_new_connection_tip", "Obdržen nový požadavek na řízení zařízení, který chce ovládat vaše stávající zařízení."),
("android_service_will_start_tip", "Zapnutí „Zachytávání obsahu obrazovky“ automaticky spustí službu, což umožní ostatním zařízením žádat o připojení k vašemu zařízení."),
("android_stop_service_tip", "Zastavení služby automaticky ukončí veškerá navázaná spojení."),
("android_version_audio_tip", "Vámi nyní používaná verze systému Android nepodporuje zachytávání zvuku přejděte na Android 10 nebo novější."),
("android_start_service_tip", ""),
("android_permission_may_not_change_tip", ""),
("Account", ""),
("android_version_audio_tip", "Vámi nyní používaná verze systému Android nepodporuje zachytávání zvuku přejděte na Android 10, nebo novější."),
("android_start_service_tip", "Klepnutím na možnost [Spustit službu], nebo povolením oprávnění [Snímání obrazovky] spustíte službu sdílení obrazovky."),
("android_permission_may_not_change_tip", "Oprávnění pro navázaná připojení lze změnit až po opětovném připojení."),
("Account", "Účet"),
("Overwrite", "Přepsat"),
("This file exists, skip or overwrite this file?", "Tento soubor existuje přeskočit ho nebo přepsat?"),
("This file exists, skip or overwrite this file?", "Tento soubor existuje, přeskočit, nebo přepsat tento soubor?"),
("Quit", "Ukončit"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("Help", "Nápověda"),
("Failed", "Nepodařilo se"),
("Succeeded", "Uspěl"),
("Someone turns on privacy mode, exit", "Někdo zapne režim soukromí, ukončete ho"),
("Succeeded", "Úspěšný"),
("Someone turns on privacy mode, exit", "Někdo zapne režim ochrany soukromí, ukončete ho"),
("Unsupported", "Nepodporováno"),
("Peer denied", "Peer popřel"),
("Peer denied", "Protistana odmítnula"),
("Please install plugins", "Nainstalujte si prosím pluginy"),
("Peer exit", "Peer exit"),
("Peer exit", "Ukončení protistrany"),
("Failed to turn off", "Nepodařilo se vypnout"),
("Turned off", "Vypnutý"),
("In privacy mode", "v režimu soukromí"),
("Out privacy mode", "mimo režim soukromí"),
("Language", ""),
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use permanent password", ""),
("Use both passwords", ""),
("Set permanent password", ""),
("Enable Remote Restart", ""),
("Allow remote restart", ""),
("Restart Remote Device", ""),
("Are you sure you want to restart", ""),
("Restarting Remote Device", ""),
("remote_restarting_tip", ""),
("Copied", ""),
("Exit Fullscreen", "Ukončete celou obrazovku"),
("In privacy mode", "v režimu ochrany soukromí"),
("Out privacy mode", "mimo režim ochrany soukromí"),
("Language", "Jazyk"),
("Keep RustDesk background service", "Zachovat službu RustDesk na pozadí"),
("Ignore Battery Optimizations", "Ignorovat optimalizaci baterie"),
("android_open_battery_optimizations_tip", "Pokud chcete tuto funkci zakázat, přejděte na další stránku nastavení aplikace RustDesk, najděte a zadejte [Baterie], zrušte zaškrtnutí [Neomezeno]."),
("Start on Boot", "Spustit při startu systému"),
("Start the screen sharing service on boot, requires special permissions", "Spuštění služby sdílení obrazovky při spuštění systému, vyžaduje zvláštní oprávnění"),
("Connection not allowed", "Připojení není povoleno"),
("Legacy mode", "Režim Legacy"),
("Map mode", "Režim mapování"),
("Translate mode", "Režim překladu"),
("Use permanent password", "Použít trvalé heslo"),
("Use both passwords", "Použít obě hesla"),
("Set permanent password", "Nastavit trvalé heslo"),
("Enable Remote Restart", "Povolit vzdálené restartování"),
("Allow remote restart", "Povolit vzdálený restart"),
("Restart Remote Device", "Restartovat vzdálené zařízení"),
("Are you sure you want to restart", "Jste si jisti, že chcete restartovat"),
("Restarting Remote Device", "Restartování vzdáleného zařízení"),
("remote_restarting_tip", "Vzdálené zařízení se restartuje, zavřete prosím toto okno a po chvíli se znovu připojte pomocí trvalého hesla."),
("Copied", "Zkopírováno"),
("Exit Fullscreen", "Ukončit celou obrazovku"),
("Fullscreen", "Celá obrazovka"),
("Mobile Actions", "Mobilní akce"),
("Select Monitor", "Vyberte možnost Monitor"),
("Select Monitor", "Vybrat monitor"),
("Control Actions", "Ovládací akce"),
("Display Settings", "Nastavení obrazovky"),
("Ratio", "Poměr"),
("Image Quality", "Kvalita obrazu"),
("Scroll Style", "Štýl posúvania"),
("Show Toolbar", ""),
("Hide Toolbar", ""),
("Show Toolbar", "Zobrazit panel nástrojů"),
("Hide Toolbar", "Skrýt panel nástrojů"),
("Direct Connection", "Přímé spojení"),
("Relay Connection", "Připojení relé"),
("Relay Connection", "Připojení předávací server"),
("Secure Connection", "Zabezpečené připojení"),
("Insecure Connection", "Nezabezpečené připojení"),
("Scale original", "Měřítko původní"),
("Scale adaptive", "Měřítko adaptivní"),
("General", ""),
("Security", ""),
("Theme", ""),
("Dark Theme", ""),
("Light Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
("Unlock Network Settings", ""),
("Server", ""),
("Direct IP Access", ""),
("Proxy", ""),
("Apply", ""),
("Disconnect all devices?", ""),
("Clear", ""),
("Audio Input Device", ""),
("Use IP Whitelisting", ""),
("Network", ""),
("Enable RDP", ""),
("Pin Toolbar", ""),
("Unpin Toolbar", ""),
("Recording", ""),
("Directory", ""),
("Automatically record incoming sessions", ""),
("Change", ""),
("Start session recording", ""),
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
("Prompt", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Disconnected", ""),
("Other", ""),
("Confirm before closing multiple tabs", ""),
("Keyboard Settings", ""),
("Full Access", ""),
("Screen Share", ""),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland vyžaduje Ubuntu 21.04 nebo vyšší verzi."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland vyžaduje vyšší verzi linuxové distribuce. Zkuste prosím X11 desktop nebo změňte OS."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Vyberte prosím obrazovku, kterou chcete sdílet (Ovládejte na straně protějšku)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
("Elevate", ""),
("Zoom cursor", ""),
("Accept sessions via password", ""),
("Accept sessions via click", ""),
("Accept sessions via both", ""),
("Please wait for the remote side to accept your session request...", ""),
("One-time Password", ""),
("Use one-time password", ""),
("One-time password length", ""),
("Request access to your device", ""),
("Hide connection management window", ""),
("hide_cm_tip", ""),
("wayland_experiment_tip", ""),
("Right click to select tabs", ""),
("Skipped", ""),
("Add to Address Book", ""),
("Group", ""),
("Search", ""),
("Closed manually by web console", ""),
("Local keyboard type", ""),
("Select local keyboard type", ""),
("software_render_tip", ""),
("Always use software rendering", ""),
("config_input", ""),
("config_microphone", ""),
("request_elevation_tip", ""),
("Wait", ""),
("Elevation Error", ""),
("Ask the remote user for authentication", ""),
("Choose this if the remote account is administrator", ""),
("Transmit the username and password of administrator", ""),
("still_click_uac_tip", ""),
("Request Elevation", ""),
("wait_accept_uac_tip", ""),
("Elevate successfully", ""),
("uppercase", ""),
("lowercase", ""),
("digit", ""),
("special character", ""),
("length>=8", ""),
("Weak", ""),
("Medium", ""),
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
("Restore", ""),
("Minimize", ""),
("Maximize", ""),
("Your Device", ""),
("empty_recent_tip", ""),
("empty_favorite_tip", ""),
("empty_lan_tip", ""),
("empty_address_book_tip", ""),
("eg: admin", ""),
("Empty Username", ""),
("Empty Password", ""),
("Me", ""),
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
("No need to elevate", ""),
("System Sound", ""),
("Default", ""),
("New RDP", ""),
("Fingerprint", ""),
("Copy Fingerprint", ""),
("no fingerprints", ""),
("Select a peer", ""),
("Select peers", ""),
("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
("resolution_original_tip", ""),
("resolution_fit_local_tip", ""),
("resolution_custom_tip", ""),
("Collapse toolbar", ""),
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Scale original", "Originální měřítko"),
("Scale adaptive", "Adaptivní měřítko"),
("General", "Obecné"),
("Security", "Zabezpečení"),
("Theme", "Motiv"),
("Dark Theme", "Tmavý motiv"),
("Light Theme", "Světlý motiv"),
("Dark", "Tmavý"),
("Light", "Světlý"),
("Follow System", "Podle systému"),
("Enable hardware codec", "Povolit hardwarový kodek"),
("Unlock Security Settings", "Odemknout nastavení zabezpečení"),
("Enable Audio", "Povolit zvuk"),
("Unlock Network Settings", "Odemknout nastavení sítě"),
("Server", "Server"),
("Direct IP Access", "Přímý IP přístup"),
("Proxy", "Proxy"),
("Apply", "Použít"),
("Disconnect all devices?", "Odpojit všechna zařízení?"),
("Clear", "Smazat"),
("Audio Input Device", "Vstupní zvukové zařízení"),
("Use IP Whitelisting", "Použít bílou listinu IP"),
("Network", "Síť"),
("Enable RDP", "Povolit protokol RDP"),
("Pin Toolbar", "Připnout panel nástrojů"),
("Unpin Toolbar", "Odepnout panel nástrojů"),
("Recording", "Nahrávání"),
("Directory", "Adresář"),
("Automatically record incoming sessions", "Automaticky nahrávat příchozí relace"),
("Change", "Změnit"),
("Start session recording", "Spustit záznam relace"),
("Stop session recording", "Zastavit záznam relace"),
("Enable Recording Session", "Povolit nahrávání relace"),
("Allow recording session", "Povolit nahrávání relace"),
("Enable LAN Discovery", "Povolit zjišťování sítě LAN"),
("Deny LAN Discovery", "Zakázat zjišťování sítě LAN"),
("Write a message", "Napsat zprávu"),
("Prompt", "Výzva"),
("Please wait for confirmation of UAC...", "Počkejte prosím na potvrzení UAC..."),
("elevated_foreground_window_tip", "Aktuální okno vzdálené plochy vyžaduje vyšší oprávnění, takže dočasně nemůže používat myš a klávesnici. Můžete vzdáleného uživatele požádat, aby aktuální okno minimalizoval, nebo kliknout na tlačítko pro zvýšení v okně správy připojení. Chcete-li se tomuto problému vyhnout, doporučujeme nainstalovat software na vzdálené zařízení."),
("Disconnected", "Odpojeno"),
("Other", "Jiné"),
("Confirm before closing multiple tabs", "Potvrdit před zavřením více karet"),
("Keyboard Settings", "Nastavení klávesnice"),
("Full Access", "Úplný přístup"),
("Screen Share", "Sdílení obrazovky"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland vyžaduje Ubuntu 21.04, nebo vyšší verzi."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland vyžaduje vyšší verzi linuxové distribuce. Zkuste prosím X11 desktop, nebo změňte OS."),
("JumpLink", "JumpLink"),
("Please Select the screen to be shared(Operate on the peer side).", "Vyberte prosím obrazovku, kterou chcete sdílet (Ovládejte na straně protistrany)."),
("Show RustDesk", "Zobrazit RustDesk"),
("This PC", "Tento počítač"),
("or", "nebo"),
("Continue with", "Pokračovat s"),
("Elevate", "Zvýšit"),
("Zoom cursor", "Kurzor přiblížení"),
("Accept sessions via password", "Přijímat relace pomocí hesla"),
("Accept sessions via click", "Přijímat relace kliknutím"),
("Accept sessions via both", "Přijímat relace prostřednictvím obou"),
("Please wait for the remote side to accept your session request...", "Počkejte prosím, až vzdálená strana přijme váš požadavek na relaci..."),
("One-time Password", "Jednorázové heslo"),
("Use one-time password", "Použít jednorázové heslo"),
("One-time password length", "Délka jednorázového hesla"),
("Request access to your device", "Žádost o přístup k vašemu zařízení"),
("Hide connection management window", "Skrýt okno správy připojení"),
("hide_cm_tip", "Povolit skrývání pouze v případě, že přijímáte relace pomocí hesla a používáte trvalé heslo."),
("wayland_experiment_tip", "Podpora Waylandu je v experimentální fázi, pokud potřebujete bezobslužný přístup, použijte prosím X11."),
("Right click to select tabs", "Výběr karet kliknutím pravým tlačítkem myši"),
("Skipped", "Vynecháno"),
("Add to Address Book", "Přidat do adresáře"),
("Group", "Skupina"),
("Search", "Vyhledávání"),
("Closed manually by web console", "Uzavřeno ručně pomocí webové konzole"),
("Local keyboard type", "Typ místní klávesnice"),
("Select local keyboard type", "Výběr typu místní klávesnice"),
("software_render_tip", "Pokud používáte grafickou kartu Nvidia v systému Linux a vzdálené okno se po připojení ihned zavře, může vám pomoci přepnutí na open-source ovladač Nouveau a volba softwarového vykreslování. Je nutný restart softwaru."),
("Always use software rendering", "Vždy použít softwarové vykreslování"),
("config_input", "Chcete-li ovládat vzdálenou plochu pomocí klávesnice, musíte udělit oprávnění RustDesk \"Sledování vstupu\"."),
("config_microphone", "Abyste mohli mluvit na dálku, musíte udělit oprávnění RustDesk \"Nahrávat zvuk\"."),
("request_elevation_tip", "Můžete také požádat o zvýšení, pokud je někdo na vzdálené straně."),
("Wait", "Počkejte"),
("Elevation Error", "Chyba navýšení"),
("Ask the remote user for authentication", "Požádat vzdáleného uživatele o ověření"),
("Choose this if the remote account is administrator", "Tuto možnost vyberte, pokud je vzdálený účet správce"),
("Transmit the username and password of administrator", "Přenos uživatelského jména a hesla správce"),
("still_click_uac_tip", "Stále vyžaduje, aby vzdálený uživatel kliknul na OK v okně UAC spuštěného RustDesku."),
("Request Elevation", "Žádost o navýšení"),
("wait_accept_uac_tip", "Počkejte, až vzdálený uživatel přijme dialogové okno UAC."),
("Elevate successfully", "Úspěšné navýšení"),
("uppercase", "velká písmena"),
("lowercase", "malá písmena"),
("digit", "číslice"),
("special character", "speciální znak"),
("length>=8", "délka>=8"),
("Weak", "Slabé"),
("Medium", "Střední"),
("Strong", "Silné"),
("Switch Sides", "Přepínání stran"),
("Please confirm if you want to share your desktop?", "Potvrďte prosím, zda chcete sdílet svou plochu?"),
("Display", "Obrazovka"),
("Default View Style", "Výchozí styl zobrazení"),
("Default Scroll Style", "Výchozí styl rolování"),
("Default Image Quality", "Výchozí kvalita obrazu"),
("Default Codec", "Výchozí kodek"),
("Bitrate", "Datový tok"),
("FPS", "FPS"),
("Auto", "Auto"),
("Other Default Options", "Ostatní výchozí možnosti"),
("Voice call", "Hlasové volání"),
("Text chat", "Textový chat"),
("Stop voice call", "Zastavit hlasové volání"),
("relay_hint_tip", "Přímé připojení nemusí být možné, můžete se zkusit připojit přes předávací server. Pokud navíc chcete při prvním pokusu použít předávací server, můžete k ID přidat příponu \"/r\", nebo v kartě posledních relací vybrat možnost \"Vždy se připojovat přes bránu\", pokud existuje."),
("Reconnect", "Znovu připojit"),
("Codec", "Kodek"),
("Resolution", "Rozlišení"),
("No transfers in progress", "Žádné probíhající přenosy"),
("Set one-time password length", "Nastavení délky jednorázového hesla"),
("install_cert_tip", "Instalace certifikátu RustDesk"),
("confirm_install_cert_tip", "Jedná se o testovací certifikát RustDesk, kterému lze důvěřovat. Certifikát bude v případě potřeby použit k důvěryhodnosti a instalaci ovladačů RustDesk."),
("RDP Settings", "Nastavení RDP"),
("Sort by", "Seřadit podle"),
("New Connection", "Nové připojení"),
("Restore", "Obnovit"),
("Minimize", "Minimalizovat"),
("Maximize", "Maximalizovat"),
("Your Device", "Vaše zařízení"),
("empty_recent_tip", "Ups, žádná nedávná relace!\nČas naplánovat nové."),
("empty_favorite_tip", "Ještě nemáte oblíbené protistrany?\nNajděte někoho, s kým se můžete spojit, a přidejte si ho do oblíbených!"),
("empty_lan_tip", "Ale ne, vypadá, že jsme ještě neobjevili žádné protistrany."),
("empty_address_book_tip", "Ach bože, zdá se, že ve vašem adresáři nejsou v současné době uvedeni žádní kolegové."),
("eg: admin", "např. admin"),
("Empty Username", "Prázdné uživatelské jméno"),
("Empty Password", "Prázdné heslo"),
("Me", ""),
("identical_file_tip", "Tento soubor je totožný se souborem partnera."),
("show_monitors_tip", "Zobrazit monitory na panelu nástrojů"),
("View Mode", "Režim zobrazení"),
("login_linux_tip", "Chcete-li povolit relaci plochy X, musíte se přihlásit ke vzdálenému účtu systému Linux."),
("verify_rustdesk_password_tip", "Ověření hesla RustDesk"),
("remember_account_tip", "Zapamatovat si tento účet"),
("os_account_desk_tip", "Tento účet se používá k přihlášení do vzdáleného operačního systému a k povolení relace plochy v režimu headless."),
("OS Account", "Účet operačního systému"),
("another_user_login_title_tip", "Další uživatel je již přihlášen"),
("another_user_login_text_tip", "Odpojit"),
("xorg_not_found_title_tip", "Xorg nebyl nalezen"),
("xorg_not_found_text_tip", "Prosím, nainstalujte Xorg"),
("no_desktop_title_tip", "Není k dispozici žádná plocha"),
("no_desktop_text_tip", "Nainstalujte si prosím prostředí GNOME"),
("No need to elevate", "Není třeba navýšení"),
("System Sound", "Systémový zvuk"),
("Default", "Výchozí"),
("New RDP", "Nové RDP"),
("Fingerprint", "Otisk"),
("Copy Fingerprint", "Kopírovat otisk"),
("no fingerprints", "žádný otisk"),
("Select a peer", "Výběr protistrany"),
("Select peers", "Vybrat protistrany"),
("Plugins", "Pluginy"),
("Uninstall", "Odinstalovat"),
("Update", "Aktualizovat"),
("Enable", "Povolit"),
("Disable", "Zakázat"),
("Options", "Možnosti"),
("resolution_original_tip", "Původní rozlišení"),
("resolution_fit_local_tip", "Přizpůsobit místní rozlišení"),
("resolution_custom_tip", "Vlastní rozlišení"),
("Collapse toolbar", "Sbalit panel nástrojů"),
("Accept and Elevate", "Přijmout navýšení"),
("accept_and_elevate_btn_tooltip", "Přijměte připojení a zvyšte oprávnění UAC."),
("clipboard_wait_response_timeout_tip", "Vypršel čas čekání odpovědi na kopii."),
("Incoming connection", "Příchozí připojení"),
("Outgoing connection", "Odchozí připojení"),
("Exit", "Ukončit"),
("Open", "Otevřít"),
("logout_tip", "Opravdu se chcete odhlásit?"),
("Service", "Služba"),
("Start", "Spustit"),
("Stop", "Zastavit"),
("exceed_max_devices", "Dosáhli jste maximálního počtu spravovaných zařízení."),
("Sync with recent sessions", "Synchronizace s posledními relacemi"),
("Sort tags", "Seřadit štítky"),
("Open connection in new tab", "Otevřít připojení na nové kartě"),
("Move tab to new window", "Přesunout kartu do nového okna"),
("Can not be empty", "Nemůže být prázdné"),
("Already exists", "Již existuje"),
("Change Password", "Změnit heslo"),
("Refresh Password", "Obnovit heslo"),
("ID", "ID"),
("Grid View", "Mřížka"),
("List View", "Seznam"),
("Select", "Vybrat"),
("Toggle Tags", "Přepnout štítky"),
("pull_ab_failed_tip", "Nepodařilo se obnovit adresář"),
("push_ab_failed_tip", "Nepodařilo se synchronizovat adresář se serverem"),
("synced_peer_readded_tip", "Zařízení, která byla přítomna v posledních relacích, budou synchronizována zpět do adresáře."),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Lydinput"),
("Enhancements", "Forbedringer"),
("Hardware Codec", "Hardware-codec"),
("Adaptive Bitrate", "Adaptiv Bitrate"),
("Adaptive bitrate", "Adaptiv bitrate"),
("ID Server", "ID Server"),
("Relay Server", "Relay Server"),
("API Server", "API Server"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Glemt brugernavn"),
("Password missed", "Glemt kodeord"),
("Wrong credentials", "Forkerte registreringsdata"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Rediger nøgleord"),
("Unremember Password", "Glem adgangskoden"),
("Favorites", "Favoritter"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", "Opløsning"),
("No transfers in progress", "Ingen overførsler i gang"),
("Set one-time password length", "Sæt engangsadgangskode længde"),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", "RDP indstillinger"),
("Sort by", "Sortér efter"),
("New Connection", "Ny forbindelse"),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Audioeingang"),
("Enhancements", "Verbesserungen"),
("Hardware Codec", "Hardware-Codec"),
("Adaptive Bitrate", "Bitrate automatisch anpassen"),
("Adaptive bitrate", "Bitrate automatisch anpassen"),
("ID Server", "ID-Server"),
("Relay Server", "Relay-Server"),
("API Server", "API-Server"),
@@ -223,17 +223,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Verification code", "Verifizierungscode"),
("verification_tip", "Ein Verifizierungscode wurde an die registrierte E-Mail-Adresse gesendet. Geben Sie den Verifizierungscode ein, um sich erneut anzumelden."),
("Logout", "Abmelden"),
("Tags", "Schlagworte"),
("Tags", "Tags"),
("Search ID", "ID suchen"),
("whitelist_sep", "Getrennt durch Komma, Semikolon, Leerzeichen oder Zeilenumbruch"),
("Add ID", "ID hinzufügen"),
("Add Tag", "Stichwort hinzufügen"),
("Unselect all tags", "Alle Stichworte abwählen"),
("Add Tag", "Tag hinzufügen"),
("Unselect all tags", "Alle Tags abwählen"),
("Network error", "Netzwerkfehler"),
("Username missed", "Benutzername fehlt"),
("Password missed", "Passwort fehlt"),
("Wrong credentials", "Falsche Anmeldedaten"),
("Edit Tag", "Schlagwort bearbeiten"),
("The verification code is incorrect or has expired", "Der Verifizierungscode ist falsch oder abgelaufen"),
("Edit Tag", "Tag bearbeiten"),
("Unremember Password", "Gespeichertes Passwort löschen"),
("Favorites", "Favoriten"),
("Add to Favorites", "Zu Favoriten hinzufügen"),
@@ -409,7 +410,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Hide connection management window", "Fenster zur Verwaltung der Verbindung verstecken"),
("hide_cm_tip", "Dies ist nur möglich, wenn der Zugriff über ein permanentes Passwort erfolgt."),
("wayland_experiment_tip", "Die Unterstützung von Wayland ist nur experimentell. Bitte nutzen Sie X11, wenn Sie einen unbeaufsichtigten Zugriff benötigen."),
("Right click to select tabs", "Register mit rechtem Mausklick auswählen"),
("Right click to select tabs", "Tabs mit rechtem Mausklick auswählen"),
("Skipped", "Übersprungen"),
("Add to Address Book", "Zum Adressbuch hinzufügen"),
("Group", "Gruppe"),
@@ -453,14 +454,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", "Sprachanruf"),
("Text chat", "Text-Chat"),
("Stop voice call", "Sprachanruf beenden"),
("relay_hint_tip", "Wenn eine direkte Verbindung nicht möglich ist, können Sie versuchen, eine Verbindung über einen Relay-Server herzustellen. \nWenn Sie eine Relay-Verbindung beim ersten Versuch herstellen möchten, können Sie das Suffix \"/r\" an die ID anhängen oder die Option \"Immer über Relay-Server verbinden\" auf der Gegenstelle auswählen."),
("relay_hint_tip", "Wenn eine direkte Verbindung nicht möglich ist, können Sie versuchen, eine Verbindung über einen Relay-Server herzustellen.\nWenn Sie eine Relay-Verbindung beim ersten Versuch herstellen möchten, können Sie das Suffix \"/r\" an die ID anhängen oder die Option \"Immer über Relay-Server verbinden\" in der Liste der letzten Sitzungen auswählen, sofern diese vorhanden ist."),
("Reconnect", "Erneut verbinden"),
("Codec", "Codec"),
("Resolution", "Auflösung"),
("No transfers in progress", "Keine Übertragungen im Gange"),
("Set one-time password length", "Länge des Einmalpassworts festlegen"),
("idd_driver_tip", "Installieren Sie den virtuellen Anzeigetreiber, der verwendet wird, wenn Sie keine physischen Anzeigen haben."),
("confirm_idd_driver_tip", "Die Option zur Installation des virtuellen Anzeigetreibers ist aktiviert. Beachten Sie, dass ein Testzertifikat installiert wird, um dem virtuellen Anzeigetreiber zu vertrauen. Dieses Testzertifikat wird nur verwendet, um RustDesk-Treibern zu vertrauen."),
("install_cert_tip", "RustDesk-Zertifikat installieren"),
("confirm_install_cert_tip", "Dies ist ein RustDesk-Testzertifikat, dem vertraut werden kann. Das Zertifikat wird verwendet, um RustDesk-Treibern bei Bedarf zu vertrauen und diese zu installieren."),
("RDP Settings", "RDP-Einstellungen"),
("Sort by", "Sortieren nach"),
("New Connection", "Neue Verbindung"),
@@ -505,12 +506,42 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable", "Aktivieren"),
("Disable", "Deaktivieren"),
("Options", "Einstellungen"),
("resolution_original_tip", "Originalauflösung"),
("resolution_original_tip", "Originale Auflösung"),
("resolution_fit_local_tip", "Lokale Auflösung anpassen"),
("resolution_custom_tip", "Benutzerdefinierte Auflösung"),
("Collapse toolbar", "Symbolleiste einklappen"),
("Accept and Elevate", "Akzeptieren und Rechte erhöhen"),
("accept_and_elevate_btn_tooltip", "Akzeptieren Sie die Verbindung und erhöhen Sie die UAC-Berechtigungen."),
("clipboard_wait_response_timeout_tip", "Zeitüberschreitung beim Warten auf die Antwort der Kopie."),
("Incoming connection", "Eingehende Verbindung"),
("Outgoing connection", "Ausgehende Verbindung"),
("Exit", "Beenden"),
("Open", "Öffnen"),
("logout_tip", "Sind Sie sicher, dass Sie sich abmelden wollen?"),
("Service", "Vermittlungsdienst"),
("Start", "Start"),
("Stop", "Stopp"),
("exceed_max_devices", "Sie haben die maximale Anzahl der verwalteten Geräte erreicht."),
("Sync with recent sessions", "Synchronisierung mit den letzten Sitzungen"),
("Sort tags", "Tags sortieren"),
("Open connection in new tab", "Verbindung in neuem Tab öffnen"),
("Move tab to new window", "Tab in neues Fenster verschieben"),
("Can not be empty", "Darf nicht leer sein"),
("Already exists", "Existiert bereits"),
("Change Password", "Passwort ändern"),
("Refresh Password", "Passwort aktualisieren"),
("ID", "ID"),
("Grid View", "Rasteransicht"),
("List View", "Listenansicht"),
("Select", "Auswählen"),
("Toggle Tags", "Tags umschalten"),
("pull_ab_failed_tip", "Aktualisierung des Adressbuchs fehlgeschlagen"),
("push_ab_failed_tip", "Synchronisierung des Adressbuchs mit dem Server fehlgeschlagen"),
("synced_peer_readded_tip", "Die Geräte, die in den letzten Sitzungen vorhanden waren, werden erneut zum Adressbuch hinzugefügt."),
("Change Color", "Farbe ändern"),
("Primary Color", "Primärfarbe"),
("HSV Color", "HSV-Farbe"),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Είσοδος ήχου"),
("Enhancements", "Βελτιώσεις"),
("Hardware Codec", "Κωδικοποιητής υλικού"),
("Adaptive Bitrate", "Adaptive Bitrate"),
("Adaptive bitrate", "Adaptive bitrate"),
("ID Server", "Διακομιστής ID"),
("Relay Server", "Διακομιστής αναμετάδοσης"),
("API Server", "Διακομιστής API"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Δεν συμπληρώσατε το όνομα χρήστη"),
("Password missed", "Δεν συμπληρώσατε τον κωδικό πρόσβασης"),
("Wrong credentials", "Λάθος διαπιστευτήρια"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Επεξεργασία ετικέτας"),
("Unremember Password", "Διαγραφή απομνημονευμένου κωδικού"),
("Favorites", "Αγαπημένα"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", "Ανάλυση"),
("No transfers in progress", "Δεν υπάρχει μεταφορά σε εξέλιξη"),
("Set one-time password length", "Μέγεθος κωδικού μιας χρήσης"),
("idd_driver_tip", "Εγκαταστήστε το πρόγραμμα οδήγησης εικονικής οθόνης που χρησιμοποιείται όταν δεν έχετε φυσικές οθόνες."),
("confirm_idd_driver_tip", "Είναι ενεργοποιημένη η επιλογή εγκατάστασης του προγράμματος οδήγησης εικονικής οθόνης. Λάβετε υπόψη ότι θα εγκατασταθεί ένα δοκιμαστικό πιστοποιητικό για το πρόγραμμα οδήγησης εικονικής οθόνης. Αυτό το πιστοποιητικό θα χρησιμοποιηθεί μόνο για την πιστοποίηση των προγραμμάτων οδήγησης του Rustdesk."),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", "Ρυθμίσεις RDP"),
("Sort by", "Ταξινόμηση κατά"),
("New Connection", "Νέα σύνδεση"),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -12,6 +12,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("not_close_tcp_tip", "Don't close this window while you are using the tunnel"),
("setup_server_tip", "For faster connection, please set up your own server"),
("Auto Login", "Auto Login (Only valid if you set \"Lock after session end\")"),
("Always connect via relay", "Always Connect via Relay"),
("whitelist_tip", "Only whitelisted IP can access me"),
("whitelist_sep", "Separated by comma, semicolon, spaces or new line"),
("Wrong credentials", "Wrong username or password"),
@@ -44,10 +45,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wait_accept_uac_tip", "Please wait for the remote user to accept the UAC dialog."),
("still_click_uac_tip", "Still requires the remote user to click OK on the UAC window of running RustDesk."),
("config_microphone", "In order to speak remotely, you need to grant RustDesk \"Record Audio\" permissions."),
("relay_hint_tip", "It may not be possible to connect directly, you can try to connect via relay. \nIn addition, if you want to use relay on your first try, you can add the \"/r\" suffix to the ID, or select the option \"Always connect via relay\" in the peer card."),
("relay_hint_tip", "It may not be possible to connect directly; you can try connecting via relay. Additionally, if you want to use a relay on your first attempt, you can add the \"/r\" suffix to the ID or select the option \"Always connect via relay\" in the card of recent sessions if it exists."),
("No transfers in progress", ""),
("idd_driver_tip", "Install virtual display driver which is used when you have no physical displays."),
("confirm_idd_driver_tip", "The option to install the virtual display driver is checked. Note that a test certificate will be installed to trust the virtual display driver. This test certificate will only be used to trust Rustdesk drivers."),
("install_cert_tip", "Install RustDesk certificate"),
("confirm_install_cert_tip", "This is a RustDesk testing certificate, which can be trusted. The certificate will be used to trust and install RustDesk drivers when required."),
("empty_recent_tip", "Oops, no recent sessions!\nTime to plan a new one."),
("empty_favorite_tip", "No favorite peers yet?\nLet's find someone to connect with and add it to your favorites!"),
("empty_lan_tip", "Oh no, it looks like we haven't discovered any peers yet."),
@@ -71,5 +72,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("resolution_custom_tip", "Custom resolution"),
("accept_and_elevate_btn_tooltip", "Accept the connection and elevate UAC permissions."),
("clipboard_wait_response_timeout_tip", "Timed out waiting for copy response."),
("logout_tip", "Are you sure you want to log out?"),
("exceed_max_devices", "You have reached the maximum number of managed devices."),
("pull_ab_failed_tip", "Failed to refresh address book"),
("push_ab_failed_tip", "Failed to sync address book to server"),
("synced_peer_readded_tip", "The devices that were present in the recent sessions will be synchronized back to the address book."),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Aŭdia enigo"),
("Enhancements", ""),
("Hardware Codec", ""),
("Adaptive Bitrate", ""),
("Adaptive bitrate", ""),
("ID Server", "Servilo de identigiloj"),
("Relay Server", "Relajsa servilo"),
("API Server", "Servilo de API"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Uzantnomo forgesita"),
("Password missed", "Pasvorto forgesita"),
("Wrong credentials", "Identigilo aŭ pasvorto erara"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Redakti etikedo"),
("Unremember Password", "Forgesi pasvorton"),
("Favorites", "Favorataj"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -17,7 +17,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Transfer File", "Transferir archivo"),
("Connect", "Conectar"),
("Recent Sessions", "Sesiones recientes"),
("Address Book", "Libreta de direcciones"),
("Address Book", "Directorio"),
("Confirmation", "Confirmación"),
("TCP Tunneling", "Túnel TCP"),
("Remove", "Quitar"),
@@ -37,23 +37,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "El portapapeles está vacío"),
("Stop service", "Detener servicio"),
("Change ID", "Cambiar ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("Your new ID", "Tu nueva ID"),
("length %min% to %max%", "de %min% a %max% de longitud"),
("starts with a letter", "comenzar con una letra"),
("allowed characters", "Caracteres permitidos"),
("id_change_tip", "Solo puedes usar caracteres a-z, A-Z, 0-9 e _ (guion bajo). El primer carácter debe ser a-z o A-Z. La longitud debe estar entre 6 y 16 caracteres."),
("Website", "Sitio web"),
("About", "Acerca de"),
("Slogan_tip", "¡Hecho con corazón en este mundo caótico!"),
("Privacy Statement", "Declaración de privacidad"),
("Mute", "Silenciar"),
("Build Date", ""),
("Build Date", "Fecha de compilación"),
("Version", ""),
("Home", ""),
("Home", "Inicio"),
("Audio Input", "Entrada de audio"),
("Enhancements", "Mejoras"),
("Hardware Codec", "Códec de hardware"),
("Adaptive Bitrate", "Tasa de bits adaptativa"),
("Adaptive bitrate", "Tasa de bits adaptativa"),
("ID Server", "Servidor de IDs"),
("Relay Server", "Servidor Relay"),
("API Server", "Servidor API"),
@@ -208,7 +208,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("x11 expected", "x11 necesario"),
("Port", "Puerto"),
("Settings", "Ajustes"),
("Username", " Nombre de usuario"),
("Username", "Nombre de usuario"),
("Invalid port", "Puerto incorrecto"),
("Closed manually by the peer", "Cerrado manualmente por el par"),
("Enable remote configuration modification", "Habilitar modificación remota de configuración"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Olvidó su nombre de usuario"),
("Password missed", "Olvidó su contraseña"),
("Wrong credentials", "Credenciales incorrectas"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Editar tag"),
("Unremember Password", "Olvidar contraseña"),
("Favorites", "Favoritos"),
@@ -411,7 +412,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland_experiment_tip", "El soporte para Wayland está en fase experimental, por favor, use X11 si necesita acceso desatendido."),
("Right click to select tabs", "Clic derecho para seleccionar pestañas"),
("Skipped", "Omitido"),
("Add to Address Book", "Añadir a la libreta de direcciones"),
("Add to Address Book", "Añadir al directorio"),
("Group", "Grupo"),
("Search", "Búsqueda"),
("Closed manually by web console", "Cerrado manualmente por la consola web"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", "Resolución"),
("No transfers in progress", "No hay transferencias en curso"),
("Set one-time password length", "Establecer contraseña de un solo uso"),
("idd_driver_tip", "Instalar controlador virtual de pantalla a usar cuando no hay pantalla física."),
("confirm_idd_driver_tip", "La opción de instalar el controlador de pantalla virtual está marcada. Hay que tener en cuenta que se instalará un certificado de prueba para confirar en el controlador de pantalla. Este certificado solo se usará para confiar en controladores Rustdesk."),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", "Ajustes RDP"),
("Sort by", "Ordenar por"),
("New Connection", "Nueva conexión"),
@@ -471,7 +472,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("empty_recent_tip", "¡Vaya, no hay conexiones recientes!\nEs hora de planificar una nueva."),
("empty_favorite_tip", "¿Sin pares favoritos aún?\nEncontremos uno al que conectarte y ¡añádelo a tus favoritos!"),
("empty_lan_tip", "Oh no, parece que aún no has descubierto ningún par."),
("empty_address_book_tip", "Parece que actualmente no hay pares en tu libreta de direcciones."),
("empty_address_book_tip", "Parece que actualmente no hay pares en tu directorio."),
("eg: admin", "ej.: admin"),
("Empty Username", "Nombre de usuario vacío"),
("Empty Password", "Contraseña vacía"),
@@ -511,6 +512,36 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Collapse toolbar", "Contraer barra de herramientas"),
("Accept and Elevate", "Aceptar y Elevar"),
("accept_and_elevate_btn_tooltip", "Aceptar la conexión y elevar permisos UAC."),
("clipboard_wait_response_timeout_tip", ""),
("clipboard_wait_response_timeout_tip", "Tiempo de espera para copia agotado."),
("Incoming connection", "Conexión entrante"),
("Outgoing connection", "Conexión saliente"),
("Exit", "Salir"),
("Open", "Abrir"),
("logout_tip", "¿Seguro que deseas cerrar sesión?"),
("Service", "Servicio"),
("Start", "Iniciar"),
("Stop", "Detener"),
("exceed_max_devices", "Has alcanzado el máximo número de dispositivos administrados."),
("Sync with recent sessions", "Sincronizar con sesiones recientes"),
("Sort tags", "Ordenar etiquetas"),
("Open connection in new tab", "Abrir conexión en nueva pestaña"),
("Move tab to new window", "Mover pestaña a nueva ventana"),
("Can not be empty", "No puede estar vacío"),
("Already exists", "Ya existe"),
("Change Password", "Cambiar contraseña"),
("Refresh Password", "Refrescar contraseña"),
("ID", ""),
("Grid View", "Vista Cuadrícula"),
("List View", "Vista Lista"),
("Select", "Seleccionar"),
("Toggle Tags", "Alternar Etiquetas"),
("pull_ab_failed_tip", "No se ha podido refrescar el directorio"),
("push_ab_failed_tip", "No se ha podido sincronizar el directorio con el servidor"),
("synced_peer_readded_tip", "Los dispositivos presentes en sesiones recientes se sincronizarán con el directorio."),
("Change Color", "Cambiar Color"),
("Primary Color", "Color Primario"),
("HSV Color", "Color HSV"),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "ورودی صدا"),
("Enhancements", "بهبودها"),
("Hardware Codec", "کدک سخت افزاری"),
("Adaptive Bitrate", "سازگار Bitrate"),
("Adaptive bitrate", "سازگار Bitrate"),
("ID Server", "شناسه سرور"),
("Relay Server", "Relay سرور"),
("API Server", "API سرور"),
@@ -80,7 +80,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Connection in progress. Please wait.", "در حال اتصال. لطفا متظر بمانید"),
("Please try 1 minute later", "لطفا بعد از 1 دقیقه مجددا تلاش کنید"),
("Login Error", "ورود ناموفق بود"),
("Successful", "ورود با موفقیت انجام شد"),
("Successful", "با موفقیت انجام شد"),
("Connected, waiting for image...", "...ارتباط برقرار شد. انتظار برای دریافت تصاویر"),
("Name", "نام"),
("Type", "نوع فایل"),
@@ -144,7 +144,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Failed to connect via relay server", "انجام نشد Relay اتصال از طریق سرور"),
("Failed to make direct connection to remote desktop", "اتصال مستقیم به دسکتاپ راه دور انجام نشد"),
("Set Password", "تنظیم رمزعبور"),
("OS Password", "رمز عیور سیستم عامل"),
("OS Password", "رمز عبور سیستم عامل"),
("install_tip", "لطفا برنامه را نصب کنید UAC و جلوگیری از خطای RustDesk برای راحتی در استفاده از نرم افزار"),
("Click to upgrade", "برای ارتقا کلیک کنید"),
("Click to download", "برای دانلود کلیک کنید"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "نام کاربری وجود ندارد"),
("Password missed", "رمزعبور وجود ندارد"),
("Wrong credentials", "اعتبارنامه نادرست است"),
("The verification code is incorrect or has expired", "کد تأیید نادرست است یا منقضی شده است"),
("Edit Tag", "ویرایش برچسب"),
("Unremember Password", "رمز عبور ذخیره نشود"),
("Favorites", "اتصالات دلخواه"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", "وضوح"),
("No transfers in progress", "هیچ انتقالی در حال انجام نیست"),
("Set one-time password length", "طول رمز یکبار مصرف را تعیین کنید"),
("idd_driver_tip", "درایور صفحه نمایش مجازی را نصب کنید این برای زمانیست که هیچ نمایشگر فیزیکی ندارید."),
("confirm_idd_driver_tip", "استفاده خواهد شد Rustdesk گزینه نصب درایور نمایش مجازی تیک خورده است. توجه داشته باشید که یک گواهی آزمایشی برای اعتماد به درایور نمایش مجازی نصب خواهد شد. این گواهی آزمایشی فقط برای اعتماد به درایورهای."),
("install_cert_tip", "RustDesk نصب گواهی"),
("confirm_install_cert_tip", "استفاده خواهد شد RustDesk است و شما می توانید به این گواهی اعتماد کنید. این گواهی برای اعتماد و نصب درایورهای RustDesk این گواهینامه یک گواهی تست"),
("RDP Settings", "RDP تنظیمات"),
("Sort by", "مرتب سازی بر اساس"),
("New Connection", "اتصال جدید"),
@@ -511,6 +512,36 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Collapse toolbar", "جمع کردن نوار ابزار"),
("Accept and Elevate", "بپذیرید و افزایش دهید"),
("accept_and_elevate_btn_tooltip", "را افزایش دهید UAC اتصال را بپذیرید و مجوزهای."),
("clipboard_wait_response_timeout_tip", ""),
("clipboard_wait_response_timeout_tip", "زمان انتظار برای مشخص شدن وضعیت کپی تمام شد."),
("Incoming connection", "اتصال ورودی"),
("Outgoing connection", "اتصال خروجی"),
("Exit", "خروج"),
("Open", "باز کردن"),
("logout_tip", "آیا برای خارج شدن مطمئن هستید؟"),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Entrée audio"),
("Enhancements", "Améliorations"),
("Hardware Codec", "Transcodage matériel"),
("Adaptive Bitrate", "Débit adaptatif"),
("Adaptive bitrate", "Débit adaptatif"),
("ID Server", "Serveur ID"),
("Relay Server", "Serveur relais"),
("API Server", "Serveur API"),
@@ -75,7 +75,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Do you want to enter again?", "Voulez-vous participer à nouveau ?"),
("Connection Error", "Erreur de connexion"),
("Error", "Erreur"),
("Reset by the peer", "La connexion a été fermée par la machine distante"),
("Reset by the peer", "La connexion a été fermée par l'appareil distant"),
("Connecting...", "Connexion..."),
("Connection in progress. Please wait.", "Connexion en cours. Veuillez patienter."),
("Please try 1 minute later", "Réessayez dans une minute"),
@@ -92,8 +92,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Refresh File", "Rafraîchir le contenu"),
("Local", "Local"),
("Remote", "Distant"),
("Remote Computer", "Ordinateur distant"),
("Local Computer", "Ordinateur local"),
("Remote Computer", "Appareil distant"),
("Local Computer", "Appareil local"),
("Confirm Delete", "Confirmer la suppression"),
("Delete", "Supprimer"),
("Properties", "Propriétés"),
@@ -129,9 +129,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show remote cursor", "Afficher le curseur distant"),
("Show quality monitor", "Afficher le moniteur de qualité"),
("Disable clipboard", "Désactiver le presse-papier"),
("Lock after session end", "Verrouiller l'ordinateur distant après la déconnexion"),
("Lock after session end", "Verrouiller l'appareil distant après la déconnexion"),
("Insert", "Envoyer"),
("Insert Lock", "Verrouiller l'ordinateur distant"),
("Insert Lock", "Verrouiller l'appareil distant"),
("Refresh", "Rafraîchir l'écran"),
("ID does not exist", "L'ID n'existe pas"),
("Failed to connect to rendezvous server", "Échec de la connexion au serveur rendezvous"),
@@ -188,7 +188,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relayed and encrypted connection", "Connexion relais chiffrée"),
("Direct and unencrypted connection", "Connexion directe non chiffrée"),
("Relayed and unencrypted connection", "Connexion relais non chiffrée"),
("Enter Remote ID", "Entrer l'ID de l'appareil à distance"),
("Enter Remote ID", "Entrer l'ID de l'appareil distant"),
("Enter your password", "Entrer votre mot de passe"),
("Logging in...", "En cours de connexion ..."),
("Enable RDP session sharing", "Activer le partage de session RDP"),
@@ -210,7 +210,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Settings", "Paramètres"),
("Username", " Nom d'utilisateur"),
("Invalid port", "Port invalide"),
("Closed manually by the peer", "Fermé manuellement par la machine distante"),
("Closed manually by the peer", "Fermé manuellement par l'appareil distant"),
("Enable remote configuration modification", "Autoriser la modification de la configuration à distance"),
("Run without install", "Exécuter sans installer"),
("Connect via relay", "Connexion via relais"),
@@ -223,17 +223,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Verification code", "Code de vérification"),
("verification_tip", "Un nouvel appareil a été détecté et un code de vérification a été envoyé à l'adresse e-mail enregistrée, entrez le code de vérification pour continuer la connexion."),
("Logout", "Déconnexion"),
("Tags", "Étiqueter"),
("Tags", "Étiquettes"),
("Search ID", "Rechercher un ID"),
("whitelist_sep", "Vous pouvez utiliser une virgule, un point-virgule, un espace ou une nouvelle ligne comme séparateur"),
("Add ID", "Ajouter un ID"),
("Add Tag", "Ajouter une balise"),
("Unselect all tags", "Désélectionner toutes les balises"),
("Add Tag", "Ajout étiquette(s)"),
("Unselect all tags", "Désélectionner toutes les étiquettes"),
("Network error", "Erreur réseau"),
("Username missed", "Nom d'utilisateur manquant"),
("Password missed", "Mot de passe manquant"),
("Wrong credentials", "Identifiant ou mot de passe erroné"),
("Edit Tag", "Modifier la balise"),
("The verification code is incorrect or has expired", "Le code de vérification est incorrect ou a expiré"),
("Edit Tag", "Gestion étiquettes"),
("Unremember Password", "Oublier le Mot de passe"),
("Favorites", "Favoris"),
("Add to Favorites", "Ajouter aux Favoris"),
@@ -244,7 +245,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Hostname", "Nom d'hôte"),
("Discovered", "Découvert"),
("install_daemon_tip", "Pour une exécution au démarrage du système, vous devez installer le service système."),
("Remote ID", "ID de l'appareil à distance"),
("Remote ID", "ID de l'appareil distant"),
("Paste", "Coller"),
("Paste here?", "Coller ici?"),
("Are you sure to close the connection?", "Êtes-vous sûr de fermer la connexion?"),
@@ -273,7 +274,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Chat", "Discuter"),
("Total", "Total"),
("items", "éléments"),
("Selected", "Sélectionné"),
("Selected", "Sélectionné(s)"),
("Screen Capture", "Capture d'écran"),
("Input Control", "Contrôle de saisie"),
("Audio Capture", "Capture audio"),
@@ -300,9 +301,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Succeeded", "Succès"),
("Someone turns on privacy mode, exit", "Quelqu'un active le mode de confidentialité, quittez"),
("Unsupported", "Non pris en charge"),
("Peer denied", "Machine distante refusée"),
("Peer denied", "Appareil distant refusé"),
("Please install plugins", "Veuillez installer les plugins"),
("Peer exit", ""),
("Peer exit", "Appareil distant déconnecté"),
("Failed to turn off", "Échec de la désactivation"),
("Turned off", "Désactivé"),
("In privacy mode", "en mode privé"),
@@ -336,8 +337,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ratio", "Rapport"),
("Image Quality", "Qualité d'image"),
("Scroll Style", "Style de défilement"),
("Show Toolbar", ""),
("Hide Toolbar", ""),
("Show Toolbar", "Afficher la barre d'outils"),
("Hide Toolbar", "Masquer la barre d'outils"),
("Direct Connection", "Connexion directe"),
("Relay Connection", "Connexion relais"),
("Secure Connection", "Connexion sécurisée"),
@@ -366,8 +367,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Use IP Whitelisting", "Utiliser une liste blanche d'IP"),
("Network", "Réseau"),
("Enable RDP", "Activer connection RDP"),
("Pin Toolbar", ""),
("Unpin Toolbar", ""),
("Pin Toolbar", "Épingler la barre d'outil"),
("Unpin Toolbar", "Détacher la barre d'outil"),
("Recording", "Enregistrement"),
("Directory", "Répertoire"),
("Automatically record incoming sessions", "Enregistrement automatique des sessions entrantes"),
@@ -381,7 +382,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Write a message", "Ecrire un message"),
("Prompt", ""),
("Please wait for confirmation of UAC...", "Veuillez attendre la confirmation de l'UAC..."),
("elevated_foreground_window_tip", "La fenêtre actuelle que la machine distante nécessite des privilèges plus élevés pour fonctionner, elle ne peut donc pas être atteinte par la souris et le clavier. Vous pouvez demander à l'utilisateur distant de réduire la fenêtre actuelle ou de cliquer sur le bouton d'élévation dans la fenêtre de gestion des connexions. Pour éviter ce problème, il est recommandé d'installer le logiciel sur l'appareil distant."),
("elevated_foreground_window_tip", "La fenêtre actuelle de l'appareil distant nécessite des privilèges plus élevés pour fonctionner, elle ne peut donc pas être atteinte par la souris et le clavier. Vous pouvez demander à l'utilisateur distant de réduire la fenêtre actuelle ou de cliquer sur le bouton d'élévation dans la fenêtre de gestion des connexions. Pour éviter ce problème, il est recommandé d'installer le logiciel sur l'appareil distant."),
("Disconnected", "Déconnecté"),
("Other", "Divers"),
("Confirm before closing multiple tabs", "Confirmer avant de fermer plusieurs onglets"),
@@ -391,7 +392,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland nécessite Ubuntu 21.04 ou une version supérieure."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland nécessite une version supérieure de la distribution Linux. Veuillez essayer le bureau X11 ou changer votre système d'exploitation."),
("JumpLink", "Afficher"),
("Please Select the screen to be shared(Operate on the peer side).", "Veuillez sélectionner l'écran à partager (côté machine distante)."),
("Please Select the screen to be shared(Operate on the peer side).", "Veuillez sélectionner l'écran à partager (côté appareil distant)."),
("Show RustDesk", "Afficher RustDesk"),
("This PC", "Ce PC"),
("or", "ou"),
@@ -451,16 +452,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Auto", "Auto"),
("Other Default Options", "Autres options par défaut"),
("Voice call", "Appel voix"),
("Text chat", "Conversation textuelfle"),
("Text chat", "Conversation textuelle"),
("Stop voice call", "Stopper l'appel voix"),
("relay_hint_tip", "Il se peut qu'il ne doit pas possible de se connecter directement, vous pouvez essayer de vous connecter via un relais. \nEn outre, si vous souhaitez utiliser directement le relais, vous pouvez ajouter le suffixe \"/r\" à l'ID ou sélectionner l'option \"Toujours se connecter via le relais\" dans la fiche pair."),
("relay_hint_tip", "Il se peut qu'il ne doit pas possible de se connecter directement, vous pouvez essayer de vous connecter via un relais. \nEn outre, si vous souhaitez utiliser directement le relais, vous pouvez ajouter le suffixe \"/r\" à l'ID ou sélectionner l'option \"Toujours se connecter via le relais\" dans la fiche appareils distants."),
("Reconnect", "Se reconnecter"),
("Codec", "Codec"),
("Resolution", "Résolution"),
("No transfers in progress", "Pas de transfert en cours"),
("Set one-time password length", "Définir la longueur du mot de passe à usage unique"),
("idd_driver_tip", "Installez le pilote d'affichage virtuel pour être utilisé lorsque vous n'avez pas d'affichage physique."),
("confirm_idd_driver_tip", "L'option d'installation du pilote d'affichage virtuel est cochée. Notez qu'un certificat de test sera installé pour faire confiance au pilote d'affichage virtuel. Ce certificat de test ne sera utilisé que pour faire confiance aux pilotes Rustdesk."),
("install_cert_tip", "Installer le certificat RustDesk"),
("confirm_install_cert_tip", "Il s'agit d'un certificat RustDesk, auquel on peut faire confiance. Le certificat sera utilisé pour approuver et installer les pilotes RustDesk si nécessaire."),
("RDP Settings", "Configuration RDP"),
("Sort by", "Trier par"),
("New Connection", "Nouvelle connexion"),
@@ -469,14 +470,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Maximize", "Maximiser"),
("Your Device", "Votre appareil"),
("empty_recent_tip", "Oups, pas de sessions récentes!\nIl est temps d'en prévoir une nouvelle."),
("empty_favorite_tip", "Vous n'avez pas encore de pairs favoris?\nTrouvons quelqu'un avec qui vous connecter et ajoutez-le à vos favoris!"),
("empty_lan_tip", "Oh non, il semble que nous n'ayons pas encore de pairs découverts."),
("empty_address_book_tip", "Ouh là là! il semble qu'il n'y ait actuellement aucun pair répertorié dans votre carnet d'adresses."),
("empty_favorite_tip", "Vous n'avez pas encore d'appareils distants favorits?\nTrouvons quelqu'un avec qui vous connecter et ajoutez-les à vos favoris!"),
("empty_lan_tip", "Oh non, il semble que nous n'ayons pas encore d'appareils réseau local découverts."),
("empty_address_book_tip", "Ouh là là! il semble qu'il n'y ait actuellement aucun appareil distant répertorié dans votre carnet d'adresses."),
("eg: admin", "ex: admin"),
("Empty Username", "Nom d'utilisation non spécifié"),
("Empty Password", "Mot de passe non spécifié"),
("Me", "Moi"),
("identical_file_tip", "Ce fichier est identique à celui du pair."),
("identical_file_tip", "Ce fichier est identique à celui de l'appareil distant."),
("show_monitors_tip", "Afficher les moniteurs dans la barre d'outils"),
("View Mode", "Mode vue"),
("login_linux_tip", "Se connecter au compte Linux distant"),
@@ -497,20 +498,50 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "Empreinte digitale"),
("Copy Fingerprint", "Copier empreinte digitale"),
("no fingerprints", "Pas d'empreintes digitales"),
("Select a peer", "Sélectionnez la machine distante"),
("Select peers", "Sélectionnez des machines distantes"),
("Select a peer", "Sélectionnez l'appareil distant"),
("Select peers", "Sélectionnez des appareils distants"),
("Plugins", "Plugins"),
("Uninstall", "Désinstaller"),
("Update", "Mise à jour"),
("Enable", "Activé"),
("Disable", "Desactivé"),
("Options", "Options"),
("resolution_original_tip", ""),
("resolution_fit_local_tip", ""),
("resolution_custom_tip", ""),
("Collapse toolbar", ""),
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("resolution_original_tip", "Résolution d'origine"),
("resolution_fit_local_tip", "Adapter la résolution local"),
("resolution_custom_tip", "Résolution personnalisée"),
("Collapse toolbar", "Réduire la barre d'outils"),
("Accept and Elevate", "Accepter et autoriser l'augmentation des privilèges"),
("accept_and_elevate_btn_tooltip", "Accepter la connexion l'augmentation des privilèges UAC."),
("clipboard_wait_response_timeout_tip", "Expiration du délai d'attente presse-papiers."),
("Incoming connection", "Connexion entrante"),
("Outgoing connection", "Connexion sortante"),
("Exit", "Quitter"),
("Open", "Ouvrir"),
("logout_tip", "Êtes-vous sûr de vouloir vous déconnecter?"),
("Service", "Service"),
("Start", "Lancer"),
("Stop", "Stopper"),
("exceed_max_devices", "Vous avez atteint le nombre maximal d'appareils gérés."),
("Sync with recent sessions", "Synchroniser avec les sessions récentes"),
("Sort tags", "Trier les étiquettes"),
("Open connection in new tab", "Ouvrir la connexion dans un nouvel onglet"),
("Move tab to new window", "Déplacer l'onglet vers une nouvelle fenêtre"),
("Can not be empty", "Ne peux pas être vide"),
("Already exists", "Existe déjà"),
("Change Password", "Changer le mot de passe"),
("Refresh Password", "Actualiser le mot de passe"),
("ID", "ID"),
("Grid View", "Vue Grille"),
("List View", "Vue Liste"),
("Select", "Sélectionner"),
("Toggle Tags", "Basculer vers les étiquettes"),
("pull_ab_failed_tip", "Impossible d'actualiser le carnet d'adresses"),
("push_ab_failed_tip", "Échec de la synchronisation du carnet d'adresses"),
("synced_peer_readded_tip", "Les appareils qui étaient présents dans les sessions récentes seront synchronisés avec le carnet d'adresses."),
("Change Color", "Modifier la couleur"),
("Primary Color", "Couleur primaire"),
("HSV Color", "Couleur TSL"),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Hangátvitel"),
("Enhancements", "Fejlesztések"),
("Hardware Codec", "Hardware kodek"),
("Adaptive Bitrate", "Adaptív bitráta"),
("Adaptive bitrate", "Adaptív bitráta"),
("ID Server", "Szerver azonosító/domain"),
("Relay Server", "Kiszolgáló szerver"),
("API Server", "API szerver"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Üres felhasználónév"),
("Password missed", "Üres jelszó"),
("Wrong credentials", "Hibás felhasználónév vagy jelszó"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Címke szerkesztése"),
("Unremember Password", "A jelszó megjegyzésének törlése"),
("Favorites", "Kedvencek"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -4,7 +4,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Status", "Status"),
("Your Desktop", "Desktop Anda"),
("desk_tip", "Desktop Anda dapat diakses dengan ID dan kata sandi ini."),
("Password", "Password"),
("Password", "Kata sandi"),
("Ready", "Siap"),
("Established", "Didirikan"),
("connecting_status", "Menghubungkan ke jaringan RustDesk..."),
@@ -15,48 +15,48 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("not_ready_status", "Belum siap. Silakan periksa koneksi Anda"),
("Control Remote Desktop", "Kontrol Remote Desktop"),
("Transfer File", "File Transfer"),
("Connect", "Terhubung"),
("Connect", "Hubungkan"),
("Recent Sessions", "Sesi Terkini"),
("Address Book", "Buku Alamat"),
("Confirmation", "Konfirmasi"),
("TCP Tunneling", "TCP Tunneling"),
("Remove", "Hapus"),
("Refresh random password", "Segarkan kata sandi acak"),
("Set your own password", "Tetapkan kata sandi Anda sendiri"),
("Refresh random password", "Perbarui kata sandi acak"),
("Set your own password", "Tetapkan kata sandi Anda"),
("Enable Keyboard/Mouse", "Aktifkan Keyboard/Mouse"),
("Enable Clipboard", "Aktifkan Papan Klip"),
("Enable File Transfer", "Aktifkan Transfer File"),
("Enable TCP Tunneling", "Aktifkan TCP Tunneling"),
("IP Whitelisting", "Daftar Putih IP"),
("IP Whitelisting", "Daftar IP yang diizinkan"),
("ID/Relay Server", "ID/Relay Server"),
("Import Server Config", "Impor Konfigurasi Server"),
("Export Server Config", "Ekspor Konfigutasi Server"),
("Export Server Config", "Ekspor Konfigurasi Server"),
("Import server configuration successfully", "Impor konfigurasi server berhasil"),
("Export server configuration successfully", "Ekspor konfigurasi server berhasil"),
("Invalid server configuration", "Konfigurasi server tidak valid"),
("Clipboard is empty", "Papan klip kosong"),
("Stop service", "Hentikan Layanan"),
("Change ID", "Ubah ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("Your new ID", "ID baru anda"),
("length %min% to %max%", "panjang %min% s/d %max%"),
("starts with a letter", "Dimulai dengan huruf"),
("allowed characters", "Karakter yang dapat digunakan"),
("id_change_tip", "Hanya karakter a-z, A-Z, 0-9 dan _ (underscore) yang diperbolehkan. Huruf pertama harus a-z, A-Z. Panjang antara 6 dan 16."),
("Website", "Website"),
("Website", "Situs Web"),
("About", "Tentang"),
("Slogan_tip", ""),
("Slogan_tip", "Dibuat dengan penuh kasih sayang dalam dunia yang penuh kekacauan ini"),
("Privacy Statement", "Pernyataan Privasi"),
("Mute", "Bisukan"),
("Build Date", ""),
("Version", ""),
("Build Date", "Tanggal Build"),
("Version", "Versi"),
("Home", ""),
("Audio Input", "Masukkan Audio"),
("Audio Input", "Input Audio"),
("Enhancements", "Peningkatan"),
("Hardware Codec", "Codec Perangkat Keras"),
("Adaptive Bitrate", "Kecepatan Bitrate Adaptif"),
("Adaptive bitrate", "Kecepatan Bitrate Adaptif"),
("ID Server", "Server ID"),
("Relay Server", "Server Relay"),
("API Server", "API Server"),
("API Server", "Server API"),
("invalid_http", "harus dimulai dengan http:// atau https://"),
("Invalid IP", "IP tidak valid"),
("Invalid format", "Format tidak valid"),
@@ -66,16 +66,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Cancel", "Batal"),
("Skip", "Lanjutkan"),
("Close", "Tutup"),
("Retry", "Ulangi"),
("Retry", "Coba lagi"),
("OK", "Oke"),
("Password Required", "Kata sandi dibutuhkan"),
("Please enter your password", "Silahkan masukkan kata sandi anda"),
("Remember password", "Ingat Password"),
("Remember password", "Ingat kata sandi"),
("Wrong Password", "Kata sandi Salah"),
("Do you want to enter again?", "Apakah anda ingin masuk lagi?"),
("Connection Error", "Kesalahan koneksi"),
("Error", "Kesalahan"),
("Reset by the peer", "Setel ulang oleh rekan"),
("Reset by the peer", "Direset oleh rekan"),
("Connecting...", "Menghubungkan..."),
("Connection in progress. Please wait.", "Koneksi sedang berlangsung. Mohon tunggu."),
("Please try 1 minute later", "Silahkan coba 1 menit lagi"),
@@ -114,19 +114,19 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Speed", "Kecepatan"),
("Custom Image Quality", "Sesuaikan Kualitas Gambar"),
("Privacy mode", "Mode Privasi"),
("Block user input", "Blokir masukan pengguna"),
("Unblock user input", "Jangan blokir masukan pengguna"),
("Block user input", "Blokir input pengguna"),
("Unblock user input", "Jangan blokir input pengguna"),
("Adjust Window", "Sesuaikan Jendela"),
("Original", "Asli"),
("Shrink", "Susutkan"),
("Stretch", "Regangkan"),
("Scrollbar", "Scroll bar"),
("ScrollAuto", "Gulir Otomatis"),
("Scrollbar", "Scrollbar"),
("ScrollAuto", "Scroll Otomatis"),
("Good image quality", "Kualitas Gambar Baik"),
("Balanced", "Seimbang"),
("Optimize reaction time", "Optimalkan waktu reaksi"),
("Custom", "Kustom"),
("Show remote cursor", "Tampilkan remote kursor"),
("Show remote cursor", "Tampilkan kursor remote"),
("Show quality monitor", "Tampilkan kualitas monitor"),
("Disable clipboard", "Matikan papan klip"),
("Lock after session end", "Kunci setelah sesi berakhir"),
@@ -143,36 +143,36 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Failed to connect via rendezvous server", "Gagal terkoneksi via rendezvous server"),
("Failed to connect via relay server", "Gagal terkoneksi via relay server"),
("Failed to make direct connection to remote desktop", "Gagal membuat koneksi langsung ke desktop jarak jauh"),
("Set Password", "Tetapkan Password"),
("Set Password", "Tetapkan kata sandi"),
("OS Password", "Kata Sandi OS"),
("install_tip", "Karena UAC, RustDesk tidak dapat bekerja dengan baik sebagai sisi remote dalam beberapa kasus. Untuk menghindari UAC, silakan klik tombol di bawah ini untuk menginstal RustDesk ke sistem."),
("Click to upgrade", "Klik untuk upgrade"),
("Click to download", "Kli untuk download"),
("Click to update", "Klik untuk update"),
("Click to download", "Klik untuk unduh"),
("Click to update", "Klik untuk memperbarui"),
("Configure", "Konfigurasi"),
("config_acc", "Untuk mengontrol Desktop Anda dari jarak jauh, Anda perlu memberikan izin \"Aksesibilitas\" RustDesk."),
("config_screen", "Untuk mengakses Desktop Anda dari jarak jauh, Anda perlu memberikan izin \"Perekaman Layar\" RustDesk."),
("Installing ...", "Menginstall"),
("Install", "Instal"),
("Installation", "Instalasi"),
("Installation Path", "Jalur Instalasi"),
("Installation Path", "Direktori Instalasi"),
("Create start menu shortcuts", "Buat pintasan start menu"),
("Create desktop icon", "Buat icon desktop"),
("agreement_tip", "Dengan memulai instalasi, Anda menerima perjanjian lisensi."),
("Accept and Install", "Terima dan Install"),
("End-user license agreement", "Perjanjian lisensi pengguna akhir"),
("Generating ...", "Menghasilkan..."),
("End-user license agreement", "Perjanjian lisensi pengguna"),
("Generating ...", "Memproses..."),
("Your installation is lower version.", "Instalasi Anda adalah versi yang lebih rendah."),
("not_close_tcp_tip", "Jangan tutup jendela ini saat menggunakan tunnel"),
("Listening ...", "Mendengarkan..."),
("Remote Host", "Remote Host"),
("Remote Port", "Remote Port"),
("Listening ...", "Menghubungkan..."),
("Remote Host", "Host Remote"),
("Remote Port", "Port Remote"),
("Action", "Aksi"),
("Add", "Tambah"),
("Local Port", "Port Lokal"),
("Local Address", "Alamat lokal"),
("Change Local Port", "Ubah Port Lokal"),
("setup_server_tip", "Untuk koneksi yang lebih cepat, silakan atur server Anda sendiri"),
("setup_server_tip", "Sudah siap, Untuk mendapatkan koneksi yang lebih baik, disarankan untuk menginstal di server anda sendiri"),
("Too short, at least 6 characters.", "Terlalu pendek, setidaknya 6 karekter."),
("The confirmation is not identical.", "Konfirmasi tidak identik."),
("Permissions", "Izin"),
@@ -182,46 +182,46 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Allow using keyboard and mouse", "Izinkan menggunakan keyboard dan mouse"),
("Allow using clipboard", "Izinkan menggunakan papan klip"),
("Allow hearing sound", "Izinkan mendengarkan suara"),
("Allow file copy and paste", "Izinkan penyalinan dan tempel file"),
("Connected", "Terkoneksi"),
("Allow file copy and paste", "Izinkan salin dan tempel file"),
("Connected", "Terhubung"),
("Direct and encrypted connection", "Koneksi langsung dan terenkripsi"),
("Relayed and encrypted connection", "Koneksi relai dan terenkripsi"),
("Direct and unencrypted connection", "Koneksi langsung dan tidak terenkripsi"),
("Relayed and unencrypted connection", "Koneksi relai dan tidak terenkripsi"),
("Enter Remote ID", "Masukkan Remote ID"),
("Enter your password", "Masukkan password anda"),
("Relayed and encrypted connection", "Koneksi relay dan terenkripsi"),
("Direct and unencrypted connection", "Koneksi langsung dan tanpa enkripsi"),
("Relayed and unencrypted connection", "Koneksi relay dan tanpa enkripsi"),
("Enter Remote ID", "Masukkan ID Remote"),
("Enter your password", "Masukkan kata sandi anda"),
("Logging in...", "Masuk..."),
("Enable RDP session sharing", "Aktifkan berbagi sesi RDP"),
("Auto Login", "Auto Login (Hanya valid jika Anda menyetel \"Kunci setelah sesi berakhir\")"),
("Auto Login", "Login Otomatis (Hanya berlaku jika Anda mengatur \"Kunci setelah sesi berakhir\")"),
("Enable Direct IP Access", "Aktifkan Akses IP Langsung"),
("Rename", "Ubah nama"),
("Space", "Spasi"),
("Create Desktop Shortcut", "Buat Pintasan Desktop"),
("Change Path", "Ubah Jalur"),
("Change Path", "Ubah Direktori"),
("Create Folder", "Buat Folder"),
("Please enter the folder name", "Silahkan masukkan nama folder"),
("Fix it", "Memperbaiki"),
("Fix it", "Perbaiki"),
("Warning", "Peringatan"),
("Login screen using Wayland is not supported", "Layar masuk menggunakan Wayland tidak didukung"),
("Reboot required", "Diperlukan boot ulang"),
("Unsupported display server", "Server tampilan tidak didukung "),
("x11 expected", "x11 diharapkan"),
("x11 expected", "Diperlukan x11"),
("Port", "Port"),
("Settings", "Pengaturan"),
("Username", "Username"),
("Username", "Nama pengguna"),
("Invalid port", "Kesalahan port"),
("Closed manually by the peer", "Ditutup secara manual oleh peer"),
("Enable remote configuration modification", "Aktifkan modifikasi konfigurasi jarak jauh"),
("Closed manually by the peer", "Ditutup secara manual oleh rekan"),
("Enable remote configuration modification", "Aktifkan modifikasi konfigurasi remotE"),
("Run without install", "Jalankan tanpa menginstal"),
("Connect via relay", ""),
("Always connect via relay", "Selalu terhubung melalui relai"),
("whitelist_tip", "Hanya whitelisted IP yang dapat mengakses saya"),
("Connect via relay", "Sambungkan via relay"),
("Always connect via relay", "Selalu terhubung melalui relay"),
("whitelist_tip", "Hanya IP yang diizikan dapat mengakses"),
("Login", "Masuk"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Verify", "Verifikasi"),
("Remember me", "Ingatkan saya"),
("Trust this device", "Izinkan perangkat ini"),
("Verification code", "Kode verifikasi"),
("verification_tip", "Kode verifikasi sudah dikirim ke email yang terdaftar, masukkan kode verifikasi untuk melanjutkan."),
("Logout", "Keluar"),
("Tags", "Tag"),
("Search ID", "Cari ID"),
@@ -230,30 +230,31 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add Tag", "Tambah Tag"),
("Unselect all tags", "Batalkan pilihan semua tag"),
("Network error", "Kesalahan Jaringan"),
("Username missed", "Username tidak sesuai"),
("Username missed", "Nama pengguna tidak sesuai"),
("Password missed", "Kata sandi tidak sesuai"),
("Wrong credentials", "Username atau password salah"),
("Wrong credentials", "Nama pengguna atau kata sandi salah"),
("The verification code is incorrect or has expired", "Kode verifikasi salah atau sudah kadaluarsa"),
("Edit Tag", "Ubah Tag"),
("Unremember Password", "Lupa Kata Sandi"),
("Unremember Password", "Lupakan Kata Sandi"),
("Favorites", "Favorit"),
("Add to Favorites", "Tambah ke Favorit"),
("Remove from Favorites", "Hapus dari favorit"),
("Empty", "Kosong"),
("Invalid folder name", "Nama folder tidak valid"),
("Socks5 Proxy", "Socks5 Proxy"),
("Socks5 Proxy", "Proxy Socks5"),
("Hostname", "Hostname"),
("Discovered", "Telah ditemukan"),
("install_daemon_tip", "Untuk memulai saat boot, Anda perlu menginstal system service."),
("Remote ID", "Remote ID"),
("Remote ID", "ID Remote"),
("Paste", "Tempel"),
("Paste here?", "Tempel disini?"),
("Are you sure to close the connection?", "Apakah anda yakin akan menutup koneksi?"),
("Download new version", "Untuk versi baru"),
("Touch mode", "Mode Sentuh"),
("Download new version", "Unduh versi baru"),
("Touch mode", "Mode Layar Sentuh"),
("Mouse mode", "Mode Mouse"),
("One-Finger Tap", "Ketuk Satu Jari"),
("Left Mouse", "Mouse Kiri"),
("One-Long Tap", "Ketuk Satu Panjang"),
("One-Long Tap", "Ketuk Tahan"),
("Two-Finger Tap", "Ketuk Dua Jari"),
("Right Mouse", "Mouse Kanan"),
("One-Finger Move", "Gerakan Satu Jari"),
@@ -274,25 +275,25 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Total", "Total"),
("items", "item"),
("Selected", "Dipilih"),
("Screen Capture", "Rekam Layar"),
("Input Control", "kontrol input"),
("Screen Capture", "Tangkapan Layar"),
("Input Control", "Kontrol input"),
("Audio Capture", "Rekam Suara"),
("File Connection", "Koneksi File"),
("Screen Connection", "koneksi layar"),
("Do you accept?", "Apakah diperbolehkan?"),
("Screen Connection", "Koneksi layar"),
("Do you accept?", "Apakah anda setuju?"),
("Open System Setting", "Buka Pengaturan Sistem"),
("How to get Android input permission?", ""),
("How to get Android input permission?", "Bagaimana cara mendapatkan izin input dari Android?"),
("android_input_permission_tip1", "Agar perangkat jarak jauh dapat mengontrol perangkat Android Anda melalui mouse atau sentuhan, Anda harus mengizinkan RustDesk untuk menggunakan layanan \"Aksesibilitas\"."),
("android_input_permission_tip2", "Silakan buka halaman pengaturan sistem berikutnya, temukan dan masuk ke [Layanan Terinstal], aktifkan layanan [Input RustDesk]."),
("android_new_connection_tip", "Permintaan kontrol baru telah diterima, yang ingin mengontrol perangkat Anda saat ini."),
("android_service_will_start_tip", "Mengaktifkan \"Tangkapan Layar\" akan memulai layanan secara otomatis, memungkinkan perangkat lain untuk meminta sambungan ke perangkat Anda."),
("android_stop_service_tip", "Menutup layanan akan secara otomatis menutup semua koneksi yang dibuat."),
("android_new_connection_tip", "Permintaan akses remote telah diterima"),
("android_service_will_start_tip", "Mengaktifkan \"Tangkapan Layar\" akan memulai secara otomatis, memungkinkan perangkat lain untuk meminta koneksi ke perangkat Anda."),
("android_stop_service_tip", "Menutup layanan secara otomatis akan menutup semua koneksi yang dibuat."),
("android_version_audio_tip", "Versi Android saat ini tidak mendukung pengambilan audio, harap tingkatkan ke Android 10 atau lebih tinggi."),
("android_start_service_tip", ""),
("android_permission_may_not_change_tip", ""),
("android_start_service_tip", "Tap [Mulai Layanan] atau aktifkan izin [Tangkapan Layar] untuk memulai berbagi layar."),
("android_permission_may_not_change_tip", "Izin untuk koneksi yang sudah terhubung mungkin tidak dapat diubah secara instan hingga terhubung kembali"),
("Account", "Akun"),
("Overwrite", "Timpa"),
("This file exists, skip or overwrite this file?", "File ini sudah ada, lewati atau timpa file ini?"),
("Overwrite", "Ganti"),
("This file exists, skip or overwrite this file?", "File ini sudah ada, lewati atau ganti file ini?"),
("Quit", "Keluar"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("Help", "Bantuan"),
@@ -300,32 +301,32 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Succeeded", "Berhasil"),
("Someone turns on privacy mode, exit", "Seseorang mengaktifkan mode privasi, keluar"),
("Unsupported", "Tidak didukung"),
("Peer denied", "Rekan ditolak"),
("Peer denied", "Rekan menolak"),
("Please install plugins", "Silakan instal plugin"),
("Peer exit", "keluar rekan"),
("Peer exit", "Rekan keluar"),
("Failed to turn off", "Gagal mematikan"),
("Turned off", "Matikan"),
("Turned off", "Dimatikan"),
("In privacy mode", "Dalam mode privasi"),
("Out privacy mode", "Keluar dari mode privasi"),
("Language", "Bahasa"),
("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada background service"),
("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada service background"),
("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"),
("android_open_battery_optimizations_tip", ""),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Connection not allowed", "Koneksi tidak dijinkan"),
("Legacy mode", "Mode lama"),
("android_open_battery_optimizations_tip", "Jika anda ingin menonaktifkan fitur ini, buka halam pengaturan, cari dan pilih [Baterai], Uncheck [Tidak dibatasi]"),
("Start on Boot", "Mulai saat dihidupkan"),
("Start the screen sharing service on boot, requires special permissions", "Mulai layanan berbagi layar saat sistem dinyalakan, memerlukan izin khusus."),
("Connection not allowed", "Koneksi tidak dizinkan"),
("Legacy mode", "Mode lawas"),
("Map mode", "Mode peta"),
("Translate mode", "Mode terjemahan"),
("Use permanent password", "Gunakan kata sandi permanaen"),
("Use both passwords", "Gunakan kedua kata sandi "),
("Use both passwords", "Gunakan kedua kata sandi"),
("Set permanent password", "Setel kata sandi permanen"),
("Enable Remote Restart", "Aktifkan Restart Jarak Jauh"),
("Allow remote restart", "Ijinkan Restart Jarak Jauh"),
("Restart Remote Device", "Restart Perangkat Jarak Jauh"),
("Are you sure you want to restart", "Apakah Anda yakin untuk memulai ulang"),
("Restarting Remote Device", "Memulai Ulang Perangkat Jarak Jauh"),
("remote_restarting_tip", ""),
("Enable Remote Restart", "Aktifkan Restart Secara Remote"),
("Allow remote restart", "Ijinkan Restart Secara Remote"),
("Restart Remote Device", "Restart Perangkat Secara Remote"),
("Are you sure you want to restart", "Apakah Anda yakin ingin merestart"),
("Restarting Remote Device", "Merestart Perangkat Remote"),
("remote_restarting_tip", "Perangkat remote sedang merestart, harap tutup pesan ini dan sambungkan kembali dengan kata sandi permanen setelah beberapa saat."),
("Copied", "Disalin"),
("Exit Fullscreen", "Keluar dari Layar Penuh"),
("Fullscreen", "Layar penuh"),
@@ -333,11 +334,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Select Monitor", "Pilih Monitor"),
("Control Actions", "Tindakan Kontrol"),
("Display Settings", "Pengaturan tampilan"),
("Ratio", "Perbandingan"),
("Ratio", "Rasio"),
("Image Quality", "Kualitas gambar"),
("Scroll Style", "Gaya Gulir"),
("Show Toolbar", ""),
("Hide Toolbar", ""),
("Scroll Style", "Gaya Scroll"),
("Show Toolbar", "Tampilkan Toolbar"),
("Hide Toolbar", "Sembunyikan Toolbar"),
("Direct Connection", "Koneksi langsung"),
("Relay Connection", "Koneksi Relay"),
("Secure Connection", "Koneksi aman"),
@@ -347,31 +348,31 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("General", "Umum"),
("Security", "Keamanan"),
("Theme", "Tema"),
("Dark Theme", "Tema gelap"),
("Light Theme", ""),
("Dark Theme", "Tema Gelap"),
("Light Theme", "Tema Terang"),
("Dark", "Gelap"),
("Light", "Terang"),
("Follow System", "Ikuti sistem"),
("Follow System", "Ikuti Sistem"),
("Enable hardware codec", "Aktifkan codec perangkat keras"),
("Unlock Security Settings", "Buka Kunci Pengaturan Keamanan"),
("Unlock Security Settings", "Buka Keamanan Pengaturan"),
("Enable Audio", "Aktifkan Audio"),
("Unlock Network Settings", "Buka Kunci Pengaturan Jaringan"),
("Unlock Network Settings", "Buka Keamanan Pengaturan Jaringan"),
("Server", "Server"),
("Direct IP Access", "Direct IP Access"),
("Direct IP Access", "Akses IP Langsung"),
("Proxy", "Proxy"),
("Apply", "Terapkan"),
("Disconnect all devices?", "Putuskan sambungan semua perangkat?"),
("Clear", ""),
("Audio Input Device", ""),
("Use IP Whitelisting", "Gunakan Daftar Putih IP"),
("Clear", "Bersihkan"),
("Audio Input Device", "Input Perangkat Audio"),
("Use IP Whitelisting", "Gunakan daftar IP yang diizinkan"),
("Network", "Jaringan"),
("Enable RDP", "Aktifkan RDP"),
("Pin Toolbar", ""),
("Unpin Toolbar", ""),
("Recording", "Rekaman"),
("Pin Toolbar", "Sematkan Toolbar"),
("Unpin Toolbar", "Batal sematkan Toolbar"),
("Recording", "Sedang Merekam"),
("Directory", "Direktori"),
("Automatically record incoming sessions", "Secara otomatis merekam sesi masuk"),
("Change", "Mengubah"),
("Change", "Ubah"),
("Start session recording", "Mulai sesi perekaman"),
("Stop session recording", "Hentikan sesi perekaman"),
("Enable Recording Session", "Aktifkan Sesi Perekaman"),
@@ -380,8 +381,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", "Tolak Penemuan LAN"),
("Write a message", "Menulis pesan"),
("Prompt", ""),
("Please wait for confirmation of UAC...", ""),
("elevated_foreground_window_tip", ""),
("Please wait for confirmation of UAC...", "Harap tunggu konfirmasi UAC"),
("elevated_foreground_window_tip", "Jendela remote desktop ini memerlukan hak akses khusus, jadi anda tidak bisa menggunakan mouse dan keyboard untuk sementara. Anda bisa meminta pihak pengguna yang diremote untuk menyembunyikan jendela ini atau klik tombol elevasi di jendela pengaturan koneksi. Untuk menghindari masalah ini, direkomendasikan untuk menginstall aplikasi secara permanen"),
("Disconnected", "Terputus"),
("Other", "Lainnya"),
("Confirm before closing multiple tabs", "Konfirmasi sebelum menutup banyak tab"),
@@ -390,127 +391,157 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Share", "Berbagi Layar"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland membutuhkan Ubuntu 21.04 atau versi yang lebih tinggi."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland membutuhkan versi distro linux yang lebih tinggi. Silakan coba desktop X11 atau ubah OS Anda."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Silakan Pilih layar yang akan dibagikan (Operasi di sisi rekan)."),
("JumpLink", "Tautan Cepat"),
("Please Select the screen to be shared(Operate on the peer side).", "Silakan Pilih layar yang akan dibagikan kepada rekan anda."),
("Show RustDesk", "Tampilkan RustDesk"),
("This PC", "PC ini"),
("or", "atau"),
("Continue with", "Lanjutkan dengan"),
("Elevate", ""),
("Zoom cursor", ""),
("Elevate", "Elevasi"),
("Zoom cursor", "Perbersar Kursor"),
("Accept sessions via password", "Izinkan sesi dengan kata sandi"),
("Accept sessions via click", "Izinkan sesi dengan klik"),
("Accept sessions via both", "Izinkan sesi dengan keduanya"),
("Please wait for the remote side to accept your session request...", "Harap tunggu sisi jarak jauh untuk menerima permintaan sesi Anda..."),
("Please wait for the remote side to accept your session request...", "Harap tunggu pihak pengguna remote untuk menerima permintaan sesi..."),
("One-time Password", "Kata sandi satu kali"),
("Use one-time password", "Gunakan kata sandi satu kali"),
("One-time password length", ""),
("Request access to your device", ""),
("Hide connection management window", ""),
("hide_cm_tip", ""),
("wayland_experiment_tip", ""),
("Right click to select tabs", ""),
("Skipped", ""),
("Add to Address Book", ""),
("Group", ""),
("One-time password length", "Panjang kata sandi satu kali pakai"),
("Request access to your device", "Permintaan akses ke perangkat ini"),
("Hide connection management window", "Sembunyikan jendela pengaturan koneksi"),
("hide_cm_tip", "Izinkan untuk menyembunyikan hanya jika menerima sesi melalui kata sandi dan menggunakan kata sandi permanen"),
("wayland_experiment_tip", "Dukungan Wayland masih dalam tahap percobaan, harap gunakan X11 jika Anda memerlukan akses tanpa pengawasan"),
("Right click to select tabs", "Klik kanan untuk memilih tab"),
("Skipped", "Dilewati"),
("Add to Address Book", "Tambahkan ke Buku Alamat"),
("Group", "Grup"),
("Search", "Pencarian"),
("Closed manually by web console", ""),
("Local keyboard type", ""),
("Select local keyboard type", ""),
("software_render_tip", ""),
("Always use software rendering", ""),
("config_input", ""),
("config_microphone", ""),
("request_elevation_tip", ""),
("Wait", ""),
("Elevation Error", ""),
("Ask the remote user for authentication", ""),
("Choose this if the remote account is administrator", ""),
("Transmit the username and password of administrator", ""),
("still_click_uac_tip", ""),
("Request Elevation", ""),
("wait_accept_uac_tip", ""),
("Elevate successfully", ""),
("uppercase", ""),
("lowercase", ""),
("digit", ""),
("special character", ""),
("length>=8", ""),
("Weak", ""),
("Medium", ""),
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
("Restore", ""),
("Minimize", ""),
("Maximize", ""),
("Your Device", ""),
("empty_recent_tip", ""),
("empty_favorite_tip", ""),
("empty_lan_tip", ""),
("empty_address_book_tip", ""),
("Closed manually by web console", "Ditutup secara manual dari konsol web."),
("Local keyboard type", "Tipe papan ketik"),
("Select local keyboard type", "Pilih tipe papan ketik"),
("software_render_tip", "Jika anda menggunakan kartu grafis Nvidia pada sistem linux dan jendela windows ditutup secara instan setelah terhung, silahkan ubah ke driver open-source Nouveau, dibutukan untuk merestart aplikasi"),
("Always use software rendering", "Selalu gunakan software rendering"),
("config_input", "Untuk menggunakan input keyboard remote, anda perlu memberikan izin \"Pemantauan Input\" pada RustDesk"),
("config_microphone", "Untuk berbicara secara remote, anda perlu memberikan izin \"Rekam Audio\" pada RustDesk"),
("request_elevation_tip", "Anda juga bisa meminta izin elevasi jika ada pihak pengguna remote"),
("Wait", "Tunggu"),
("Elevation Error", "Kesalahan Elevasi"),
("Ask the remote user for authentication", "Minta pihak pengguna remote untuk otentikasi"),
("Choose this if the remote account is administrator", "Pilih ini jika akun adalah \"administrator\""),
("Transmit the username and password of administrator", "Transmisikan nama pengguna dan kata sandi administrator"),
("still_click_uac_tip", "Masih memerlukan persetujuan pihak pengguna remote untuk mengklik OK pada jendela UAC RustDesk yang sedang berjalan"),
("Request Elevation", "Permintaan Elevasi"),
("wait_accept_uac_tip", "Harap tunggu pihak pengguna remote menerima jendela UAC."),
("Elevate successfully", "Elevasi berhasil"),
("uppercase", "Huruf besar"),
("lowercase", "Huruf kecil"),
("digit", "angka"),
("special character", "Karakter spesial"),
("length>=8", "panjang>=8"),
("Weak", "Lemah"),
("Medium", "Sedang"),
("Strong", "Kuat"),
("Switch Sides", "Ganti Posisi"),
("Please confirm if you want to share your desktop?", "Harap konfirmasi apakah Anda ingin berbagi layar?"),
("Display", "Tampilan"),
("Default View Style", "Gaya Tampilan Default"),
("Default Scroll Style", "Gaya Scroll Default"),
("Default Image Quality", "Kualitas Gambar Default"),
("Default Codec", "Codec default"),
("Bitrate", "Bitrate"),
("FPS", "FPS"),
("Auto", "Otomatis"),
("Other Default Options", "Opsi Default Lainnya"),
("Voice call", "Panggilan suara"),
("Text chat", "Obrolan Teks"),
("Stop voice call", "Hentikan panggilan suara"),
("relay_hint_tip", "Tidak memungkinkan untuk terhubung secara langsung; anda bisa mencoba terhubung via relay. Selain itu, jika ingin menggunakan relay pada percobaan pertama, silahkan tambah akhiran \"/r\" pada ID atau pilih \"Selalu terhubung via relay\" di pilihan sesi terbaru."),
("Reconnect", "Menyambungkan ulang"),
("Codec", "Codec"),
("Resolution", "Resolusi"),
("No transfers in progress", "Tidak ada transfer data yang sedang berlangsung"),
("Set one-time password length", "Atur panjang kata sandi satu kali pakai"),
("install_cert_tip", "Install sertifikat RustDesk"),
("confirm_install_cert_tip", "Ini adalah sertifikat pengujian RustDesk, yang dapat dipercaya. Sertifikat ini akan digunakan untuk menginstal driver RustDesk saat diperlukan"),
("RDP Settings", "Pengaturan RDP"),
("Sort by", "Urutkan berdasarkan"),
("New Connection", "Koneksi baru"),
("Restore", "Mengembalikan"),
("Minimize", "Meminimalkan"),
("Maximize", "Memaksimalkan"),
("Your Device", "Perangkat anda"),
("empty_recent_tip", "Tidak ada sesi terbaru!"),
("empty_favorite_tip", "Belum ada rekan favorit?\nTemukan seseorang untuk terhubung dan tambahkan ke favorit!"),
("empty_lan_tip", "Sepertinya kami belum menemukan rekan"),
("empty_address_book_tip", "Tampaknya saat ini tidak ada rekan yang terdaftar dalam buku alamat Anda"),
("eg: admin", ""),
("Empty Username", ""),
("Empty Password", ""),
("Me", ""),
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
("No need to elevate", ""),
("System Sound", ""),
("Default", ""),
("New RDP", ""),
("Empty Username", "Nama pengguna kosong"),
("Empty Password", "Kata sandi kosong"),
("Me", "Saya"),
("identical_file_tip", "Data ini identik dengan milik rekan"),
("show_monitors_tip", "Tampilkan monitor di toolbar"),
("View Mode", "Mode Tampilan"),
("login_linux_tip", "Anda harus masuk ke akun remote linux untuk mengaktifkan sesi X desktop"),
("verify_rustdesk_password_tip", "Verifikasi Kata Sandi RustDesk"),
("remember_account_tip", "Ingat akun ini"),
("os_account_desk_tip", "Akun ini digunakan untuk masuk ke sistem operasi remote dan mengaktifkan sesi desktop dalam mode tanpa tampilan (headless)"),
("OS Account", "Akun OS"),
("another_user_login_title_tip", "Akun ini sedang digunakan"),
("another_user_login_text_tip", "Putuskan koneksi diperangkat lain"),
("xorg_not_found_title_tip", "Xorg tidak ditemukan"),
("xorg_not_found_text_tip", "Silahkan install Xorg"),
("no_desktop_title_tip", "Desktop tidak tersedia"),
("no_desktop_text_tip", "Silahkan install GNOME Desktop"),
("No need to elevate", "Tidak perlu elevasi"),
("System Sound", "Suara Sistem"),
("Default", "Default"),
("New RDP", "RDP Baru"),
("Fingerprint", ""),
("Copy Fingerprint", ""),
("no fingerprints", ""),
("Select a peer", ""),
("Select peers", ""),
("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
("resolution_original_tip", ""),
("resolution_fit_local_tip", ""),
("resolution_custom_tip", ""),
("Select a peer", "Pilih rekan"),
("Select peers", "Pilih rekan-rekan"),
("Plugins", "Plugin"),
("Uninstall", "Hapus instalasi"),
("Update", "Perbarui"),
("Enable", "Aktifkan"),
("Disable", "Nonaktifkan"),
("Options", "Opsi"),
("resolution_original_tip", "Resolusi original"),
("resolution_fit_local_tip", "Sesuaikan resolusi lokal"),
("resolution_custom_tip", "Resolusi kustom"),
("Collapse toolbar", ""),
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Accept and Elevate", "Terima dan Elevasi"),
("accept_and_elevate_btn_tooltip", "Terima koneksi dan elevasi izin UAC"),
("clipboard_wait_response_timeout_tip", "Batas waktu habis saat menunggu respons salinan"),
("Incoming connection", "Koneksi akan masuk"),
("Outgoing connection", "Koneksi akan keluar"),
("Exit", "Keluar"),
("Open", "Buka"),
("logout_tip", "Apakah Anda yakin ingin keluar?"),
("Service", "Service"),
("Start", "Mulai"),
("Stop", "Berhenti"),
("exceed_max_devices", "Anda telah mencapai jumlah maksimal perangkat yang dikelola"),
("Sync with recent sessions", "Sinkronkan dengan sesi terbaru"),
("Sort tags", "Urutkan tag"),
("Open connection in new tab", "Buka koneksi di tab baru"),
("Move tab to new window", "Pindahkan tab ke jendela baru"),
("Can not be empty", "Tidak boleh kosong"),
("Already exists", "Sudah ada"),
("Change Password", "Ganti kata sandi"),
("Refresh Password", "Perbarui Kata Sandi"),
("ID", "ID"),
("Grid View", "Tampilan Kotak"),
("List View", "Tampilan Daftar"),
("Select", "Pilih"),
("Toggle Tags", "Toggle Tag"),
("pull_ab_failed_tip", "Gagal memuat ulang buku alamat"),
("push_ab_failed_tip", "Gagal menyinkronkan buku alamat ke server"),
("synced_peer_readded_tip", "Perangkat yang terdaftar dalam sesi-sesi terbaru akan di-sinkronkan kembali ke buku alamat."),
("Change Color", "Ganti warna"),
("Primary Color", "Warna utama"),
("HSV Color", "Warna HSV"),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Ingresso audio"),
("Enhancements", "Miglioramenti"),
("Hardware Codec", "Codec hardware"),
("Adaptive Bitrate", "Bitrate adattivo"),
("Adaptive bitrate", "Bitrate adattivo"),
("ID Server", "ID server"),
("Relay Server", "Server relay"),
("API Server", "Server API"),
@@ -223,17 +223,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Verification code", "Codice di verifica"),
("verification_tip", "È stato inviato un codice di verifica all'indirizzo email registrato, per accedere inserisci il codice di verifica."),
("Logout", "Esci"),
("Tags", "Tag"),
("Tags", "Etichette"),
("Search ID", "Cerca ID"),
("whitelist_sep", "Separati da virgola, punto e virgola, spazio o a capo"),
("Add ID", "Aggiungi ID"),
("Add Tag", "Aggiungi tag"),
("Unselect all tags", "Deseleziona tutti i tag"),
("Add Tag", "Aggiungi etichetta"),
("Unselect all tags", "Deseleziona tutte le etichette"),
("Network error", "Errore di rete"),
("Username missed", "Nome utente mancante"),
("Password missed", "Password mancante"),
("Wrong credentials", "Credenziali errate"),
("Edit Tag", "Modifica tag"),
("The verification code is incorrect or has expired", "Il codice di verifica non è corretto o è scaduto"),
("Edit Tag", "Modifica etichetta"),
("Unremember Password", "Dimentica password"),
("Favorites", "Preferiti"),
("Add to Favorites", "Aggiungi ai preferiti"),
@@ -453,14 +454,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", "Chiamata vocale"),
("Text chat", "Chat testuale"),
("Stop voice call", "Interrompi chiamata vocale"),
("relay_hint_tip", "Se non è possibile connettersi direttamente, puoi provare a farlo tramite relay.\nInoltre, se si vuoi usare il relay al primo tentativo, è possibile aggiungere all'ID il suffisso '/r\' o selezionare nella scheda peer l'opzione 'Collegati sempre tramite relay'."),
("relay_hint_tip", "Se non è possibile connettersi direttamente, puoi provare a farlo tramite relay.\nInoltre, se si vuoi usare il relay al primo tentativo, è possibile aggiungere all'ID il suffisso '/r\' o selezionare nella scheda se esiste l'opzione 'Collegati sempre tramite relay'."),
("Reconnect", "Riconnetti"),
("Codec", "Codec"),
("Resolution", "Risoluzione"),
("No transfers in progress", "Nessun trasferimento in corso"),
("Set one-time password length", "Imposta lunghezza password monouso"),
("idd_driver_tip", "Installa driver schermo virtuale che verrà usato quando non sono disponibili schermi fisici."),
("confirm_idd_driver_tip", "È stata selezionata l'opzione per installare il driver schermo virtuale.\nNota che verrà installato un certificato di test per l'attendibilità del driver dello schermo virtuale.\nQuesto certificato di test verrà utilizzato solo per l'attendibilità dei driver di RustDesk."),
("install_cert_tip", "Installa certificato RustDesk"),
("confirm_install_cert_tip", "Questo è un certificato di test RustDesk, che può essere considerato attendibile.\nIl certificato verrà usato per certificarsi ed installare i driver RustDesk quando richiesto."),
("RDP Settings", "Impostazioni RDP"),
("Sort by", "Ordina per"),
("New Connection", "Nuova connessione"),
@@ -511,6 +512,36 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Collapse toolbar", "Comprimi barra strumenti"),
("Accept and Elevate", "Accetta ed eleva"),
("accept_and_elevate_btn_tooltip", "Accetta la connessione ed eleva le autorizzazioni UAC."),
("clipboard_wait_response_timeout_tip", ""),
("clipboard_wait_response_timeout_tip", "Timeout attesa risposta della copia."),
("Incoming connection", "Connessioni in entrata"),
("Outgoing connection", "Connessioni in uscita"),
("Exit", "Esci da RustDesk"),
("Open", "Apri RustDesk"),
("logout_tip", "Sei sicuro di voler uscire?"),
("Service", "Servizio"),
("Start", "Avvia"),
("Stop", "Ferma"),
("exceed_max_devices", "Hai raggiunto il numero massimo di dispositivi gestibili."),
("Sync with recent sessions", "Sincronizza con le sessioni recenti"),
("Sort tags", "Ordina etichette"),
("Open connection in new tab", "Apri connessione in una nuova scheda"),
("Move tab to new window", "Sposta scheda nella finestra successiva"),
("Can not be empty", "Non può essere vuoto"),
("Already exists", "Esiste già"),
("Change Password", "Modifica password"),
("Refresh Password", "Aggiorna password"),
("ID", "ID"),
("Grid View", "Vista griglia"),
("List View", "Vista elenco"),
("Select", "Seleziona"),
("Toggle Tags", "Attiva/disattiva tag"),
("pull_ab_failed_tip", "Impossibile aggiornare la rubrica"),
("push_ab_failed_tip", "Impossibile sincronizzare la rubrica con il server"),
("synced_peer_readded_tip", "I dispositivi presenti nelle sessioni recenti saranno sincronizzati di nuovo nella rubrica."),
("Change Color", "Modifica colore"),
("Primary Color", "Colore primario"),
("HSV Color", "Colore HSV"),
("Installation Successful!", "Installazione completata"),
("Installation failed!", "Installazione fallita"),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "音声入力デバイス"),
("Enhancements", "追加機能"),
("Hardware Codec", "ハードウェア コーデック"),
("Adaptive Bitrate", "アダプティブビットレート"),
("Adaptive bitrate", "アダプティブビットレート"),
("ID Server", "認証サーバー"),
("Relay Server", "中継サーバー"),
("API Server", "APIサーバー"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "ユーザー名がありません"),
("Password missed", "パスワードがありません"),
("Wrong credentials", "資格情報が間違っています"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "タグを編集"),
("Unremember Password", "パスワードの記憶を解除"),
("Favorites", "お気に入り"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "오디오 입력"),
("Enhancements", ""),
("Hardware Codec", "하드웨어 코덱"),
("Adaptive Bitrate", "가변 비트레이트"),
("Adaptive bitrate", "가변 비트레이트"),
("ID Server", "ID 서버"),
("Relay Server", "Relay 서버"),
("API Server", "API 서버"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "사용자명 누락"),
("Password missed", "비밀번호 누락"),
("Wrong credentials", "틀린 인증 정보"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "태그 수정"),
("Unremember Password", "패스워드 기억하지 않기"),
("Favorites", "즐겨찾기"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Аудио Еңгізу"),
("Enhancements", "Жақсартулар"),
("Hardware Codec", "Hardware Codec"),
("Adaptive Bitrate", "Adaptive Bitrate"),
("Adaptive bitrate", "Adaptive bitrate"),
("ID Server", "ID Сербері"),
("Relay Server", "Relay Сербері"),
("API Server", "API Сербері"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Қолданушы аты бос"),
("Password missed", "Құпия сөз бос"),
("Wrong credentials", "Бұрыс тіркелгі деректер"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Тақты Өндеу"),
("Unremember Password", "Құпия сөзді Ұмыту"),
("Favorites", "Таңдаулылар"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Garso įvestis"),
("Enhancements", "Patobulinimai"),
("Hardware Codec", "Aparatinės įrangos paspartinimas"),
("Adaptive Bitrate", "Adaptyvusis pralaidumas"),
("Adaptive bitrate", "Adaptyvusis pralaidumas"),
("ID Server", "ID serveris"),
("Relay Server", "Perdavimo serveris"),
("API Server", "API serveris"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Prarastas vartotojo vardas"),
("Password missed", "Slaptažodis praleistas"),
("Wrong credentials", "Klaidingi kredencialai"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Redaguoti žymą"),
("Unremember Password", "Nebeprisiminti slaptažodžio"),
("Favorites", "Parankiniai"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", "Rezoliucija"),
("No transfers in progress", "Nevyksta jokių perdavimų"),
("Set one-time password length", "Nustatyti vienkartinio slaptažodžio ilgį"),
("idd_driver_tip", "Įdiekite virtualaus ekrano tvarkyklę (naudojama, kai nėra fizinių ekranų)"),
("confirm_idd_driver_tip", "Įjungta virtualaus ekrano tvarkyklės diegimo funkcija. Atminkite, kad bus įdiegtas bandomasis sertifikatas, kad būtų galima pasitikėti tvarkykle. Šis sertifikatas bus naudojamas tik pasitikėjimui Rustdesk tvarkyklėmis patikrinti."),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", "RDP nustatymai"),
("Sort by", "Rūšiuoti pagal"),
("New Connection", "Naujas ryšys"),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Audio Ingang"),
("Enhancements", "Verbeteringen"),
("Hardware Codec", "Hardware Codec"),
("Adaptive Bitrate", "Aangepaste Bitsnelheid"),
("Adaptive bitrate", "Aangepaste Bitsnelheid"),
("ID Server", "Server ID"),
("Relay Server", "Relay Server"),
("API Server", "API Server"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Gebruikersnaam gemist"),
("Password missed", "Wachtwoord vergeten"),
("Wrong credentials", "Verkeerde inloggegevens"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Label Bewerken"),
("Unremember Password", "Wachtwoord vergeten"),
("Favorites", "Favorieten"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", "Resolutie"),
("No transfers in progress", "Geen overdrachten in uitvoering"),
("Set one-time password length", "Stel de lengte van het eenmalige wachtwoord in"),
("idd_driver_tip", "Installeer het virtuele beeldschermstuurprogramma dat wordt gebruikt wanneer u geen fysieke beeldschermen hebt."),
("confirm_idd_driver_tip", "De optie om het virtuele displaystuurprogramma te installeren is ingeschakeld. Er wordt een testcertificaat geplaatst om het virtuele displaystuurprogramma te vertrouwen. Dit testcertificaat wordt alleen gebruikt om RustDesk-stuurprogramma's te vertrouwen."),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", "RDP Instellingen"),
("Sort by", "Sorteren op"),
("New Connection", "Nieuwe Verbinding"),
@@ -511,6 +512,36 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Collapse toolbar", "Werkbalk samenvouwen"),
("Accept and Elevate", "Accepteren en Verheffen"),
("accept_and_elevate_btn_tooltip", "Accepteer de verbinding en verhoog de UAC-machtigingen."),
("clipboard_wait_response_timeout_tip", ""),
("clipboard_wait_response_timeout_tip", "Time-out in afwachting van kopieer-antwoord."),
("Incoming connection", "Inkomende verbinding"),
("Outgoing connection", "Uitgaande verbinding"),
("Exit", "Verlaten"),
("Open", "Open"),
("logout_tip", "Weet je zeker dat je je wilt afmelden?"),
("Service", "Service"),
("Start", "Start"),
("Stop", "Stop"),
("exceed_max_devices", "Het maximum aantal gecontroleerde apparaten is bereikt."),
("Sync with recent sessions", "Recente sessies synchroniseren"),
("Sort tags", "Labels sorteren"),
("Open connection in new tab", "Verbinding openen in een nieuw tabblad"),
("Move tab to new window", "Tabblad verplaatsen naar nieuw venster"),
("Can not be empty", "Mag niet leeg zijn"),
("Already exists", "Bestaat reeds"),
("Change Password", "Wijzig Wachtwoord"),
("Refresh Password", "Wachtwoord Vernieuwen"),
("ID", "ID"),
("Grid View", "Rasterweergave"),
("List View", "Lijstweergave"),
("Select", "Selecteer"),
("Toggle Tags", "Schakel Tags"),
("pull_ab_failed_tip", "Adresboek kan niet worden bijgewerkt"),
("push_ab_failed_tip", "Synchronisatie van adresboek mislukt"),
("synced_peer_readded_tip", "Apparaten die aanwezig waren in recente sessies worden gesynchroniseerd met het adresboek."),
("Change Color", "Kleur Aanpassen"),
("Primary Color", "Hoofdkleur"),
("HSV Color", "HSV Kleur"),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Wejście audio"),
("Enhancements", "Ulepszenia"),
("Hardware Codec", "Kodek sprzętowy"),
("Adaptive Bitrate", "Adaptacyjny bitrate"),
("Adaptive bitrate", "Adaptacyjny bitrate"),
("ID Server", "Serwer ID"),
("Relay Server", "Serwer pośredniczący"),
("API Server", "Serwer API"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Nieprawidłowe nazwa użytkownika"),
("Password missed", "Nieprawidłowe hasło"),
("Wrong credentials", "Błędne dane uwierzytelniające"),
("The verification code is incorrect or has expired", "Kod weryfikacyjny jest niepoprawny lub wygasł"),
("Edit Tag", "Edytuj tag"),
("Unremember Password", "Zapomnij hasło"),
("Favorites", "Ulubione"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", "Rozdzielczość"),
("No transfers in progress", "Brak transferów w toku"),
("Set one-time password length", "Ustaw długość jednorazowego hasła"),
("idd_driver_tip", "Zainstaluj sterownik wirtualnego wyświetlacza, który jest używany, gdy nie masz fizycznych monitorów."),
("confirm_idd_driver_tip", "Opcja instalacji sterownika wirtualnego wyświetlacza jest zaznaczona. Pamiętaj, że zostanie zainstalowany testowy certyfikat, aby zaufać wirtualnemu sterownikowi. Ten certyfikat będzie używany tylko do weryfikacji sterowników RustDesk."),
("install_cert_tip", "Instalacja certyfikatu RustDesk"),
("confirm_install_cert_tip", "To jest certyfikat testowy RustDesk, któremu można zaufać. Certyfikat jest używany do zaufania i instalowania sterowników RustDesk w razie potrzeby."),
("RDP Settings", "Ustawienia RDP"),
("Sort by", "Sortuj wg"),
("New Connection", "Nowe połączenie"),
@@ -505,12 +506,42 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable", "Włącz"),
("Disable", "Wyłącz"),
("Options", "Opcje"),
("resolution_original_tip", ""),
("resolution_fit_local_tip", ""),
("resolution_custom_tip", ""),
("resolution_original_tip", "Oryginalna rozdzielczość"),
("resolution_fit_local_tip", "Dostosuj rozdzielczość lokalną"),
("resolution_custom_tip", "Rozdzielczość niestandardowa"),
("Collapse toolbar", "Zwiń pasek narzędzi"),
("Accept and Elevate", "Akceptuj i Podnieś uprawnienia"),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("accept_and_elevate_btn_tooltip", "Zaakceptuj połączenie i podnieś uprawnienia UAC"),
("clipboard_wait_response_timeout_tip", "Upłynął limit czasu oczekiwania na schowek."),
("Incoming connection", "Połączenie przychodzące"),
("Outgoing connection", "Połączenie wychodzące"),
("Exit", "Wyjście"),
("Open", "Otwórz"),
("logout_tip", "Na pewno chcesz się wylogować?"),
("Service", "Usługa"),
("Start", "Uruchom"),
("Stop", "Zatrzymaj"),
("exceed_max_devices", "Przekroczona maks. liczba urządzeń"),
("Sync with recent sessions", "Synchronizacja z ostatnimi sesjami"),
("Sort tags", "Znaczniki sortowania"),
("Open connection in new tab", "Otwórz połączenie w nowej zakładce"),
("Move tab to new window", "Przenieś zakładkę do nowego okna"),
("Can not be empty", "Nie może być puste"),
("Already exists", "Już istnieje"),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Entrada de Áudio"),
("Enhancements", "Melhorias"),
("Hardware Codec", ""),
("Adaptive Bitrate", ""),
("Adaptive bitrate", ""),
("ID Server", "Servidor de ID"),
("Relay Server", "Servidor de Relay"),
("API Server", "Servidor da API"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Nome de utilizador em falta"),
("Password missed", "Palavra-chave em falta"),
("Wrong credentials", "Nome de utilizador ou palavra-chave incorrectos"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Editar Tag"),
("Unremember Password", "Esquecer Palavra-chave"),
("Favorites", "Favoritos"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Entrada de Áudio"),
("Enhancements", "Melhorias"),
("Hardware Codec", "Codec de hardware"),
("Adaptive Bitrate", "Taxa de bits adaptável"),
("Adaptive bitrate", "Taxa de bits adaptável"),
("ID Server", "Servidor de ID"),
("Relay Server", "Servidor de Relay"),
("API Server", "Servidor da API"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Nome de usuário requerido"),
("Password missed", "Senha requerida"),
("Wrong credentials", "Nome de usuário ou senha incorretos"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Editar Tag"),
("Unremember Password", "Esquecer Senha"),
("Favorites", "Favoritos"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", "Resolução"),
("No transfers in progress", "Nenhuma transferência em andamento"),
("Set one-time password length", "Definir comprimento de senha descartável"),
("idd_driver_tip", "Instale o driver de exibição virtual que é usado quando você não possui displays físicos."),
("confirm_idd_driver_tip", "A opção para instalar o driver de exibição virtual está marcada. Observe que um certificado de teste será instalado para confiar no driver de vídeo virtual. Este certificado de teste será usado apenas para confiar nos drivers Rustdesk."),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", "Configurações RDP"),
("Sort by", "Ordenar por"),
("New Connection", "Nova Conexão"),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -4,29 +4,29 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Status", "Stare"),
("Your Desktop", "Desktopul tău"),
("desk_tip", "Desktopul tău poate fi accesat folosind ID-ul și parola de mai jos."),
("Password", "Parola"),
("Password", "Parolă"),
("Ready", "Pregătit"),
("Established", "Stabilit"),
("connecting_status", "În curs de conectare la rețeaua RustDesk..."),
("Enable Service", "Activează serviciu"),
("Start Service", "Pornește serviciu"),
("Service is running", "Serviciul este în curs de executare..."),
("Enable Service", "Activează serviciul"),
("Start Service", "Pornește serviciul"),
("Service is running", "Serviciul rulează..."),
("Service is not running", "Serviciul nu funcționează"),
("not_ready_status", "Nepregătit. Verifică conexiunea la rețea."),
("Control Remote Desktop", "Controlează desktop-ul la distanță"),
("Transfer File", "Transferă fișier"),
("Control Remote Desktop", "Controlează desktopul la distanță"),
("Transfer File", "Transferă fișiere"),
("Connect", "Conectează-te"),
("Recent Sessions", "Sesiuni recente"),
("Address Book", "Agendă"),
("Confirmation", "Confirmare"),
("TCP Tunneling", "Tunel TCP"),
("Remove", "Elimină"),
("Refresh random password", "Actualizează parolă aleatorie"),
("Refresh random password", "Actualizează parola aleatorie"),
("Set your own password", "Setează propria parolă"),
("Enable Keyboard/Mouse", "Activează control tastatură/mouse"),
("Enable Clipboard", "Activează clipboard"),
("Enable File Transfer", "Activează transfer fișiere"),
("Enable TCP Tunneling", "Activează tunel TCP"),
("Enable File Transfer", "Activează transferul de fișiere"),
("Enable TCP Tunneling", "Activează tunelul TCP"),
("IP Whitelisting", "Listă de IP-uri autorizate"),
("ID/Relay Server", "Server de ID/retransmisie"),
("Import Server Config", "Importă configurație server"),
@@ -35,25 +35,25 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Export server configuration successfully", "Configurație server exportată cu succes"),
("Invalid server configuration", "Configurație server nevalidă"),
("Clipboard is empty", "Clipboard gol"),
("Stop service", "Oprește serviciu"),
("Stop service", "Oprește serviciul"),
("Change ID", "Schimbă ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("Your new ID", "Noul tău ID"),
("length %min% to %max%", "lungime între %min% și %max%"),
("starts with a letter", "începe cu o literă"),
("allowed characters", "caractere permise"),
("id_change_tip", "Pot fi utilizate doar caractere a-z, A-Z, 0-9, _ (bară jos). Primul caracter trebuie să fie a-z, A-Z. Lungimea trebuie să fie între 6 și 16 caractere."),
("Website", "Site web"),
("About", "Despre"),
("Slogan_tip", ""),
("Privacy Statement", ""),
("Mute", "Fără sunet"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Audio Input", "Intrare audio"),
("Slogan_tip", "Făcut din inimă în lumea aceasta haotică!"),
("Privacy Statement", "Politică de confidențialitate"),
("Mute", "Dezactivează sunet"),
("Build Date", "Dată build"),
("Version", "Versiune"),
("Home", "Acasă"),
("Audio Input", "Intrări audio"),
("Enhancements", "Îmbunătățiri"),
("Hardware Codec", "Codec hardware"),
("Adaptive Bitrate", "Rată de biți adaptabilă"),
("Adaptive bitrate", "Rată de biți adaptabilă"),
("ID Server", "Server de ID"),
("Relay Server", "Server de retransmisie"),
("API Server", "Server API"),
@@ -76,7 +76,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Connection Error", "Eroare de conexiune"),
("Error", "Eroare"),
("Reset by the peer", "Conexiunea a fost închisă de dispozitivul pereche"),
("Connecting...", "Conectare..."),
("Connecting...", "Se conectează..."),
("Connection in progress. Please wait.", "Conectare în curs. Te rugăm așteaptă."),
("Please try 1 minute later", "Reîncearcă într-un minut"),
("Login Error", "Eroare de autentificare"),
@@ -105,7 +105,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Are you sure you want to delete this file?", "Sigur vrei să ștergi acest fișier?"),
("Are you sure you want to delete this empty directory?", "Sigur vrei să ștergi acest director gol?"),
("Are you sure you want to delete the file of this directory?", "Sigur vrei să ștergi fișierul din acest director?"),
("Do this for all conflicts", "Aplică pentru toate conflictele"),
("Do this for all conflicts", "Aplică la toate conflictele"),
("This is irreversible!", "Această acțiune este ireversibilă!"),
("Deleting", "În curs de ștergere..."),
("files", "fișier"),
@@ -114,8 +114,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Speed", "Viteză"),
("Custom Image Quality", "Setează calitatea imaginii"),
("Privacy mode", "Mod privat"),
("Block user input", "Blochează utilizator"),
("Unblock user input", "Deblochează utilizator"),
("Block user input", "Blochează intervenție utilizator"),
("Unblock user input", "Deblochează intervenție utilizator"),
("Adjust Window", "Ajustează fereastra"),
("Original", "Dimensiune originală"),
("Shrink", "Micșorează"),
@@ -124,10 +124,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("ScrollAuto", "Derulare automată"),
("Good image quality", "Calitate bună a imaginii"),
("Balanced", "Calitate normală a imaginii"),
("Optimize reaction time", "Optimizează timpul de reacție"),
("Optimize reaction time", "Timp de reacție optimizat"),
("Custom", "Personalizat"),
("Show remote cursor", "Afișează cursor la distanță"),
("Show quality monitor", "Afișează indicator de calitate"),
("Show quality monitor", "Afișează detalii despre conexiune"),
("Disable clipboard", "Dezactivează clipboard"),
("Lock after session end", "Blochează după deconectare"),
("Insert", "Introdu"),
@@ -137,22 +137,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Failed to connect to rendezvous server", "Conectare la server rendezvous eșuată"),
("Please try later", "Încearcă mai târziu"),
("Remote desktop is offline", "Desktopul la distanță este offline"),
("Key mismatch", "Nepotrivire chei"),
("Key mismatch", "Nepotrivire cheie"),
("Timeout", "Conexiune expirată"),
("Failed to connect to relay server", "Conectare la server de retransmisie eșuată"),
("Failed to connect via rendezvous server", "Conectare prin intermediul serverului rendezvous eșuată"),
("Failed to connect via relay server", "Conectare prin intermediul serverului de retransmisie eșuată"),
("Failed to make direct connection to remote desktop", "Imposibil de stabilit o conexiune directă cu desktopul la distanță"),
("Set Password", "Setează parola"),
("OS Password", "Parolă OS"),
("install_tip", "Din cauza restricțiilor UAC, e posibil ca RustDesk să nu funcționeze corespunzător. Pentru a evita acest lucru, fă clic pe butonul de mai jos pentru a instala RustDesk."),
("Click to upgrade", "Fă clic pentru a face upgrade"),
("Click to download", "Fă clic pentru a descărca"),
("Click to update", "Fă clic pentru a actualiza"),
("OS Password", "Parolă sistem"),
("install_tip", "Din cauza restricțiilor CCU, este posibil ca RustDesk să nu funcționeze corespunzător. Pentru a evita acest lucru, dă clic pe butonul de mai jos pentru a instala RustDesk."),
("Click to upgrade", "Dă clic pentru a face upgrade"),
("Click to download", "Dă clic pentru a descărca"),
("Click to update", "Dă clic pentru a actualiza"),
("Configure", "Configurează"),
("config_acc", "Pentru a controla desktopul la distanță, trebuie să permiți RustDesk acces la setările de Accesibilitate."),
("config_screen", "Pentru a controla desktopul la distanță, trebuie să permiți RustDesk acces la setările de Înregistrare ecran."),
("Installing ...", "Instalare în curs..."),
("Installing ...", "Se instalează..."),
("Install", "Instalează"),
("Installation", "Instalare"),
("Installation Path", "Cale de instalare"),
@@ -171,18 +171,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Add", "Adaugă"),
("Local Port", "Port local"),
("Local Address", "Adresă locală"),
("Change Local Port", "Schimbă port local"),
("Change Local Port", "Modifică port local"),
("setup_server_tip", "Pentru o conexiune mai rapidă, îți poți configura propriul server."),
("Too short, at least 6 characters.", "Prea scurt; trebuie cel puțin 6 caractere."),
("Too short, at least 6 characters.", "Prea scurtă; trebuie cel puțin 6 caractere."),
("The confirmation is not identical.", "Cele două intrări nu corespund."),
("Permissions", "Permisiuni"),
("Accept", "Acceptă"),
("Dismiss", "Respinge"),
("Disconnect", "Deconectează-te"),
("Allow using keyboard and mouse", "Permite utilizarea tastaturii și mouse-ului"),
("Allow using keyboard and mouse", "Permite utilizarea tastaturii și a mouse-ului"),
("Allow using clipboard", "Permite utilizarea clipboardului"),
("Allow hearing sound", "Permite auzirea sunetului"),
("Allow file copy and paste", "Permite copierea/lipirea fișierelor"),
("Allow file copy and paste", "Permite copierea și lipirea fișierelor"),
("Connected", "Conectat"),
("Direct and encrypted connection", "Conexiune directă criptată"),
("Relayed and encrypted connection", "Conexiune retransmisă criptată"),
@@ -192,7 +192,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enter your password", "Introdu parola"),
("Logging in...", "Se conectează..."),
("Enable RDP session sharing", "Activează partajarea sesiunii RDP"),
("Auto Login", "Conectare automată (valid doar dacă funcția de Blocare după deconectare este activă)"),
("Auto Login", "Conectare automată (validă doar dacă opțiunea Blocare după deconectare este selectată)"),
("Enable Direct IP Access", "Activează accesul direct cu IP"),
("Rename", "Redenumește"),
("Space", "Spațiu"),
@@ -205,25 +205,25 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Login screen using Wayland is not supported", "Ecranele de conectare care folosesc Wayland nu sunt acceptate"),
("Reboot required", "Repornire necesară"),
("Unsupported display server", "Tipul de server de afișaj nu este acceptat"),
("x11 expected", "E necesar X11"),
("x11 expected", "Este necesar X11"),
("Port", "Port"),
("Settings", "Setări"),
("Username", " Nume de utilizator"),
("Username", " Nume utilizator"),
("Invalid port", "Port nevalid"),
("Closed manually by the peer", "Închis manual de dispozitivul pereche"),
("Closed manually by the peer", "Conexiune închisă manual de dispozitivul pereche"),
("Enable remote configuration modification", "Activează modificarea configurației de la distanță"),
("Run without install", "Rulează fără instalare"),
("Connect via relay", ""),
("Always connect via relay", "Se conectează mereu prin retransmisie"),
("Run without install", "Rulează fără a instala"),
("Connect via relay", "Conectează-te prin retransmisie"),
("Always connect via relay", "Conectează-te mereu prin retransmisie"),
("whitelist_tip", "Doar adresele IP autorizate pot accesa acest dispozitiv"),
("Login", "Conectare"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("Login", "Conectează-te"),
("Verify", "Verificare"),
("Remember me", "Reține-mă"),
("Trust this device", "Acest dispozitiv este de încredere"),
("Verification code", "Cod de verificare"),
("verification_tip", ""),
("Logout", "Deconectare"),
("Tags", "Etichetare"),
("Logout", "Deconectează-te"),
("Tags", "Etichete"),
("Search ID", "Caută după ID"),
("whitelist_sep", "Poți folosi ca separator virgula, punctul și virgula, spațiul sau linia nouă"),
("Add ID", "Adaugă ID"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Lipsește numele de utilizator"),
("Password missed", "Lipsește parola"),
("Wrong credentials", "Nume sau parolă greșită"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Modifică etichetă"),
("Unremember Password", "Uită parola"),
("Favorites", "Favorite"),
@@ -263,32 +264,32 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Mouse Wheel", "Rotiță mouse"),
("Two-Finger Move", "Mișcă cu două degete"),
("Canvas Move", "Mută ecran"),
("Pinch to Zoom", "Apropie degetele pentru zoom"),
("Canvas Zoom", "Zoom ecran"),
("Pinch to Zoom", "Apropie degetele pentru a mări"),
("Canvas Zoom", "Mărire ecran"),
("Reset canvas", "Reinițializează ecranul"),
("No permission of file transfer", "Nicio permisiune pentru transferul de fișiere"),
("Note", "Reține"),
("Connection", "Conexiune"),
("Share Screen", "Partajează ecran"),
("Chat", "Discută"),
("Chat", "Mesaje"),
("Total", "Total"),
("items", "elemente"),
("Selected", "Selectat"),
("Screen Capture", "Captură ecran"),
("Screen Capture", "Capturare ecran"),
("Input Control", "Control intrări"),
("Audio Capture", "Captură audio"),
("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?"),
("android_input_permission_tip1", "Pentru ca un dispozitiv la distanță să poată controla un dispozitiv Android folosind mouse-ul sau suportul tactil, trebuie să permiți RustDesk să utilize serviciul Accesibilitate."),
("android_input_permission_tip2", "Accesează următoarea pagină din Setări, caută și deschide [Aplicații instalate] și activează serviciul [RustDesk Input]."),
("android_new_connection_tip", "Ai primit o nouă solicitare de control pentru dispozitivul actual."),
("android_service_will_start_tip", "Activarea setării Captură ecran va porni automat serviciul, permițând altor dispozitive să solicite conectarea la dispozitivul tău."),
("android_input_permission_tip1", "Pentru ca un dispozitiv la distanță să poată controla un dispozitiv Android folosind mouse-ul sau suportul tactil, trebuie să permiți RustDesk să utilize serviciul Accesibilitate."),
("android_input_permission_tip2", "Accesează următoarea pagină din Setări, deschide [Aplicații instalate] și pornește serviciul [RustDesk Input]."),
("android_new_connection_tip", "Ai primit o nouă solicitare de controlare a dispozitivului actual."),
("android_service_will_start_tip", "Activarea setării de capturare a ecranului va porni automat serviciul, permițând altor dispozitive să solicite conectarea la dispozitivul tău."),
("android_stop_service_tip", "Închiderea serviciului va închide automat toate conexiunile stabilite."),
("android_version_audio_tip", "Versiunea actuală de Android nu suportă captura audio. Fă upgrade la Android 10 sau la o versiune superioară."),
("android_start_service_tip", ""),
("android_start_service_tip", "Apasă [Pornește serviciu] sau DESCHIDE [Capturare ecran] pentru a porni serviciul de partajare a ecranului."),
("android_permission_may_not_change_tip", ""),
("Account", "Cont"),
("Overwrite", "Suprascrie"),
@@ -311,14 +312,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Rulează serviciul RustDesk în fundal"),
("Ignore Battery Optimizations", "Ignoră optimizările de baterie"),
("android_open_battery_optimizations_tip", "Pentru dezactivarea acestei funcții, accesează setările aplicației RustDesk, deschide secțiunea [Baterie] și deselectează [Fără restricții]."),
("Start on Boot", ""),
("Start the screen sharing service on boot, requires special permissions", ""),
("Start on Boot", "Pornește la boot"),
("Start the screen sharing service on boot, requires special permissions", "Pornește serviciul de partajare a ecranului la boot; necesită permisiuni speciale"),
("Connection not allowed", "Conexiune neautoriztă"),
("Legacy mode", "Mod legacy"),
("Map mode", "Mod hartă"),
("Translate mode", "Mod traducere"),
("Use permanent password", "Folosește parola permanentă"),
("Use both passwords", "Folosește parola unică și cea permanentă"),
("Use both passwords", "Folosește ambele programe"),
("Set permanent password", "Setează parola permanentă"),
("Enable Remote Restart", "Activează repornirea la distanță"),
("Allow remote restart", "Permite repornirea la distanță"),
@@ -329,33 +330,33 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Copied", "Copiat"),
("Exit Fullscreen", "Ieși din modul ecran complet"),
("Fullscreen", "Ecran complet"),
("Mobile Actions", "Acțiuni mobile"),
("Mobile Actions", "Bară de navigare"),
("Select Monitor", "Selectează monitor"),
("Control Actions", "Acțiuni de control"),
("Display Settings", "Setări afișaj"),
("Ratio", "Raport"),
("Image Quality", "Calitate imagine"),
("Scroll Style", "Stil de derulare"),
("Show Toolbar", ""),
("Hide Toolbar", ""),
("Show Toolbar", "Arată bară de instrumente"),
("Hide Toolbar", "Ascunde bară de instrumente"),
("Direct Connection", "Conexiune directă"),
("Relay Connection", "Conexiune prin retransmisie"),
("Secure Connection", "Conexiune securizată"),
("Insecure Connection", "Conexiune nesecurizată"),
("Scale original", "Scală originală"),
("Scale adaptive", "Scală adaptivă"),
("Scale original", "Dimensiune originală"),
("Scale adaptive", "Scalare automată"),
("General", "General"),
("Security", "Securitate"),
("Theme", "Temă"),
("Dark Theme", "Temă întunecată"),
("Light Theme", ""),
("Dark", "Întunecat"),
("Light", "Luminos"),
("Follow System", "Urmărește sistem"),
("Light Theme", "Temă luminoasă"),
("Dark", "Întunecată"),
("Light", "Luminoasă"),
("Follow System", "Temă sistem"),
("Enable hardware codec", "Activează codec hardware"),
("Unlock Security Settings", "Deblochează setări de securitate"),
("Unlock Security Settings", "Deblochează setările de securitate"),
("Enable Audio", "Activează audio"),
("Unlock Network Settings", "Deblochează setări de rețea"),
("Unlock Network Settings", "Deblochează setările de rețea"),
("Server", "Server"),
("Direct IP Access", "Acces direct IP"),
("Proxy", "Proxy"),
@@ -366,26 +367,26 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Use IP Whitelisting", "Folosește lista de IP-uri autorizate"),
("Network", "Rețea"),
("Enable RDP", "Activează RDP"),
("Pin Toolbar", ""),
("Unpin Toolbar", ""),
("Pin Toolbar", "Fixează bara de instrumente"),
("Unpin Toolbar", "Detașează bara de instrumente"),
("Recording", "Înregistrare"),
("Directory", "Director"),
("Automatically record incoming sessions", "Înregistrează automat sesiunile viitoare"),
("Change", "Modifică"),
("Start session recording", "Începe înregistrare"),
("Stop session recording", "Oprește înregistrare"),
("Start session recording", "Începe înregistrarea"),
("Stop session recording", "Oprește înregistrarea"),
("Enable Recording Session", "Activează înregistrarea sesiunii"),
("Allow recording session", "Permite înregistrarea sesiunii"),
("Enable LAN Discovery", "Activează descoperire LAN"),
("Deny LAN Discovery", "Interzice descoperire LAN"),
("Enable LAN Discovery", "Activează descoperirea LAN"),
("Deny LAN Discovery", "Interzice descoperirea LAN"),
("Write a message", "Scrie un mesaj"),
("Prompt", "Solicită"),
("Please wait for confirmation of UAC...", "Așteaptă confirmarea UAC..."),
("Prompt", "Prompt"),
("Please wait for confirmation of UAC...", "Așteaptă confirmarea CCU..."),
("elevated_foreground_window_tip", "Fereastra actuală a dispozitivului la distanță necesită privilegii sporite pentru a funcționa, astfel că mouse-ul și tastatura nu pot fi folosite. Poți cere utilizatorului la distanță să minimizeze fereastra actuală sau să facă clic pe butonul de sporire a privilegiilor din fereastra de gestionare a conexiunilor. Pentru a evita această problemă, recomandăm instalarea software-ului pe dispozitivul la distanță."),
("Disconnected", "Deconectat"),
("Other", "Altele"),
("Confirm before closing multiple tabs", "Confirmă înainte de a închide mai multe file"),
("Keyboard Settings", "Configurare tastatură"),
("Keyboard Settings", "Setări tastatură"),
("Full Access", "Acces total"),
("Screen Share", "Partajare ecran"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland necesită Ubuntu 21.04 sau o versiune superioară."),
@@ -396,121 +397,151 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("This PC", "Acest PC"),
("or", "sau"),
("Continue with", "Continuă cu"),
("Elevate", "Sporește"),
("Elevate", "Sporește privilegii"),
("Zoom cursor", "Cursor lupă"),
("Accept sessions via password", "Acceptă sesiunile folosind parola"),
("Accept sessions via click", "Acceptă sesiunile cu un clic de confirmare"),
("Accept sessions via both", "Acceptă sesiunile folosind ambele moduri"),
("Accept sessions via password", "Acceptă începerea sesiunii folosind parola"),
("Accept sessions via click", "Acceptă începerea sesiunii dând clic"),
("Accept sessions via both", "Acceptă începerea sesiunii folosind ambele moduri"),
("Please wait for the remote side to accept your session request...", "Așteaptă ca solicitarea ta de conectare la distanță să fie acceptată..."),
("One-time Password", "Parolă unică"),
("Use one-time password", "Folosește parola unică"),
("One-time password length", "Lungimea parolei unice"),
("Request access to your device", "Solicită acces la dispozitivul tău"),
("Request access to your device", "Solicitare de acces la dispozitivul tău"),
("Hide connection management window", "Ascunde fereastra de gestionare a conexiunilor"),
("hide_cm_tip", "Permite ascunderea ferestrei de gestionare doar dacă accepți începerea sesiunilor folosind parola permanentă"),
("wayland_experiment_tip", ""),
("Right click to select tabs", ""),
("Skipped", ""),
("Add to Address Book", ""),
("Group", ""),
("Search", ""),
("Closed manually by web console", ""),
("Local keyboard type", ""),
("Select local keyboard type", ""),
("software_render_tip", ""),
("Always use software rendering", ""),
("config_input", ""),
("config_microphone", ""),
("request_elevation_tip", ""),
("Wait", ""),
("Elevation Error", ""),
("Ask the remote user for authentication", ""),
("Choose this if the remote account is administrator", ""),
("Transmit the username and password of administrator", ""),
("still_click_uac_tip", ""),
("Request Elevation", ""),
("wait_accept_uac_tip", ""),
("Elevate successfully", ""),
("uppercase", ""),
("lowercase", ""),
("digit", ""),
("special character", ""),
("length>=8", ""),
("Weak", ""),
("Medium", ""),
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
("Restore", ""),
("Minimize", ""),
("Maximize", ""),
("Your Device", ""),
("empty_recent_tip", ""),
("empty_favorite_tip", ""),
("empty_lan_tip", ""),
("empty_address_book_tip", ""),
("eg: admin", ""),
("Empty Username", ""),
("Empty Password", ""),
("Me", ""),
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
("No need to elevate", ""),
("System Sound", ""),
("Default", ""),
("New RDP", ""),
("Fingerprint", ""),
("Copy Fingerprint", ""),
("no fingerprints", ""),
("Select a peer", ""),
("Select peers", ""),
("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
("resolution_original_tip", ""),
("resolution_fit_local_tip", ""),
("resolution_custom_tip", ""),
("Collapse toolbar", ""),
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("wayland_experiment_tip", "Wayland este acceptat doar într-o formă experimentală. Folosește X11 dacă nu ai nevoie de acces supravegheat."),
("Right click to select tabs", "Dă clic dreapta pentru a selecta file"),
("Skipped", "Ignorat"),
("Add to Address Book", "Adaugă la agendă"),
("Group", "Grup"),
("Search", "Caută"),
("Closed manually by web console", "Conexiune închisă manual de consola web"),
("Local keyboard type", "Tastatură locală"),
("Select local keyboard type", "Selectează tastatura locală"),
("software_render_tip", "Dacă ai o placă video Nvidia și folosești Linux, iar fereastra cu conexiunea la distanță se închide imediat după conectare, îți sugerăm să instalezi driverul gratuit Nouveau și să folosești randarea de software. Este necesară repornirea."),
("Always use software rendering", "Utilizează mereu randarea de software"),
("config_input", "Pentru a controla desktopul la distanță folosind tastatura, trebuie să acorzi RustDesk permisiunea Monitorizare intrare"),
("config_microphone", "Pentru a desfășura un apel vocal, este nevoie să acorzi RustDesk permisiunea Înregistrare audio."),
("request_elevation_tip", "Poți solicita sporirea privilegiilor și dacă este cineva la desktopul la distanță."),
("Wait", "În curs..."),
("Elevation Error", "Eroare la sporirea privilegiilor"),
("Ask the remote user for authentication", "Solicită utilizatorului de la distanță să se autentifice"),
("Choose this if the remote account is administrator", "Alege asta dacă contul la distanță este un cont de administrator"),
("Transmit the username and password of administrator", "Transmite numele de utilizator și parola administratorului"),
("still_click_uac_tip", "Este necesar ca utilizatorul la distanță să confirme în fereastra CCU din RustDesk care rulează."),
("Request Elevation", "Solicită sporirea privilegiilor"),
("wait_accept_uac_tip", "Așteaptă ca utilizatorul la distanță să accepte dialogul CCU."),
("Elevate successfully", "Sporirea privilegiilor realizată cu succes"),
("uppercase", "majuscule"),
("lowercase", "minuscule"),
("digit", "cifre"),
("special character", "caractere speciale"),
("length>=8", "lungime>=8"),
("Weak", "Slabă"),
("Medium", "Medie"),
("Strong", "Puternică"),
("Switch Sides", "Inversează controlul"),
("Please confirm if you want to share your desktop?", "Confirmi că dorești să îți partajezi desktopul?"),
("Display", "Afișare"),
("Default View Style", "Stilul implicit de vizualizare"),
("Default Scroll Style", "Stilul implicit de derulare"),
("Default Image Quality", "Calitatea implicită a imaginii"),
("Default Codec", "Codec implicit"),
("Bitrate", "Rată de biți"),
("FPS", "CPS"),
("Auto", "Auto"),
("Other Default Options", "Alte opțiuni implicite"),
("Voice call", "Apel vocal"),
("Text chat", "Conversație text"),
("Stop voice call", "Încheie apel vocal"),
("relay_hint_tip", "Este posibil să nu te poți conecta direct; poți încerca să te conectezi prin retransmisie. De asemenea, dacă dorești să te conectezi direct prin retransmisie, poți adăuga sufixul „/r” la ID sau să bifezi opțiunea Conectează-te mereu prin retransmisie."),
("Reconnect", "Reconectează-te"),
("Codec", "Codec"),
("Resolution", "Rezoluție"),
("No transfers in progress", "Niciun transfer nu este în desfășurare"),
("Set one-time password length", "Definește lungimea parolei unice"),
("install_cert_tip", "Instalează certificatul RustDesk"),
("confirm_install_cert_tip", "Acesta este un certificat de testare RustDesk și este de încredere. Certificatul va fi utilizat pentru a acorda încredere și instala drivere RustDesk atunci când este necesar."),
("RDP Settings", "Setări RDP"),
("Sort by", "Sortează după"),
("New Connection", "Conexiune nouă"),
("Restore", "Restaurează"),
("Minimize", "Minimizează"),
("Maximize", "Maximizează"),
("Your Device", "Dispozitivul tău"),
("empty_recent_tip", "Hopa! Nu există nicio sesiune recentă.\nPoate ar trebui să plănuiești una chiar acum!"),
("empty_favorite_tip", "Încă nu ai niciun dispozitiv pereche favorit?\nHai să-ți găsim pe cineva cu care să te conectezi, iar apoi poți adăuga dispozitivul la Favorite!"),
("empty_lan_tip", "Of! S-ar părea că încă nu am descoperit niciun dispozitiv."),
("empty_address_book_tip", "Măi să fie! Se pare că deocamdată nu figurează niciun dispozitiv în agenda ta."),
("eg: admin", "ex: admin"),
("Empty Username", "Nume utilizator nespecificat"),
("Empty Password", "Parolă nespecificată"),
("Me", "Eu"),
("identical_file_tip", "Acest fișier este identic cu cel al dispozitivului pereche."),
("show_monitors_tip", "Afișează monitoare în bara de instrumente"),
("View Mode", "Mod vizualizare"),
("login_linux_tip", "Este necesar să te conectezi la contul de Linux de la distanță pentru a începe o sesiune cu un desktop care folosește X11"),
("verify_rustdesk_password_tip", "Verifică parola RustDesk"),
("remember_account_tip", "Reține contul"),
("os_account_desk_tip", "Acest cont este utilizat pentru conectarea la sistemul de operare la distanță și începerea sesiunii cu desktopul în modul fără afișaj."),
("OS Account", "Cont OS"),
("another_user_login_title_tip", "Un alt utilizator este deja conectat"),
("another_user_login_text_tip", "Deconectare"),
("xorg_not_found_title_tip", "Xorg nu a fost găsit"),
("xorg_not_found_text_tip", "Instalează Xorg"),
("no_desktop_title_tip", "Nu este disponibil niciun mediu desktop"),
("no_desktop_text_tip", "Instalează mediul desktop GNOME"),
("No need to elevate", "Nu sunt necesare permisiuni de administrator"),
("System Sound", "Sunet sistem"),
("Default", "Implicit"),
("New RDP", "RDP nou"),
("Fingerprint", "Amprentă digitală"),
("Copy Fingerprint", "Copiază amprenta digitală"),
("no fingerprints", "Nicio amprentă digitală"),
("Select a peer", "Selectează un dispozitiv pereche"),
("Select peers", "Selectează dispozitive pereche"),
("Plugins", "Pluginuri"),
("Uninstall", "Dezinstalează"),
("Update", "Actualizează"),
("Enable", "Activează"),
("Disable", "Dezactivează"),
("Options", "Opțiuni"),
("resolution_original_tip", "Rezoluție originală"),
("resolution_fit_local_tip", "Adaptează la rezoluția locală"),
("resolution_custom_tip", "Rezoluție personalizată"),
("Collapse toolbar", "Restrânge bara de instrumente"),
("Accept and Elevate", "Acceptă și sporește privilegii"),
("accept_and_elevate_btn_tooltip", "Acceptă conectarea și sporește privilegiile CCU"),
("clipboard_wait_response_timeout_tip", "Procesul a expirat așteptând un răspuns la copiere"),
("Incoming connection", "Conexiune de intrare"),
("Outgoing connection", "Conexiune de ieșire"),
("Exit", "Ieși"),
("Open", "Deschide"),
("logout_tip", "Sigur vrei să te deconectezi?"),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -28,7 +28,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable File Transfer", "Включить передачу файлов"),
("Enable TCP Tunneling", "Включить туннелирование TCP"),
("IP Whitelisting", "Список разрешённых IP-адресов"),
("ID/Relay Server", "ID/Сервер ретрансляции"),
("ID/Relay Server", "ID/Ретранслятор"),
("Import Server Config", "Импортировать конфигурацию сервера"),
("Export Server Config", "Экспортировать конфигурацию сервера"),
("Import server configuration successfully", "Конфигурация сервера успешно импортирована"),
@@ -53,10 +53,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Аудиовход"),
("Enhancements", "Улучшения"),
("Hardware Codec", "Аппаратный кодек"),
("Adaptive Bitrate", "Адаптивная скорость потока"),
("ID Server", "ID-сервер"),
("Relay Server", "Сервер ретрансляции"),
("API Server", "API-сервер"),
("Adaptive bitrate", "Адаптивная скорость потока"),
("ID Server", "Сервер ID"),
("Relay Server", "Ретранслятор"),
("API Server", "Сервер API"),
("invalid_http", "Адрес должен начинаться с http:// или https://"),
("Invalid IP", "Неправильный IP-адрес"),
("Invalid format", "Неправильный формат"),
@@ -139,13 +139,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Remote desktop is offline", "Удалённый рабочий стол не в сети"),
("Key mismatch", "Несоответствие ключей"),
("Timeout", "Истекло время ожидания"),
("Failed to connect to relay server", "Невозможно подключиться к серверу ретрансляции"),
("Failed to connect to relay server", "Невозможно подключиться к ретранслятору"),
("Failed to connect via rendezvous server", "Невозможно подключиться через промежуточный сервер"),
("Failed to connect via relay server", "Невозможно подключиться через сервер ретрансляции"),
("Failed to connect via relay server", "Невозможно подключиться через ретранслятор"),
("Failed to make direct connection to remote desktop", "Невозможно установить прямое подключение к удалённому рабочему столу"),
("Set Password", "Установить пароль"),
("OS Password", "Пароль ОС"),
("install_tip", "В некоторых случаях из-за UAC RustDesk может работать неправильно на удалённом узле. Чтобы избежать UAC, нажмите кнопку ниже, чтобы установить RustDesk в системе."),
("install_tip", "В некоторых случаях из-за UAC RustDesk может работать неправильно на удалённом узле. Чтобы избежать UAC, нажмите кнопку ниже для установки RustDesk в системе."),
("Click to upgrade", "Нажмите для проверки обновлений"),
("Click to download", "Нажмите для скачивания"),
("Click to update", "Нажмите для обновления"),
@@ -225,7 +225,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Logout", "Выйти"),
("Tags", "Метки"),
("Search ID", "Поиск по ID"),
("whitelist_sep", "Раздельно запятой, точкой с запятой, пробелом или новой строкой"),
("whitelist_sep", "Разделение запятой, точкой с запятой, пробелом или новой строкой"),
("Add ID", "Добавить ID"),
("Add Tag", "Добавить ключевое слово"),
("Unselect all tags", "Отменить выбор всех меток"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Имя пользователя отсутствует"),
("Password missed", "Забыли пароль"),
("Wrong credentials", "Неправильные учётные данные"),
("The verification code is incorrect or has expired", "Проверочный код неправильный или устарел"),
("Edit Tag", "Изменить метку"),
("Unremember Password", "Не сохранять пароль"),
("Favorites", "Избранное"),
@@ -298,7 +299,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Help", "Помощь"),
("Failed", "Не выполнено"),
("Succeeded", "Выполнено"),
("Someone turns on privacy mode, exit", "Кто-то включает режим конфиденциальности, выход"),
("Someone turns on privacy mode, exit", "Кто-то включил режим конфиденциальности, выход"),
("Unsupported", "Не поддерживается"),
("Peer denied", "Отклонено удалённым узлом"),
("Please install plugins", "Установите плагины"),
@@ -399,8 +400,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Elevate", "Повысить"),
("Zoom cursor", "Масштабировать курсор"),
("Accept sessions via password", "Принимать сеансы по паролю"),
("Accept sessions via click", "Принимать сеансы по нажатию"),
("Accept sessions via both", "Принимать сеансы по паролю+нажатию"),
("Accept sessions via click", "Принимать сеансы нажатем кнопки"),
("Accept sessions via both", "Принимать сеансы по паролю и нажатию кнопки"),
("Please wait for the remote side to accept your session request...", "Подождите, пока удалённая сторона примет ваш запрос на сеанс..."),
("One-time Password", "Одноразовый пароль"),
("Use one-time password", "Использовать одноразовый пароль"),
@@ -453,14 +454,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", "Голосовой вызов"),
("Text chat", "Текстовый чат"),
("Stop voice call", "Завершить голосовой вызов"),
("relay_hint_tip", "Прямое подключение может оказаться невозможным. В этом случае можно попытаться подключиться через сервер ретрансляции. \nКроме того, если вы хотите сразу использовать сервер ретрансляции, можно добавить к ID суффикс \"/r\" или включить \"Всегда подключаться через ретранслятор\" в настройках удалённого узла."),
("relay_hint_tip", "Прямое подключение может оказаться невозможным. В этом случае можно попытаться подключиться через ретранслятор. \nКроме того, если вы хотите сразу использовать ретранслятор, можно добавить к ID суффикс \"/r\" или включить \"Всегда подключаться через ретранслятор\" в настройках удалённого узла."),
("Reconnect", "Переподключить"),
("Codec", "Кодек"),
("Resolution", "Разрешение"),
("No transfers in progress", "Передача не осуществляется"),
("Set one-time password length", "Установить длину одноразового пароля"),
("idd_driver_tip", "Установить драйвер виртуального дисплея (используется при отсутствии физических дисплеев)"),
("confirm_idd_driver_tip", "Включена функция установки драйвера виртуального дисплея. Обратите внимание, что для доверия к драйверу будет установлен тестовый сертификат. Этот сертификат будет использоваться только для подтверждения доверия драйверам Rustdesk."),
("install_cert_tip", "Установить сертификат RustDesk"),
("confirm_install_cert_tip", "Это тестовый сертификат RustDesk, которому можно доверять. Он будет использоваться только по необходимости для установки драйверов RustDesk."),
("RDP Settings", "Настройки RDP"),
("Sort by", "Сортировка"),
("New Connection", "Новое подключение"),
@@ -511,6 +512,36 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Collapse toolbar", "Свернуть панель инструментов"),
("Accept and Elevate", "Принять и повысить"),
("accept_and_elevate_btn_tooltip", "Разрешить подключение и повысить права UAC."),
("clipboard_wait_response_timeout_tip", "Время ожидания копирования буфера обмена, истекло"),
("clipboard_wait_response_timeout_tip", "Время ожидания копирования буфера обмена истекло"),
("Incoming connection", "Входящее подключение"),
("Outgoing connection", "Исходящее подключение"),
("Exit", "Выход"),
("Open", "Открыть"),
("logout_tip", "Вы действительно хотите выйти?"),
("Service", "Сервис"),
("Start", "Запустить"),
("Stop", "Остановить"),
("exceed_max_devices", "Достигнуто максимальное количество управляемых устройств."),
("Sync with recent sessions", "Синхронизация последних сеансов"),
("Sort tags", "Сортировка меток"),
("Open connection in new tab", "Открыть подключение в новой вкладке"),
("Move tab to new window", "Переместить вкладку в отдельное окно"),
("Can not be empty", "Не может быть пустым"),
("Already exists", "Уже существует"),
("Change Password", "Изменить пароль"),
("Refresh Password", "Обновить пароль"),
("ID", "ID"),
("Grid View", "Сетка"),
("List View", "Список"),
("Select", "Выбор"),
("Toggle Tags", "Переключить метки"),
("pull_ab_failed_tip", "Невозможно обновить адресную книгу"),
("push_ab_failed_tip", "Невозможно синхронизировать адресную книгу с сервером"),
("synced_peer_readded_tip", "Устройства, присутствовавшие в последних сеансах, будут синхронизированы с адресной книгой."),
("Change Color", "Изменить цвет"),
("Primary Color", "Основной цвет"),
("HSV Color", "Цвет HSV"),
("Installation Successful!", "Установка выполнена успешно!"),
("Installation failed!", "Установка не выполнена!"),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Zvukový vstup"),
("Enhancements", ""),
("Hardware Codec", ""),
("Adaptive Bitrate", ""),
("Adaptive bitrate", ""),
("ID Server", "ID server"),
("Relay Server", "Prepojovací server"),
("API Server", "API server"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Chýba užívateľské meno"),
("Password missed", "Chýba heslo"),
("Wrong credentials", "Nesprávne prihlasovacie údaje"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Upraviť štítok"),
("Unremember Password", "Zabudnúť heslo"),
("Favorites", "Obľúbené"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Avdio vhod"),
("Enhancements", "Izboljšave"),
("Hardware Codec", "Strojni kodek"),
("Adaptive Bitrate", "Prilagodljiva bitna hitrost"),
("Adaptive bitrate", "Prilagodljiva bitna hitrost"),
("ID Server", "ID strežnik"),
("Relay Server", "Posredniški strežnik"),
("API Server", "API strežnik"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Up. ime izpuščeno"),
("Password missed", "Geslo izpuščeno"),
("Wrong credentials", "Napačne poverilnice"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Uredi oznako"),
("Unremember Password", "Pozabi geslo"),
("Favorites", "Priljubljene"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Inputi zërit"),
("Enhancements", "Përmirësimet"),
("Hardware Codec", "Kodeku Harduerik"),
("Adaptive Bitrate", "Shpejtësia adaptive e biteve"),
("Adaptive bitrate", "Shpejtësia adaptive e biteve"),
("ID Server", "ID e serverit"),
("Relay Server", "Serveri rele"),
("API Server", "Serveri API"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Mungon përdorusesi"),
("Password missed", "Mungon fjalëkalimi"),
("Wrong credentials", "Kredinciale të gabuara"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Edito tagun"),
("Unremember Password", "Fjalëkalim jo i kujtueshëm"),
("Favorites", "Te preferuarat"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Audio ulaz"),
("Enhancements", "Proširenja"),
("Hardware Codec", "Hardverski kodek"),
("Adaptive Bitrate", "Prilagodljiva gustina podataka"),
("Adaptive bitrate", "Prilagodljiva gustina podataka"),
("ID Server", "ID server"),
("Relay Server", "Posredni server"),
("API Server", "API server"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Korisničko ime promašeno"),
("Password missed", "Lozinka promašena"),
("Wrong credentials", "Pogrešno korisničko ime ili lozinka"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Izmeni oznaku"),
("Unremember Password", "Zaboravi lozinku"),
("Favorites", "Favoriti"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Ljud input"),
("Enhancements", "Förbättringar"),
("Hardware Codec", "Hårdvarucodec"),
("Adaptive Bitrate", "Adaptiv Bitrate"),
("Adaptive bitrate", "Adaptiv Bitrate"),
("ID Server", "ID server"),
("Relay Server", "Relay Server"),
("API Server", "API Server"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Användarnamn saknas"),
("Password missed", "Lösenord saknas"),
("Wrong credentials", "Fel användarnamn eller lösenord"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Ändra Tagg"),
("Unremember Password", "Glöm lösenord"),
("Favorites", "Favoriter"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", ""),
("Enhancements", ""),
("Hardware Codec", ""),
("Adaptive Bitrate", ""),
("Adaptive bitrate", ""),
("ID Server", ""),
("Relay Server", ""),
("API Server", ""),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", ""),
("Password missed", ""),
("Wrong credentials", ""),
("The verification code is incorrect or has expired", ""),
("Edit Tag", ""),
("Unremember Password", ""),
("Favorites", ""),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "ออดิโออินพุท"),
("Enhancements", "การปรับปรุง"),
("Hardware Codec", "ฮาร์ดแวร์ codec"),
("Adaptive Bitrate", "บิทเรทผันแปร"),
("Adaptive bitrate", "บิทเรทผันแปร"),
("ID Server", "เซิร์ฟเวอร์ ID"),
("Relay Server", "เซิร์ฟเวอร์ Relay"),
("API Server", "เซิร์ฟเวอร์ API"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "ไม่พบข้อมูลผู้ใช้งาน"),
("Password missed", "ไม่พบรหัสผ่าน"),
("Wrong credentials", "ข้อมูลสำหรับเข้าสู่ระบบไม่ถูกต้อง"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "แก้ไขแท็ก"),
("Unremember Password", "ยกเลิกการจดจำรหัสผ่าน"),
("Favorites", "รายการโปรด"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Ses Girişi"),
("Enhancements", "Geliştirmeler"),
("Hardware Codec", "Donanımsal Codec"),
("Adaptive Bitrate", "Uyarlanabilir Bit Hızı"),
("Adaptive bitrate", "Uyarlanabilir Bit Hızı"),
("ID Server", "ID Sunucu"),
("Relay Server", "Relay Sunucu"),
("API Server", "API Sunucu"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Kullanıcı adı boş"),
("Password missed", "Şifre boş"),
("Wrong credentials", "Yanlış kimlik bilgileri"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Etiketi düzenle"),
("Unremember Password", "Şifreyi Unut"),
("Favorites", "Favoriler"),
@@ -407,110 +408,140 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("One-time password length", "Tek seferlik şifre uzunluğu"),
("Request access to your device", "Cihazınıza erişim talep edin"),
("Hide connection management window", "Bağlantı yönetimi penceresini gizle"),
("hide_cm_tip", ""),
("wayland_experiment_tip", ""),
("Right click to select tabs", ""),
("Skipped", ""),
("Add to Address Book", ""),
("Group", ""),
("Search", ""),
("Closed manually by web console", ""),
("Local keyboard type", ""),
("Select local keyboard type", ""),
("software_render_tip", ""),
("Always use software rendering", ""),
("config_input", ""),
("config_microphone", ""),
("request_elevation_tip", ""),
("Wait", ""),
("Elevation Error", ""),
("Ask the remote user for authentication", ""),
("Choose this if the remote account is administrator", ""),
("Transmit the username and password of administrator", ""),
("still_click_uac_tip", ""),
("Request Elevation", ""),
("wait_accept_uac_tip", ""),
("Elevate successfully", ""),
("uppercase", ""),
("lowercase", ""),
("digit", ""),
("special character", ""),
("length>=8", ""),
("Weak", ""),
("Medium", ""),
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
("Default Image Quality", ""),
("Default Codec", ""),
("Bitrate", ""),
("FPS", ""),
("Auto", ""),
("Other Default Options", ""),
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
("No transfers in progress", ""),
("Set one-time password length", ""),
("idd_driver_tip", ""),
("confirm_idd_driver_tip", ""),
("RDP Settings", ""),
("Sort by", ""),
("New Connection", ""),
("Restore", ""),
("Minimize", ""),
("Maximize", ""),
("Your Device", ""),
("empty_recent_tip", ""),
("empty_favorite_tip", ""),
("empty_lan_tip", ""),
("empty_address_book_tip", ""),
("eg: admin", ""),
("Empty Username", ""),
("Empty Password", ""),
("Me", ""),
("identical_file_tip", ""),
("show_monitors_tip", ""),
("View Mode", ""),
("login_linux_tip", ""),
("verify_rustdesk_password_tip", ""),
("remember_account_tip", ""),
("os_account_desk_tip", ""),
("OS Account", ""),
("another_user_login_title_tip", ""),
("another_user_login_text_tip", ""),
("xorg_not_found_title_tip", ""),
("xorg_not_found_text_tip", ""),
("no_desktop_title_tip", ""),
("no_desktop_text_tip", ""),
("No need to elevate", ""),
("System Sound", ""),
("Default", ""),
("New RDP", ""),
("Fingerprint", ""),
("Copy Fingerprint", ""),
("no fingerprints", ""),
("Select a peer", ""),
("Select peers", ""),
("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
("resolution_original_tip", ""),
("resolution_fit_local_tip", ""),
("resolution_custom_tip", ""),
("Collapse toolbar", ""),
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("hide_cm_tip", "Oturumları yalnızca parola ile kabul edebilir ve kalıcı parola kullanıyorsanız gizlemeye izin verin"),
("wayland_experiment_tip", "Wayland desteği deneysel aşamada olduğundan, gerektiğinde X11'i kullanmanız önerilir"),
("Right click to select tabs", "Sekmeleri seçmek için sağ tıklayın"),
("Skipped", "Atlandı"),
("Add to Address Book", "Adres Defterine Ekle"),
("Group", "Grup"),
("Search", "Ara"),
("Closed manually by web console", "Web konsoluyla manuel olarak kapatıldı"),
("Local keyboard type", "Yerel klavye türü"),
("Select local keyboard type", "Yerel klavye türünü seçin"),
("software_render_tip", "Linux altında Nvidia grafik kartı kullanıyorsanız ve uzak pencere bağlandıktan hemen sonra kapanıyorsa, açık kaynaklı Nouveau sürücüsüne geçmeyi ve yazılım renderleme seçeneğini seçmeyi deneyin. Yazılımı yeniden başlatmanız gerekebilir."),
("Always use software rendering", "Her zaman yazılım renderleme kullan"),
("config_input", "Uzaktaki masaüstünü klavye ile kontrol etmek için RustDesk'e \"Giriş İzleme\" izinleri vermelisiniz."),
("config_microphone", "Uzaktan konuşmak için RustDesk'e \"Ses Kaydı\" izinleri vermelisiniz."),
("request_elevation_tip", "Ayrıca, uzak tarafta biri varsa yükseltme isteğinde bulunabilirsiniz."),
("Wait", "Bekle"),
("Elevation Error", "Yükseltme Hatası"),
("Ask the remote user for authentication", "Uzaktaki kullanıcıdan kimlik doğrulamasını isteyin"),
("Choose this if the remote account is administrator", "Uzak hesap yönetici ise bunu seçin"),
("Transmit the username and password of administrator", "Yönetici kullanıcı adı ve parolasını iletim yapın"),
("still_click_uac_tip", "Uzaktaki kullanıcının çalışan RustDesk'in UAC penceresinde hala Tamam'ı tıklaması gerekmektedir."),
("Request Elevation", "Yükseltme İsteği"),
("wait_accept_uac_tip", "Lütfen uzaktaki kullanıcının UAC iletişim kutusunu kabul etmesini bekleyin."),
("Elevate successfully", "Başarıyla yükseltildi"),
("uppercase", "büyük harf"),
("lowercase", "küçük harf"),
("digit", "rakam"),
("special character", "özel karakter"),
("length>=8", "uzunluk>=8"),
("Weak", "Zayıf"),
("Medium", "Orta"),
("Strong", "Güçlü"),
("Switch Sides", "Tarafları Değiştir"),
("Please confirm if you want to share your desktop?", "Masaüstünüzü paylaşmak isteyip istemediğinizi onaylayın?"),
("Display", "Görüntüle"),
("Default View Style", "Varsayılan Görünüm Stili"),
("Default Scroll Style", "Varsayılan Kaydırma Stili"),
("Default Image Quality", "Varsayılan Görüntü Kalitesi"),
("Default Codec", "Varsayılan Kodlayıcı"),
("Bitrate", "Bit Hızı"),
("FPS", "FPS"),
("Auto", "Otomatik"),
("Other Default Options", "Diğer Varsayılan Seçenekler"),
("Voice call", "Sesli görüşme"),
("Text chat", "Metin sohbeti"),
("Stop voice call", "Sesli görüşmeyi durdur"),
("relay_hint_tip", "Doğrudan bağlanmak mümkün olmayabilir; röle aracılığıyla bağlanmayı deneyebilirsiniz. Ayrıca, ilk denemenizde bir röle kullanmak istiyorsanız, ID'nin sonuna \"/r\" ekleyebilir veya son oturum kartındaki \"Her Zaman Röle Üzerinden Bağlan\" seçeneğini seçebilirsiniz."),
("Reconnect", "Yeniden Bağlan"),
("Codec", "Kodlayıcı"),
("Resolution", "Çözünürlük"),
("No transfers in progress", "Devam eden aktarımlar yok"),
("Set one-time password length", "Bir seferlik parola uzunluğunu ayarla"),
("install_cert_tip", "RustDesk sertifikasını yükleyin"),
("confirm_install_cert_tip", "Bu, güvenilir olabilecek bir RustDesk test sertifikasıdır. Sertifika, gerekli olduğunda RustDesk sürücülerini güvenilir ve yüklemek üzere kullanacaktır."),
("RDP Settings", "RDP Ayarları"),
("Sort by", "Sırala"),
("New Connection", "Yeni Bağlantı"),
("Restore", "Geri Yükle"),
("Minimize", "Simge Durumuna Küçült"),
("Maximize", "Büyüt"),
("Your Device", "Cihazınız"),
("empty_recent_tip", "Üzgünüz, henüz son oturum yok!\nYeni bir plan yapma zamanı."),
("empty_favorite_tip", "Henüz favori cihazınız yok mu?\nBağlanacak ve favorilere eklemek için birini bulalım!"),
("empty_lan_tip", "Hayır, henüz hiçbir cihaz bulamadık gibi görünüyor."),
("empty_address_book_tip", "Üzgünüm, şu anda adres defterinizde kayıtlı cihaz yok gibi görünüyor."),
("eg: admin", "örn: admin"),
("Empty Username", "Boş Kullanıcı Adı"),
("Empty Password", "Boş Parola"),
("Me", "Ben"),
("identical_file_tip", "Bu dosya, cihazın dosyası ile aynıdır."),
("show_monitors_tip", "Monitörleri araç çubuğunda göster"),
("View Mode", "Görünüm Modu"),
("login_linux_tip", "X masaüstü oturumu başlatmak için uzaktaki Linux hesabına giriş yapmanız gerekiyor"),
("verify_rustdesk_password_tip", "RustDesk parolasını doğrulayın"),
("remember_account_tip", "Bu hesabı hatırla"),
("os_account_desk_tip", "Bu hesap, uzaktaki işletim sistemine giriş yapmak ve başsız masaüstü oturumunu etkinleştirmek için kullanılır."),
("OS Account", "İşletim Sistemi Hesabı"),
("another_user_login_title_tip", "Başka bir kullanıcı zaten oturum açtı"),
("another_user_login_text_tip", "Bağlantıyı Kapat"),
("xorg_not_found_title_tip", "Xorg bulunamadı"),
("xorg_not_found_text_tip", "Lütfen Xorg'u yükleyin"),
("no_desktop_title_tip", "Masaüstü mevcut değil"),
("no_desktop_text_tip", "Lütfen GNOME masaüstünü yükleyin"),
("No need to elevate", "Yükseltmeye gerek yok"),
("System Sound", "Sistem Ses"),
("Default", "Varsayılan"),
("New RDP", "Yeni RDP"),
("Fingerprint", "Parmak İzi"),
("Copy Fingerprint", "Parmak İzini Kopyala"),
("no fingerprints", "parmak izi yok"),
("Select a peer", "Bir cihaz seçin"),
("Select peers", "Cihazları seçin"),
("Plugins", "Eklentiler"),
("Uninstall", "Kaldır"),
("Update", "Güncelle"),
("Enable", "Etkinleştir"),
("Disable", "Devre Dışı Bırak"),
("Options", "Seçenekler"),
("resolution_original_tip", "Orijinal çözünürlük"),
("resolution_fit_local_tip", "Yerel çözünürlüğe sığdır"),
("resolution_custom_tip", "Özel çözünürlük"),
("Collapse toolbar", "Araç çubuğunu daralt"),
("Accept and Elevate", "Kabul et ve yükselt"),
("accept_and_elevate_btn_tooltip", "Bağlantıyı kabul et ve UAC izinlerini yükselt."),
("clipboard_wait_response_timeout_tip", "Kopyalama yanıtı için zaman aşımına uğradı."),
("Incoming connection", "Gelen bağlantı"),
("Outgoing connection", "Giden bağlantı"),
("Exit", "Çıkış"),
("Open", ""),
("logout_tip", "Çıkış yapmak istediğinizden emin misiniz?"),
("Service", "Hizmet"),
("Start", "Başlat"),
("Stop", "Durdur"),
("exceed_max_devices", "Yönetilen cihazların maksimum sayısına ulaştınız."),
("Sync with recent sessions", "Son oturumlarla senkronize et"),
("Sort tags", "Etiketleri sırala"),
("Open connection in new tab", "Bağlantıyı yeni sekmede aç"),
("Move tab to new window", "Sekmeyi yeni pencereye taşı"),
("Can not be empty", "Boş olamaz"),
("Already exists", "Zaten var"),
("Change Password", "Parolayı Değiştir"),
("Refresh Password", "Parolayı Yenile"),
("ID", "Kimlik"),
("Grid View", "Izgara Görünümü"),
("List View", "Liste Görünümü"),
("Select", "Seç"),
("Toggle Tags", "Etiketleri Değiştir"),
("pull_ab_failed_tip", "Adres defterini yenileyemedi"),
("push_ab_failed_tip", "Adres defterini sunucuya senkronize edemedi"),
("synced_peer_readded_tip", "Son oturumlar listesinde bulunan cihazlar adres defterine geri senkronize edilecektir."),
("Change Color", "Rengi Değiştir"),
("Primary Color", "Birincil Renk"),
("HSV Color", "HSV Rengi"),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -28,7 +28,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable File Transfer", "啟用檔案傳輸"),
("Enable TCP Tunneling", "啟用 TCP 通道"),
("IP Whitelisting", "IP 白名單"),
("ID/Relay Server", "ID / 轉送伺服器"),
("ID/Relay Server", "ID / 中繼伺服器"),
("Import Server Config", "匯入伺服器設定"),
("Export Server Config", "匯出伺服器設定"),
("Import server configuration successfully", "匯入伺服器設定成功"),
@@ -40,27 +40,27 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Your new ID", "您的新 ID"),
("length %min% to %max%", "長度在 %min% 與 %max% 之間"),
("starts with a letter", "以字母開頭"),
("allowed characters", "使用允許的字元"),
("allowed characters", "允許的字元"),
("id_change_tip", "僅能使用以下字元a-z、A-Z、0-9、_ (底線)。首字元必須為 a-z 或 A-Z。長度介於 6 到 16 之間。"),
("Website", "網站"),
("About", "關於"),
("Slogan_tip", ""),
("Slogan_tip", "在這個混沌的世界中用心製作!"),
("Privacy Statement", "隱私權聲明"),
("Mute", "靜音"),
("Build Date", "建日期"),
("Build Date", "日期"),
("Version", "版本"),
("Home", "首頁"),
("Audio Input", "音訊輸入"),
("Enhancements", "增強功能"),
("Hardware Codec", "硬體編解碼器"),
("Adaptive Bitrate", "自適應位元速率"),
("Adaptive bitrate", "自適應位元速率"),
("ID Server", "ID 伺服器"),
("Relay Server", "轉送伺服器"),
("Relay Server", "中繼伺服器"),
("API Server", "API 伺服器"),
("invalid_http", "開頭必須為 http:// 或 https://"),
("Invalid IP", "IP 無效"),
("Invalid format", "格式無效"),
("server_not_support", "伺服器暫不支持"),
("server_not_support", "伺服器尚未支援"),
("Not available", "無法使用"),
("Too frequent", "修改過於頻繁,請稍後再試。"),
("Cancel", "取消"),
@@ -90,10 +90,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Receive", "接收"),
("Send", "傳送"),
("Refresh File", "重新整理檔案"),
("Local", ""),
("Local", ""),
("Remote", "遠端"),
("Remote Computer", "遠端電腦"),
("Local Computer", "電腦"),
("Local Computer", "電腦"),
("Confirm Delete", "確認刪除"),
("Delete", "刪除"),
("Properties", "屬性"),
@@ -139,39 +139,39 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Remote desktop is offline", "遠端桌面已離線"),
("Key mismatch", "金鑰不符"),
("Timeout", "逾時"),
("Failed to connect to relay server", "無法連線到轉送伺服器"),
("Failed to connect to relay server", "無法連線到中繼伺服器"),
("Failed to connect via rendezvous server", "無法透過 rendezvous 伺服器連線"),
("Failed to connect via relay server", "無法透過轉送伺服器連線"),
("Failed to connect via relay server", "無法透過中繼伺服器連線"),
("Failed to make direct connection to remote desktop", "無法直接連線到遠端桌面"),
("Set Password", "設定密碼"),
("OS Password", "作業系統密碼"),
("install_tip", "UAC 會導致 RustDesk 在某些情況下無法正常以遠端電腦執行。若要避開 UAC請點下方按鈕將 RustDesk 安裝到系統中。"),
("Click to upgrade", "以升級"),
("Click to download", "以下載"),
("Click to update", "以更新"),
("install_tip", "UAC 會導致 RustDesk 在某些情況下無法正常作為遠端端點運作。若要避開 UAC請點下方按鈕將 RustDesk 安裝到系統中。"),
("Click to upgrade", "以升級"),
("Click to download", "以下載"),
("Click to update", "以更新"),
("Configure", "設定"),
("config_acc", "您需要授予 RustDesk「協助工具」權限才能存取遠端電腦"),
("config_screen", "您需要授予 RustDesk「畫面錄製」權限才能存取遠端電腦"),
("config_acc", "為了遠端控制您的桌面,您需要授予 RustDesk「協助工具」權限。"),
("config_screen", "為了遠端存取您的桌面,您需要授予 RustDesk「螢幕錄製」權限。"),
("Installing ...", "正在安裝 ..."),
("Install", "安裝"),
("Installation", "安裝"),
("Installation Path", "安裝路徑"),
("Create start menu shortcuts", "新增開始功能表捷徑"),
("Create desktop icon", "新增桌面捷徑"),
("agreement_tip", "開始安裝即表示接受許可協議"),
("agreement_tip", "開始安裝即表示接受授權條款。"),
("Accept and Install", "接受並安裝"),
("End-user license agreement", "使用者授權合約"),
("End-user license agreement", "終端使用者授權合約"),
("Generating ...", "正在產生 ..."),
("Your installation is lower version.", "您安裝的版本過舊。"),
("not_close_tcp_tip", "使用通道時請不要關閉此視窗"),
("not_close_tcp_tip", "使用通道時請不要關閉此視窗"),
("Listening ...", "正在等待通道連線 ..."),
("Remote Host", "遠端主機"),
("Remote Port", "遠端連線端口"),
("Remote Port", "遠端連接埠"),
("Action", "操作"),
("Add", "新增"),
("Local Port", "本機連線端口"),
("Local Port", "本機連接埠"),
("Local Address", "本機地址"),
("Change Local Port", "修改本機連線端口"),
("Change Local Port", "修改本機連接埠"),
("setup_server_tip", "若您需要更快的連線速度,可以選擇自行建立伺服器"),
("Too short, at least 6 characters.", "過短,至少需要 6 個字元。"),
("The confirmation is not identical.", "兩次輸入不相符"),
@@ -185,15 +185,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Allow file copy and paste", "允許檔案複製和貼上"),
("Connected", "已連線"),
("Direct and encrypted connection", "加密直接連線"),
("Relayed and encrypted connection", "加密轉送連線"),
("Direct and unencrypted connection", "未加密直接連線"),
("Relayed and unencrypted connection", "未加密轉送連線"),
("Relayed and encrypted connection", "加密中繼連線"),
("Direct and unencrypted connection", "直接且未加密連線"),
("Relayed and unencrypted connection", "中繼且未加密連線"),
("Enter Remote ID", "輸入遠端 ID"),
("Enter your password", "輸入您的密碼"),
("Logging in...", "正在登入 ..."),
("Enable RDP session sharing", "啟用 RDP 工作階段共享"),
("Auto Login", "自動登入 (鎖定將在設定關閉後套用)"),
("Enable Direct IP Access", "允許 IP 直接存取"),
("Auto Login", "自動登入 (只在您設定「工作階段結束後鎖定」時有效)"),
("Enable Direct IP Access", "啟用 IP 直接存取"),
("Rename", "重新命名"),
("Space", "空白"),
("Create Desktop Shortcut", "新增桌面捷徑"),
@@ -204,49 +204,50 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Warning", "警告"),
("Login screen using Wayland is not supported", "不支援使用 Wayland 的登入畫面"),
("Reboot required", "需要重新啟動"),
("Unsupported display server", "不支援顯示伺服器"),
("x11 expected", "預期 x11"),
("Port", "端口"),
("Unsupported display server", "不支援顯示伺服器"),
("x11 expected", "預期 x11"),
("Port", "連接埠"),
("Settings", "設定"),
("Username", "使用者名稱"),
("Invalid port", "線端口無效"),
("Closed manually by the peer", "遠端使用者關閉了工作階段"),
("Invalid port", "接埠無效"),
("Closed manually by the peer", "對方關閉了工作階段"),
("Enable remote configuration modification", "允許遠端使用者更改設定"),
("Run without install", "跳過安裝直接執行"),
("Connect via relay", "中繼連線"),
("Always connect via relay", "一律透過轉送連線"),
("whitelist_tip", "只有白名單的 IP 可以存取"),
("Always connect via relay", "一律透過中繼連線"),
("whitelist_tip", "只有白名單的 IP 可以存取"),
("Login", "登入"),
("Verify", "驗證"),
("Remember me", "記住我"),
("Trust this device", "信任此裝置"),
("Verification code", "驗證碼"),
("verification_tip", "已向註冊電子信箱發送了登入驗證碼,請輸入驗證碼以繼續登入"),
("verification_tip", "驗證碼已發送到註冊電子郵件地址,請輸入驗證碼以繼續登入"),
("Logout", "登出"),
("Tags", "標籤"),
("Search ID", "搜尋 ID"),
("whitelist_sep", "使用逗號、分號、空,或是換行來分隔"),
("whitelist_sep", "使用逗號、分號、空,或是換行來分隔"),
("Add ID", "新增 ID"),
("Add Tag", "新增標籤"),
("Unselect all tags", "取消選取所有標籤"),
("Network error", "網路錯誤"),
("Username missed", "缺少使用者名稱"),
("Password missed", "缺少密碼"),
("Wrong credentials", "提供的登入資訊"),
("Wrong credentials", "登入資訊"),
("The verification code is incorrect or has expired", "驗證碼錯誤或已過期"),
("Edit Tag", "編輯標籤"),
("Unremember Password", "密碼"),
("Unremember Password", "密碼"),
("Favorites", "我的最愛"),
("Add to Favorites", "新增到我的最愛"),
("Remove from Favorites", "從我的最愛中"),
("Add to Favorites", "加入我的最愛"),
("Remove from Favorites", "從我的最愛中"),
("Empty", "空空如也"),
("Invalid folder name", "資料夾名稱無效"),
("Socks5 Proxy", "Socks5 代理"),
("Socks5 Proxy", "Socks5 代理伺服器"),
("Hostname", "主機名稱"),
("Discovered", "已探索"),
("install_daemon_tip", "為了能夠開機時自動啟動,請先安裝系統服務。"),
("install_daemon_tip", "若要在開機時啟動,您需要安裝系統服務。"),
("Remote ID", "遠端 ID"),
("Paste", "貼上"),
("Paste here?", "貼上到這裡"),
("Paste here?", "在此貼上?"),
("Are you sure to close the connection?", "您確定要關閉連線嗎?"),
("Download new version", "下載新版本"),
("Touch mode", "觸控模式"),
@@ -257,8 +258,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Two-Finger Tap", "雙指輕觸"),
("Right Mouse", "滑鼠右鍵"),
("One-Finger Move", "單指移動"),
("Double Tap & Move", "雙擊並移動"),
("Mouse Drag", "滑鼠選中拖動"),
("Double Tap & Move", "點兩下並移動"),
("Mouse Drag", "滑鼠拖曳"),
("Three-Finger vertically", "三指垂直滑動"),
("Mouse Wheel", "滑鼠滾輪"),
("Two-Finger Move", "雙指移動"),
@@ -269,8 +270,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("No permission of file transfer", "沒有檔案傳輸權限"),
("Note", "備註"),
("Connection", "連線"),
("Share Screen", "共享螢幕畫面"),
("Chat", "聊天訊息"),
("Share Screen", "螢幕分享"),
("Chat", "聊天"),
("Total", "總計"),
("items", "個項目"),
("Selected", "已選擇"),
@@ -281,39 +282,39 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Connection", "畫面連線"),
("Do you accept?", "是否接受?"),
("Open System Setting", "開啟系統設定"),
("How to get Android input permission?", "如何取 Android 的輸入權限?"),
("android_input_permission_tip1", "取得輸入權限後可以讓遠端裝置透過滑鼠控制此 Android 裝置"),
("android_input_permission_tip2", "在接下來的系統設定頁面,找到並進入「已安裝的服務」頁面,並將「RustDesk Input」服務開啟"),
("android_new_connection_tip", "收到新的連線控制請求,對方想要控制您目前的裝置"),
("android_service_will_start_tip", "開啟畫面錄製權限將自動啟服務,允許其他裝置向裝置請求建立連線。"),
("How to get Android input permission?", "如何取 Android 的輸入權限?"),
("android_input_permission_tip1", "為了讓遠端裝置能夠透過滑鼠或觸控控制您的 Android 裝置,您需要允許 RustDesk 使用「輔助功能」服務。"),
("android_input_permission_tip2", "前往下一個系統設定頁面,找到並進入「已安裝的服務」,開啟「RustDesk Input」服務"),
("android_new_connection_tip", "收到新的控制請求,對方想要控制您目前的裝置"),
("android_service_will_start_tip", "開啟畫面錄製將自動啟服務,允許其他裝置向您的裝置請求連線。"),
("android_stop_service_tip", "關閉服務將自動關閉所有已建立的連線。"),
("android_version_audio_tip", "目前的 Android 版本不支援音訊錄製,請升級 Android 10 或以上版本。"),
("android_start_service_tip", "「啟動服務」或啟用「螢幕錄製」權限以啟動螢幕享服務。"),
("android_permission_may_not_change_tip", "對於已經建立連線權限可能不會立即發生改變,除非重新建立連線。"),
("android_version_audio_tip", "目前的 Android 版本不支援音訊錄製,請升級 Android 10 或更新的版本。"),
("android_start_service_tip", "「啟動服務」或啟用「畫面錄製」權限以啟動螢幕享服務。"),
("android_permission_may_not_change_tip", "建立連線權限可能不會立即改變,除非重新連線。"),
("Account", "帳號"),
("Overwrite", "取代"),
("This file exists, skip or overwrite this file?", "此檔案/資料夾已存在,要略過或是取代此檔案嗎?"),
("Quit", "退出"),
("doc_mac_permission", "https://rustdesk.com/docs/zh-tw/manual/mac/#啟用權限"),
("Help", "幫助"),
("Help", "說明"),
("Failed", "失敗"),
("Succeeded", "成功"),
("Someone turns on privacy mode, exit", "其他使用者開啟隱私模式,退出"),
("Someone turns on privacy mode, exit", "有人開啟隱私模式,退出"),
("Unsupported", "不支援"),
("Peer denied", "被控端拒絕"),
("Please install plugins", "請安裝插件"),
("Peer exit", "被控端退出"),
("Failed to turn off", "退出失敗"),
("Turned off", "退出"),
("Peer denied", "對方拒絕"),
("Please install plugins", "請安裝外掛程式"),
("Peer exit", "對方退出"),
("Failed to turn off", "關閉失敗"),
("Turned off", "已關閉"),
("In privacy mode", "開啟隱私模式"),
("Out privacy mode", "退出隱私模式"),
("Language", "語言"),
("Keep RustDesk background service", "保持 RustDesk 後台服務"),
("Ignore Battery Optimizations", "忽略電池最佳化"),
("android_open_battery_optimizations_tip", "需關閉此功能,請在接下來的 RustDesk 應用設定頁面,找到並進入 [電源] 頁面,取消勾選 [不受限制]"),
("Start on Boot", "開機自動啟動"),
("Start the screen sharing service on boot, requires special permissions", "開機自動啟動螢幕享服務,此功能需要一些特殊權限。"),
("Connection not allowed", "對方不允許連線"),
("android_open_battery_optimizations_tip", "果您想要停用此功能,請前往下一個 RustDesk 應用程式設定頁面,找到並進入「電池」,取消勾選不受限制"),
("Start on Boot", "開機啟動"),
("Start the screen sharing service on boot, requires special permissions", "開機啟動螢幕享服務,需要特殊權限。"),
("Connection not allowed", "不允許連線"),
("Legacy mode", "傳統模式"),
("Map mode", "11 傳輸模式"),
("Translate mode", "翻譯模式"),
@@ -322,10 +323,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Set permanent password", "設定固定密碼"),
("Enable Remote Restart", "啟用遠端重新啟動"),
("Allow remote restart", "允許遠端重新啟動"),
("Restart Remote Device", "重新啟動遠端電腦"),
("Are you sure you want to restart", "確定要重新啟動"),
("Restart Remote Device", "重新啟動遠端裝置"),
("Are you sure you want to restart", "確定要重新啟動嗎?"),
("Restarting Remote Device", "正在重新啟動遠端裝置"),
("remote_restarting_tip", "遠端裝置正在重新啟動,請關閉當前提示框,並在一段時間後使用永久密碼重新連線"),
("remote_restarting_tip", "遠端裝置正在重新啟動,請關閉此對話框,並在一段時間後使用永久密碼重新連線"),
("Copied", "已複製"),
("Exit Fullscreen", "退出全螢幕"),
("Fullscreen", "全螢幕"),
@@ -336,15 +337,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ratio", "比例"),
("Image Quality", "畫質"),
("Scroll Style", "滾動樣式"),
("Show Toolbar", ""),
("Hide Toolbar", ""),
("Show Toolbar", "顯示工具列"),
("Hide Toolbar", "隱藏工具列"),
("Direct Connection", "直接連線"),
("Relay Connection", "中繼連線"),
("Secure Connection", "安全連線"),
("Insecure Connection", "非安全連線"),
("Scale original", "原始尺寸"),
("Scale adaptive", "適應視窗"),
("General", "通用"),
("General", "一般"),
("Security", "安全"),
("Theme", "主題"),
("Dark Theme", "黑暗主題"),
@@ -352,22 +353,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Dark", "黑暗"),
("Light", "明亮"),
("Follow System", "跟隨系統"),
("Enable hardware codec", "使用硬體編解碼器"),
("Enable hardware codec", "用硬體編解碼器"),
("Unlock Security Settings", "解鎖安全設定"),
("Enable Audio", "允許傳輸音訊"),
("Enable Audio", "啟用音訊"),
("Unlock Network Settings", "解鎖網路設定"),
("Server", "伺服器"),
("Direct IP Access", "IP 直接連線"),
("Proxy", "代理"),
("Apply", ""),
("Proxy", "代理伺服器"),
("Apply", ""),
("Disconnect all devices?", "中斷所有遠端連線?"),
("Clear", "清空"),
("Audio Input Device", "音訊輸入裝置"),
("Use IP Whitelisting", "只允許白名單上的 IP 進行連線"),
("Network", "網路"),
("Enable RDP", "允許 RDP 訪問"),
("Pin Toolbar", ""),
("Unpin Toolbar", ""),
("Enable RDP", "允許 RDP 存取"),
("Pin Toolbar", "釘選工具列"),
("Unpin Toolbar", "取消釘選工具列"),
("Recording", "錄製"),
("Directory", "路徑"),
("Automatically record incoming sessions", "自動錄製連入的工作階段"),
@@ -381,17 +382,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Write a message", "輸入聊天訊息"),
("Prompt", "提示"),
("Please wait for confirmation of UAC...", "請等待對方確認 UAC ..."),
("elevated_foreground_window_tip", "目前的遠端桌面視窗需要更高的權限才能繼續操作,暫時無法使用滑鼠、鍵盤,可以請求對方最小化目前視窗,或者在連線管理視窗點提升權限。為了避免這個問題,建議在遠端裝置上安裝本軟體。"),
("elevated_foreground_window_tip", "目前的遠端桌面視窗需要更高的權限才能繼續操作,暫時無法使用滑鼠、鍵盤,可以請求對方最小化目前視窗,或者在連線管理視窗點提升權限。為了避免這個問題,建議在遠端裝置上安裝本軟體。"),
("Disconnected", "斷開連線"),
("Other", "其他"),
("Confirm before closing multiple tabs", "關閉多個分頁前詢問我"),
("Keyboard Settings", "鍵盤設定"),
("Full Access", "完全訪問"),
("Full Access", "完全存取"),
("Screen Share", "僅分享螢幕畫面"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland 需要 Ubuntu 21.04 或更版本。"),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland 需要更高版本的 Linux 發行版。請嘗試使用 X11 桌面或更改您的作業系統。"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland 需要 Ubuntu 21.04 或更新的版本。"),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland 需要更新的版的 Linux 發行版。請嘗試使用 X11 桌面或更改您的作業系統。"),
("JumpLink", "查看"),
("Please Select the screen to be shared(Operate on the peer side).", "請選擇要分享的螢幕畫面(在對操作)。"),
("Please Select the screen to be shared(Operate on the peer side).", "請選擇要分享的螢幕畫面(在對方的裝置上操作)。"),
("Show RustDesk", "顯示 RustDesk"),
("This PC", "此電腦"),
("or", ""),
@@ -399,25 +400,25 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Elevate", "提升權限"),
("Zoom cursor", "縮放游標"),
("Accept sessions via password", "只允許透過輸入密碼進行連線"),
("Accept sessions via click", "只允許透過點接受進行連線"),
("Accept sessions via both", "允許輸入密碼或點接受進行連線"),
("Accept sessions via click", "只允許透過點接受進行連線"),
("Accept sessions via both", "允許輸入密碼或點接受進行連線"),
("Please wait for the remote side to accept your session request...", "請等待對方接受您的連線請求 ..."),
("One-time Password", "一次性密碼"),
("Use one-time password", "使用一次性密碼"),
("One-time password length", "一次性密碼長度"),
("Request access to your device", "請求訪問您的裝置"),
("Request access to your device", "請求存取您的裝置"),
("Hide connection management window", "隱藏連線管理視窗"),
("hide_cm_tip", "在只允許密碼連線並且只用固定密碼的情況下才允許隱藏"),
("wayland_experiment_tip", "Wayland 支援處於實驗階段,如果您需要使用無人值守訪問,請使用 X11。"),
("wayland_experiment_tip", "Wayland 支援處於實驗階段,如果您需要使用無人值守存取,請使用 X11。"),
("Right click to select tabs", "右鍵選擇分頁"),
("Skipped", ""),
("Skipped", ""),
("Add to Address Book", "新增到通訊錄"),
("Group", "群組"),
("Search", "搜尋"),
("Closed manually by web console", "被 Web 控制台手動關閉"),
("Local keyboard type", "鍵盤類型"),
("Select local keyboard type", "請選擇本鍵盤類型"),
("software_render_tip", "如果您使用 NVIDIA 顯示卡,並且遠端視窗在建立連線後會立刻關閉,那麼請安裝 nouveau 顯示卡驅動程式並且選擇使用軟體渲染可能會有幫助。重新啟動軟體後生效。"),
("Local keyboard type", "鍵盤類型"),
("Select local keyboard type", "請選擇本鍵盤類型"),
("software_render_tip", "如果您使用 Nvidia 顯示卡,並且遠端視窗在建立連線後會立刻關閉,那麼請安裝 nouveau 顯示卡驅動程式並且選擇使用軟體渲染可能會有幫助。重新啟動軟體後生效。"),
("Always use software rendering", "使用軟體渲染"),
("config_input", "為了能夠透過鍵盤控制遠端桌面,請給予 RustDesk \"輸入監控\" 權限。"),
("config_microphone", "為了支援透過麥克風進行音訊傳輸,請給予 RustDesk \"錄音\"權限。"),
@@ -427,7 +428,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ask the remote user for authentication", "請求遠端使用者進行身分驗證"),
("Choose this if the remote account is administrator", "當遠端使用者帳戶是管理員時,請選擇此選項"),
("Transmit the username and password of administrator", "發送管理員的使用者名稱和密碼"),
("still_click_uac_tip", "依然需要遠端使用者在 UAC 視窗點確認。"),
("still_click_uac_tip", "依然需要遠端使用者在 UAC 視窗點確認。"),
("Request Elevation", "請求權限提升"),
("wait_accept_uac_tip", "請等待遠端使用者確認 UAC 對話框。"),
("Elevate successfully", "權限提升成功"),
@@ -453,14 +454,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", "語音通話"),
("Text chat", "文字聊天"),
("Stop voice call", "停止語音通話"),
("relay_hint_tip", "可能無法直接連線,可以嘗試中繼連線。\n另外,如果想要直接使用中繼連線,可以在 ID 後面新增/r或者在卡片選項裡選擇強制走中繼連線。"),
("relay_hint_tip", "可能無法直接連線,可以嘗試中繼連線。\n另外,如果想要直接使用中繼連線,可以在 ID 後面新增/r如果近期工作階段裏存在該分頁,也可以在分頁選項裡選擇強制走中繼連線。"),
("Reconnect", "重新連線"),
("Codec", "編解碼器"),
("Resolution", "解析度"),
("No transfers in progress", "沒有正在進行的傳輸"),
("Set one-time password length", "設定一次性密碼長度"),
("idd_driver_tip", "安裝虛擬顯示器驅動程式,以便在沒有連接顯示器的情況下啟動虛擬顯示器進行控制。"),
("confirm_idd_driver_tip", "安裝虛擬顯示器驅動程式的選項已勾選。請注意,測試證書將被安裝以信任虛擬顯示器驅動。測試證書僅會用於信任 RustDesk 的驅動程式。"),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", "RDP 設定"),
("Sort by", "排序方式"),
("New Connection", "新連線"),
@@ -468,15 +469,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Minimize", "最小化"),
("Maximize", "最大化"),
("Your Device", "您的裝置"),
("empty_recent_tip", "空空如也"),
("empty_recent_tip", "哎呀,沒有近期的工作階段!\n是時候安排點新工作了。"),
("empty_favorite_tip", "空空如也"),
("empty_lan_tip", "空空如也"),
("empty_address_book_tip", "空空如也"),
("empty_lan_tip", "喔不,看來我們目前找不到任何夥伴。"),
("empty_address_book_tip", "老天,看來您的通訊錄中沒有任何夥伴。"),
("eg: admin", "例如admin"),
("Empty Username", "空使用者帳號"),
("Empty Password", "空密碼"),
("Me", ""),
("identical_file_tip", "此檔案與對方的檔案一致"),
("identical_file_tip", "此檔案與對方的檔案一致"),
("show_monitors_tip", "在工具列中顯示顯示器"),
("View Mode", "瀏覽模式"),
("login_linux_tip", "需要登入到遠端 Linux 使用者帳戶才能啟用 X 介面"),
@@ -486,7 +487,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("OS Account", "作業系統使用者帳戶"),
("another_user_login_title_tip", "另一個使用者已經登入"),
("another_user_login_text_tip", "斷開連線"),
("xorg_not_found_title_tip", "找到 Xorg"),
("xorg_not_found_title_tip", "到 Xorg"),
("xorg_not_found_text_tip", "請安裝 Xorg"),
("no_desktop_title_tip", "沒有可用的桌面"),
("no_desktop_text_tip", "請安裝 GNOME 桌面"),
@@ -497,20 +498,50 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Fingerprint", "指紋"),
("Copy Fingerprint", "複製指紋"),
("no fingerprints", "沒有指紋"),
("Select a peer", ""),
("Select peers", ""),
("Plugins", ""),
("Uninstall", ""),
("Update", ""),
("Enable", ""),
("Disable", ""),
("Options", ""),
("resolution_original_tip", ""),
("resolution_fit_local_tip", ""),
("resolution_custom_tip", ""),
("Collapse toolbar", ""),
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Select a peer", "選擇夥伴"),
("Select peers", "選擇夥伴"),
("Plugins", "外掛程式"),
("Uninstall", "解除安裝"),
("Update", "更新"),
("Enable", "啟用"),
("Disable", "停用"),
("Options", "選項"),
("resolution_original_tip", "原始解析度"),
("resolution_fit_local_tip", "調整成本機解析度"),
("resolution_custom_tip", "自動解析度"),
("Collapse toolbar", "收回工具列"),
("Accept and Elevate", "接受並提升"),
("accept_and_elevate_btn_tooltip", "接受連線並提升 UAC 權限。"),
("clipboard_wait_response_timeout_tip", "等待複製回應逾時。"),
("Incoming connection", "接收的連線"),
("Outgoing connection", "發起的連線"),
("Exit", "退出"),
("Open", "開啟"),
("logout_tip", "確定要登出嗎?"),
("Service", "服務"),
("Start", "啟動"),
("Stop", "停止"),
("exceed_max_devices", "超過最大裝置數量"),
("Sync with recent sessions", "與近期工作階段同步"),
("Sort tags", "排序標籤"),
("Open connection in new tab", "在新分頁開啟連線"),
("Move tab to new window", "移動標籤到新視窗"),
("Can not be empty", "不能為空"),
("Already exists", "已經存在"),
("Change Password", "更改密碼"),
("Refresh Password", "重新整理密碼"),
("ID", "ID"),
("Grid View", "網格檢視"),
("List View", "清單檢視"),
("Select", "選擇"),
("Toggle Tags", "切換標籤"),
("pull_ab_failed_tip", "通訊錄更新失敗"),
("push_ab_failed_tip", "成功同步通訊錄至伺服器"),
("synced_peer_readded_tip", "最近會話中存在的設備將會被重新同步到通訊錄。"),
("Change Color", "更改顏色"),
("Primary Color", "基本色"),
("HSV Color", "HSV 色"),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,11 +53,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Аудіовхід"),
("Enhancements", "Покращення"),
("Hardware Codec", "Апаратний кодек"),
("Adaptive Bitrate", "Адаптивна швидкість потоку"),
("Adaptive bitrate", "Адаптивна швидкість потоку"),
("ID Server", "ID-сервер"),
("Relay Server", "Сервер ретрансляції"),
("API Server", "API-сервер"),
("invalid_http", "Повиннна починатися з http:// або https://"),
("invalid_http", "Повинна починатися з http:// або https://"),
("Invalid IP", "Неправильна IP-адреса"),
("Invalid format", "Неправильний формат"),
("server_not_support", "Наразі не підтримується сервером"),
@@ -121,7 +121,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Shrink", "Зменшити"),
("Stretch", "Розтягнути"),
("Scrollbar", "Смуга прокрутки"),
("ScrollAuto", "Прокрутка Авто"),
("ScrollAuto", "Автоматична прокрутка"),
("Good image quality", "Хороша якість зображення"),
("Balanced", "Збалансована"),
("Optimize reaction time", "Оптимізувати час реакції"),
@@ -178,7 +178,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Permissions", "Дозволи"),
("Accept", "Прийняти"),
("Dismiss", "Відхилити"),
("Disconnect", "Відключити"),
("Disconnect", "Відʼєднати"),
("Allow using keyboard and mouse", "Дозволити використання клавіатури та миші"),
("Allow using clipboard", "Дозволити використання буфера обміну"),
("Allow hearing sound", "Дозволити передачу звуку"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Імʼя користувача відсутнє"),
("Password missed", "Забули пароль"),
("Wrong credentials", "Неправильні дані"),
("The verification code is incorrect or has expired", "Код підтвердження некоректний або протермінований"),
("Edit Tag", "Редагувати тег"),
("Unremember Password", "Не зберігати пароль"),
("Favorites", "Вибране"),
@@ -288,7 +289,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_service_will_start_tip", "Увімкнення захоплення екрана автоматично запускає службу, дозволяючи іншим пристроям запитувати підключення до вашого пристрою."),
("android_stop_service_tip", "Зупинка служби автоматично завершить всі встановлені зʼєднання."),
("android_version_audio_tip", "Поточна версія Android не підтримує захоплення звуку, оновіть її до Android 10 або вище."),
("android_start_service_tip", "Натисніть [Запустити службу] або увімкніть дозвіл на [Захоплення екрана], шоб запустити службу спільного доступу до екрана."),
("android_start_service_tip", "Натисніть [Запустити службу] або увімкніть дозвіл на [Захоплення екрана], щоб запустити службу спільного доступу до екрана."),
("android_permission_may_not_change_tip", "Дозволи для встановлених зʼєднань можуть не змінитися миттєво аж до перепідключення."),
("Account", "Акаунт"),
("Overwrite", "Перезаписати"),
@@ -302,7 +303,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Unsupported", "Не підтримується"),
("Peer denied", "Відхилено віддаленим пристроєм"),
("Please install plugins", "Будь ласка, встановіть плагіни"),
("Peer exit", "Відключення віддаленого пристрою"),
("Peer exit", "Вийти з віддаленого пристрою"),
("Failed to turn off", "Не вдалося вимкнути"),
("Turned off", "Вимкнений"),
("In privacy mode", "У режимі конфіденційності"),
@@ -325,19 +326,19 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Restart Remote Device", "Перезапустити віддалений пристрій"),
("Are you sure you want to restart", "Ви впевнені, що хочете виконати перезапуск?"),
("Restarting Remote Device", "Перезавантаження віддаленого пристрою"),
("remote_restarting_tip", "Віддалений пристрій перезапускається. Будь ласка, закрийте це повідомлення та через деякий час перепідключіться, використовуючи постійний пароль."),
("remote_restarting_tip", "Віддалений пристрій перезапускається. Будь ласка, закрийте це повідомлення та через деякий час перепідʼєднайтесь, використовуючи постійний пароль."),
("Copied", "Скопійовано"),
("Exit Fullscreen", "Вийти з повноекранного режиму"),
("Fullscreen", "Повноекранний"),
("Mobile Actions", "Мобільні дії"),
("Select Monitor", "Виберіть монітор"),
("Control Actions", "Дії для керування"),
("Display Settings", "Налаштування відображення"),
("Display Settings", "Налаштування дисплею"),
("Ratio", "Співвідношення"),
("Image Quality", "Якість зображення"),
("Scroll Style", "Стиль прокрутки"),
("Show Toolbar", ""),
("Hide Toolbar", ""),
("Show Toolbar", "Показати панель інструментів"),
("Hide Toolbar", "Приховати панель інструментів"),
("Direct Connection", "Пряме підключення"),
("Relay Connection", "Релейне підключення"),
("Secure Connection", "Безпечне підключення"),
@@ -360,14 +361,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Direct IP Access", "Прямий IP доступ"),
("Proxy", "Проксі"),
("Apply", "Застосувати"),
("Disconnect all devices?", "Відключити всі прилади?"),
("Disconnect all devices?", "Відʼєднати всі прилади?"),
("Clear", "Очистити"),
("Audio Input Device", "Пристрій введення звуку"),
("Use IP Whitelisting", "Використовувати білий список IP"),
("Network", "Мережа"),
("Enable RDP", "Увімкнути RDP"),
("Pin Toolbar", ""),
("Unpin Toolbar", ""),
("Pin Toolbar", "Закріпити панель інструментів"),
("Unpin Toolbar", "Відкріпити панель інструментів"),
("Recording", "Запис"),
("Directory", "Директорія"),
("Automatically record incoming sessions", "Автоматично записувати вхідні сеанси"),
@@ -382,7 +383,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Prompt", "Підказка"),
("Please wait for confirmation of UAC...", "Будь ласка, зачекайте підтвердження UAC..."),
("elevated_foreground_window_tip", "Поточне вікно віддаленої стільниці потребує розширених прав для роботи, тому наразі неможливо використати мишу та клавіатуру. Ви можете запропонувати віддаленому користувачу згорнути поточне вікно чи натиснути кнопку розширення прав у вікні керування підключеннями. Для уникнення цієї проблеми, рекомендується встановити програму на віддаленому пристрої"),
("Disconnected", "Відключено"),
("Disconnected", "Відʼєднано"),
("Other", "Інше"),
("Confirm before closing multiple tabs", "Підтверджувати перед закриттям кількох вкладок"),
("Keyboard Settings", "Налаштування клавіатури"),
@@ -440,7 +441,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Medium", "Середній"),
("Strong", "Сильний"),
("Switch Sides", "Поміняти місцями"),
("Please confirm if you want to share your desktop?", "Будь ласка, пітвердіть дозвіл на спільне використання стільниці"),
("Please confirm if you want to share your desktop?", "Будь ласка, підтвердіть дозвіл на спільне використання стільниці"),
("Display", "Екран"),
("Default View Style", "Типовий стиль перегляду"),
("Default Scroll Style", "Типовий стиль гортання"),
@@ -450,17 +451,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("FPS", "FPS"),
("Auto", "Авто"),
("Other Default Options", "Інші типові параметри"),
("Voice call", "Голосовий дзвінок"),
("Voice call", "Голосовий виклик"),
("Text chat", "Текстовий чат"),
("Stop voice call", "Покласти слухавку"),
("relay_hint_tip", "Якщо відсутня можливості підключитись напряму, ви можете спробувати підключення по реле. \nТакож, якщо ви хочете відразу використовувати реле, можна додати суфікс \"/r\" до ID, або ж вибрати опцію \"Завжди підключатися через реле\" в картці вузла."),
("Stop voice call", "Завершити голосовий виклик"),
("relay_hint_tip", "Якщо відсутня можливості підключитись напряму, ви можете спробувати підключення по реле. \nТакож, якщо ви хочете відразу використовувати реле, можна додати суфікс \"/r\" до ID, або ж вибрати опцію \"Завжди підключатися через реле\" в картці нещодавніх сеансів."),
("Reconnect", "Перепідключитися"),
("Codec", "Кодек"),
("Resolution", "Роздільна здатність"),
("No transfers in progress", "Наразі нічого не пересилається"),
("Set one-time password length", "Вказати довжину одноразового пароля"),
("idd_driver_tip", "Встановити драйвер віртуального дисплея, який використовується в разі відсутності фізичних екранів."),
("confirm_idd_driver_tip", "Позначено встановлення драйвера віртуального дисплея. Зважайте, що буде встановлено тестовий сертифікат для засвідчення драйвера віртуального дисплея. Цей тестовий сертифікат буде використовуватись лише для засвідчення драйверів Rustdesk."),
("install_cert_tip", "Додати сертифікат Rustdesk"),
("confirm_install_cert_tip", "Це сертифікат тестування Rustdesk, якому можна довіряти. За потреби сертифікат буде використано для погодження та встановлення драйверів Rustdesk."),
("RDP Settings", "Налаштування RDP"),
("Sort by", "Сортувати за"),
("New Connection", "Нове підключення"),
@@ -468,8 +469,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Minimize", "Згорнути"),
("Maximize", "Розгорнути"),
("Your Device", "Вам пристрій"),
("empty_recent_tip", "Овва, відсутні нещодавні сеанси!\nСаме час запланувати новий."),
("empty_favorite_tip", "Поки немає улюблених вузлів?\nДавайте знайдемо нове підключення та додамо його до улюблених!"),
("empty_recent_tip", "Овва, відсутні нещодавні сеанси!\nСаме час запланувати нове підключення."),
("empty_favorite_tip", "Досі немає улюблених вузлів?\nДавайте організуємо нове підключення та додамо його до улюблених!"),
("empty_lan_tip", "О ні, схоже ми поки не виявили жодного віддаленого пристрою"),
("empty_address_book_tip", "Ой лишенько, схоже до вашої адресної книги немає жодного віддаленого пристрою"),
("eg: admin", "напр. admin"),
@@ -508,9 +509,39 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("resolution_original_tip", "Початкова роздільна здатність"),
("resolution_fit_local_tip", "Припасувати поточну роздільну здатність"),
("resolution_custom_tip", "Користувацька роздільна здатність"),
("Collapse toolbar", ""),
("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""),
("Collapse toolbar", "Згорнути панель інструментів"),
("Accept and Elevate", "Погодитись та розширити права"),
("accept_and_elevate_btn_tooltip", "Погодити підключення та розширити дозволи UAC."),
("clipboard_wait_response_timeout_tip", "Вийшов час очікування копіювання."),
("Incoming connection", "Вхідне підключення"),
("Outgoing connection", "Вихідне підключення"),
("Exit", "Вийти"),
("Open", "Відкрити"),
("logout_tip", "Ви впевнені, що хочете вилогуватися?"),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -53,7 +53,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Audio Input", "Đầu vào âm thanh"),
("Enhancements", "Các tiện ích"),
("Hardware Codec", "Codec phần cứng"),
("Adaptive Bitrate", "Bitrate thích ứng"),
("Adaptive bitrate", "Bitrate thích ứng"),
("ID Server", "Máy chủ ID"),
("Relay Server", "Máy chủ Chuyển tiếp"),
("API Server", "Máy chủ API"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Mất tên người dùng"),
("Password missed", "Mất mật khẩu"),
("Wrong credentials", "Chứng danh bị sai"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Chỉnh sửa Tag"),
("Unremember Password", "Quên mật khẩu"),
("Favorites", "Ưa thích"),
@@ -459,8 +460,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Resolution", "Độ phân giải"),
("No transfers in progress", "Không có tệp tin nào đang được truyền"),
("Set one-time password length", "Thiết lập độ dài mật khẩu một lần"),
("idd_driver_tip", "Cài đặt driver màn hình ảo để sử dụng khi bạn không có màn hình vật lý."),
("confirm_idd_driver_tip", "Tùy chọn cài đặt driver màn hình ảo đã được bật. Lưu ý rằng một chứng nhận thử sẽ được cài đặt để tin cậy driver màn hình ảo. Chứng nhận thử này sẽ chỉ được dùng để tin cậy những driver của RustDesk."),
("install_cert_tip", ""),
("confirm_install_cert_tip", ""),
("RDP Settings", "Cài đặt RDP"),
("Sort by", "Sắp xếp theo"),
("New Connection", "Kết nối mới"),
@@ -512,5 +513,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", "Chấp nhận và Cấp Quyền"),
("accept_and_elevate_btn_tooltip", "Chấp nhận kết nối và cấp các quyền UAC."),
("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
("logout_tip", ""),
("Service", ""),
("Start", ""),
("Stop", ""),
("exceed_max_devices", ""),
("Sync with recent sessions", ""),
("Sort tags", ""),
("Open connection in new tab", ""),
("Move tab to new window", ""),
("Can not be empty", ""),
("Already exists", ""),
("Change Password", ""),
("Refresh Password", ""),
("ID", ""),
("Grid View", ""),
("List View", ""),
("Select", ""),
("Toggle Tags", ""),
("pull_ab_failed_tip", ""),
("push_ab_failed_tip", ""),
("synced_peer_readded_tip", ""),
("Change Color", ""),
("Primary Color", ""),
("HSV Color", ""),
("Installation Successful!", ""),
("Installation failed!", ""),
].iter().cloned().collect();
}

View File

@@ -41,7 +41,6 @@ pub mod cli;
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
pub mod core_main;
mod lang;
#[cfg(windows)]
mod license;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
mod port_forward;

View File

@@ -31,7 +31,9 @@ fn get_license_from_string_(s: &str) -> ResultType<License> {
}
pub fn get_license_from_string(s: &str) -> ResultType<License> {
let s = if s.to_lowercase().ends_with(".exe") {
let s = if s.to_lowercase().ends_with(".exe.exe") {
&s[0..s.len() - 8]
} else if s.to_lowercase().ends_with(".exe") {
&s[0..s.len() - 4]
} else {
s
@@ -53,20 +55,23 @@ pub fn get_license_from_string(s: &str) -> ResultType<License> {
let strs: Vec<&str> = stripped.split(",").collect();
let mut host = "";
let mut key = "";
let mut api = "";
let strs_iter = strs.iter();
for el in strs_iter {
if el.starts_with("host=") {
host = &el[5..el.len()];
}
if el.starts_with("key=") {
key = &el[4..el.len()];
}
if el.starts_with("api=") {
api = &el[4..el.len()];
}
}
return Ok(License {
host: host.to_owned(),
key: key.to_owned(),
api: "".to_owned(),
api: api.to_owned(),
});
} else {
let strs = if s.contains("-licensed-") {
@@ -110,12 +115,14 @@ mod test {
);
// key in these tests is "foobar.,2" base64 encoded
assert_eq!(
get_license_from_string("rustdesk-host=server.example.net,key=Zm9vYmFyLiwyCg==.exe")
.unwrap(),
get_license_from_string(
"rustdesk-host=server.example.net,api=abc,key=Zm9vYmFyLiwyCg==.exe"
)
.unwrap(),
License {
host: "server.example.net".to_owned(),
key: "Zm9vYmFyLiwyCg==".to_owned(),
api: "".to_owned(),
api: "abc".to_owned(),
}
);
assert_eq!(

View File

@@ -12,15 +12,14 @@ fn main() {
let args: Vec<_> = std::env::args().skip(1).collect();
let api = args.get(2).cloned().unwrap_or_default();
if args.len() >= 2 {
println!(
"rustdesk-licensed-{}.exe",
gen_name(&License {
key: args[0].clone(),
host: args[1].clone(),
api,
})
.unwrap()
);
match gen_name(&License {
key: args[0].clone(),
host: args[1].clone(),
api,
}) {
Ok(name) => println!("rustdesk-licensed-{}.exe", name),
Err(e) => println!("{:?}", e),
}
}
if args.len() == 1 {
println!("{:?}", get_license_from_string(&args[0]));

View File

@@ -64,11 +64,10 @@ impl AppHandler for Rc<Host> {
// https://github.com/xi-editor/druid/blob/master/druid-shell/src/platform/mac/application.rs
pub unsafe fn set_delegate(handler: Option<Box<dyn AppHandler>>) {
let decl = ClassDecl::new("AppDelegate", class!(NSObject));
if decl.is_none() {
let Some(mut decl) = ClassDecl::new("AppDelegate", class!(NSObject)) else {
log::error!("Failed to new AppDelegate");
return;
}
let mut decl = decl.unwrap();
};
decl.add_ivar::<*mut c_void>(APP_HANDLER_IVAR);
decl.add_method(
@@ -116,7 +115,10 @@ pub unsafe fn set_delegate(handler: Option<Box<dyn AppHandler>>) {
let handler_ptr = Box::into_raw(Box::new(state));
(*delegate).set_ivar(APP_HANDLER_IVAR, handler_ptr as *mut c_void);
// Set the url scheme handler
let cls = Class::get("NSAppleEventManager").unwrap();
let Some(cls) = Class::get("NSAppleEventManager") else {
log::error!("Failed to get NSAppleEventManager");
return;
};
let manager: *mut Object = msg_send![cls, sharedAppleEventManager];
let _: () = msg_send![manager,
setEventHandler: delegate
@@ -199,10 +201,10 @@ fn service_should_handle_reopen(
_sel: Sel,
_sender: id,
_has_visible_windows: BOOL,
) -> BOOL {
) -> BOOL {
log::debug!("Invoking the main rustdesk process");
std::thread::spawn(move || crate::handle_url_scheme("".to_string()));
// Prevent default logic.
std::thread::spawn(move || crate::handle_url_scheme("".to_string()));
// Prevent default logic.
NO
}

View File

@@ -1,5 +1,8 @@
use super::{CursorData, ResultType};
use desktop::Desktop;
#[cfg(all(feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
use hbb_common::config::CONFIG_OPTION_ALLOW_LINUX_HEADLESS;
pub use hbb_common::platform::linux::*;
use hbb_common::{
allow_err, bail,
@@ -69,6 +72,19 @@ pub struct xcb_xfixes_get_cursor_image {
pub pixels: *const c_long,
}
#[inline]
#[cfg(feature = "linux_headless")]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
pub fn is_headless_allowed() -> bool {
Config::get_option(CONFIG_OPTION_ALLOW_LINUX_HEADLESS) == "Y"
}
#[inline]
pub fn is_login_screen_wayland() -> bool {
let values = get_values_of_seat0_with_gdm_wayland(&[0, 2]);
is_gdm_user(&values[1]) && get_display_server_of_session(&values[0]) == DISPLAY_SERVER_WAYLAND
}
#[inline]
fn sleep_millis(millis: u64) {
std::thread::sleep(Duration::from_millis(millis));
@@ -429,13 +445,21 @@ fn get_cm() -> bool {
}
pub fn is_login_wayland() -> bool {
if let Ok(contents) = std::fs::read_to_string("/etc/gdm3/custom.conf") {
contents.contains("#WaylandEnable=false") || contents.contains("WaylandEnable=true")
} else if let Ok(contents) = std::fs::read_to_string("/etc/gdm/custom.conf") {
contents.contains("#WaylandEnable=false") || contents.contains("WaylandEnable=true")
} else {
false
let files = ["/etc/gdm3/custom.conf", "/etc/gdm/custom.conf"];
match (
Regex::new(r"# *WaylandEnable *= *false"),
Regex::new(r"WaylandEnable *= *true"),
) {
(Ok(pat1), Ok(pat2)) => {
for file in files {
if let Ok(contents) = std::fs::read_to_string(file) {
return pat1.is_match(&contents) || pat2.is_match(&contents);
}
}
}
_ => {}
}
false
}
#[inline]
@@ -669,7 +693,8 @@ pub fn check_super_user_permission() -> ResultType<bool> {
}
// https://github.com/rustdesk/rustdesk/issues/2756
if let Ok(status) = Command::new("pkexec").arg(arg).status() {
Ok(status.code() != Some(126))
// https://github.com/rustdesk/rustdesk/issues/5205#issuecomment-1658059657s
Ok(status.code() != Some(126) && status.code() != Some(127))
} else {
Ok(true)
}
@@ -728,7 +753,9 @@ pub fn get_double_click_time() -> u32 {
// g_object_get (settings, "gtk-double-click-time", &double_click_time, NULL);
unsafe {
let mut double_click_time = 0u32;
let property = std::ffi::CString::new("gtk-double-click-time").unwrap();
let Ok(property) = std::ffi::CString::new("gtk-double-click-time") else {
return 0;
};
let settings = gtk_settings_get_default();
g_object_get(
settings,
@@ -801,7 +828,10 @@ pub fn resolutions(name: &str) -> Vec<Resolution> {
if let Some(resolutions) = caps.name("resolutions") {
let resolution_pat =
r"\s*(?P<width>\d+)x(?P<height>\d+)\s+(?P<rates>(\d+\.\d+\D*)+)\s*\n";
let resolution_re = Regex::new(&format!(r"{}", resolution_pat)).unwrap();
let Ok(resolution_re) = Regex::new(&format!(r"{}", resolution_pat)) else {
log::error!("Regex new failed");
return vec![];
};
for resolution_caps in resolution_re.captures_iter(resolutions.as_str()) {
if let Some((width, height)) =
get_width_height_from_captures(&resolution_caps)
@@ -1043,8 +1073,8 @@ mod desktop {
}
pub fn refresh(&mut self) {
if !self.sid.is_empty() && is_active(&self.sid) {
return;
if !self.sid.is_empty() && is_active_and_seat0(&self.sid) {
return;
}
let seat0_values = get_values_of_seat0(&[0, 1, 2]);

View File

@@ -109,6 +109,10 @@ pub fn try_start_desktop(_username: &str, _passsword: &str) -> String {
// No need to verify password here.
return "".to_owned();
}
if !username.is_empty() {
// Another user is logged in. No need to start a new xsession.
return "".to_owned();
}
if let Some(msg) = detect_headless() {
return msg.to_owned();

View File

@@ -4,6 +4,27 @@
#include <Security/Authorization.h>
#include <Security/AuthorizationTags.h>
extern "C" bool CanUseNewApiForScreenCaptureCheck() {
#ifdef NO_InputMonitoringAuthStatus
return false;
#else
NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion];
return version.majorVersion >= 11;
#endif
}
extern "C" bool IsCanScreenRecording(bool prompt) {
#ifdef NO_InputMonitoringAuthStatus
return false;
#else
bool res = CGPreflightScreenCaptureAccess();
if (!res && prompt) {
CGRequestScreenCaptureAccess();
}
return res;
#endif
}
// https://github.com/codebytere/node-mac-permissions/blob/main/permissions.mm

View File

@@ -34,6 +34,8 @@ extern "C" {
static kAXTrustedCheckOptionPrompt: CFStringRef;
fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> BOOL;
fn InputMonitoringAuthStatus(_: BOOL) -> BOOL;
fn IsCanScreenRecording(_: BOOL) -> BOOL;
fn CanUseNewApiForScreenCaptureCheck() -> BOOL;
fn MacCheckAdminAuthorization() -> BOOL;
fn MacGetModeNum(display: u32, numModes: *mut u32) -> BOOL;
fn MacGetModes(
@@ -71,6 +73,14 @@ pub fn is_can_input_monitoring(prompt: bool) -> bool {
// https://stackoverflow.com/questions/56597221/detecting-screen-recording-settings-on-macos-catalina/
// remove just one app from all the permissions: tccutil reset All com.carriez.rustdesk
pub fn is_can_screen_recording(prompt: bool) -> bool {
// we got some report that we show no permission even after set it, so we try to use new api for screen recording check
// the new api is only available on macOS >= 10.15, but on stackoverflow, some people said it works on >= 10.16 (crash on 10.15),
// but also some said it has bug on 10.16, so we just use it on 11.0.
unsafe {
if CanUseNewApiForScreenCaptureCheck() == YES {
return IsCanScreenRecording(if prompt { YES } else { NO }) == YES;
}
}
let mut can_record_screen: bool = false;
unsafe {
let our_pid: i32 = std::process::id() as _;
@@ -122,6 +132,10 @@ pub fn is_can_screen_recording(prompt: bool) -> bool {
can_record_screen
}
pub fn install_service() -> bool {
is_installed_daemon(false)
}
pub fn is_installed_daemon(prompt: bool) -> bool {
let daemon = format!("{}_service.plist", crate::get_full_name());
let agent = format!("{}_server.plist", crate::get_full_name());
@@ -136,14 +150,26 @@ pub fn is_installed_daemon(prompt: bool) -> bool {
return true;
}
let install_script = PRIVILEGES_SCRIPTS_DIR.get_file("install.scpt").unwrap();
let install_script_body = install_script.contents_utf8().unwrap();
let Some(install_script) = PRIVILEGES_SCRIPTS_DIR.get_file("install.scpt") else {
return false;
};
let Some(install_script_body) = install_script.contents_utf8() else {
return false;
};
let daemon_plist = PRIVILEGES_SCRIPTS_DIR.get_file(&daemon).unwrap();
let daemon_plist_body = daemon_plist.contents_utf8().unwrap();
let Some(daemon_plist) = PRIVILEGES_SCRIPTS_DIR.get_file(&daemon) else {
return false;
};
let Some(daemon_plist_body) = daemon_plist.contents_utf8() else {
return false;
};
let agent_plist = PRIVILEGES_SCRIPTS_DIR.get_file(&agent).unwrap();
let agent_plist_body = agent_plist.contents_utf8().unwrap();
let Some(agent_plist) = PRIVILEGES_SCRIPTS_DIR.get_file(&agent) else {
return false;
};
let Some(agent_plist_body) = agent_plist.contents_utf8() else {
return false;
};
std::thread::spawn(move || {
match std::process::Command::new("osascript")
@@ -188,8 +214,12 @@ pub fn uninstall_service(show_new_window: bool) -> bool {
return false;
}
let script_file = PRIVILEGES_SCRIPTS_DIR.get_file("uninstall.scpt").unwrap();
let script_body = script_file.contents_utf8().unwrap();
let Some(script_file) = PRIVILEGES_SCRIPTS_DIR.get_file("uninstall.scpt") else {
return false;
};
let Some(script_body) = script_file.contents_utf8() else {
return false;
};
std::thread::spawn(move || {
match std::process::Command::new("osascript")
@@ -210,10 +240,12 @@ pub fn uninstall_service(show_new_window: bool) -> bool {
uninstalled
);
if uninstalled {
if !show_new_window {
let _ = crate::ipc::close_all_instances();
// leave ipc a little time
std::thread::sleep(std::time::Duration::from_millis(300));
}
crate::ipc::set_option("stop-service", "Y");
let _ = crate::ipc::close_all_instances();
// leave ipc a little time
std::thread::sleep(std::time::Duration::from_millis(300));
std::process::Command::new("launchctl")
.args(&["remove", &format!("{}_server", crate::get_full_name())])
.status()
@@ -227,11 +259,6 @@ pub fn uninstall_service(show_new_window: bool) -> bool {
))
.spawn()
.ok();
} else {
std::process::Command::new("pkill")
.arg(crate::get_app_name())
.status()
.ok();
}
quit_gui();
}

View File

@@ -48,7 +48,7 @@ pub fn breakdown_callback() {
pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<()> {
let cur_resolution = current_resolution(name)?;
// For MacOS
// to-do: Make sure the following comparison works.
// to-do: Make sure the following comparison works.
// For Linux
// Just run "xrandr", dpi may not be taken into consideration.
// For Windows

View File

@@ -628,4 +628,8 @@ extern "C"
return bSystem;
}
void alloc_console_and_redirect() {
AllocConsole();
freopen("CONOUT$", "w", stdout);
}
} // end of extern "C"

View File

@@ -6,7 +6,9 @@ use crate::{
privacy_win_mag::{self, WIN_MAG_INJECTED_PROCESS_EXE},
};
use hbb_common::{
allow_err, bail,
allow_err,
anyhow::anyhow,
bail,
config::{self, Config},
log,
message_proto::Resolution,
@@ -455,6 +457,7 @@ extern "C" {
fn win_stop_system_key_propagate(v: BOOL);
fn is_win_down() -> BOOL;
fn is_local_system() -> BOOL;
fn alloc_console_and_redirect();
}
extern "system" {
@@ -847,10 +850,9 @@ pub fn check_update_broker_process() -> ResultType<()> {
let origin_process_exe = privacy_win_mag::ORIGIN_PROCESS_EXE;
let exe_file = std::env::current_exe()?;
if exe_file.parent().is_none() {
let Some(cur_dir) = exe_file.parent() else {
bail!("Cannot get parent of current exe file");
}
let cur_dir = exe_file.parent().unwrap();
};
let cur_exe = cur_dir.join(process_exe);
if !std::path::Path::new(&cur_exe).exists() {
@@ -901,60 +903,31 @@ fn get_install_info_with_subkey(subkey: String) -> (String, String, String, Stri
(subkey, path, start_menu, exe)
}
pub fn copy_raw_cmd(src_raw: &str, _raw: &str, _path: &str) -> String {
pub fn copy_raw_cmd(src_raw: &str, _raw: &str, _path: &str) -> ResultType<String> {
let main_raw = format!(
"XCOPY \"{}\" \"{}\" /Y /E /H /C /I /K /R /Z",
PathBuf::from(src_raw)
.parent()
.unwrap()
.ok_or(anyhow!("Can't get parent directory of {src_raw}"))?
.to_string_lossy()
.to_string(),
_path
);
return main_raw;
return Ok(main_raw);
}
pub fn copy_exe_cmd(src_exe: &str, exe: &str, path: &str) -> String {
let main_exe = copy_raw_cmd(src_exe, exe, path);
format!(
pub fn copy_exe_cmd(src_exe: &str, exe: &str, path: &str) -> ResultType<String> {
let main_exe = copy_raw_cmd(src_exe, exe, path)?;
Ok(format!(
"
{main_exe}
copy /Y \"{ORIGIN_PROCESS_EXE}\" \"{path}\\{broker_exe}\"
",
ORIGIN_PROCESS_EXE = privacy_win_mag::ORIGIN_PROCESS_EXE,
broker_exe = privacy_win_mag::INJECTED_PROCESS_EXE,
)
))
}
/* // update_me has bad compatibility, so disable it.
pub fn update_me() -> ResultType<()> {
let (_, path, _, exe) = get_install_info();
let src_exe = std::env::current_exe()?.to_str().unwrap_or("").to_owned();
let cmds = format!(
"
chcp 65001
sc stop {app_name}
taskkill /F /IM {broker_exe}
taskkill /F /IM {app_name}.exe /FI \"PID ne {cur_pid}\"
{copy_exe}
sc start {app_name}
{lic}
",
copy_exe = copy_exe_cmd(&src_exe, &exe, &path),
broker_exe = WIN_MAG_INJECTED_PROCESS_EXE,
app_name = crate::get_app_name(),
lic = register_licence(),
cur_pid = get_current_pid(),
);
run_cmds(cmds, false, "update")?;
run_after_run_cmds(false);
std::process::Command::new(&exe)
.args(&["--remove", &src_exe])
.spawn()?;
Ok(())
}
*/
fn get_after_install(exe: &str) -> String {
let app_name = crate::get_app_name();
let ext = app_name.to_lowercase();
@@ -981,6 +954,7 @@ fn get_after_install(exe: &str) -> String {
reg add HKEY_CLASSES_ROOT\\{ext}\\shell\\open /f
reg add HKEY_CLASSES_ROOT\\{ext}\\shell\\open\\command /f
reg add HKEY_CLASSES_ROOT\\{ext}\\shell\\open\\command /f /ve /t REG_SZ /d \"\\\"{exe}\\\" \\\"%%1\\\"\"
netsh advfirewall firewall add rule name=\"{app_name} Service\" dir=out action=allow program=\"{exe}\" enable=yes
netsh advfirewall firewall add rule name=\"{app_name} Service\" dir=in action=allow program=\"{exe}\" enable=yes
{create_service}
reg add HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System /f /v SoftwareSASGeneration /t REG_DWORD /d 1
@@ -1092,6 +1066,13 @@ if exist \"{tmp_path}\\{app_name} Tray.lnk\" del /f /q \"{tmp_path}\\{app_name}
"".to_owned()
};
// potential bug here: if run_cmd cancelled, but config file is changed.
if let Some(lic) = get_license() {
Config::set_option("key".into(), lic.key);
Config::set_option("custom-rendezvous-server".into(), lic.host);
Config::set_option("api-server".into(), lic.api);
}
let cmds = format!(
"
{uninstall_str}
@@ -1112,7 +1093,6 @@ reg add {subkey} /f /v VersionBuild /t REG_DWORD /d {version_build}
reg add {subkey} /f /v UninstallString /t REG_SZ /d \"\\\"{exe}\\\" --uninstall\"
reg add {subkey} /f /v EstimatedSize /t REG_DWORD /d {size}
reg add {subkey} /f /v WindowsInstaller /t REG_DWORD /d 0
{lic}
cscript \"{mk_shortcut}\"
cscript \"{uninstall_shortcut}\"
cscript \"{tray_shortcut}\"
@@ -1127,7 +1107,6 @@ copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{path}\\\"
",
version=crate::VERSION,
build_date=crate::BUILD_DATE,
lic=register_licence(),
after_install=get_after_install(&exe),
sleep=if debug {
"timeout 300"
@@ -1139,7 +1118,7 @@ copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{path}\\\"
} else {
&dels
},
copy_exe = copy_exe_cmd(&src_exe, &exe, &path),
copy_exe = copy_exe_cmd(&src_exe, &exe, &path)?,
import_config = get_import_config(&exe),
);
run_cmds(cmds, debug, "install")?;
@@ -1221,7 +1200,7 @@ fn write_cmds(cmds: String, ext: &str, tip: &str) -> ResultType<std::path::PathB
tmp.push(format!("{}_{}.{}", crate::get_app_name(), tip, ext));
let mut file = std::fs::File::create(&tmp)?;
if ext == "bat" {
let tmp2 = get_undone_file(&tmp);
let tmp2 = get_undone_file(&tmp)?;
std::fs::File::create(&tmp2).ok();
cmds = format!(
"
@@ -1252,18 +1231,20 @@ fn to_le(v: &mut [u16]) -> &[u8] {
unsafe { v.align_to().1 }
}
fn get_undone_file(tmp: &PathBuf) -> PathBuf {
fn get_undone_file(tmp: &PathBuf) -> ResultType<PathBuf> {
let mut tmp1 = tmp.clone();
tmp1.set_file_name(format!(
"{}.undone",
tmp.file_name().unwrap().to_string_lossy()
tmp.file_name()
.ok_or(anyhow!("Failed to get filename of {:?}", tmp))?
.to_string_lossy()
));
tmp1
Ok(tmp1)
}
fn run_cmds(cmds: String, show: bool, tip: &str) -> ResultType<()> {
let tmp = write_cmds(cmds, "bat", tip)?;
let tmp2 = get_undone_file(&tmp);
let tmp2 = get_undone_file(&tmp)?;
let tmp_fn = tmp.to_str().unwrap_or("");
let res = runas::Command::new("cmd")
.args(&["/C", &tmp_fn])
@@ -1346,12 +1327,11 @@ fn get_reg_of(subkey: &str, name: &str) -> String {
"".to_owned()
}
fn get_license_from_exe_name() -> ResultType<License> {
pub fn get_license_from_exe_name() -> ResultType<License> {
let mut exe = std::env::current_exe()?.to_str().unwrap_or("").to_owned();
// if defined portable appname entry, replace original executable name with it.
if let Ok(portable_exe) = std::env::var(PORTABLE_APPNAME_RUNTIME_ENV_KEY) {
exe = portable_exe;
log::debug!("update portable executable name to {}", exe);
}
get_license_from_string(&exe)
}
@@ -1361,42 +1341,9 @@ pub fn is_win_server() -> bool {
unsafe { is_windows_server() > 0 }
}
pub fn get_license() -> Option<License> {
let mut lic: License = Default::default();
if let Ok(tmp) = get_license_from_exe_name() {
lic = tmp;
} else {
lic.key = get_reg("Key");
lic.host = get_reg("Host");
lic.api = get_reg("Api");
}
if lic.key.is_empty() || lic.host.is_empty() {
return None;
}
Some(lic)
}
pub fn bootstrap() {
if let Some(lic) = get_license() {
*config::PROD_RENDEZVOUS_SERVER.write().unwrap() = lic.host.clone();
}
}
fn register_licence() -> String {
let (subkey, _, _, _) = get_install_info();
if let Ok(lic) = get_license_from_exe_name() {
format!(
"
reg add {subkey} /f /v Key /t REG_SZ /d \"{key}\"
reg add {subkey} /f /v Host /t REG_SZ /d \"{host}\"
reg add {subkey} /f /v Api /t REG_SZ /d \"{api}\"
",
key = &lic.key,
host = &lic.host,
api = &lic.api,
)
} else {
"".to_owned()
*config::EXE_RENDEZVOUS_SERVER.write().unwrap() = lic.host.clone();
}
}
@@ -1601,18 +1548,8 @@ pub fn elevate_or_run_as_system(is_setup: bool, is_elevate: bool, is_run_as_syst
}
}
// https://github.com/mgostIH/process_list/blob/master/src/windows/mod.rs
#[repr(transparent)]
pub(self) struct RAIIHandle(pub HANDLE);
impl Drop for RAIIHandle {
fn drop(&mut self) {
// This never gives problem except when running under a debugger.
unsafe { CloseHandle(self.0) };
}
}
pub fn is_elevated(process_id: Option<DWORD>) -> ResultType<bool> {
use hbb_common::platform::windows::RAIIHandle;
unsafe {
let handle: HANDLE = match process_id {
Some(process_id) => OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id),
@@ -1923,18 +1860,22 @@ pub fn uninstall_cert() -> ResultType<()> {
mod cert {
use hbb_common::{allow_err, bail, log, ResultType};
use std::{path::Path, str::from_utf8};
use winapi::shared::{
minwindef::{BYTE, DWORD, TRUE},
ntdef::NULL,
};
use winapi::um::{
errhandlingapi::GetLastError,
wincrypt::{
CertCloseStore, CertEnumCertificatesInStore, CertNameToStrA, CertOpenSystemStoreA,
CryptHashCertificate, ALG_ID, CALG_SHA1, CERT_ID_SHA1_HASH, CERT_X500_NAME_STR,
PCCERT_CONTEXT,
use winapi::{
shared::{
minwindef::{BYTE, DWORD, FALSE, TRUE},
ntdef::NULL,
},
um::{
errhandlingapi::GetLastError,
wincrypt::{
CertAddEncodedCertificateToStore, CertCloseStore, CertDeleteCertificateFromStore,
CertEnumCertificatesInStore, CertNameToStrA, CertOpenSystemStoreW,
CryptHashCertificate, ALG_ID, CALG_SHA1, CERT_ID_SHA1_HASH,
CERT_STORE_ADD_REPLACE_EXISTING, CERT_X500_NAME_STR, PCCERT_CONTEXT,
X509_ASN_ENCODING,
},
winreg::HKEY_LOCAL_MACHINE,
},
winreg::HKEY_LOCAL_MACHINE,
};
use winreg::{
enums::{KEY_WRITE, REG_BINARY},
@@ -1946,6 +1887,8 @@ mod cert {
const THUMBPRINT_ALG: ALG_ID = CALG_SHA1;
const THUMBPRINT_LEN: DWORD = 20;
const CERT_ISSUER_1: &str = "CN=\"WDKTestCert admin,133225435702113567\"\0";
#[inline]
unsafe fn compute_thumbprint(pb_encoded: *const BYTE, cb_encoded: DWORD) -> (Vec<u8>, String) {
let mut size = THUMBPRINT_LEN;
@@ -2000,6 +1943,12 @@ mod cert {
pub fn install_cert<P: AsRef<Path>>(path: P) -> ResultType<()> {
let mut cert_bytes = std::fs::read(path)?;
install_cert_reg(&mut cert_bytes)?;
install_cert_add_cert_store(&mut cert_bytes)?;
Ok(())
}
fn install_cert_reg(cert_bytes: &mut [u8]) -> ResultType<()> {
unsafe {
let thumbprint = compute_thumbprint(cert_bytes.as_mut_ptr(), cert_bytes.len() as _);
log::debug!("Thumbprint of cert {}", &thumbprint.1);
@@ -2008,25 +1957,56 @@ mod cert {
let (cert_key, _) = reg_cert_key.create_subkey(&thumbprint.1)?;
let data = winreg::RegValue {
vtype: REG_BINARY,
bytes: create_cert_blob(thumbprint.0, cert_bytes),
bytes: create_cert_blob(thumbprint.0, cert_bytes.to_vec()),
};
cert_key.set_raw_value("Blob", &data)?;
}
Ok(())
}
fn install_cert_add_cert_store(cert_bytes: &mut [u8]) -> ResultType<()> {
unsafe {
let store_handle = CertOpenSystemStoreW(0 as _, "ROOT\0".as_ptr() as _);
if store_handle.is_null() {
bail!("Error opening certificate store: {}", GetLastError());
}
let mut cert_ctx: PCCERT_CONTEXT = std::ptr::null_mut();
if FALSE
== CertAddEncodedCertificateToStore(
store_handle,
X509_ASN_ENCODING,
cert_bytes.as_mut_ptr(),
cert_bytes.len() as _,
CERT_STORE_ADD_REPLACE_EXISTING,
&mut cert_ctx as _,
)
{
log::error!(
"Failed to call CertAddEncodedCertificateToStore: {}",
GetLastError()
);
} else {
log::info!("Add cert to store successfully");
}
CertCloseStore(store_handle, 0);
}
Ok(())
}
fn get_thumbprints_to_rm() -> ResultType<Vec<String>> {
let issuers_to_rm = ["CN=\"WDKTestCert admin,133225435702113567\""];
let issuers_to_rm = [CERT_ISSUER_1];
let mut thumbprints = Vec::new();
let mut buf = [0u8; 1024];
unsafe {
let store_handle = CertOpenSystemStoreA(0 as _, "ROOT\0".as_ptr() as _);
let store_handle = CertOpenSystemStoreW(0 as _, "ROOT\0".as_ptr() as _);
if store_handle.is_null() {
bail!("Error opening certificate store: {}", GetLastError());
}
let mut vec_ctx = Vec::new();
let mut cert_ctx: PCCERT_CONTEXT = CertEnumCertificatesInStore(store_handle, NULL as _);
while !cert_ctx.is_null() {
// https://stackoverflow.com/a/66432736
@@ -2038,9 +2018,11 @@ mod cert {
buf.len() as _,
);
if cb_size != 1 {
let mut add_ctx = false;
if let Ok(issuer) = from_utf8(&buf[..cb_size as _]) {
for iss in issuers_to_rm.iter() {
if issuer.contains(iss) {
if issuer == *iss {
add_ctx = true;
let (_, thumbprint) = compute_thumbprint(
(*cert_ctx).pbCertEncoded,
(*cert_ctx).cbCertEncoded,
@@ -2051,9 +2033,15 @@ mod cert {
}
}
}
if add_ctx {
vec_ctx.push(cert_ctx);
}
}
cert_ctx = CertEnumCertificatesInStore(store_handle, cert_ctx);
}
for ctx in vec_ctx {
CertDeleteCertificateFromStore(ctx);
}
CertCloseStore(store_handle, 0);
}
@@ -2063,6 +2051,7 @@ mod cert {
pub fn uninstall_cert() -> ResultType<()> {
let thumbprints = get_thumbprints_to_rm()?;
let reg_cert_key = unsafe { open_reg_cert_store()? };
log::info!("Found {} certs to remove", thumbprints.len());
for thumbprint in thumbprints.iter() {
allow_err!(reg_cert_key.delete_subkey(thumbprint));
}
@@ -2161,9 +2150,11 @@ pub fn uninstall_service(show_new_window: bool) -> bool {
sc stop {app_name}
sc delete {app_name}
if exist \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" del /f /q \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\"
taskkill /F /IM {broker_exe}
taskkill /F /IM {app_name}.exe{filter}
",
app_name = crate::get_app_name(),
broker_exe = WIN_MAG_INJECTED_PROCESS_EXE,
);
if let Err(err) = run_cmds(cmds, false, "uninstall") {
Config::set_option("stop-service".into(), "".into());
@@ -2273,6 +2264,15 @@ fn run_after_run_cmds(silent: bool) {
std::thread::sleep(std::time::Duration::from_millis(300));
}
#[inline]
pub fn try_kill_broker() {
allow_err!(std::process::Command::new("cmd")
.arg("/c")
.arg(&format!("taskkill /F /IM {}", WIN_MAG_INJECTED_PROCESS_EXE))
.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW)
.spawn());
}
#[cfg(test)]
mod tests {
use super::*;
@@ -2297,3 +2297,62 @@ mod tests {
assert_eq!(chr, None)
}
}
pub fn message_box(text: &str) {
let mut text = text.to_owned();
let nodialog = std::env::var("NO_DIALOG").unwrap_or_default() == "Y";
if !text.ends_with("!") || nodialog {
use arboard::Clipboard as ClipboardContext;
match ClipboardContext::new() {
Ok(mut ctx) => {
ctx.set_text(&text).ok();
if !nodialog {
text = format!("{}\n\nAbove text has been copied to clipboard", &text);
}
}
_ => {}
}
}
if nodialog {
if std::env::var("PRINT_OUT").unwrap_or_default() == "Y" {
println!("{text}");
}
if let Ok(x) = std::env::var("WRITE_TO_FILE") {
if !x.is_empty() {
allow_err!(std::fs::write(x, text));
}
}
return;
}
let text = text
.encode_utf16()
.chain(std::iter::once(0))
.collect::<Vec<u16>>();
let caption = "RustDesk Output"
.encode_utf16()
.chain(std::iter::once(0))
.collect::<Vec<u16>>();
unsafe { MessageBoxW(std::ptr::null_mut(), text.as_ptr(), caption.as_ptr(), MB_OK) };
}
pub fn alloc_console() {
unsafe {
alloc_console_and_redirect();
}
}
fn get_license() -> Option<License> {
let mut lic: License = Default::default();
if let Ok(tmp) = get_license_from_exe_name() {
lic = tmp;
} else {
// for back compatibility from migrating from <= 1.2.1 to 1.2.2
lic.key = get_reg("Key");
lic.host = get_reg("Host");
lic.api = get_reg("Api");
}
if lic.key.is_empty() || lic.host.is_empty() {
return None;
}
Some(lic)
}

View File

@@ -368,6 +368,7 @@ fn push_event_to_ui(channel: u16, peer: &str, content: &str) {
m.insert("peer", &peer);
m.insert("content", &content);
let event = serde_json::to_string(&m).unwrap_or("".to_string());
// Send to main and cm
for (k, v) in MSG_TO_UI_FLUTTER_CHANNELS.iter() {
if channel & k != 0 {
let _res = flutter::push_global_event(v as _, event.to_string());

View File

@@ -131,6 +131,8 @@ impl PluginNativeSessionHandler {
let mut m = HashMap::new();
m.insert("name", MSG_TO_UI_TYPE_SESSION_CREATED);
m.insert("session_id", &session_id);
// todo: APP_TYPE_DESKTOP_REMOTE is not used anymore.
// crate::flutter::APP_TYPE_DESKTOP_REMOTE + window id, is used for multi-window support.
crate::flutter::push_global_event(
crate::flutter::APP_TYPE_DESKTOP_REMOTE,
serde_json::to_string(&m).unwrap_or("".to_string()),

View File

@@ -2,10 +2,7 @@ use std::{collections::HashMap, ffi::c_void, os::raw::c_int};
use serde_json::json;
use crate::{
define_method_prefix,
flutter::{APP_TYPE_MAIN},
};
use crate::{define_method_prefix, flutter::APP_TYPE_MAIN};
use super::PluginNativeHandler;
@@ -26,7 +23,8 @@ pub struct PluginNativeUIHandler;
/// ```
/// [Safety]
/// Please make sure the callback u provided is VALID, or memory or calling issues may occur to cause the program crash!
pub type OnUIReturnCallback = extern "C" fn(return_code: c_int, data: *const c_void, data_len: u64, user_data: *const c_void);
pub type OnUIReturnCallback =
extern "C" fn(return_code: c_int, data: *const c_void, data_len: u64, user_data: *const c_void);
impl PluginNativeHandler for PluginNativeUIHandler {
define_method_prefix!("ui_");
@@ -41,9 +39,7 @@ impl PluginNativeHandler for PluginNativeUIHandler {
if let Some(cb) = data.get("cb") {
if let Some(cb) = cb.as_u64() {
let user_data = match data.get("user_data") {
Some(user_data) => {
user_data.as_u64().unwrap_or(0)
},
Some(user_data) => user_data.as_u64().unwrap_or(0),
None => 0,
};
self.select_peers_async(cb, user_data);
@@ -68,9 +64,7 @@ impl PluginNativeHandler for PluginNativeUIHandler {
if let Some(on_tap_cb) = data.get("on_tap_cb") {
if let Some(on_tap_cb) = on_tap_cb.as_u64() {
let user_data = match data.get("user_data") {
Some(user_data) => {
user_data.as_u64().unwrap_or(0)
},
Some(user_data) => user_data.as_u64().unwrap_or(0),
None => 0,
};
self.register_ui_entry(title, on_tap_cb, user_data);
@@ -109,10 +103,10 @@ impl PluginNativeUIHandler {
/// "user_data": 0 // An opaque pointer value passed to the callback.
/// }
/// ```
///
/// [Arguments]
///
/// [Arguments]
/// @param cb: the function address with type [OnUIReturnCallback].
/// @param user_data: the function will be called with this value.
/// @param user_data: the function will be called with this value.
fn select_peers_async(&self, cb: u64, user_data: u64) {
let mut param = HashMap::new();
param.insert("name", json!("native_ui"));

View File

@@ -628,13 +628,7 @@ fn reload_ui(desc: &Desc, sync_to: Option<&str>) {
// The first element is the "client" or "host".
// The second element is the "main", "remote", "cm", "file transfer", "port forward".
if v.len() >= 2 {
let available_channels = vec![
flutter::APP_TYPE_MAIN,
flutter::APP_TYPE_DESKTOP_REMOTE,
flutter::APP_TYPE_CM,
flutter::APP_TYPE_DESKTOP_FILE_TRANSFER,
flutter::APP_TYPE_DESKTOP_PORT_FORWARD,
];
let available_channels = flutter::get_global_event_channels();
if available_channels.contains(&v[1]) {
let _res = flutter::push_global_event(v[1], make_event(&ui));
}

View File

@@ -81,7 +81,7 @@ pub async fn listen(
});
}
Err(err) => {
interface.msgbox("error", "Error", &err.to_string(), "");
interface.on_establish_connection_error(err.to_string());
}
_ => {}
}
@@ -112,43 +112,6 @@ async fn connect_and_login(
key: &str,
token: &str,
is_rdp: bool,
) -> ResultType<Option<Stream>> {
let mut res = connect_and_login_2(
id,
password,
ui_receiver,
interface.clone(),
forward,
key,
token,
is_rdp,
)
.await;
if res.is_err() && interface.is_force_relay() {
res = connect_and_login_2(
id,
password,
ui_receiver,
interface,
forward,
key,
token,
is_rdp,
)
.await;
}
res
}
async fn connect_and_login_2(
id: &str,
password: &str,
ui_receiver: &mut mpsc::UnboundedReceiver<Data>,
interface: impl Interface,
forward: &mut Framed<TcpStream, BytesCodec>,
key: &str,
token: &str,
is_rdp: bool,
) -> ResultType<Option<Stream>> {
let conn_type = if is_rdp {
ConnType::RDP
@@ -157,6 +120,7 @@ async fn connect_and_login_2(
};
let (mut stream, direct, _pk) =
Client::start(id, key, token, conn_type, interface.clone()).await?;
interface.update_direct(Some(direct));
let mut interface = interface;
let mut buffer = Vec::new();
let mut received = false;
@@ -167,7 +131,10 @@ async fn connect_and_login_2(
bail!("Timeout");
}
Ok(Some(Ok(bytes))) => {
received = true;
if !received {
received = true;
interface.update_received(true);
}
let msg_in = Message::parse_from_bytes(&bytes)?;
match msg_in.union {
Some(message::Union::Hash(hash)) => {
@@ -191,8 +158,6 @@ async fn connect_and_login_2(
}
}
Ok(Some(Err(err))) => {
log::error!("Connection closed: {}", err);
interface.set_force_relay(direct, received);
bail!("Connection closed: {}", err);
}
_ => {

View File

@@ -21,7 +21,7 @@ use winapi::{
libloaderapi::{GetModuleHandleA, GetModuleHandleExA, GetProcAddress},
memoryapi::{VirtualAllocEx, WriteProcessMemory},
processthreadsapi::{
CreateProcessAsUserW, GetCurrentThreadId, QueueUserAPC, ResumeThread,
CreateProcessAsUserW, GetCurrentThreadId, QueueUserAPC, ResumeThread, TerminateProcess,
PROCESS_INFORMATION, STARTUPINFOW,
},
winbase::{WTSGetActiveConsoleSessionId, CREATE_SUSPENDED, DETACHED_PROCESS},
@@ -46,7 +46,6 @@ pub const GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS: u32 = 4;
const WM_USER_EXIT_HOOK: u32 = WM_USER + 1;
lazy_static::lazy_static! {
static ref DLL_FOUND: Mutex<bool> = Mutex::new(false);
static ref CONN_ID: Mutex<i32> = Mutex::new(0);
static ref CUR_HOOK_THREAD_ID: Mutex<DWORD> = Mutex::new(0);
static ref WND_HANDLERS: Mutex<WindowHandlers> = Mutex::new(WindowHandlers{hthread: 0, hprocess: 0});
@@ -59,17 +58,28 @@ struct WindowHandlers {
impl Drop for WindowHandlers {
fn drop(&mut self) {
self.reset();
}
}
impl WindowHandlers {
fn reset(&mut self) {
unsafe {
if self.hprocess != 0 {
let _res = TerminateProcess(self.hprocess as _, 0);
CloseHandle(self.hprocess as _);
}
self.hprocess = 0;
if self.hthread != 0 {
CloseHandle(self.hthread as _);
}
self.hthread = 0;
if self.hprocess != 0 {
CloseHandle(self.hprocess as _);
}
self.hprocess = 0;
}
}
fn is_default(&self) -> bool {
self.hthread == 0 && self.hprocess == 0
}
}
pub fn turn_on_privacy(conn_id: i32) -> ResultType<bool> {
@@ -85,7 +95,7 @@ pub fn turn_on_privacy(conn_id: i32) -> ResultType<bool> {
);
}
if !*DLL_FOUND.lock().unwrap() {
if WND_HANDLERS.lock().unwrap().is_default() {
log::info!("turn_on_privacy, dll not found when started, try start");
start()?;
std::thread::sleep(std::time::Duration::from_millis(1_000));
@@ -143,10 +153,10 @@ pub fn start() -> ResultType<()> {
}
let exe_file = std::env::current_exe()?;
if exe_file.parent().is_none() {
let Some(cur_dir) = exe_file
.parent() else {
bail!("Cannot get parent of current exe file");
}
let cur_dir = exe_file.parent().unwrap();
};
let dll_file = cur_dir.join("WindowInjection.dll");
if !dll_file.exists() {
@@ -156,8 +166,6 @@ pub fn start() -> ResultType<()> {
);
}
*DLL_FOUND.lock().unwrap() = true;
let hwnd = wait_find_privacy_hwnd(1_000)?;
if !hwnd.is_null() {
log::info!("Privacy window is ready");
@@ -257,6 +265,11 @@ pub fn start() -> ResultType<()> {
Ok(())
}
#[inline]
pub fn stop() {
WND_HANDLERS.lock().unwrap().reset();
}
unsafe fn inject_dll<'a>(hproc: HANDLE, hthread: HANDLE, dll_file: &'a str) -> ResultType<()> {
let dll_file_utf16: Vec<u16> = dll_file.encode_utf16().chain(Some(0).into_iter()).collect();
@@ -331,6 +344,7 @@ async fn set_privacy_mode_state(
}
pub(super) mod privacy_hook {
use super::*;
use std::sync::mpsc::{channel, Sender};
@@ -413,7 +427,7 @@ pub(super) mod privacy_hook {
}
Err(e) => {
// Fatal error
tx.send(format!("Unexpected err when hook {}", e)).unwrap();
allow_err!(tx.send(format!("Unexpected err when hook {}", e)));
return;
}
}

View File

@@ -52,6 +52,7 @@ impl RendezvousMediator {
}
pub async fn start_all() {
crate::hbbs_http::sync::start();
let mut nat_tested = false;
check_zombie();
let server = new_server();
@@ -72,6 +73,7 @@ impl RendezvousMediator {
allow_err!(super::lan::start_listening());
});
}
// It is ok to run xdesktop manager when the headless function is not allowed.
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
crate::platform::linux_desktop_manager::start_xdesktop();
@@ -88,7 +90,9 @@ impl RendezvousMediator {
for host in servers.clone() {
let server = server.clone();
futs.push(tokio::spawn(async move {
allow_err!(Self::start(server, host).await);
if let Err(err) = Self::start(server, host).await {
log::error!("rendezvous mediator error: {err}");
}
// SHOULD_EXIT here is to ensure once one exits, the others also exit.
SHOULD_EXIT.store(true, Ordering::SeqCst);
}));
@@ -183,16 +187,18 @@ impl RendezvousMediator {
}
Some(rendezvous_message::Union::RegisterPkResponse(rpr)) => {
update_latency();
match rpr.result.enum_value_or_default() {
register_pk_response::Result::OK => {
match rpr.result.enum_value() {
Ok(register_pk_response::Result::OK) => {
Config::set_key_confirmed(true);
Config::set_host_key_confirmed(&rz.host_prefix, true);
*SOLVING_PK_MISMATCH.lock().unwrap() = "".to_owned();
}
register_pk_response::Result::UUID_MISMATCH => {
Ok(register_pk_response::Result::UUID_MISMATCH) => {
allow_err!(rz.handle_uuid_mismatch(&mut socket).await);
}
_ => {}
_ => {
log::error!("unknown RegisterPkResponse");
}
}
}
Some(rendezvous_message::Union::PunchHole(ph)) => {
@@ -373,7 +379,7 @@ impl RendezvousMediator {
async fn handle_punch_hole(&self, ph: PunchHole, server: ServerPtr) -> ResultType<()> {
let relay_server = self.get_relay_server(ph.relay_server);
if ph.nat_type.enum_value_or_default() == NatType::SYMMETRIC
if ph.nat_type.enum_value() == Ok(NatType::SYMMETRIC)
|| Config::get_nat_type() == NatType::SYMMETRIC as i32
{
let uuid = Uuid::new_v4().to_string();
@@ -503,7 +509,8 @@ async fn direct_server(server: ServerPtr) {
let mut listener = None;
let mut port = 0;
loop {
let disabled = Config::get_option("direct-server").is_empty();
let disabled = Config::get_option("direct-server").is_empty()
|| !Config::get_option("stop-service").is_empty();
if !disabled && listener.is_none() {
port = get_direct_port();
match hbb_common::tcp::listen_any(port as _).await {
@@ -511,7 +518,7 @@ async fn direct_server(server: ServerPtr) {
listener = Some(l);
log::info!(
"Direct server listening on: {:?}",
listener.as_ref().unwrap().local_addr()
listener.as_ref().map(|l| l.local_addr())
);
}
Err(err) => {

View File

@@ -72,6 +72,7 @@ lazy_static::lazy_static! {
// for all initiative connections.
//
// [Note]
// ugly
// Now we use this [`CLIENT_SERVER`] to do following operations:
// - record local audio, and send to remote
pub static ref CLIENT_SERVER: ServerPtr = new();
@@ -90,7 +91,7 @@ pub fn new() -> ServerPtr {
let mut server = Server {
connections: HashMap::new(),
services: HashMap::new(),
id_count: 0,
id_count: hbb_common::rand::random::<i32>() % 1000 + 1000, // ensure positive
};
server.add_service(Box::new(audio_service::new()));
server.add_service(Box::new(video_service::new()));
@@ -128,11 +129,7 @@ pub async fn create_tcp_connection(
secure: bool,
) -> ResultType<()> {
let mut stream = stream;
let id = {
let mut w = server.write().unwrap();
w.id_count += 1;
w.id_count
};
let id = server.write().unwrap().get_new_id();
let (sk, pk) = Config::get_key_pair();
if secure && pk.len() == sign::PUBLICKEYBYTES && sk.len() == sign::SECRETKEYBYTES {
let mut sk_ = [0u8; sign::SECRETKEYBYTES];
@@ -305,9 +302,8 @@ impl Server {
// get a new unique id
pub fn get_new_id(&mut self) -> i32 {
let new_id = self.id_count;
self.id_count += 1;
new_id
self.id_count
}
}
@@ -367,13 +363,9 @@ pub async fn start_server(is_server: bool) {
log::info!("XAUTHORITY={:?}", std::env::var("XAUTHORITY"));
}
#[cfg(feature = "hwcodec")]
{
use std::sync::Once;
static ONCE: Once = Once::new();
ONCE.call_once(|| {
scrap::hwcodec::check_config_process();
})
}
scrap::hwcodec::check_config_process();
#[cfg(windows)]
hbb_common::platform::windows::start_cpu_performance_monitor();
if is_server {
crate::common::set_server_running(true);
@@ -383,16 +375,15 @@ pub async fn start_server(is_server: bool) {
std::process::exit(-1);
}
});
#[cfg(windows)]
crate::platform::windows::bootstrap();
input_service::fix_key_down_timeout_loop();
crate::hbbs_http::sync::start();
#[cfg(target_os = "linux")]
if crate::platform::current_is_wayland() {
allow_err!(input_service::setup_uinput(0, 1920, 0, 1080).await);
}
#[cfg(any(target_os = "macos", target_os = "linux"))]
tokio::spawn(async { sync_and_watch_config_dir().await });
#[cfg(target_os = "windows")]
crate::platform::try_kill_broker();
crate::RendezvousMediator::start_all().await;
} else {
match crate::ipc::connect(1000, "").await {

View File

@@ -13,10 +13,10 @@
// https://github.com/krruzic/pulsectl
use super::*;
use magnum_opus::{Application::*, Channels::*, Encoder};
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(not(any(target_os = "linux", target_os = "android")))]
use hbb_common::anyhow::anyhow;
use magnum_opus::{Application::*, Channels::*, Encoder};
use std::sync::atomic::{AtomicBool, Ordering};
pub const NAME: &'static str = "audio";
pub const AUDIO_DATA_SIZE_U8: usize = 960 * 4; // 10ms in 48000 stereo
@@ -206,13 +206,10 @@ mod cpal_impl {
}
}
}
if device.is_none() {
device = Some(
HOST.default_input_device()
.with_context(|| "Failed to get default input device for loopback")?,
);
}
let device = device.unwrap();
let device = device.unwrap_or(
HOST.default_input_device()
.with_context(|| "Failed to get default input device for loopback")?,
);
log::info!("Input device: {}", device.name().unwrap_or("".to_owned()));
let format = device
.default_input_config()

View File

@@ -39,7 +39,7 @@ use hbb_common::{
tokio_util::codec::{BytesCodec, Framed},
};
#[cfg(any(target_os = "android", target_os = "ios"))]
use scrap::android::call_main_service_mouse_input;
use scrap::android::call_main_service_pointer_input;
use serde_json::{json, value::Value};
use sha2::{Digest, Sha256};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -115,6 +115,8 @@ enum MessageInput {
Mouse((MouseEvent, i32)),
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Key((KeyEvent, bool)),
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Pointer((PointerDeviceEvent, i32)),
BlockOn,
BlockOff,
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
@@ -133,6 +135,14 @@ struct Session {
random_password: String,
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
struct StartCmIpcPara {
rx_to_cm: mpsc::UnboundedReceiver<ipc::Data>,
tx_from_cm: mpsc::UnboundedSender<ipc::Data>,
rx_desktop_ready: mpsc::Receiver<()>,
tx_cm_stream_ready: mpsc::Sender<()>,
}
pub struct Connection {
inner: ConnInner,
stream: super::Stream,
@@ -153,6 +163,7 @@ pub struct Connection {
restart: bool,
recording: bool,
last_test_delay: i64,
network_delay: Option<u32>,
lock_after_session_end: bool,
show_remote_cursor: bool,
// by peer
@@ -172,12 +183,12 @@ pub struct Connection {
tx_input: std_mpsc::Sender<MessageInput>,
// handle input messages
video_ack_required: bool,
peer_info: (String, String),
server_audit_conn: String,
server_audit_file: String,
lr: LoginRequest,
last_recv_time: Arc<Mutex<Instant>>,
chat_unanswered: bool,
file_transferred: bool,
#[cfg(windows)]
portable: PortableState,
from_switch: bool,
@@ -188,10 +199,11 @@ pub struct Connection {
pressed_modifiers: HashSet<rdev::Key>,
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
rx_cm_stream_ready: mpsc::Receiver<()>,
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
tx_desktop_ready: mpsc::Sender<()>,
linux_headless_handle: LinuxHeadlessHandle,
closed: bool,
delay_response_instant: Instant,
#[cfg(not(any(target_os = "android", target_os = "ios")))]
start_cm_ipc_para: Option<StartCmIpcPara>,
}
impl ConnInner {
@@ -261,6 +273,10 @@ impl Connection {
let (tx_cm_stream_ready, _rx_cm_stream_ready) = mpsc::channel(1);
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let (_tx_desktop_ready, rx_desktop_ready) = mpsc::channel(1);
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
let linux_headless_handle =
LinuxHeadlessHandle::new(_rx_cm_stream_ready, _tx_desktop_ready);
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let tx_cloned = tx.clone();
@@ -289,6 +305,7 @@ impl Connection {
restart: Connection::permission("enable-remote-restart"),
recording: Connection::permission("enable-record-session"),
last_test_delay: 0,
network_delay: None,
lock_after_session_end: false,
show_remote_cursor: false,
ip: "".to_owned(),
@@ -299,12 +316,12 @@ impl Connection {
disable_keyboard: false,
tx_input,
video_ack_required: false,
peer_info: Default::default(),
server_audit_conn: "".to_owned(),
server_audit_file: "".to_owned(),
lr: Default::default(),
last_recv_time: Arc::new(Mutex::new(Instant::now())),
chat_unanswered: false,
file_transferred: false,
#[cfg(windows)]
portable: Default::default(),
from_switch: false,
@@ -316,24 +333,24 @@ impl Connection {
pressed_modifiers: Default::default(),
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
rx_cm_stream_ready: _rx_cm_stream_ready,
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
tx_desktop_ready: _tx_desktop_ready,
linux_headless_handle,
closed: false,
delay_response_instant: Instant::now(),
#[cfg(not(any(target_os = "android", target_os = "ios")))]
start_cm_ipc_para: Some(StartCmIpcPara {
rx_to_cm,
tx_from_cm,
rx_desktop_ready,
tx_cm_stream_ready,
}),
};
let addr = hbb_common::try_into_v4(addr);
if !conn.on_open(addr).await {
conn.closed = true;
// sleep to ensure msg got received.
sleep(1.).await;
return;
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
tokio::spawn(async move {
if let Err(err) =
start_ipc(rx_to_cm, tx_from_cm, rx_desktop_ready, tx_cm_stream_ready).await
{
log::error!("ipc to connection manager exit: {}", err);
}
});
#[cfg(target_os = "android")]
start_channel(rx_to_cm, tx_from_cm);
if !conn.keyboard {
@@ -384,6 +401,7 @@ impl Connection {
}
ipc::Data::Close => {
conn.chat_unanswered = false; // seen
conn.file_transferred = false; //seen
conn.send_close_reason_no_retry("").await;
conn.on_close("connection manager", true).await;
break;
@@ -521,9 +539,17 @@ impl Connection {
},
_ = conn.file_timer.tick() => {
if !conn.read_jobs.is_empty() {
if let Err(err) = fs::handle_read_jobs(&mut conn.read_jobs, &mut conn.stream).await {
conn.on_close(&err.to_string(), false).await;
break;
conn.send_to_cm(ipc::Data::FileTransferLog(fs::serialize_transfer_jobs(&conn.read_jobs)));
match fs::handle_read_jobs(&mut conn.read_jobs, &mut conn.stream).await {
Ok(log) => {
if !log.is_empty() {
conn.send_to_cm(ipc::Data::FileTransferLog(log));
}
}
Err(err) => {
conn.on_close(&err.to_string(), false).await;
break;
}
}
} else {
conn.file_timer = time::interval_at(Instant::now() + SEC30, SEC30);
@@ -563,7 +589,7 @@ impl Connection {
match &m.union {
Some(misc::Union::StopService(_)) => {
conn.send_close_reason_no_retry("").await;
conn.on_close("stop service", true).await;
conn.on_close("stop service", false).await;
break;
}
_ => {},
@@ -586,18 +612,19 @@ impl Connection {
break;
}
let time = get_time();
let mut qos = video_service::VIDEO_QOS.lock().unwrap();
if time > 0 && conn.last_test_delay == 0 {
conn.last_test_delay = time;
let mut msg_out = Message::new();
let qos = video_service::VIDEO_QOS.lock().unwrap();
msg_out.set_test_delay(TestDelay{
time,
last_delay:qos.current_delay,
target_bitrate:qos.target_bitrate,
last_delay:conn.network_delay.unwrap_or_default(),
target_bitrate: qos.bitrate(),
..Default::default()
});
conn.inner.send(msg_out.into());
}
qos.user_delay_response_elapsed(conn.inner.id(), conn.delay_response_instant.elapsed().as_millis());
}
}
}
@@ -617,7 +644,6 @@ impl Connection {
);
video_service::notify_video_frame_fetched(id, None);
scrap::codec::Encoder::update(id, scrap::codec::EncodingUpdate::Remove);
video_service::VIDEO_QOS.lock().unwrap().reset();
if conn.authorized {
password::update_temporary_password();
}
@@ -634,6 +660,7 @@ impl Connection {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
try_stop_record_cursor_pos();
}
conn.on_close("End", true).await;
log::info!("#{} connection loop exited", id);
}
@@ -655,15 +682,18 @@ impl Connection {
}
MessageInput::Key((mut msg, press)) => {
// todo: press and down have similar meanings.
if press && msg.mode.unwrap() == KeyboardMode::Legacy {
if press && msg.mode.enum_value() == Ok(KeyboardMode::Legacy) {
msg.down = true;
}
handle_key(&msg);
if press && msg.mode.unwrap() == KeyboardMode::Legacy {
if press && msg.mode.enum_value() == Ok(KeyboardMode::Legacy) {
msg.down = false;
handle_key(&msg);
}
}
MessageInput::Pointer((msg, id)) => {
handle_pointer(&msg, id);
}
MessageInput::BlockOn => {
if crate::platform::block_input(true) {
block_input_mode = true;
@@ -867,6 +897,7 @@ impl Connection {
v["id"] = json!(Config::get_id());
v["uuid"] = json!(crate::encode64(hbb_common::get_uuid()));
v["conn_id"] = json!(self.inner.id);
v["session_id"] = json!(self.lr.session_id);
tokio::spawn(async move {
allow_err!(Self::post_audit_async(url, v).await);
});
@@ -942,13 +973,14 @@ impl Connection {
} else {
0
};
self.post_conn_audit(json!({"peer": self.peer_info, "type": conn_type}));
self.post_conn_audit(
json!({"peer": ((&self.lr.my_id, &self.lr.my_name)), "type": conn_type}),
);
#[allow(unused_mut)]
let mut username = crate::platform::get_active_username();
let mut res = LoginResponse::new();
let mut pi = PeerInfo {
username: username.clone(),
conn_id: self.inner.id,
version: VERSION.to_owned(),
..Default::default()
};
@@ -971,8 +1003,10 @@ impl Connection {
}
#[cfg(feature = "linux_headless")]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
if linux_desktop_manager::is_headless() {
platform_additions.insert("headless".into(), json!(true));
if crate::platform::is_headless_allowed() {
if linux_desktop_manager::is_headless() {
platform_additions.insert("headless".into(), json!(true));
}
}
if !platform_additions.is_empty() {
pi.platform_additions =
@@ -995,10 +1029,15 @@ impl Connection {
if dtype != crate::platform::linux::DISPLAY_SERVER_X11
&& dtype != crate::platform::linux::DISPLAY_SERVER_WAYLAND
{
res.set_error(format!(
"Unsupported display server type \"{}\", x11 or wayland expected",
dtype
));
let msg = if crate::platform::linux::is_login_screen_wayland() {
crate::client::LOGIN_SCREEN_WAYLAND.to_owned()
} else {
format!(
"Unsupported display server type \"{}\", x11 or wayland expected",
dtype
)
};
res.set_error(msg);
let mut msg_out = Message::new();
msg_out.set_login_response(res);
self.send(msg_out).await;
@@ -1032,13 +1071,15 @@ impl Connection {
..Default::default()
})
.into();
// `try_reset_current_display` is needed because `get_displays` may change the current display,
// which may cause the mismatch of current display and the current display name.
#[cfg(not(any(target_os = "android", target_os = "ios")))]
video_service::try_reset_current_display();
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
pi.resolutions = Some(SupportedResolutions {
resolutions: video_service::get_current_display_name()
.map(|name| crate::platform::resolutions(&name))
resolutions: video_service::get_current_display()
.map(|(_, _, d)| crate::platform::resolutions(&d.name()))
.unwrap_or(vec![]),
..Default::default()
})
@@ -1121,7 +1162,6 @@ impl Connection {
}
fn try_start_cm(&mut self, peer_id: String, name: String, authorized: bool) {
self.peer_info = (peer_id.clone(), name.clone());
self.send_to_cm(ipc::Data::Login {
id: self.inner.id(),
is_file_transfer: self.file_transfer.is_some(),
@@ -1175,6 +1215,14 @@ impl Connection {
self.tx_input.send(MessageInput::Mouse((msg, conn_id))).ok();
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn input_pointer(&self, msg: PointerDeviceEvent, conn_id: i32) {
self.tx_input
.send(MessageInput::Pointer((msg, conn_id)))
.ok();
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn input_key(&self, msg: KeyEvent, press: bool) {
@@ -1228,6 +1276,7 @@ impl Connection {
.lock()
.unwrap()
.retain(|_, s| s.last_recv_time.lock().unwrap().elapsed() < SESSION_TIMEOUT);
// last_recv_time is a mutex variable shared with connection, can be updated lively.
if let Some(session) = session {
if session.name == self.lr.my_name
&& session.session_id == self.lr.session_id
@@ -1287,6 +1336,24 @@ impl Connection {
self.video_ack_required = lr.video_ack_required;
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn try_start_cm_ipc(&mut self) {
if let Some(p) = self.start_cm_ipc_para.take() {
tokio::spawn(async move {
if let Err(err) = start_ipc(
p.rx_to_cm,
p.tx_from_cm,
p.rx_desktop_ready,
p.tx_cm_stream_ready,
)
.await
{
log::error!("ipc to connection manager exit: {}", err);
}
});
}
}
async fn on_message(&mut self, msg: Message) -> bool {
if let Some(message::Union::LoginRequest(lr)) = msg.union {
self.handle_login_request_without_validation(&lr).await;
@@ -1350,28 +1417,25 @@ impl Connection {
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
self.try_start_cm_ipc();
#[cfg(any(
feature = "flatpak",
feature = "appimage",
not(all(target_os = "linux", feature = "linux_headless"))
))]
let err_msg = "".to_owned();
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
let desktop_err = match lr.os_login.as_ref() {
Some(os_login) => {
linux_desktop_manager::try_start_desktop(&os_login.username, &os_login.password)
}
None => linux_desktop_manager::try_start_desktop("", ""),
};
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
let is_headless = linux_desktop_manager::is_headless();
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
let wait_ipc_timeout = 10_000;
let err_msg = self
.linux_headless_handle
.try_start_desktop(lr.os_login.as_ref());
// If err is LOGIN_MSG_DESKTOP_SESSION_NOT_READY, just keep this msg and go on checking password.
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
if !desktop_err.is_empty()
&& desktop_err != crate::client::LOGIN_MSG_DESKTOP_SESSION_NOT_READY
if !err_msg.is_empty() && err_msg != crate::client::LOGIN_MSG_DESKTOP_SESSION_NOT_READY
{
self.send_login_error(desktop_err).await;
self.send_login_error(err_msg).await;
return true;
}
@@ -1399,34 +1463,20 @@ impl Connection {
self.send_login_error("Connection not allowed").await;
return false;
} else if self.is_recent_session() {
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
if desktop_err.is_empty() {
#[cfg(target_os = "linux")]
if is_headless {
self.tx_desktop_ready.send(()).await.ok();
let _res = timeout(wait_ipc_timeout, self.rx_cm_stream_ready.recv()).await;
}
self.try_start_cm(lr.my_id, lr.my_name, true);
if err_msg.is_empty() {
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
self.linux_headless_handle.wait_desktop_cm_ready().await;
self.try_start_cm(lr.my_id.clone(), lr.my_name.clone(), true);
self.send_logon_response().await;
if self.port_forward_socket.is_some() {
return false;
}
} else {
self.send_login_error(desktop_err).await;
}
#[cfg(not(all(target_os = "linux", feature = "linux_headless")))]
{
self.try_start_cm(lr.my_id, lr.my_name, true);
self.send_logon_response().await;
if self.port_forward_socket.is_some() {
return false;
}
self.send_login_error(err_msg).await;
}
} else if lr.password.is_empty() {
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
if desktop_err.is_empty() {
if err_msg.is_empty() {
self.try_start_cm(lr.my_id, lr.my_name, false);
} else {
self.send_login_error(
@@ -1434,8 +1484,6 @@ impl Connection {
)
.await;
}
#[cfg(not(all(target_os = "linux", feature = "linux_headless")))]
self.try_start_cm(lr.my_id, lr.my_name, false);
} else {
let mut failure = LOGIN_FAILURES
.lock()
@@ -1448,17 +1496,21 @@ impl Connection {
self.send_login_error("Too many wrong password attempts")
.await;
Self::post_alarm_audit(
AlarmAuditType::ManyWrongPassword,
AlarmAuditType::ExceedThirtyAttempts,
json!({
"ip":self.ip,
"id":lr.my_id.clone(),
"name": lr.my_name.clone(),
}),
);
} else if time == failure.0 && failure.1 > 6 {
self.send_login_error("Please try 1 minute later").await;
Self::post_alarm_audit(
AlarmAuditType::FrequentAttempt,
AlarmAuditType::SixAttemptsWithinOneMinute,
json!({
"ip":self.ip,
"id":lr.my_id.clone(),
"name": lr.my_name.clone(),
}),
);
} else if !self.validate_password() {
@@ -1474,9 +1526,7 @@ impl Connection {
.lock()
.unwrap()
.insert(self.ip.clone(), failure);
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
if desktop_err.is_empty() {
if err_msg.is_empty() {
self.send_login_error(crate::client::LOGIN_MSG_PASSWORD_WRONG)
.await;
self.try_start_cm(lr.my_id, lr.my_name, false);
@@ -1486,40 +1536,21 @@ impl Connection {
)
.await;
}
#[cfg(not(all(target_os = "linux", feature = "linux_headless")))]
{
self.send_login_error(crate::client::LOGIN_MSG_PASSWORD_WRONG)
.await;
self.try_start_cm(lr.my_id, lr.my_name, false);
}
} else {
if failure.0 != 0 {
LOGIN_FAILURES.lock().unwrap().remove(&self.ip);
}
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
if desktop_err.is_empty() {
#[cfg(target_os = "linux")]
if is_headless {
self.tx_desktop_ready.send(()).await.ok();
let _res =
timeout(wait_ipc_timeout, self.rx_cm_stream_ready.recv()).await;
}
if err_msg.is_empty() {
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
self.linux_headless_handle.wait_desktop_cm_ready().await;
self.send_logon_response().await;
self.try_start_cm(lr.my_id, lr.my_name, true);
if self.port_forward_socket.is_some() {
return false;
}
} else {
self.send_login_error(desktop_err).await;
}
#[cfg(not(all(target_os = "linux", feature = "linux_headless")))]
{
self.send_logon_response().await;
self.try_start_cm(lr.my_id, lr.my_name, true);
if self.port_forward_socket.is_some() {
return false;
}
self.send_login_error(err_msg).await;
}
}
}
@@ -1534,7 +1565,9 @@ impl Connection {
video_service::VIDEO_QOS
.lock()
.unwrap()
.update_network_delay(new_delay);
.user_network_delay(self.inner.id(), new_delay);
self.network_delay = Some(new_delay);
self.delay_response_instant = Instant::now();
}
} else if let Some(message::Union::SwitchSidesResponse(_s)) = msg.union {
#[cfg(feature = "flutter")]
@@ -1551,6 +1584,8 @@ impl Connection {
self.from_switch = true;
self.try_start_cm(lr.my_id.clone(), lr.my_name.clone(), true);
self.send_logon_response().await;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
self.try_start_cm_ipc();
}
}
}
@@ -1559,8 +1594,8 @@ impl Connection {
match msg.union {
Some(message::Union::MouseEvent(me)) => {
#[cfg(any(target_os = "android", target_os = "ios"))]
if let Err(e) = call_main_service_mouse_input(me.mask, me.x, me.y) {
log::debug!("call_main_service_mouse_input fail:{}", e);
if let Err(e) = call_main_service_pointer_input("mouse", me.mask, me.x, me.y) {
log::debug!("call_main_service_pointer_input fail:{}", e);
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if self.peer_keyboard_enabled() {
@@ -1572,6 +1607,41 @@ impl Connection {
self.input_mouse(me, self.inner.id());
}
}
Some(message::Union::PointerDeviceEvent(pde)) => {
#[cfg(any(target_os = "android", target_os = "ios"))]
if let Err(e) = match pde.union {
Some(pointer_device_event::Union::TouchEvent(touch)) => match touch.union {
Some(touch_event::Union::PanStart(pan_start)) => {
call_main_service_pointer_input(
"touch",
4,
pan_start.x,
pan_start.y,
)
}
Some(touch_event::Union::PanUpdate(pan_update)) => {
call_main_service_pointer_input(
"touch",
5,
pan_update.x,
pan_update.y,
)
}
Some(touch_event::Union::PanEnd(pan_end)) => {
call_main_service_pointer_input("touch", 6, pan_end.x, pan_end.y)
}
_ => Ok(()),
},
_ => Ok(()),
} {
log::debug!("call_main_service_pointer_input fail:{}", e);
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if self.peer_keyboard_enabled() {
MOUSE_MOVE_TIME.store(get_time(), Ordering::SeqCst);
self.input_pointer(pde, self.inner.id());
}
}
#[cfg(any(target_os = "android", target_os = "ios"))]
Some(message::Union::KeyEvent(..)) => {}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -1589,11 +1659,11 @@ impl Connection {
me.press
};
let key = match me.mode.enum_value_or_default() {
KeyboardMode::Map => {
let key = match me.mode.enum_value() {
Ok(KeyboardMode::Map) => {
Some(crate::keyboard::keycode_to_rdev_key(me.chr()))
}
KeyboardMode::Translate => {
Ok(KeyboardMode::Translate) => {
if let Some(key_event::Union::Chr(code)) = me.union {
Some(crate::keyboard::keycode_to_rdev_key(code & 0x0000FFFF))
} else {
@@ -1658,6 +1728,7 @@ impl Connection {
}
}
Some(file_action::Union::Send(s)) => {
// server to client
let id = s.id;
let od = can_enable_overwrite_detection(get_version_number(
&self.lr.version,
@@ -1675,10 +1746,12 @@ impl Connection {
Err(err) => {
self.send(fs::new_error(id, err, 0)).await;
}
Ok(job) => {
Ok(mut job) => {
self.send(fs::new_dir(id, path, job.files().to_vec()))
.await;
let mut files = job.files().to_owned();
job.is_remote = true;
job.conn_id = self.inner.id();
self.read_jobs.push(job);
self.file_timer = time::interval(MILLI1);
self.post_file_audit(
@@ -1692,8 +1765,10 @@ impl Connection {
);
}
}
self.file_transferred = true;
}
Some(file_action::Union::Receive(r)) => {
// client to server
// note: 1.1.10 introduced identical file detection, which breaks original logic of send/recv files
// whenever got send/recv request, check peer version to ensure old version of rustdesk
let od = can_enable_overwrite_detection(get_version_number(
@@ -1710,6 +1785,8 @@ impl Connection {
.map(|f| (f.name, f.modified_time))
.collect(),
overwrite_detection: od,
total_size: r.total_size,
conn_id: self.inner.id(),
});
self.post_file_audit(
FileAuditType::RemoteReceive,
@@ -1721,6 +1798,7 @@ impl Connection {
.collect(),
json!({}),
);
self.file_transferred = true;
}
Some(file_action::Union::RemoveDir(d)) => {
self.send_fs(ipc::FS::RemoveDir {
@@ -1744,6 +1822,11 @@ impl Connection {
}
Some(file_action::Union::Cancel(c)) => {
self.send_fs(ipc::FS::CancelWrite { id: c.id });
if let Some(job) = fs::get_job_immutable(c.id, &self.read_jobs) {
self.send_to_cm(ipc::Data::FileTransferLog(
fs::serialize_transfer_job(job, false, true, ""),
));
}
fs::remove_job(c.id, &mut self.read_jobs);
}
Some(file_action::Union::SendConfirm(r)) => {
@@ -1832,42 +1915,17 @@ impl Connection {
}
}
}
#[cfg(windows)]
Some(misc::Union::ElevationRequest(r)) => match r.union {
Some(elevation_request::Union::Direct(_)) => {
#[cfg(windows)]
{
let mut err = "No need to elevate".to_string();
if !crate::platform::is_installed() && !portable_client::running() {
err = portable_client::start_portable_service(
portable_client::StartPara::Direct,
)
.err()
.map_or("".to_string(), |e| e.to_string());
}
let mut misc = Misc::new();
misc.set_elevation_response(err);
let mut msg = Message::new();
msg.set_misc(misc);
self.send(msg).await;
}
self.handle_elevation_request(portable_client::StartPara::Direct)
.await;
}
Some(elevation_request::Union::Logon(_r)) => {
#[cfg(windows)]
{
let mut err = "No need to elevate".to_string();
if !crate::platform::is_installed() && !portable_client::running() {
err = portable_client::start_portable_service(
portable_client::StartPara::Logon(_r.username, _r.password),
)
.err()
.map_or("".to_string(), |e| e.to_string());
}
let mut misc = Misc::new();
misc.set_elevation_response(err);
let mut msg = Message::new();
msg.set_misc(misc);
self.send(msg).await;
}
Some(elevation_request::Union::Logon(r)) => {
self.handle_elevation_request(portable_client::StartPara::Logon(
r.username, r.password,
))
.await;
}
_ => {}
},
@@ -1876,11 +1934,9 @@ impl Connection {
// Drop the audio sender previously.
drop(std::mem::replace(&mut self.audio_sender, None));
self.audio_sender = Some(start_audio_thread());
allow_err!(self
.audio_sender
self.audio_sender
.as_ref()
.unwrap()
.send(MediaData::AudioFormat(format)));
.map(|a| allow_err!(a.send(MediaData::AudioFormat(format))));
}
}
#[cfg(feature = "flutter")]
@@ -1906,6 +1962,18 @@ impl Connection {
crate::plugin::handle_client_event(&p.id, &self.lr.my_id, &p.content);
self.send(msg).await;
}
Some(misc::Union::FullSpeedFps(fps)) => video_service::VIDEO_QOS
.lock()
.unwrap()
.user_full_speed_fps(self.inner.id(), fps),
Some(misc::Union::AutoAdjustFps(fps)) => video_service::VIDEO_QOS
.lock()
.unwrap()
.user_auto_adjust_fps(self.inner.id(), fps),
Some(misc::Union::ClientRecordStatus(status)) => video_service::VIDEO_QOS
.lock()
.unwrap()
.user_record(self.inner.id(), status),
_ => {}
},
Some(message::Union::AudioFrame(frame)) => {
@@ -1940,10 +2008,26 @@ impl Connection {
true
}
#[cfg(windows)]
async fn handle_elevation_request(&mut self, para: portable_client::StartPara) {
let mut err = "No need to elevate".to_string();
if !crate::platform::is_installed() && !portable_client::running() {
err = portable_client::start_portable_service(para)
.err()
.map_or("".to_string(), |e| e.to_string());
}
let mut misc = Misc::new();
misc.set_elevation_response(err);
let mut msg = Message::new();
msg.set_misc(misc);
self.send(msg).await;
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn change_resolution(&mut self, r: &Resolution) {
if self.keyboard {
if let Ok(name) = video_service::get_current_display_name() {
if let Ok((_, _, display)) = video_service::get_current_display() {
let name = display.name();
#[cfg(all(windows, feature = "virtual_display_driver"))]
if let Some(_ok) =
crate::virtual_display_manager::change_resolution_if_is_virtual_display(
@@ -1954,6 +2038,11 @@ impl Connection {
{
return;
}
video_service::set_last_changed_resolution(
&name,
(display.width() as _, display.height() as _),
(r.width, r.height),
);
if let Err(e) =
crate::platform::change_resolution(&name, r.width as _, r.height as _)
{
@@ -2020,14 +2109,14 @@ impl Connection {
video_service::VIDEO_QOS
.lock()
.unwrap()
.update_image_quality(image_quality);
.user_image_quality(self.inner.id(), image_quality);
}
}
if o.custom_fps > 0 {
video_service::VIDEO_QOS
.lock()
.unwrap()
.update_user_fps(o.custom_fps as _);
.user_custom_fps(self.inner.id(), o.custom_fps as _);
}
if let Some(q) = o.supported_decoding.clone().take() {
scrap::codec::Encoder::update(self.inner.id(), scrap::codec::EncodingUpdate::New(q));
@@ -2179,13 +2268,17 @@ impl Connection {
}
async fn on_close(&mut self, reason: &str, lock: bool) {
if self.closed {
return;
}
self.closed = true;
log::info!("#{} Connection closed: {}", self.inner.id(), reason);
if lock && self.lock_after_session_end && self.keyboard {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
lock_screen().await;
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let data = if self.chat_unanswered {
let data = if self.chat_unanswered || self.file_transferred {
ipc::Data::Disconnected
} else {
ipc::Data::Close
@@ -2301,6 +2394,8 @@ async fn start_ipc(
mut _rx_desktop_ready: mpsc::Receiver<()>,
tx_stream_ready: mpsc::Sender<()>,
) -> ResultType<()> {
use hbb_common::anyhow::anyhow;
loop {
if !crate::platform::is_prelogin() {
break;
@@ -2312,23 +2407,17 @@ async fn start_ipc(
stream = Some(s);
} else {
let mut args = vec!["--cm"];
if password::hide_cm() {
if crate::hbbs_http::sync::is_pro() && password::hide_cm() {
args.push("--hide");
};
}
#[allow(unused_mut)]
#[cfg(target_os = "linux")]
#[cfg(not(feature = "linux_headless"))]
let user = None;
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(any(feature = "flatpak", feature = "appimage"))]
let user = None;
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
let mut user = None;
// Cm run as user, wait until desktop session is ready.
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
if linux_desktop_manager::is_headless() {
if crate::platform::is_headless_allowed() && linux_desktop_manager::is_headless() {
let mut username = linux_desktop_manager::get_username();
loop {
if !username.is_empty() {
@@ -2394,7 +2483,7 @@ async fn start_ipc(
}
let _res = tx_stream_ready.send(()).await;
let mut stream = stream.unwrap();
let mut stream = stream.ok_or(anyhow!("none stream"))?;
loop {
tokio::select! {
res = stream.next() => {
@@ -2491,8 +2580,8 @@ mod privacy_mode {
pub enum AlarmAuditType {
IpWhitelist = 0,
ManyWrongPassword = 1,
FrequentAttempt = 2,
ExceedThirtyAttempts = 1,
SixAttemptsWithinOneMinute = 2,
}
pub enum FileAuditType {
@@ -2527,6 +2616,52 @@ impl Drop for Connection {
}
}
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
struct LinuxHeadlessHandle {
pub is_headless_allowed: bool,
pub is_headless: bool,
pub wait_ipc_timeout: u64,
pub rx_cm_stream_ready: mpsc::Receiver<()>,
pub tx_desktop_ready: mpsc::Sender<()>,
}
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
impl LinuxHeadlessHandle {
pub fn new(rx_cm_stream_ready: mpsc::Receiver<()>, tx_desktop_ready: mpsc::Sender<()>) -> Self {
let is_headless_allowed = crate::platform::is_headless_allowed();
let is_headless = is_headless_allowed && linux_desktop_manager::is_headless();
Self {
is_headless_allowed,
is_headless,
wait_ipc_timeout: 10_000,
rx_cm_stream_ready,
tx_desktop_ready,
}
}
pub fn try_start_desktop(&mut self, os_login: Option<&OSLogin>) -> String {
if self.is_headless_allowed {
match os_login {
Some(os_login) => {
linux_desktop_manager::try_start_desktop(&os_login.username, &os_login.password)
}
None => linux_desktop_manager::try_start_desktop("", ""),
}
} else {
"".to_string()
}
}
pub async fn wait_desktop_cm_ready(&mut self) {
if self.is_headless {
self.tx_desktop_ready.send(()).await.ok();
let _res = timeout(self.wait_ipc_timeout, self.rx_cm_stream_ready.recv()).await;
}
}
}
mod raii {
use super::*;
pub struct ConnectionID(i32);
@@ -2550,6 +2685,14 @@ mod raii {
if active_conns_lock.is_empty() {
video_service::try_plug_out_virtual_display();
}
#[cfg(all(windows))]
if active_conns_lock.is_empty() {
crate::privacy_win_mag::stop();
}
video_service::VIDEO_QOS
.lock()
.unwrap()
.on_connection_close(self.0);
}
}
}

View File

@@ -1,13 +1,17 @@
use super::*;
use crate::input::*;
#[cfg(target_os = "macos")]
use crate::common::is_server;
#[cfg(target_os = "linux")]
use crate::common::IS_X11;
use crate::input::*;
#[cfg(target_os = "macos")]
use dispatch::Queue;
use enigo::{Enigo, Key, KeyboardControllable, MouseButton, MouseControllable};
use hbb_common::{get_time, protobuf::EnumOrUnknown};
use hbb_common::{
get_time,
message_proto::{pointer_device_event::Union::TouchEvent, touch_event::Union::ScaleUpdate},
protobuf::EnumOrUnknown,
};
use rdev::{self, EventType, Key as RdevKey, KeyCode, RawKey};
#[cfg(target_os = "macos")]
use rdev::{CGEventSourceStateID, CGEventTapLocation, VirtualInput};
@@ -523,6 +527,21 @@ pub fn handle_mouse(evt: &MouseEvent, conn: i32) {
handle_mouse_(evt, conn);
}
// to-do: merge handle_mouse and handle_pointer
pub fn handle_pointer(evt: &PointerDeviceEvent, conn: i32) {
#[cfg(target_os = "macos")]
if !is_server() {
// having GUI, run main GUI thread, otherwise crash
let evt = evt.clone();
QUEUE.exec_async(move || handle_pointer_(&evt, conn));
return;
}
#[cfg(windows)]
crate::portable_service::client::handle_pointer(evt, conn);
#[cfg(not(windows))]
handle_pointer_(evt, conn);
}
pub fn fix_key_down_timeout_loop() {
std::thread::spawn(move || loop {
std::thread::sleep(std::time::Duration::from_millis(10_000));
@@ -688,7 +707,10 @@ fn get_last_input_cursor_pos() -> (i32, i32) {
(lock.x, lock.y)
}
// check if mouse is moved by the controlled side user to make controlled side has higher mouse priority than remote.
fn active_mouse_(conn: i32) -> bool {
true
/* this method is buggy (not working on macOS, making fast moving mouse event discarded here) and added latency (this is blocking way, must do in async way), so we disable it for now
// out of time protection
if LATEST_SYS_CURSOR_POS.lock().unwrap().0.elapsed() > MOUSE_MOVE_PROTECTION_TIMEOUT {
return true;
@@ -741,6 +763,28 @@ fn active_mouse_(conn: i32) -> bool {
}
None => true,
}
*/
}
pub fn handle_pointer_(evt: &PointerDeviceEvent, conn: i32) {
if !active_mouse_(conn) {
return;
}
if EXITING.load(Ordering::SeqCst) {
return;
}
match &evt.union {
Some(TouchEvent(evt)) => match &evt.union {
Some(ScaleUpdate(_scale_evt)) => {
#[cfg(target_os = "windows")]
handle_scale(_scale_evt.scale);
}
_ => {}
},
_ => {}
}
}
pub fn handle_mouse_(evt: &MouseEvent, conn: i32) {
@@ -759,7 +803,7 @@ pub fn handle_mouse_(evt: &MouseEvent, conn: i32) {
let mut en = ENIGO.lock().unwrap();
#[cfg(not(target_os = "macos"))]
let mut to_release = Vec::new();
if evt_type == 1 {
if evt_type == MOUSE_TYPE_DOWN {
fix_modifiers(&evt.modifiers[..], &mut en, 0);
#[cfg(target_os = "macos")]
en.reset_flag();
@@ -885,6 +929,18 @@ pub fn handle_mouse_(evt: &MouseEvent, conn: i32) {
}
}
#[cfg(target_os = "windows")]
fn handle_scale(scale: i32) {
let mut en = ENIGO.lock().unwrap();
if scale == 0 {
en.key_up(Key::Control);
} else {
if en.key_down(Key::Control).is_ok() {
en.mouse_scroll_y(scale);
}
}
}
pub fn is_enter(evt: &KeyEvent) -> bool {
if let Some(key_event::Union::ControlKey(ck)) = evt.union {
if ck.value() == ControlKey::Return.value() || ck.value() == ControlKey::NumpadEnter.value()
@@ -1445,11 +1501,11 @@ pub fn handle_key_(evt: &KeyEvent) {
_ => {}
};
match evt.mode.unwrap() {
KeyboardMode::Map => {
match evt.mode.enum_value() {
Ok(KeyboardMode::Map) => {
map_keyboard_mode(evt);
}
KeyboardMode::Translate => {
Ok(KeyboardMode::Translate) => {
translate_keyboard_mode(evt);
}
_ => {

View File

@@ -222,6 +222,8 @@ mod utils {
// functions called in separate SYSTEM user process.
pub mod server {
use hbb_common::message_proto::PointerDeviceEvent;
use super::*;
lazy_static::lazy_static! {
@@ -229,7 +231,13 @@ pub mod server {
}
pub fn run_portable_service() {
let shmem = Arc::new(SharedMemory::open_existing(SHMEM_NAME).unwrap());
let shmem = match SharedMemory::open_existing(SHMEM_NAME) {
Ok(shmem) => Arc::new(shmem),
Err(e) => {
log::error!("Failed to open existing shared memory: {:?}", e);
return;
}
};
let shmem1 = shmem.clone();
let shmem2 = shmem.clone();
let mut threads = vec![];
@@ -247,7 +255,7 @@ pub mod server {
}));
let record_pos_handle = crate::input_service::try_start_record_cursor_pos();
for th in threads.drain(..) {
th.join().unwrap();
th.join().ok();
log::info!("thread joined");
}
@@ -317,7 +325,11 @@ pub mod server {
}
if c.is_none() {
*crate::video_service::CURRENT_DISPLAY.lock().unwrap() = current_display;
let (_, _current, display) = get_current_display().unwrap();
let Ok((_, _current, display)) = get_current_display() else {
log::error!("Failed to get current display");
*EXIT.lock().unwrap() = true;
return;
};
display_width = display.width();
display_height = display.height();
match Capturer::new(display, use_yuv) {
@@ -378,8 +390,8 @@ pub mod server {
continue;
}
}
match c.as_mut().unwrap().frame(spf) {
Ok(f) => {
match c.as_mut().map(|f| f.frame(spf)) {
Some(Ok(f)) => {
utils::set_frame_info(
&shmem,
FrameInfo {
@@ -394,7 +406,7 @@ pub mod server {
first_frame_captured = true;
dxgi_failed_times = 0;
}
Err(e) => {
Some(Err(e)) => {
if e.kind() != std::io::ErrorKind::WouldBlock {
// DXGI_ERROR_INVALID_CALL after each success on Microsoft GPU driver
// log::error!("capture frame failed:{:?}", e);
@@ -404,7 +416,8 @@ pub mod server {
std::thread::sleep(spf);
continue;
}
if !c.as_ref().unwrap().is_gdi() {
if c.as_ref().map(|c| c.is_gdi()) == Some(false) {
// nog gdi
dxgi_failed_times += 1;
}
if dxgi_failed_times > MAX_DXGI_FAIL_TIME {
@@ -416,6 +429,9 @@ pub mod server {
shmem.write(ADDR_CAPTURE_WOULDBLOCK, &utils::i32_to_vec(TRUE));
}
}
_ => {
println!("unreachable!");
}
}
}
}
@@ -466,6 +482,11 @@ pub mod server {
crate::input_service::handle_mouse_(&evt, conn);
}
}
Pointer((v, conn)) => {
if let Ok(evt) = PointerDeviceEvent::parse_from_bytes(&v) {
crate::input_service::handle_pointer_(&evt, conn);
}
}
Key(v) => {
if let Ok(evt) = KeyEvent::parse_from_bytes(&v) {
crate::input_service::handle_key_(&evt);
@@ -499,7 +520,7 @@ pub mod server {
// functions called in main process.
pub mod client {
use hbb_common::anyhow::Context;
use hbb_common::{anyhow::Context, message_proto::PointerDeviceEvent};
use super::*;
@@ -864,6 +885,14 @@ pub mod client {
))))
}
fn handle_pointer_(evt: &PointerDeviceEvent, conn: i32) -> ResultType<()> {
let mut v = vec![];
evt.write_to_vec(&mut v)?;
ipc_send(Data::DataPortableService(DataPortableService::Pointer((
v, conn,
))))
}
fn handle_key_(evt: &KeyEvent) -> ResultType<()> {
let mut v = vec![];
evt.write_to_vec(&mut v)?;
@@ -910,6 +939,15 @@ pub mod client {
}
}
pub fn handle_pointer(evt: &PointerDeviceEvent, conn: i32) {
if RUNNING.lock().unwrap().clone() {
crate::input_service::update_latest_input_cursor_time(conn);
handle_pointer_(evt, conn).ok();
} else {
crate::input_service::handle_pointer_(evt, conn);
}
}
pub fn handle_key(evt: &KeyEvent) {
if RUNNING.lock().unwrap().clone() {
handle_key_(evt).ok();

View File

@@ -1,8 +1,9 @@
use super::*;
use scrap::codec::Quality;
use std::time::Duration;
pub const FPS: u8 = 30;
pub const MIN_FPS: u8 = 1;
pub const MAX_FPS: u8 = 120;
pub const FPS: u32 = 30;
pub const MIN_FPS: u32 = 1;
pub const MAX_FPS: u32 = 120;
trait Percent {
fn as_percent(&self) -> u32;
}
@@ -18,22 +19,34 @@ impl Percent for ImageQuality {
}
}
pub struct VideoQoS {
width: u32,
height: u32,
user_image_quality: u32,
current_image_quality: u32,
enable_abr: bool,
pub current_delay: u32,
pub fps: u8, // abr
pub user_fps: u8,
pub target_bitrate: u32, // abr
updated: bool,
#[derive(Default, Debug, Copy, Clone)]
struct Delay {
state: DelayState,
debounce_count: u32,
staging_state: DelayState,
delay: u32,
counter: u32,
slower_than_old_state: Option<bool>,
}
#[derive(PartialEq, Debug)]
#[derive(Default, Debug, Copy, Clone)]
struct UserData {
full_speed_fps: Option<u32>,
auto_adjust_fps: Option<u32>,
custom_fps: Option<u32>,
quality: Option<(i64, Quality)>, // (time, quality)
delay: Option<Delay>,
response_delayed: bool,
record: bool,
}
pub struct VideoQoS {
fps: u32,
quality: Quality,
users: HashMap<i32, UserData>,
bitrate_store: u32,
}
#[derive(PartialEq, Debug, Clone, Copy)]
enum DelayState {
Normal = 0,
LowDelay = 200,
@@ -41,6 +54,12 @@ enum DelayState {
Broken = 1000,
}
impl Default for DelayState {
fn default() -> Self {
DelayState::Normal
}
}
impl DelayState {
fn from_delay(delay: u32) -> Self {
if delay > DelayState::Broken as u32 {
@@ -59,187 +78,329 @@ impl Default for VideoQoS {
fn default() -> Self {
VideoQoS {
fps: FPS,
user_fps: FPS,
user_image_quality: ImageQuality::Balanced.as_percent(),
current_image_quality: ImageQuality::Balanced.as_percent(),
enable_abr: false,
width: 0,
height: 0,
current_delay: 0,
target_bitrate: 0,
updated: false,
state: DelayState::Normal,
debounce_count: 0,
quality: Default::default(),
users: Default::default(),
bitrate_store: 0,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum RefreshType {
SetImageQuality,
}
impl VideoQoS {
pub fn set_size(&mut self, width: u32, height: u32) {
if width == 0 || height == 0 {
return;
}
self.width = width;
self.height = height;
pub fn spf(&self) -> Duration {
Duration::from_secs_f32(1. / (self.fps() as f32))
}
pub fn spf(&mut self) -> Duration {
if self.fps < MIN_FPS || self.fps > MAX_FPS {
self.fps = self.base_fps();
pub fn fps(&self) -> u32 {
if self.fps >= MIN_FPS && self.fps <= MAX_FPS {
self.fps
} else {
FPS
}
Duration::from_secs_f32(1. / (self.fps as f32))
}
fn base_fps(&self) -> u8 {
if self.user_fps >= MIN_FPS && self.user_fps <= MAX_FPS {
return self.user_fps;
}
return FPS;
pub fn store_bitrate(&mut self, bitrate: u32) {
self.bitrate_store = bitrate;
}
// update_network_delay periodically
// decrease the bitrate when the delay gets bigger
pub fn update_network_delay(&mut self, delay: u32) {
if self.current_delay.eq(&0) {
self.current_delay = delay;
pub fn bitrate(&self) -> u32 {
self.bitrate_store
}
pub fn quality(&self) -> Quality {
self.quality
}
pub fn record(&self) -> bool {
self.users.iter().any(|u| u.1.record)
}
pub fn abr_enabled() -> bool {
"N" != Config::get_option("enable-abr")
}
pub fn refresh(&mut self, typ: Option<RefreshType>) {
// fps
let user_fps = |u: &UserData| {
// full_speed_fps
let mut fps = u.full_speed_fps.unwrap_or_default() * 9 / 10;
// auto adjust fps
if let Some(auto_adjust_fps) = u.auto_adjust_fps {
if fps == 0 || auto_adjust_fps < fps {
fps = auto_adjust_fps;
}
}
// custom_fps
if let Some(custom_fps) = u.custom_fps {
if fps == 0 || custom_fps < fps {
fps = custom_fps;
}
}
// delay
if let Some(delay) = u.delay {
fps = match delay.state {
DelayState::Normal => fps,
DelayState::LowDelay => fps * 3 / 4,
DelayState::HighDelay => fps / 2,
DelayState::Broken => fps / 4,
}
}
// delay response
if u.response_delayed {
if fps > MIN_FPS + 2 {
fps = MIN_FPS + 2;
}
}
return fps;
};
let mut fps = self
.users
.iter()
.map(|(_, u)| user_fps(u))
.filter(|u| *u >= MIN_FPS)
.min()
.unwrap_or(FPS);
if fps > MAX_FPS {
fps = MAX_FPS;
}
self.fps = fps;
// quality
// latest image quality
let latest_quality = self
.users
.iter()
.map(|(_, u)| u.quality)
.filter(|q| *q != None)
.max_by(|a, b| a.unwrap_or_default().0.cmp(&b.unwrap_or_default().0))
.unwrap_or_default()
.unwrap_or_default()
.1;
let mut quality = latest_quality;
// network delay
if Self::abr_enabled() && typ != Some(RefreshType::SetImageQuality) {
// max delay
let delay = self
.users
.iter()
.map(|u| u.1.delay)
.filter(|d| d.is_some())
.max_by(|a, b| {
(a.unwrap_or_default().state as u32).cmp(&(b.unwrap_or_default().state as u32))
});
let delay = delay.unwrap_or_default().unwrap_or_default().state;
if delay != DelayState::Normal {
match self.quality {
Quality::Best => {
quality = if delay == DelayState::Broken {
Quality::Low
} else {
Quality::Balanced
};
}
Quality::Balanced => {
quality = Quality::Low;
}
Quality::Low => {
quality = Quality::Low;
}
Quality::Custom(b) => match delay {
DelayState::LowDelay => {
quality =
Quality::Custom(if b >= 150 { 100 } else { std::cmp::min(50, b) });
}
DelayState::HighDelay => {
quality =
Quality::Custom(if b >= 100 { 50 } else { std::cmp::min(25, b) });
}
DelayState::Broken => {
quality =
Quality::Custom(if b >= 50 { 25 } else { std::cmp::min(10, b) });
}
DelayState::Normal => {}
},
}
} else {
match self.quality {
Quality::Low => {
if latest_quality == Quality::Best {
quality = Quality::Balanced;
}
}
Quality::Custom(current_b) => {
if let Quality::Custom(latest_b) = latest_quality {
if current_b < latest_b / 2 {
quality = Quality::Custom(latest_b / 2);
}
}
}
_ => {}
}
}
}
self.quality = quality;
}
pub fn user_custom_fps(&mut self, id: i32, fps: u32) {
if fps < MIN_FPS {
return;
}
self.current_delay = delay / 2 + self.current_delay / 2;
log::trace!(
"VideoQoS update_network_delay:{}, {}, state:{:?}",
self.current_delay,
delay,
self.state,
);
// ABR
if !self.enable_abr {
return;
}
let current_state = DelayState::from_delay(self.current_delay);
if current_state != self.state && self.debounce_count > 5 {
log::debug!(
"VideoQoS state changed:{:?} -> {:?}",
self.state,
current_state
if let Some(user) = self.users.get_mut(&id) {
user.custom_fps = Some(fps);
} else {
self.users.insert(
id,
UserData {
custom_fps: Some(fps),
..Default::default()
},
);
self.state = current_state;
self.debounce_count = 0;
self.refresh_quality();
}
self.refresh(None);
}
pub fn user_full_speed_fps(&mut self, id: i32, full_speed_fps: u32) {
if let Some(user) = self.users.get_mut(&id) {
user.full_speed_fps = Some(full_speed_fps);
} else {
self.debounce_count += 1;
self.users.insert(
id,
UserData {
full_speed_fps: Some(full_speed_fps),
..Default::default()
},
);
}
self.refresh(None);
}
fn refresh_quality(&mut self) {
match self.state {
DelayState::Normal => {
self.fps = self.base_fps();
self.current_image_quality = self.user_image_quality;
}
DelayState::LowDelay => {
self.fps = self.base_fps();
self.current_image_quality = std::cmp::min(self.user_image_quality, 50);
}
DelayState::HighDelay => {
self.fps = self.base_fps() / 2;
self.current_image_quality = std::cmp::min(self.user_image_quality, 25);
}
DelayState::Broken => {
self.fps = self.base_fps() / 4;
self.current_image_quality = 10;
}
}
let _ = self.generate_bitrate().ok();
self.updated = true;
}
// handle image_quality change from peer
pub fn update_image_quality(&mut self, image_quality: i32) {
if image_quality == ImageQuality::Low.value()
|| image_quality == ImageQuality::Balanced.value()
|| image_quality == ImageQuality::Best.value()
{
// not custom
self.user_fps = FPS;
self.fps = FPS;
}
let image_quality = Self::convert_quality(image_quality) as _;
if self.current_image_quality != image_quality {
self.current_image_quality = image_quality;
let _ = self.generate_bitrate().ok();
self.updated = true;
}
self.user_image_quality = self.current_image_quality;
}
pub fn update_user_fps(&mut self, fps: u8) {
if fps >= MIN_FPS && fps <= MAX_FPS {
if self.user_fps != fps {
self.user_fps = fps;
self.fps = fps;
self.updated = true;
}
}
}
pub fn generate_bitrate(&mut self) -> ResultType<u32> {
// https://www.nvidia.com/en-us/geforce/guides/broadcasting-guide/
if self.width == 0 || self.height == 0 {
bail!("Fail to generate_bitrate, width or height is not set");
}
if self.current_image_quality == 0 {
self.current_image_quality = ImageQuality::Balanced.as_percent();
}
let base_bitrate = ((self.width * self.height) / 800) as u32;
#[cfg(target_os = "android")]
{
// fix when android screen shrinks
let fix = scrap::Display::fix_quality() as u32;
log::debug!("Android screen, fix quality:{}", fix);
let base_bitrate = base_bitrate * fix;
self.target_bitrate = base_bitrate * self.current_image_quality / 100;
Ok(self.target_bitrate)
}
#[cfg(not(target_os = "android"))]
{
self.target_bitrate = base_bitrate * self.current_image_quality / 100;
Ok(self.target_bitrate)
}
}
pub fn check_if_updated(&mut self) -> bool {
if self.updated {
self.updated = false;
return true;
}
return false;
}
pub fn reset(&mut self) {
self.fps = FPS;
self.user_fps = FPS;
self.updated = true;
}
pub fn check_abr_config(&mut self) -> bool {
self.enable_abr = "N" != Config::get_option("enable-abr");
self.enable_abr
}
pub fn convert_quality(q: i32) -> i32 {
if q == ImageQuality::Balanced.value() {
100 * 2 / 3
} else if q == ImageQuality::Low.value() {
100 / 2
} else if q == ImageQuality::Best.value() {
100
pub fn user_auto_adjust_fps(&mut self, id: i32, fps: u32) {
if let Some(user) = self.users.get_mut(&id) {
user.auto_adjust_fps = Some(fps);
} else {
(q >> 8 & 0xFF) * 2
self.users.insert(
id,
UserData {
auto_adjust_fps: Some(fps),
..Default::default()
},
);
}
self.refresh(None);
}
pub fn user_image_quality(&mut self, id: i32, image_quality: i32) {
// https://github.com/rustdesk/rustdesk/blob/d716e2b40c38737f1aa3f16de0dec67394a6ac68/src/server/video_service.rs#L493
let convert_quality = |q: i32| {
if q == ImageQuality::Balanced.value() {
Quality::Balanced
} else if q == ImageQuality::Low.value() {
Quality::Low
} else if q == ImageQuality::Best.value() {
Quality::Best
} else {
let mut b = (q >> 8 & 0xFFF) * 2;
b = std::cmp::max(b, 20);
b = std::cmp::min(b, 8000);
Quality::Custom(b as u32)
}
};
let quality = Some((hbb_common::get_time(), convert_quality(image_quality)));
if let Some(user) = self.users.get_mut(&id) {
user.quality = quality;
} else {
self.users.insert(
id,
UserData {
quality,
..Default::default()
},
);
}
self.refresh(Some(RefreshType::SetImageQuality));
}
pub fn user_network_delay(&mut self, id: i32, delay: u32) {
let state = DelayState::from_delay(delay);
let debounce = 3;
if let Some(user) = self.users.get_mut(&id) {
if let Some(d) = &mut user.delay {
d.delay = (delay + d.delay) / 2;
let new_state = DelayState::from_delay(d.delay);
let slower_than_old_state = new_state as i32 - d.staging_state as i32;
let slower_than_old_state = if slower_than_old_state > 0 {
Some(true)
} else if slower_than_old_state < 0 {
Some(false)
} else {
None
};
if d.slower_than_old_state == slower_than_old_state {
let old_counter = d.counter;
d.counter += delay / 1000 + 1;
if old_counter < debounce && d.counter >= debounce {
d.counter = 0;
d.state = d.staging_state;
d.staging_state = new_state;
}
if d.counter % debounce == 0 {
self.refresh(None);
}
} else {
d.counter = 0;
d.staging_state = new_state;
d.slower_than_old_state = slower_than_old_state;
}
} else {
user.delay = Some(Delay {
state: DelayState::Normal,
staging_state: state,
delay,
counter: 0,
slower_than_old_state: None,
});
}
} else {
self.users.insert(
id,
UserData {
delay: Some(Delay {
state: DelayState::Normal,
staging_state: state,
delay,
counter: 0,
slower_than_old_state: None,
}),
..Default::default()
},
);
}
}
pub fn user_delay_response_elapsed(&mut self, id: i32, elapsed: u128) {
if let Some(user) = self.users.get_mut(&id) {
let old = user.response_delayed;
user.response_delayed = elapsed > 3000;
if old != user.response_delayed {
self.refresh(None);
}
}
}
pub fn user_record(&mut self, id: i32, v: bool) {
if let Some(user) = self.users.get_mut(&id) {
user.record = v;
}
}
pub fn on_connection_close(&mut self, id: i32) {
self.users.remove(&id);
self.refresh(None);
}
}

View File

@@ -36,7 +36,7 @@ use hbb_common::{
use scrap::Capturer;
use scrap::{
aom::AomEncoderConfig,
codec::{Encoder, EncoderCfg, HwEncoderConfig},
codec::{Encoder, EncoderCfg, HwEncoderConfig, Quality},
record::{Recorder, RecorderContext},
vpxcodec::{VpxEncoderConfig, VpxVideoCodecId},
CodecName, Display, TraitCapturer,
@@ -52,6 +52,11 @@ use std::{
pub const NAME: &'static str = "video";
struct ChangedResolution {
original: (i32, i32),
changed: (i32, i32),
}
lazy_static::lazy_static! {
pub static ref CURRENT_DISPLAY: Arc<Mutex<usize>> = Arc::new(Mutex::new(usize::MAX));
static ref LAST_ACTIVE: Arc<Mutex<Instant>> = Arc::new(Mutex::new(Instant::now()));
@@ -66,58 +71,29 @@ lazy_static::lazy_static! {
pub static ref IS_UAC_RUNNING: Arc<Mutex<bool>> = Default::default();
pub static ref IS_FOREGROUND_WINDOW_ELEVATED: Arc<Mutex<bool>> = Default::default();
pub static ref LAST_SYNC_DISPLAYS: Arc<RwLock<Vec<DisplayInfo>>> = Default::default();
static ref ORIGINAL_RESOLUTIONS: Arc<RwLock<HashMap<String, (i32, i32)>>> = Default::default();
static ref CHANGED_RESOLUTIONS: Arc<RwLock<HashMap<String, ChangedResolution>>> = Default::default();
}
// Not virtual display
#[inline]
fn set_original_resolution_(display_name: &str, wh: (i32, i32)) -> (i32, i32) {
let mut original_resolutions = ORIGINAL_RESOLUTIONS.write().unwrap();
match original_resolutions.get(display_name) {
Some(r) => r.clone(),
pub fn set_last_changed_resolution(display_name: &str, original: (i32, i32), changed: (i32, i32)) {
let mut lock = CHANGED_RESOLUTIONS.write().unwrap();
match lock.get_mut(display_name) {
Some(res) => res.changed = changed,
None => {
original_resolutions.insert(display_name.to_owned(), wh.clone());
wh
lock.insert(
display_name.to_owned(),
ChangedResolution { original, changed },
);
}
}
}
// Not virtual display
#[inline]
fn get_original_resolution_(display_name: &str) -> Option<(i32, i32)> {
ORIGINAL_RESOLUTIONS
.read()
.unwrap()
.get(display_name)
.map(|r| r.clone())
}
// Not virtual display
#[inline]
fn get_or_set_original_resolution_(display_name: &str, wh: (i32, i32)) -> (i32, i32) {
let r = get_original_resolution_(display_name);
if let Some(r) = r {
return r;
}
set_original_resolution_(display_name, wh)
}
// Not virtual display
#[inline]
fn update_get_original_resolution_(display_name: &str, w: usize, h: usize) -> Resolution {
let wh = get_or_set_original_resolution_(display_name, (w as _, h as _));
Resolution {
width: wh.0,
height: wh.1,
..Default::default()
}
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn reset_resolutions() {
for (name, (w, h)) in ORIGINAL_RESOLUTIONS.read().unwrap().iter() {
if let Err(e) = crate::platform::change_resolution(name, *w as _, *h as _) {
for (name, res) in CHANGED_RESOLUTIONS.read().unwrap().iter() {
let (w, h) = res.original;
if let Err(e) = crate::platform::change_resolution(name, w as _, h as _) {
log::error!(
"Failed to reset resolution of display '{}' to ({},{}): {}",
name,
@@ -144,7 +120,7 @@ pub fn capture_cursor_embedded() -> bool {
#[inline]
pub fn notify_video_frame_fetched(conn_id: i32, frame_tm: Option<Instant>) {
FRAME_FETCHED_NOTIFIER.0.send((conn_id, frame_tm)).unwrap()
FRAME_FETCHED_NOTIFIER.0.send((conn_id, frame_tm)).ok();
}
#[inline]
@@ -461,30 +437,19 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
fn check_displays_new() -> Option<Vec<Display>> {
let displays = try_get_displays().ok()?;
let last_sync_displays = &*LAST_SYNC_DISPLAYS.read().unwrap();
if displays.len() != last_sync_displays.len() {
// No need to check if the resolutions are changed by third process.
Some(displays)
} else {
for i in 0..displays.len() {
if displays[i].height() != (last_sync_displays[i].height as usize) {
return Some(displays);
}
if displays[i].width() != (last_sync_displays[i].width as usize) {
return Some(displays);
}
if displays[i].origin() != (last_sync_displays[i].x, last_sync_displays[i].y) {
return Some(displays);
}
}
None
}
}
fn check_get_displays_changed_msg() -> Option<Message> {
let displays = check_displays_new()?;
// Display to DisplayInfo
let (current, displays) = get_displays_2(&displays);
let mut pi = PeerInfo {
conn_id: crate::SYNC_PEER_INFO_DISPLAYS,
..Default::default()
};
pi.displays = displays.clone();
@@ -504,7 +469,7 @@ fn run(sp: GenericService) -> ResultType<()> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let _wake_lock = get_wake_lock();
// ensure_inited() is needed because release_resource() may be called.
// ensure_inited() is needed because clear() may be called.
#[cfg(target_os = "linux")]
super::wayland::ensure_inited()?;
#[cfg(windows)]
@@ -515,40 +480,17 @@ fn run(sp: GenericService) -> ResultType<()> {
let mut c = get_capturer(true, last_portable_service_running)?;
let mut video_qos = VIDEO_QOS.lock().unwrap();
video_qos.set_size(c.width as _, c.height as _);
let mut spf = video_qos.spf();
let bitrate = video_qos.generate_bitrate()?;
let abr = video_qos.check_abr_config();
video_qos.refresh(None);
let mut spf;
let mut quality = video_qos.quality();
let abr = VideoQoS::abr_enabled();
log::info!("init quality={:?}, abr enabled:{}", quality, abr);
let codec_name = Encoder::negotiated_codec();
let recorder = get_recorder(c.width, c.height, &codec_name);
let last_recording =
(recorder.lock().unwrap().is_some() || video_qos.record()) && codec_name != CodecName::AV1;
drop(video_qos);
log::info!("init bitrate={}, abr enabled:{}", bitrate, abr);
let encoder_cfg = match Encoder::negotiated_codec() {
scrap::CodecName::H264(name) | scrap::CodecName::H265(name) => {
EncoderCfg::HW(HwEncoderConfig {
name,
width: c.width,
height: c.height,
bitrate: bitrate as _,
})
}
name @ (scrap::CodecName::VP8 | scrap::CodecName::VP9) => {
EncoderCfg::VPX(VpxEncoderConfig {
width: c.width as _,
height: c.height as _,
bitrate,
codec: if name == scrap::CodecName::VP8 {
VpxVideoCodecId::VP8
} else {
VpxVideoCodecId::VP9
},
})
}
scrap::CodecName::AV1 => EncoderCfg::AOM(AomEncoderConfig {
width: c.width as _,
height: c.height as _,
bitrate: bitrate as _,
}),
};
let encoder_cfg = get_encoder_config(&c, quality, last_recording);
let mut encoder;
match Encoder::new(encoder_cfg) {
@@ -556,11 +498,14 @@ fn run(sp: GenericService) -> ResultType<()> {
Err(err) => bail!("Failed to create encoder: {}", err),
}
c.set_use_yuv(encoder.use_yuv());
VIDEO_QOS.lock().unwrap().store_bitrate(encoder.bitrate());
if *SWITCH.lock().unwrap() {
log::debug!("Broadcasting display switch");
let mut misc = Misc::new();
let display_name = get_current_display_name().unwrap_or_default();
let display_name = get_current_display()
.map(|(_, _, d)| d.name())
.unwrap_or_default();
let original_resolution = get_original_resolution(&display_name, c.width, c.height);
misc.set_switch_display(SwitchDisplay {
display: c.current as _,
@@ -596,8 +541,6 @@ fn run(sp: GenericService) -> ResultType<()> {
let mut try_gdi = 1;
#[cfg(windows)]
log::info!("gdi: {}", c.is_gdi());
let codec_name = Encoder::negotiated_codec();
let recorder = get_recorder(c.width, c.height, &codec_name);
#[cfg(windows)]
start_uac_elevation_check();
@@ -609,14 +552,17 @@ fn run(sp: GenericService) -> ResultType<()> {
check_uac_switch(c.privacy_mode_id, c._capturer_privacy_mode_id)?;
let mut video_qos = VIDEO_QOS.lock().unwrap();
if video_qos.check_if_updated() && video_qos.target_bitrate > 0 {
log::debug!(
"qos is updated, target_bitrate:{}, fps:{}",
video_qos.target_bitrate,
video_qos.fps
);
allow_err!(encoder.set_bitrate(video_qos.target_bitrate));
spf = video_qos.spf();
spf = video_qos.spf();
if quality != video_qos.quality() {
log::debug!("quality: {:?} -> {:?}", quality, video_qos.quality());
quality = video_qos.quality();
allow_err!(encoder.set_quality(quality));
video_qos.store_bitrate(encoder.bitrate());
}
let recording = (recorder.lock().unwrap().is_some() || video_qos.record())
&& codec_name != CodecName::AV1;
if recording != last_recording {
bail!("SWITCH");
}
drop(video_qos);
@@ -624,6 +570,8 @@ fn run(sp: GenericService) -> ResultType<()> {
bail!("SWITCH");
}
if c.current != *CURRENT_DISPLAY.lock().unwrap() {
#[cfg(target_os = "linux")]
super::wayland::clear();
*SWITCH.lock().unwrap() = true;
bail!("SWITCH");
}
@@ -658,6 +606,8 @@ fn run(sp: GenericService) -> ResultType<()> {
if let Some(msg_out) = check_get_displays_changed_msg() {
sp.send(msg_out);
log::info!("Displays changed");
#[cfg(target_os = "linux")]
super::wayland::clear();
*SWITCH.lock().unwrap() = true;
bail!("SWITCH");
}
@@ -726,7 +676,7 @@ fn run(sp: GenericService) -> ResultType<()> {
// Do not reset the capturer for now, as it will cause the prompt to show every few minutes.
// https://github.com/rustdesk/rustdesk/issues/4276
//
// super::wayland::release_resource();
// super::wayland::clear();
// bail!("Wayland capturer none 100 times, try restart capture");
}
}
@@ -735,6 +685,8 @@ fn run(sp: GenericService) -> ResultType<()> {
Err(err) => {
if check_display_changed(c.ndisplay, c.current, c.width, c.height) {
log::info!("Displays changed");
#[cfg(target_os = "linux")]
super::wayland::clear();
*SWITCH.lock().unwrap() = true;
bail!("SWITCH");
}
@@ -779,13 +731,46 @@ fn run(sp: GenericService) -> ResultType<()> {
}
#[cfg(target_os = "linux")]
if !scrap::is_x11() {
super::wayland::release_resource();
}
super::wayland::clear();
Ok(())
}
fn get_encoder_config(c: &CapturerInfo, quality: Quality, recording: bool) -> EncoderCfg {
// https://www.wowza.com/community/t/the-correct-keyframe-interval-in-obs-studio/95162
let keyframe_interval = if recording { Some(240) } else { None };
match Encoder::negotiated_codec() {
scrap::CodecName::H264(name) | scrap::CodecName::H265(name) => {
EncoderCfg::HW(HwEncoderConfig {
name,
width: c.width,
height: c.height,
quality,
keyframe_interval,
})
}
name @ (scrap::CodecName::VP8 | scrap::CodecName::VP9) => {
EncoderCfg::VPX(VpxEncoderConfig {
width: c.width as _,
height: c.height as _,
quality,
codec: if name == scrap::CodecName::VP8 {
VpxVideoCodecId::VP8
} else {
VpxVideoCodecId::VP9
},
keyframe_interval,
})
}
scrap::CodecName::AV1 => EncoderCfg::AOM(AomEncoderConfig {
width: c.width as _,
height: c.height as _,
quality,
keyframe_interval,
}),
}
}
fn get_recorder(
width: usize,
height: usize,
@@ -878,7 +863,24 @@ fn get_original_resolution(display_name: &str, w: usize, h: usize) -> MessageFie
..Default::default()
}
} else {
update_get_original_resolution_(&display_name, w, h)
let mut changed_resolutions = CHANGED_RESOLUTIONS.write().unwrap();
let (width, height) = match changed_resolutions.get(display_name) {
Some(res) => {
if res.changed.0 != w as i32 || res.changed.1 != h as i32 {
// If the resolution is changed by third process, remove the record in changed_resolutions.
changed_resolutions.remove(display_name);
(w as _, h as _)
} else {
res.original
}
}
None => (w as _, h as _),
};
Resolution {
width,
height,
..Default::default()
}
})
.into()
}
@@ -995,8 +997,18 @@ fn no_displays(displays: &Vec<Display>) -> bool {
} else if display_len == 1 {
let display = &displays[0];
let dummy_display_side_max_size = 800;
display.width() <= dummy_display_side_max_size
&& display.height() <= dummy_display_side_max_size
if display.width() > dummy_display_side_max_size
|| display.height() > dummy_display_side_max_size
{
return false;
}
let any_real = crate::platform::resolutions(&display.name())
.iter()
.any(|r| {
(r.height as usize) > dummy_display_side_max_size
|| (r.width as usize) > dummy_display_side_max_size
});
!any_real
} else {
false
}
@@ -1004,16 +1016,16 @@ fn no_displays(displays: &Vec<Display>) -> bool {
#[cfg(all(windows, feature = "virtual_display_driver"))]
fn try_get_displays() -> ResultType<Vec<Display>> {
let mut displays = Display::all()?;
if no_displays(&displays) {
log::debug!("no displays, create virtual display");
if let Err(e) = virtual_display_manager::plug_in_headless() {
log::error!("plug in headless failed {}", e);
} else {
displays = Display::all()?;
}
}
Ok(displays)
// let mut displays = Display::all()?;
// if no_displays(&displays) {
// log::debug!("no displays, create virtual display");
// if let Err(e) = virtual_display_manager::plug_in_headless() {
// log::error!("plug in headless failed {}", e);
// } else {
// displays = Display::all()?;
// }
// }
Ok( Display::all()?)
}
pub(super) fn get_current_display_2(mut all: Vec<Display>) -> ResultType<(usize, usize, Display)> {
@@ -1040,13 +1052,6 @@ pub fn get_current_display() -> ResultType<(usize, usize, Display)> {
get_current_display_2(try_get_displays()?)
}
// `try_reset_current_display` is needed because `get_displays` may change the current display,
// which may cause the mismatch of current display and the current display name.
#[inline]
pub fn get_current_display_name() -> ResultType<String> {
Ok(get_current_display_2(try_get_displays()?)?.2.name())
}
#[cfg(windows)]
fn start_uac_elevation_check() {
static START: Once = Once::new();

View File

@@ -186,21 +186,6 @@ pub(super) async fn check_init() -> ResultType<()> {
Ok(())
}
pub fn clear() {
if scrap::is_x11() {
return;
}
let mut lock = CAP_DISPLAY_INFO.write().unwrap();
if *lock != 0 {
unsafe {
let cap_display_info = Box::from_raw(*lock as *mut CapDisplayInfo);
let _ = Box::from_raw(cap_display_info.capturer.0);
}
*lock = 0;
}
}
pub(super) async fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
check_init().await?;
let addr = *CAP_DISPLAY_INFO.read().unwrap();
@@ -230,8 +215,7 @@ pub(super) fn get_primary() -> ResultType<usize> {
}
}
#[allow(dead_code)]
pub(super) fn release_resource() {
pub fn clear() {
if scrap::is_x11() {
return;
}

View File

@@ -61,7 +61,7 @@ pub fn make_tray() -> hbb_common::ResultType<()> {
let mut docker_hiden = false;
let open_func = move || {
#[cfg(not(feature = "flutter"))]
if cfg!(not(feature = "flutter"))
{
crate::run_me::<&str>(vec![]).ok();
return;
@@ -101,11 +101,15 @@ pub fn make_tray() -> hbb_common::ResultType<()> {
if let Ok(event) = menu_channel.try_recv() {
if event.id == quit_i.id() {
/* failed in windows, seems no permission to check system process
if !crate::check_process("--server", false) {
*control_flow = ControlFlow::Exit;
return;
}
crate::platform::uninstall_service(false);
*/
if !crate::platform::uninstall_service(false) {
*control_flow = ControlFlow::Exit;
}
} else if event.id == open_i.id() {
open_func();
}

View File

@@ -94,8 +94,7 @@ pub fn start(args: &mut [String]) {
args[1] = id;
}
if args.is_empty() {
let children: Children = Default::default();
std::thread::spawn(move || check_zombie(children));
std::thread::spawn(move || check_zombie());
crate::common::check_software_update();
frame.event_handler(UI {});
frame.sciter_handler(UIHostHandler {});
@@ -124,8 +123,16 @@ pub fn start(args: &mut [String]) {
crate::platform::windows::enable_lowlevel_keyboard(hw as _);
}
let mut iter = args.iter();
let cmd = iter.next().unwrap().clone();
let id = iter.next().unwrap().clone();
let Some(cmd) = iter.next() else {
log::error!("Failed to get cmd arg");
return;
};
let cmd = cmd.to_owned();
let Some(id) = iter.next() else {
log::error!("Failed to get id arg");
return;
};
let id = id.to_owned();
let pass = iter.next().unwrap_or(&"".to_owned()).clone();
let args: Vec<String> = iter.map(|x| x.clone()).collect();
frame.set_title(&id);
@@ -259,7 +266,8 @@ impl UI {
}
fn get_options(&self) -> Value {
let hashmap: HashMap<String, String> = serde_json::from_str(&get_options()).unwrap();
let hashmap: HashMap<String, String> =
serde_json::from_str(&get_options()).unwrap_or_default();
let mut m = Value::map();
for (k, v) in hashmap {
m.set_item(k, v);
@@ -403,7 +411,7 @@ impl UI {
fn get_recent_sessions(&mut self) -> Value {
// to-do: limit number of recent sessions, and remove old peer file
let peers: Vec<Value> = PeerConfig::peers()
let peers: Vec<Value> = PeerConfig::peers(None)
.drain(..)
.map(|p| Self::get_peer_value(p.0, p.2))
.collect();
@@ -552,7 +560,7 @@ impl UI {
}
fn is_ok_change_id(&self) -> bool {
machine_uid::get().is_ok()
hbb_common::machine_uid::get().is_ok()
}
fn get_async_job_status(&self) -> String {
@@ -587,8 +595,8 @@ impl UI {
handle_relay_id(id)
}
fn get_hostname(&self) -> String {
get_hostname()
fn get_login_device_info(&self) -> String {
get_login_device_info_json()
}
}
@@ -674,7 +682,7 @@ impl sciter::EventHandler for UI {
fn get_langs();
fn default_video_save_directory();
fn handle_relay_id(String);
fn get_hostname();
fn get_login_device_info();
}
}
@@ -684,28 +692,6 @@ impl sciter::host::HostHandler for UIHostHandler {
}
}
pub fn check_zombie(children: Children) {
let mut deads = Vec::new();
loop {
let mut lock = children.lock().unwrap();
let mut n = 0;
for (id, c) in lock.1.iter_mut() {
if let Ok(Some(_)) = c.try_wait() {
deads.push(id.clone());
n += 1;
}
}
for ref id in deads.drain(..) {
lock.1.remove(id);
}
if n > 0 {
lock.0 = true;
}
drop(lock);
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
#[cfg(not(target_os = "linux"))]
fn get_sound_inputs() -> Vec<String> {
let mut out = Vec::new();
@@ -739,50 +725,6 @@ pub fn value_crash_workaround(values: &[Value]) -> Arc<Vec<Value>> {
persist
}
#[inline]
pub fn new_remote(id: String, remote_type: String, force_relay: bool) {
let mut lock = CHILDREN.lock().unwrap();
let mut args = vec![format!("--{}", remote_type), id.clone()];
if force_relay {
args.push("".to_string()); // password
args.push("--relay".to_string());
}
let key = (id.clone(), remote_type.clone());
if let Some(c) = lock.1.get_mut(&key) {
if let Ok(Some(_)) = c.try_wait() {
lock.1.remove(&key);
} else {
if remote_type == "rdp" {
allow_err!(c.kill());
std::thread::sleep(std::time::Duration::from_millis(30));
c.try_wait().ok();
lock.1.remove(&key);
} else {
return;
}
}
}
match crate::run_me(args) {
Ok(child) => {
lock.1.insert(key, child);
}
Err(err) => {
log::error!("Failed to spawn remote: {}", err);
}
}
}
#[inline]
pub fn recent_sessions_updated() -> bool {
let mut children = CHILDREN.lock().unwrap();
if children.0 {
children.0 = false;
true
} else {
false
}
}
pub fn get_icon() -> String {
// 128x128
#[cfg(target_os = "macos")]

View File

@@ -59,13 +59,11 @@ impl InvokeUiCM for SciterHandler {
fn update_voice_call_state(&self, client: &crate::ui_cm_interface::Client) {
self.call(
"updateVoiceCallState",
&make_args!(
client.id,
client.in_voice_call,
client.incoming_voice_call
),
&make_args!(client.id, client.in_voice_call, client.incoming_voice_call),
);
}
fn file_transfer_log(&self, _log: String) {}
}
impl SciterHandler {

View File

@@ -297,6 +297,7 @@ class Header: Reactor.Component {
event click $(span#recording) (_, me) {
recording = !recording;
header.update();
handler.record_status(recording);
if (recording)
handler.refresh_video();
else
@@ -424,10 +425,22 @@ class Header: Reactor.Component {
function handle_custom_image_quality() {
var tmp = handler.get_custom_image_quality();
var bitrate = (tmp[0] || 50);
msgbox("custom", "Custom Image Quality", "<div .form> \
<div><input type=\"hslider\" style=\"width: 50%\" name=\"bitrate\" max=\"100\" min=\"10\" value=\"" + bitrate + "\"/ buddy=\"bitrate-buddy\"><b #bitrate-buddy>x</b>% Bitrate</div> \
var extendedBitrate = bitrate > 100;
var maxRate = extendedBitrate ? 2000 : 100;
msgbox("custom-image-quality", "Custom Image Quality", "<div .form> \
<div><input #bitrate-slider type=\"hslider\" style=\"width: 50%\" name=\"bitrate\" max=\"" + maxRate + "\" min=\"10\" value=\"" + bitrate + "\"/ buddy=\"bitrate-buddy\"><b #bitrate-buddy>x</b>% Bitrate <button|checkbox #extended-slider .custom-event " + (extendedBitrate ? "checked" : "") + ">More</button></div> \
</div>", "", function(res=null) {
if (!res) return;
if (res.id === "extended-slider") {
var slider = res.parent.$(#bitrate-slider)
slider.slider.max = res.checked ? 2000 : 100;
if (slider.value > slider.slider.max) {
slider.value = slider.slider.max;
}
var buddy = res.parent.$(#bitrate-buddy);
buddy.value = slider.value;
return;
}
if (!res.bitrate) return;
handler.save_custom_image_quality(res.bitrate);
toggleMenuState();

View File

@@ -215,7 +215,7 @@ class Enhancements: Reactor.Component {
return <li>{translate('Enhancements')}
<menu #enhancements-menu>
{has_hwcodec ? <li #enable-hwcodec><span>{svg_checkmark}</span>{translate("Hardware Codec")} (beta)</li> : ""}
<li #enable-abr><span>{svg_checkmark}</span>{translate("Adaptive Bitrate")} (beta)</li>
<li #enable-abr><span>{svg_checkmark}</span>{translate("Adaptive bitrate")} (beta)</li>
<li #screen-recording>{translate("Recording")}</li>
</menu>
</li>;
@@ -365,7 +365,7 @@ class MyIdMenu: Reactor.Component {
msgbox("custom-nocancel-nook-hasclose", translate("About") + " " + name, "<div style='line-height: 2em'> \
<div>Version: " + handler.get_version() + " \
<div>Fingerprint: " + handler.get_fingerprint() + " \
<div .link .custom-event url='https://rustdesk.com/privacy'>" + translate("Privacy Statement") + "</div> \
<div .link .custom-event url='https://rustdesk.com/privacy.html'>" + translate("Privacy Statement") + "</div> \
<div .link .custom-event url='https://rustdesk.com'>" + translate("Website") + "</div> \
<div style='background: #2c8cff; color: white; padding: 1em; margin-top: 1em;'>Copyright &copy; 2023 Purslane Ltd.\
<br />" + handler.get_license() + " \
@@ -659,7 +659,7 @@ class UpdateMe: Reactor.Component {
}
event click $(#install-me) {
handler.open_url("https://rustdesk.com");
handler.open_url("https://rustdesk.com/download");
return;
if (!is_win) {
handler.open_url("https://rustdesk.com");
@@ -1323,18 +1323,5 @@ function getHttpHeaders() {
}
function getDeviceInfo() {
var os;
if (is_win) {
os = 'windows';
} else if (is_linux) {
os = 'linux';
} else if (is_osx) {
os = 'macos';
}
return {
os: os,
type: 'client',
name: handler.get_hostname()
};
return JSON.parse(handler.get_login_device_info());
}

View File

@@ -377,7 +377,7 @@ impl sciter::EventHandler for SciterSession {
let source = Element::from(source);
use sciter::dom::ELEMENT_AREAS;
let flags = ELEMENT_AREAS::CONTENT_BOX as u32 | ELEMENT_AREAS::SELF_RELATIVE as u32;
let rc = source.get_location(flags).unwrap();
let rc = source.get_location(flags).unwrap_or_default();
log::debug!(
"[video] start video thread on <{}> which is about {:?} pixels",
source,
@@ -451,6 +451,7 @@ impl sciter::EventHandler for SciterSession {
fn save_custom_image_quality(i32);
fn refresh_video();
fn record_screen(bool, i32, i32);
fn record_status(bool);
fn get_toggle_option(String);
fn is_privacy_mode_supported();
fn toggle_option(String);

View File

@@ -69,7 +69,6 @@ struct IpcTaskRunner<T: InvokeUiCM> {
rx: mpsc::UnboundedReceiver<Data>,
close: bool,
running: bool,
authorized: bool,
conn_id: i32,
#[cfg(any(target_os = "windows", target_os = "linux"))]
file_transfer_enabled: bool,
@@ -101,6 +100,8 @@ pub trait InvokeUiCM: Send + Clone + 'static + Sized {
fn show_elevation(&self, show: bool);
fn update_voice_call_state(&self, client: &Client);
fn file_transfer_log(&self, log: String);
}
impl<T: InvokeUiCM> Deref for ConnectionManager<T> {
@@ -163,6 +164,16 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
self.ui_handler.add_connection(&client);
}
#[inline]
fn is_authorized(&self, id: i32) -> bool {
CLIENTS
.read()
.unwrap()
.get(&id)
.map(|c| c.authorized)
.unwrap_or(false)
}
fn remove_connection(&self, id: i32, close: bool) {
if close {
CLIENTS.write().unwrap().remove(&id);
@@ -318,12 +329,15 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
// for tmp use, without real conn id
let mut write_jobs: Vec<fs::TransferJob> = Vec::new();
#[cfg(any(target_os = "windows", target_os = "linux"))]
let is_authorized = self.cm.is_authorized(self.conn_id);
#[cfg(any(target_os = "windows", target_os = "linux"))]
let rx_clip1;
let mut rx_clip;
let _tx_clip;
#[cfg(any(target_os = "windows", target_os = "linux"))]
if self.conn_id > 0 && self.authorized {
if self.conn_id > 0 && is_authorized {
rx_clip1 = clipboard::get_rx_cliprdr_server(self.conn_id);
rx_clip = rx_clip1.lock().await;
} else {
@@ -347,6 +361,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
);
}
}
let (tx_log, mut rx_log) = mpsc::unbounded_channel::<String>();
self.running = false;
loop {
@@ -362,7 +377,6 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
Data::Login{id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, file_transfer_enabled: _file_transfer_enabled, restart, recording, from_switch} => {
log::debug!("conn_id: {}", id);
self.cm.add_connection(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart, recording, from_switch,self.tx.clone());
self.authorized = authorized;
self.conn_id = id;
#[cfg(windows)]
{
@@ -394,11 +408,16 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
if let ipc::FS::WriteBlock { id, file_num, data: _, compressed } = fs {
if let Ok(bytes) = self.stream.next_raw().await {
fs = ipc::FS::WriteBlock{id, file_num, data:bytes.into(), compressed};
handle_fs(fs, &mut write_jobs, &self.tx).await;
handle_fs(fs, &mut write_jobs, &self.tx, Some(&tx_log)).await;
}
} else {
handle_fs(fs, &mut write_jobs, &self.tx).await;
handle_fs(fs, &mut write_jobs, &self.tx, Some(&tx_log)).await;
}
let log = fs::serialize_transfer_jobs(&write_jobs);
self.cm.ui_handler.file_transfer_log(log);
}
Data::FileTransferLog(log) => {
self.cm.ui_handler.file_transfer_log(log);
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Data::ClipboardFile(_clip) => {
@@ -414,6 +433,10 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
if stop {
ContextSend::set_is_stopped();
} else {
if !is_authorized {
log::debug!("Clipboard message from client peer, but not authorized");
continue;
}
let conn_id = self.conn_id;
let _ = ContextSend::proc(|context| -> ResultType<()> {
context.server_clip_file(conn_id, _clip)
@@ -456,16 +479,24 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
}
}
Some(data) = self.rx.recv() => {
if let Data::SwitchPermission{name: _name, enabled: _enabled} = &data {
#[cfg(windows)]
if _name == "file" {
self.file_transfer_enabled = *_enabled;
}
}
if self.stream.send(&data).await.is_err() {
break;
}
}
match &data {
Data::SwitchPermission{name: _name, enabled: _enabled} => {
#[cfg(windows)]
if _name == "file" {
self.file_transfer_enabled = *_enabled;
}
}
Data::Authorize => {
self.running = true;
break;
}
_ => {
}
}
},
clip_file = rx_clip.recv() => match clip_file {
Some(_clip) => {
#[cfg(any(target_os = "windows", target_os ="linux"))]
@@ -489,6 +520,9 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
//
}
},
Some(job_log) = rx_log.recv() => {
self.cm.ui_handler.file_transfer_log(job_log);
}
}
}
}
@@ -503,7 +537,6 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
rx,
close: true,
running: true,
authorized: false,
conn_id: 0,
#[cfg(any(target_os = "windows", target_os = "linux"))]
file_transfer_enabled: false,
@@ -535,7 +568,6 @@ pub async fn start_ipc<T: InvokeUiCM>(cm: ConnectionManager<T>) {
e
);
}
allow_err!(crate::privacy_win_mag::start());
});
#[cfg(target_os = "windows")]
@@ -614,7 +646,7 @@ pub async fn start_listen<T: InvokeUiCM>(
cm.new_message(current_id, text);
}
Some(Data::FS(fs)) => {
handle_fs(fs, &mut write_jobs, &tx).await;
handle_fs(fs, &mut write_jobs, &tx, None).await;
}
Some(Data::Close) => {
break;
@@ -629,7 +661,14 @@ pub async fn start_listen<T: InvokeUiCM>(
}
#[cfg(not(any(target_os = "ios")))]
async fn handle_fs(fs: ipc::FS, write_jobs: &mut Vec<fs::TransferJob>, tx: &UnboundedSender<Data>) {
async fn handle_fs(
fs: ipc::FS,
write_jobs: &mut Vec<fs::TransferJob>,
tx: &UnboundedSender<Data>,
tx_log: Option<&UnboundedSender<String>>,
) {
use hbb_common::fs::serialize_transfer_job;
match fs {
ipc::FS::ReadDir {
dir,
@@ -656,10 +695,12 @@ async fn handle_fs(fs: ipc::FS, write_jobs: &mut Vec<fs::TransferJob>, tx: &Unbo
file_num,
mut files,
overwrite_detection,
total_size,
conn_id,
} => {
// cm has no show_hidden context
// dummy remote, show_hidden, is_remote
write_jobs.push(fs::TransferJob::new_write(
let mut job = fs::TransferJob::new_write(
id,
"".to_string(),
path,
@@ -675,11 +716,17 @@ async fn handle_fs(fs: ipc::FS, write_jobs: &mut Vec<fs::TransferJob>, tx: &Unbo
})
.collect(),
overwrite_detection,
));
);
job.total_size = total_size;
job.conn_id = conn_id;
write_jobs.push(job);
}
ipc::FS::CancelWrite { id } => {
if let Some(job) = fs::get_job(id, write_jobs) {
job.remove_download_file();
tx_log.map(|tx: &UnboundedSender<String>| {
tx.send(serialize_transfer_job(job, false, true, ""))
});
fs::remove_job(id, write_jobs);
}
}
@@ -687,11 +734,13 @@ async fn handle_fs(fs: ipc::FS, write_jobs: &mut Vec<fs::TransferJob>, tx: &Unbo
if let Some(job) = fs::get_job(id, write_jobs) {
job.modify_time();
send_raw(fs::new_done(id, file_num), tx);
tx_log.map(|tx| tx.send(serialize_transfer_job(job, true, false, "")));
fs::remove_job(id, write_jobs);
}
}
ipc::FS::WriteError { id, file_num, err } => {
if let Some(job) = fs::get_job(id, write_jobs) {
tx_log.map(|tx| tx.send(serialize_transfer_job(job, false, false, &err)));
send_raw(fs::new_error(job.id(), err, file_num), tx);
fs::remove_job(job.id(), write_jobs);
}

View File

@@ -44,6 +44,13 @@ pub struct UiStatus {
pub id: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct LoginDeviceInfo {
pub os: String,
pub r#type: String,
pub name: String,
}
lazy_static::lazy_static! {
static ref UI_STATUS : Arc<Mutex<UiStatus>> = Arc::new(Mutex::new(UiStatus{
status_num: 0,
@@ -63,6 +70,7 @@ lazy_static::lazy_static! {
static ref OPTION_SYNCED: Arc<Mutex<bool>> = Default::default();
static ref OPTIONS : Arc<Mutex<HashMap<String, String>>> = Arc::new(Mutex::new(Config::get_options()));
pub static ref SENDER : Mutex<mpsc::UnboundedSender<ipc::Data>> = Mutex::new(check_connect_status(true));
static ref CHILDREN : Children = Default::default();
}
const INIT_ASYNC_JOB_STATUS: &str = " ";
@@ -118,7 +126,7 @@ pub fn show_run_without_install() -> bool {
#[inline]
pub fn get_license() -> String {
#[cfg(windows)]
if let Some(lic) = crate::platform::windows::get_license() {
if let Ok(lic) = crate::platform::windows::get_license_from_exe_name() {
#[cfg(feature = "flutter")]
return format!("Key: {}\nHost: {}\nApi: {}", lic.key, lic.host, lic.api);
// default license format is html formed (sciter)
@@ -160,14 +168,14 @@ pub fn set_local_option(key: String, value: String) {
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[inline]
pub fn get_local_flutter_config(key: String) -> String {
LocalConfig::get_flutter_config(&key)
pub fn get_local_flutter_option(key: String) -> String {
LocalConfig::get_flutter_option(&key)
}
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[inline]
pub fn set_local_flutter_config(key: String, value: String) {
LocalConfig::set_flutter_config(key, value);
pub fn set_local_flutter_option(key: String, value: String) {
LocalConfig::set_flutter_option(key, value);
}
#[cfg(feature = "flutter")]
@@ -200,6 +208,25 @@ pub fn get_peer_option(id: String, name: String) -> String {
c.options.get(&name).unwrap_or(&"".to_owned()).to_owned()
}
#[inline]
#[cfg(feature = "flutter")]
pub fn get_peer_flutter_option(id: String, name: String) -> String {
let c = PeerConfig::load(&id);
c.ui_flutter.get(&name).unwrap_or(&"".to_owned()).to_owned()
}
#[inline]
#[cfg(feature = "flutter")]
pub fn set_peer_flutter_option(id: String, name: String, value: String) {
let mut c = PeerConfig::load(&id);
if value.is_empty() {
c.ui_flutter.remove(&name);
} else {
c.ui_flutter.insert(name, value);
}
c.store(&id);
}
#[inline]
pub fn set_peer_option(id: String, name: String, value: String) {
let mut c = PeerConfig::load(&id);
@@ -233,7 +260,7 @@ pub fn get_options() -> String {
for (k, v) in options.iter() {
m.insert(k.into(), v.to_owned().into());
}
serde_json::to_string(&m).unwrap()
serde_json::to_string(&m).unwrap_or_default()
}
#[inline]
@@ -567,7 +594,7 @@ pub fn current_is_wayland() -> bool {
#[inline]
pub fn get_new_version() -> String {
hbb_common::get_version_from_url(&*SOFTWARE_UPDATE_URL.lock().unwrap())
(*SOFTWARE_UPDATE_URL.lock().unwrap().rsplit('/').next().unwrap_or("")).to_string()
}
#[inline]
@@ -598,6 +625,7 @@ pub fn discover() {
#[cfg(feature = "flutter")]
pub fn peer_to_map(id: String, p: PeerConfig) -> HashMap<&'static str, String> {
use hbb_common::sodiumoxide::base64;
HashMap::<&str, String>::from_iter([
("id", id),
("username", p.info.username.clone()),
@@ -607,9 +635,18 @@ pub fn peer_to_map(id: String, p: PeerConfig) -> HashMap<&'static str, String> {
"alias",
p.options.get("alias").unwrap_or(&"".to_owned()).to_owned(),
),
(
"hash",
base64::encode(p.password, base64::Variant::Original),
),
])
}
#[cfg(feature = "flutter")]
pub fn peer_exists(id: &str) -> bool {
PeerConfig::exists(id)
}
#[inline]
pub fn get_lan_peers() -> Vec<HashMap<&'static str, String>> {
config::LanPeers::load()
@@ -815,11 +852,11 @@ pub fn check_super_user_permission() -> bool {
return true;
}
#[allow(dead_code)]
pub fn check_zombie(children: Children) {
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "flutter")))]
pub fn check_zombie() {
let mut deads = Vec::new();
loop {
let mut lock = children.lock().unwrap();
let mut lock = CHILDREN.lock().unwrap();
let mut n = 0;
for (id, c) in lock.1.iter_mut() {
if let Ok(Some(_)) = c.try_wait() {
@@ -838,6 +875,51 @@ pub fn check_zombie(children: Children) {
}
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "flutter")))]
pub fn recent_sessions_updated() -> bool {
let mut children = CHILDREN.lock().unwrap();
if children.0 {
children.0 = false;
true
} else {
false
}
}
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "flutter")))]
pub fn new_remote(id: String, remote_type: String, force_relay: bool) {
let mut lock = CHILDREN.lock().unwrap();
let mut args = vec![format!("--{}", remote_type), id.clone()];
if force_relay {
args.push("".to_string()); // password
args.push("--relay".to_string());
}
let key = (id.clone(), remote_type.clone());
if let Some(c) = lock.1.get_mut(&key) {
if let Ok(Some(_)) = c.try_wait() {
lock.1.remove(&key);
} else {
if remote_type == "rdp" {
allow_err!(c.kill());
std::thread::sleep(std::time::Duration::from_millis(30));
c.try_wait().ok();
lock.1.remove(&key);
} else {
return;
}
}
}
match crate::run_me(args) {
Ok(child) => {
lock.1.insert(key, child);
}
Err(err) => {
log::error!("Failed to spawn remote: {}", err);
}
}
}
// Make sure `SENDER` is inited here.
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -855,7 +937,7 @@ fn check_connect_status(reconnect: bool) -> mpsc::UnboundedSender<ipc::Data> {
#[cfg(feature = "flutter")]
pub fn account_auth(op: String, id: String, uuid: String, remember_me: bool) {
account::OidcSession::account_auth(op, id, uuid, remember_me);
account::OidcSession::account_auth(get_api_server(), op, id, uuid, remember_me);
}
#[cfg(feature = "flutter")]
@@ -891,8 +973,19 @@ pub fn get_fingerprint() -> String {
return ipc::get_fingerprint();
}
pub fn get_hostname() -> String {
crate::common::hostname()
#[inline]
pub fn get_login_device_info() -> LoginDeviceInfo {
LoginDeviceInfo {
// std::env::consts::OS is better than whoami::platform() here.
os: std::env::consts::OS.to_owned(),
r#type: "client".to_owned(),
name: crate::common::hostname(),
}
}
#[inline]
pub fn get_login_device_info_json() -> String {
serde_json::to_string(&get_login_device_info()).unwrap_or("{}".to_string())
}
// notice: avoiding create ipc connection repeatedly,
@@ -1027,8 +1120,10 @@ const UNKNOWN_ERROR: &'static str = "Unknown error";
#[inline]
#[tokio::main(flavor = "current_thread")]
pub async fn change_id_shared(id: String, old_id: String) {
*ASYNC_JOB_STATUS.lock().unwrap() = change_id_shared_(id, old_id).await.to_owned();
pub async fn change_id_shared(id: String, old_id: String) -> String {
let res = change_id_shared_(id, old_id).await.to_owned();
*ASYNC_JOB_STATUS.lock().unwrap() = res.clone();
res
}
pub async fn change_id_shared_(id: String, old_id: String) -> &'static str {
@@ -1037,7 +1132,12 @@ pub async fn change_id_shared_(id: String, old_id: String) -> &'static str {
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let uuid = Bytes::from(machine_uid::get().unwrap_or("".to_owned()).as_bytes().to_vec());
let uuid = Bytes::from(
hbb_common::machine_uid::get()
.unwrap_or("".to_owned())
.as_bytes()
.to_vec(),
);
#[cfg(any(target_os = "android", target_os = "ios"))]
let uuid = Bytes::from(hbb_common::get_uuid());
@@ -1105,23 +1205,23 @@ async fn check_id(
{
match msg_in.union {
Some(rendezvous_message::Union::RegisterPkResponse(rpr)) => {
match rpr.result.enum_value_or_default() {
register_pk_response::Result::OK => {
match rpr.result.enum_value() {
Ok(register_pk_response::Result::OK) => {
ok = true;
}
register_pk_response::Result::ID_EXISTS => {
Ok(register_pk_response::Result::ID_EXISTS) => {
return "Not available";
}
register_pk_response::Result::TOO_FREQUENT => {
Ok(register_pk_response::Result::TOO_FREQUENT) => {
return "Too frequent";
}
register_pk_response::Result::NOT_SUPPORT => {
Ok(register_pk_response::Result::NOT_SUPPORT) => {
return "server_not_support";
}
register_pk_response::Result::SERVER_ERROR => {
Ok(register_pk_response::Result::SERVER_ERROR) => {
return "Server error";
}
register_pk_response::Result::INVALID_ID_FORMAT => {
Ok(register_pk_response::Result::INVALID_ID_FORMAT) => {
return INVALID_FORMAT;
}
_ => {}

View File

@@ -1,3 +1,4 @@
use crate::input::{MOUSE_BUTTON_LEFT, MOUSE_TYPE_DOWN, MOUSE_TYPE_UP};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use std::{collections::HashMap, sync::atomic::AtomicBool};
use std::{
@@ -7,7 +8,7 @@ use std::{
atomic::{AtomicUsize, Ordering},
Arc, Mutex, RwLock,
},
time::{Duration, SystemTime},
time::SystemTime,
};
use async_trait::async_trait;
@@ -34,8 +35,8 @@ use hbb_common::{
use crate::client::io_loop::Remote;
use crate::client::{
check_if_retry, handle_hash, handle_login_error, handle_login_from_ui, handle_test_delay,
input_os_password, load_config, send_mouse, start_video_audio_threads, FileManager, Key,
LoginConfigHandler, QualityStatus, KEY_MAP,
input_os_password, load_config, send_mouse, send_pointer_device_event,
start_video_audio_threads, FileManager, Key, LoginConfigHandler, QualityStatus, KEY_MAP,
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::common::GrabState;
@@ -49,8 +50,8 @@ const CHANGE_RESOLUTION_VALID_TIMEOUT_SECS: u64 = 15;
#[derive(Clone, Default)]
pub struct Session<T: InvokeUiSession> {
pub session_id: SessionID,
pub id: String, // peer id
pub session_id: SessionID, // different from the one in LoginConfigHandler, used for flutter UI message pass
pub id: String, // peer id
pub password: String,
pub args: Vec<String>,
pub lc: Arc<RwLock<LoginConfigHandler>>,
@@ -146,12 +147,6 @@ impl<T: InvokeUiSession> Session<T> {
self.lc.read().unwrap().conn_type.eq(&ConnType::RDP)
}
pub fn set_connection_info(&mut self, direct: bool, received: bool) {
let mut lc = self.lc.write().unwrap();
lc.direct = Some(direct);
lc.received = received;
}
pub fn get_view_style(&self) -> String {
self.lc.read().unwrap().view_style.clone()
}
@@ -188,12 +183,12 @@ impl<T: InvokeUiSession> Session<T> {
self.lc.write().unwrap().save_scroll_style(value);
}
pub fn save_flutter_config(&mut self, k: String, v: String) {
pub fn save_flutter_option(&mut self, k: String, v: String) {
self.lc.write().unwrap().save_ui_flutter(k, v);
}
pub fn get_flutter_config(&self, k: String) -> String {
self.lc.write().unwrap().get_ui_flutter(&k)
pub fn get_flutter_option(&self, k: String) -> String {
self.lc.read().unwrap().get_ui_flutter(&k)
}
pub fn toggle_option(&mut self, name: String) {
@@ -231,6 +226,14 @@ impl<T: InvokeUiSession> Session<T> {
self.send(Data::RecordScreen(start, w, h, self.id.clone()));
}
pub fn record_status(&self, status: bool) {
let mut misc = Misc::new();
misc.set_client_record_status(status);
let mut msg = Message::new();
msg.set_misc(misc);
self.send(Data::Message(msg));
}
pub fn save_custom_image_quality(&mut self, custom_image_quality: i32) {
let msg = self
.lc
@@ -312,9 +315,7 @@ impl<T: InvokeUiSession> Session<T> {
}
pub fn get_audit_server(&self, typ: String) -> String {
if self.lc.read().unwrap().conn_id <= 0
|| LocalConfig::get_option("access_token").is_empty()
{
if LocalConfig::get_option("access_token").is_empty() {
return "".to_owned();
}
crate::get_audit_server(
@@ -327,9 +328,9 @@ impl<T: InvokeUiSession> Session<T> {
pub fn send_note(&self, note: String) {
let url = self.get_audit_server("conn".to_string());
let id = self.id.clone();
let conn_id = self.lc.read().unwrap().conn_id;
let session_id = self.lc.read().unwrap().session_id;
std::thread::spawn(move || {
send_note(url, id, conn_id, note);
send_note(url, id, session_id, note);
});
}
@@ -419,7 +420,7 @@ impl<T: InvokeUiSession> Session<T> {
pub fn get_path_sep(&self, is_remote: bool) -> &'static str {
let p = self.get_platform(is_remote);
if &p == "Windows" {
if &p == crate::PLATFORM_WINDOWS {
return "\\";
} else {
return "/";
@@ -697,6 +698,61 @@ impl<T: InvokeUiSession> Session<T> {
self.send_key_event(&key_event);
}
pub fn send_touch_scale(&self, scale: i32, alt: bool, ctrl: bool, shift: bool, command: bool) {
let scale_evt = TouchScaleUpdate {
scale,
..Default::default()
};
let mut touch_evt = TouchEvent::new();
touch_evt.set_scale_update(scale_evt);
let mut evt = PointerDeviceEvent::new();
evt.set_touch_event(touch_evt);
send_pointer_device_event(evt, alt, ctrl, shift, command, self);
}
pub fn send_touch_pan_event(
&self,
event: &str,
x: i32,
y: i32,
alt: bool,
ctrl: bool,
shift: bool,
command: bool,
) {
let mut touch_evt = TouchEvent::new();
match event {
"pan_start" => {
touch_evt.set_pan_start(TouchPanStart {
x,
y,
..Default::default()
});
}
"pan_update" => {
touch_evt.set_pan_update(TouchPanUpdate {
x,
y,
..Default::default()
});
}
"pan_end" => {
touch_evt.set_pan_end(TouchPanEnd {
x,
y,
..Default::default()
});
}
_ => {
log::warn!("unknown touch pan event: {}", event);
return;
}
};
let mut evt = PointerDeviceEvent::new();
evt.set_touch_event(touch_evt);
send_pointer_device_event(evt, alt, ctrl, shift, command, self);
}
pub fn send_mouse(
&self,
mask: i32,
@@ -727,8 +783,20 @@ impl<T: InvokeUiSession> Session<T> {
if cfg!(target_os = "macos") {
let buttons = mask >> 3;
let evt_type = mask & 0x7;
if buttons == 1 && evt_type == 1 && ctrl && self.peer_platform() != "Mac OS" {
self.send_mouse((1 << 3 | 2) as _, x, y, alt, ctrl, shift, command);
if buttons == MOUSE_BUTTON_LEFT
&& evt_type == MOUSE_TYPE_DOWN
&& ctrl
&& self.peer_platform() != "Mac OS"
{
self.send_mouse(
(MOUSE_BUTTON_LEFT << 3 | MOUSE_TYPE_UP) as _,
x,
y,
alt,
ctrl,
shift,
command,
);
}
}
}
@@ -888,34 +956,49 @@ impl<T: InvokeUiSession> Session<T> {
}
}
pub fn handle_peer_switch_display(&self, display: &SwitchDisplay) {
self.ui_handler.switch_display(display);
if self.last_change_display.lock().unwrap().is_the_same_record(
display.display,
display.width,
display.height,
) {
let custom_resolution = if display.width != display.original_resolution.width
|| display.height != display.original_resolution.height
{
Some((display.width, display.height))
} else {
None
};
fn set_custom_resolution(&self, display: &SwitchDisplay) {
if display.width == display.original_resolution.width
&& display.height == display.original_resolution.height
{
self.lc
.write()
.unwrap()
.set_custom_resolution(display.display, custom_resolution);
.set_custom_resolution(display.display, None);
} else {
let last_change_display = self.last_change_display.lock().unwrap();
if last_change_display.display == display.display {
let wh = if last_change_display.is_the_same_record(
display.display,
display.width,
display.height,
) {
Some((display.width, display.height))
} else {
// display origin is changed, or some other events.
None
};
self.lc
.write()
.unwrap()
.set_custom_resolution(display.display, wh);
}
}
}
#[inline]
pub fn handle_peer_switch_display(&self, display: &SwitchDisplay) {
self.ui_handler.switch_display(display);
self.set_custom_resolution(display);
}
#[inline]
pub fn change_resolution(&self, display: i32, width: i32, height: i32) {
*self.last_change_display.lock().unwrap() =
ChangeDisplayRecord::new(display, width, height);
self.do_change_resolution(width, height);
}
#[inline]
fn try_change_init_resolution(&self, display: i32) {
if let Some((w, h)) = self.lc.read().unwrap().get_custom_resolution(display) {
self.do_change_resolution(w, h);
@@ -934,45 +1017,15 @@ impl<T: InvokeUiSession> Session<T> {
self.send(Data::Message(msg));
}
#[inline]
pub fn request_voice_call(&self) {
self.send(Data::NewVoiceCall);
}
#[inline]
pub fn close_voice_call(&self) {
self.send(Data::CloseVoiceCall);
}
pub fn show_relay_hint(
&mut self,
last_recv_time: tokio::time::Instant,
msgtype: &str,
title: &str,
text: &str,
) -> bool {
let duration = Duration::from_secs(3);
let counter_interval = 3;
let lock = self.lc.read().unwrap();
let success_time = lock.success_time;
let direct = lock.direct.unwrap_or(false);
let received = lock.received;
drop(lock);
if let Some(success_time) = success_time {
if direct && last_recv_time.duration_since(success_time) < duration {
let retry_for_relay = direct && !received;
let retry = check_if_retry(msgtype, title, text, retry_for_relay);
if retry && !retry_for_relay {
self.lc.write().unwrap().direct_error_counter += 1;
if self.lc.read().unwrap().direct_error_counter % counter_interval == 0 {
#[cfg(feature = "flutter")]
return true;
}
}
} else {
self.lc.write().unwrap().direct_error_counter = 0;
}
}
false
}
}
pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
@@ -1060,9 +1113,9 @@ impl<T: InvokeUiSession> Interface for Session<T> {
}
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str) {
let direct = self.lc.read().unwrap().direct.unwrap_or_default();
let direct = self.lc.read().unwrap().direct;
let received = self.lc.read().unwrap().received;
let retry_for_relay = direct && !received;
let retry_for_relay = direct == Some(true) && !received;
let retry = check_if_retry(msgtype, title, text, retry_for_relay);
self.ui_handler.msgbox(msgtype, title, text, link, retry);
}
@@ -1119,7 +1172,6 @@ impl<T: InvokeUiSession> Interface for Session<T> {
"Connected, waiting for image...",
"",
);
self.lc.write().unwrap().success_time = Some(tokio::time::Instant::now());
}
self.on_connected(self.lc.read().unwrap().conn_type);
#[cfg(windows)]
@@ -1201,6 +1253,12 @@ impl<T: InvokeUiSession> Session<T> {
#[tokio::main(flavor = "current_thread")]
pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>) {
// It is ok to call this function multiple times.
#[cfg(target_os = "windows")]
if !handler.is_file_transfer() && !handler.is_port_forward() {
clipboard::ContextSend::enable(true);
}
#[cfg(any(target_os = "android", target_os = "ios"))]
let (sender, receiver) = mpsc::unbounded_channel::<Data>();
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -1347,7 +1405,7 @@ async fn start_one_port_forward<T: InvokeUiSession>(
}
#[tokio::main(flavor = "current_thread")]
async fn send_note(url: String, id: String, conn_id: i32, note: String) {
let body = serde_json::json!({ "id": id, "conn_id": conn_id, "note": note });
async fn send_note(url: String, id: String, sid: u64, note: String) {
let body = serde_json::json!({ "id": id, "session_id": sid, "note": note });
allow_err!(crate::post_request(url, body.to_string(), "").await);
}