Merge remote-tracking branch 'github/master' into sigma

# Conflicts:
#	flutter/lib/desktop/widgets/remote_menubar.dart
This commit is contained in:
sjpark
2023-02-25 11:37:12 +09:00
238 changed files with 8183 additions and 4234 deletions

View File

@@ -36,7 +36,7 @@ impl Session {
.lc
.write()
.unwrap()
.initialize(id.to_owned(), ConnType::PORT_FORWARD);
.initialize(id.to_owned(), ConnType::PORT_FORWARD, None);
session
}
}

View File

@@ -3,46 +3,49 @@ use std::{
net::SocketAddr,
ops::{Deref, Not},
str::FromStr,
sync::{Arc, atomic::AtomicBool, mpsc, Mutex, RwLock},
sync::{mpsc, Arc, Mutex, RwLock},
};
pub use async_trait::async_trait;
use bytes::Bytes;
#[cfg(not(any(target_os = "android", target_os = "linux")))]
use cpal::{
Device,
Host, StreamConfig, traits::{DeviceTrait, HostTrait, StreamTrait},
traits::{DeviceTrait, HostTrait, StreamTrait},
Device, Host, StreamConfig,
};
use magnum_opus::{Channels::*, Decoder as AudioDecoder};
use sha2::{Digest, Sha256};
use uuid::Uuid;
pub use file_trait::FileManager;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::tokio::sync::mpsc::UnboundedSender;
use hbb_common::{
AddrMangle,
allow_err,
anyhow::{anyhow, Context},
bail,
config::{
Config, CONNECT_TIMEOUT, PeerConfig, PeerInfoSerde, READ_TIMEOUT, RELAY_PORT,
Config, PeerConfig, PeerInfoSerde, CONNECT_TIMEOUT, READ_TIMEOUT, RELAY_PORT,
RENDEZVOUS_TIMEOUT,
}, get_version_number,
log,
message_proto::{*, option_message::BoolOption},
},
get_version_number, log,
message_proto::{option_message::BoolOption, *},
protobuf::Message as _,
rand,
rendezvous_proto::*,
ResultType,
socket_client,
sodiumoxide::crypto::{box_, secretbox, sign},
Stream, timeout, tokio::time::Duration,
timeout,
tokio::time::Duration,
AddrMangle, ResultType, Stream,
};
pub use helper::*;
pub use helper::LatencyController;
pub use helper::*;
use scrap::{
codec::{Decoder, DecoderCfg},
record::{Recorder, RecorderContext},
VpxDecoderConfig, VpxVideoCodecId,
ImageFormat,
};
use crate::{
@@ -50,21 +53,30 @@ use crate::{
server::video_service::{SCRAP_X11_REF_URL, SCRAP_X11_REQUIRED},
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::{
common::{check_clipboard, ClipboardContext, CLIPBOARD_INTERVAL},
ui_session_interface::SessionPermissionConfig,
};
pub use super::lang::*;
pub mod file_trait;
pub mod helper;
pub mod io_loop;
pub static SERVER_KEYBOARD_ENABLED: AtomicBool = AtomicBool::new(true);
pub static SERVER_FILE_TRANSFER_ENABLED: AtomicBool = AtomicBool::new(true);
pub static SERVER_CLIPBOARD_ENABLED: AtomicBool = AtomicBool::new(true);
pub const MILLI1: Duration = Duration::from_millis(1);
pub const SEC30: Duration = Duration::from_secs(30);
/// Client of the remote desktop.
pub struct Client;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
struct TextClipboardState {
is_required: bool,
running: bool,
}
#[cfg(not(any(target_os = "android", target_os = "linux")))]
lazy_static::lazy_static! {
static ref AUDIO_HOST: Host = cpal::default_host();
@@ -73,6 +85,8 @@ lazy_static::lazy_static! {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
lazy_static::lazy_static! {
static ref ENIGO: Arc<Mutex<enigo::Enigo>> = Arc::new(Mutex::new(enigo::Enigo::new()));
static ref OLD_CLIPBOARD_TEXT: Arc<Mutex<String>> = Default::default();
static ref TEXT_CLIPBOARD_STATE: Arc<Mutex<TextClipboardState>> = Arc::new(Mutex::new(TextClipboardState::new()));
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -88,7 +102,7 @@ pub fn get_key_state(key: enigo::Key) -> bool {
cfg_if::cfg_if! {
if #[cfg(target_os = "android")] {
use libc::{c_float, c_int, c_void};
use hbb_common::libc::{c_float, c_int, c_void};
type Oboe = *mut c_void;
extern "C" {
fn create_oboe_player(channels: c_int, sample_rate: c_int) -> Oboe;
@@ -598,6 +612,86 @@ impl Client {
conn.send(&msg_out).await?;
Ok(conn)
}
#[inline]
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn set_is_text_clipboard_required(b: bool) {
TEXT_CLIPBOARD_STATE.lock().unwrap().is_required = b;
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn try_stop_clipboard(_self_id: &str) {
#[cfg(feature = "flutter")]
if crate::flutter::other_sessions_running(_self_id) {
return;
}
TEXT_CLIPBOARD_STATE.lock().unwrap().running = false;
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn try_start_clipboard(_conf_tx: Option<(SessionPermissionConfig, UnboundedSender<Data>)>) {
let mut clipboard_lock = TEXT_CLIPBOARD_STATE.lock().unwrap();
if clipboard_lock.running {
return;
}
match ClipboardContext::new() {
Ok(mut ctx) => {
clipboard_lock.running = true;
// ignore clipboard update before service start
check_clipboard(&mut ctx, Some(&OLD_CLIPBOARD_TEXT));
std::thread::spawn(move || {
log::info!("Start text clipboard loop");
loop {
std::thread::sleep(Duration::from_millis(CLIPBOARD_INTERVAL));
if !TEXT_CLIPBOARD_STATE.lock().unwrap().running {
break;
}
if !TEXT_CLIPBOARD_STATE.lock().unwrap().is_required {
continue;
}
if let Some(msg) = check_clipboard(&mut ctx, Some(&OLD_CLIPBOARD_TEXT)) {
#[cfg(feature = "flutter")]
crate::flutter::send_text_clipboard_msg(msg);
#[cfg(not(feature = "flutter"))]
if let Some((cfg, tx)) = &_conf_tx {
if cfg.is_text_clipboard_required() {
let _ = tx.send(Data::Message(msg));
}
}
}
}
log::info!("Stop text clipboard loop");
});
}
Err(err) => {
log::error!("Failed to start clipboard service of client: {}", err);
}
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn get_current_text_clipboard_msg() -> Option<Message> {
let txt = &*OLD_CLIPBOARD_TEXT.lock().unwrap();
if txt.is_empty() {
None
} else {
Some(crate::create_clipboard_msg(txt.clone()))
}
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
impl TextClipboardState {
fn new() -> Self {
Self {
is_required: true,
running: false,
}
}
}
/// Audio handler for the [`Client`].
@@ -850,7 +944,12 @@ impl VideoHandler {
}
match &vf.union {
Some(frame) => {
let res = self.decoder.handle_video_frame(frame, &mut self.rgb);
// windows && flutter_texture_render, fmt is ImageFormat::ABGR
#[cfg(all(target_os = "windows", feature = "flutter_texture_render"))]
let fmt = ImageFormat::ABGR;
#[cfg(not(all(target_os = "windows", feature = "flutter_texture_render")))]
let fmt = ImageFormat::ARGB;
let res = self.decoder.handle_video_frame(frame, fmt, &mut self.rgb);
if self.record {
self.recorder
.lock()
@@ -916,6 +1015,8 @@ pub struct LoginConfigHandler {
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,
}
impl Deref for LoginConfigHandler {
@@ -943,7 +1044,13 @@ impl LoginConfigHandler {
///
/// * `id` - id of peer
/// * `conn_type` - Connection type enum.
pub fn initialize(&mut self, id: String, conn_type: ConnType, switch_uuid: Option<String>) {
pub fn initialize(
&mut self,
id: String,
conn_type: ConnType,
switch_uuid: Option<String>,
force_relay: bool,
) {
self.id = id;
self.conn_type = conn_type;
let config = self.load_config();
@@ -952,10 +1059,12 @@ impl LoginConfigHandler {
self.session_id = rand::random();
self.supported_encoding = None;
self.restarting_remote_device = false;
self.force_relay = !self.get_option("force-always-relay").is_empty();
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.
@@ -1549,7 +1658,7 @@ pub type MediaSender = mpsc::Sender<MediaData>;
/// * `video_callback` - The callback for video frame. Being called when a video frame is ready.
pub fn start_video_audio_threads<F>(video_callback: F) -> (MediaSender, MediaSender)
where
F: 'static + FnMut(&[u8]) + Send,
F: 'static + FnMut(&mut Vec<u8>) + Send,
{
let (video_sender, video_receiver) = mpsc::channel::<MediaData>();
let mut video_callback = video_callback;
@@ -1564,7 +1673,7 @@ where
match data {
MediaData::VideoFrame(vf) => {
if let Ok(true) = video_handler.handle_frame(vf) {
video_callback(&video_handler.rgb);
video_callback(&mut video_handler.rgb);
}
}
MediaData::Reset => {
@@ -2110,9 +2219,7 @@ pub fn check_if_retry(msgtype: &str, title: &str, text: &str, retry_for_relay: b
&& !text.to_lowercase().contains("resolve")
&& !text.to_lowercase().contains("mismatch")
&& !text.to_lowercase().contains("manually")
&& !text.to_lowercase().contains("not allowed")
&& !text.to_lowercase().contains("as expected")
&& !text.to_lowercase().contains("reset by the peer")))
&& !text.to_lowercase().contains("not allowed")))
}
#[inline]

View File

@@ -7,7 +7,7 @@ pub trait FileManager: Interface {
fs::get_home_as_string()
}
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli", feature = "flutter")))]
fn read_dir(&self, path: String, include_hidden: bool) -> sciter::Value {
match fs::read_dir(&fs::get_path(&path), include_hidden) {
Err(_) => sciter::Value::null(),
@@ -20,7 +20,7 @@ pub trait FileManager: Interface {
}
}
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli", feature = "flutter"))]
fn read_dir(&self, path: &str, include_hidden: bool) -> String {
use crate::common::make_fd_to_json;
match fs::read_dir(&fs::get_path(path), include_hidden) {

View File

@@ -25,12 +25,11 @@ use hbb_common::{allow_err, get_time, message_proto::*, sleep};
use hbb_common::{fs, log, Stream};
use crate::client::{
new_voice_call_request, Client, CodecFormat, MediaData, MediaSender,
QualityStatus, MILLI1, SEC30, SERVER_CLIPBOARD_ENABLED, SERVER_FILE_TRANSFER_ENABLED,
SERVER_KEYBOARD_ENABLED,
new_voice_call_request, Client, CodecFormat, MediaData, MediaSender, QualityStatus, MILLI1,
SEC30,
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::common::{check_clipboard, update_clipboard, ClipboardContext, CLIPBOARD_INTERVAL};
use crate::common::update_clipboard;
use crate::common::{get_default_sound_input, set_sound_input};
use crate::ui_session_interface::{InvokeUiSession, Session};
use crate::{audio_service, common, ConnInner, CLIENT_SERVER};
@@ -92,7 +91,6 @@ impl<T: InvokeUiSession> Remote<T> {
}
pub async fn io_loop(&mut self, key: &str, token: &str) {
let stop_clipboard = self.start_clipboard();
let mut last_recv_time = Instant::now();
let mut received = false;
let conn_type = if self.handler.is_file_transfer() {
@@ -111,9 +109,6 @@ impl<T: InvokeUiSession> Remote<T> {
.await
{
Ok((mut peer, direct)) => {
SERVER_KEYBOARD_ENABLED.store(true, Ordering::SeqCst);
SERVER_CLIPBOARD_ENABLED.store(true, Ordering::SeqCst);
SERVER_FILE_TRANSFER_ENABLED.store(true, Ordering::SeqCst);
self.handler.set_connection_type(peer.is_secured(), direct); // flutter -> connection_ready
self.handler.set_connection_info(direct, false);
@@ -148,7 +143,15 @@ impl<T: InvokeUiSession> Remote<T> {
Err(err) => {
log::error!("Connection closed: {}", err);
self.handler.set_force_relay(direct, received);
self.handler.msgbox("error", "Connection Error", &err.to_string(), "");
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, "");
}
break;
}
Ok(ref bytes) => {
@@ -230,12 +233,8 @@ impl<T: InvokeUiSession> Remote<T> {
.msgbox("error", "Connection Error", &err.to_string(), "");
}
}
if let Some(stop) = stop_clipboard {
stop.send(()).ok();
}
SERVER_KEYBOARD_ENABLED.store(false, Ordering::SeqCst);
SERVER_CLIPBOARD_ENABLED.store(false, Ordering::SeqCst);
SERVER_FILE_TRANSFER_ENABLED.store(false, Ordering::SeqCst);
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Client::try_stop_clipboard(&self.handler.id);
}
fn handle_job_status(&mut self, id: i32, file_num: i32, err: Option<String>) {
@@ -340,46 +339,6 @@ impl<T: InvokeUiSession> Remote<T> {
Some(tx)
}
fn start_clipboard(&mut self) -> Option<std::sync::mpsc::Sender<()>> {
if self.handler.is_file_transfer() || self.handler.is_port_forward() {
return None;
}
let (tx, rx) = std::sync::mpsc::channel();
let old_clipboard = self.old_clipboard.clone();
let tx_protobuf = self.sender.clone();
let lc = self.handler.lc.clone();
#[cfg(not(any(target_os = "android", target_os = "ios")))]
match ClipboardContext::new() {
Ok(mut ctx) => {
// ignore clipboard update before service start
check_clipboard(&mut ctx, Some(&old_clipboard));
std::thread::spawn(move || loop {
std::thread::sleep(Duration::from_millis(CLIPBOARD_INTERVAL));
match rx.try_recv() {
Ok(_) | Err(std::sync::mpsc::TryRecvError::Disconnected) => {
log::debug!("Exit clipboard service of client");
break;
}
_ => {}
}
if !SERVER_CLIPBOARD_ENABLED.load(Ordering::SeqCst)
|| !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst)
|| lc.read().unwrap().disable_clipboard.v
{
continue;
}
if let Some(msg) = check_clipboard(&mut ctx, Some(&old_clipboard)) {
tx_protobuf.send(Data::Message(msg)).ok();
}
});
}
Err(err) => {
log::error!("Failed to start clipboard service of client: {}", err);
}
}
Some(tx)
}
async fn handle_msg_from_ui(&mut self, data: Data, peer: &mut Stream) -> bool {
match data {
Data::Close => {
@@ -754,7 +713,8 @@ impl<T: InvokeUiSession> Remote<T> {
Data::CloseVoiceCall => {
self.stop_voice_call();
let msg = new_voice_call_request(false);
self.handler.on_voice_call_closed("Closed manually by the peer");
self.handler
.on_voice_call_closed("Closed manually by the peer");
allow_err!(peer.send(&msg).await);
}
_ => {}
@@ -877,22 +837,31 @@ impl<T: InvokeUiSession> Remote<T> {
Some(login_response::Union::PeerInfo(pi)) => {
self.handler.handle_peer_info(pi);
self.check_clipboard_file_context();
if !(self.handler.is_file_transfer()
|| self.handler.is_port_forward()
|| !SERVER_CLIPBOARD_ENABLED.load(Ordering::SeqCst)
|| !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst)
|| self.handler.lc.read().unwrap().disable_clipboard.v)
{
let txt = self.old_clipboard.lock().unwrap().clone();
if !txt.is_empty() {
let msg_out = crate::create_clipboard_msg(txt);
let sender = self.sender.clone();
tokio::spawn(async move {
// due to clipboard service interval time
sleep(common::CLIPBOARD_INTERVAL as f32 / 1_000.).await;
sender.send(Data::Message(msg_out)).ok();
});
}
if !(self.handler.is_file_transfer() || self.handler.is_port_forward()) {
let sender = self.sender.clone();
let permission_config = self.handler.get_permission_config();
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Client::try_start_clipboard(None);
#[cfg(not(feature = "flutter"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Client::try_start_clipboard(Some((
permission_config.clone(),
sender.clone(),
)));
#[cfg(not(any(target_os = "android", target_os = "ios")))]
tokio::spawn(async move {
// due to clipboard service interval time
sleep(common::CLIPBOARD_INTERVAL as f32 / 1_000.).await;
if permission_config.is_text_clipboard_required() {
if let Some(msg_out) = Client::get_current_text_clipboard_msg()
{
sender.send(Data::Message(msg_out)).ok();
}
}
});
}
if self.handler.is_file_transfer() {
@@ -1084,18 +1053,25 @@ impl<T: InvokeUiSession> Remote<T> {
log::info!("Change permission {:?} -> {}", p.permission, p.enabled);
match p.permission.enum_value_or_default() {
Permission::Keyboard => {
SERVER_KEYBOARD_ENABLED.store(p.enabled, Ordering::SeqCst);
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
crate::flutter::update_text_clipboard_required();
*self.handler.server_keyboard_enabled.write().unwrap() = p.enabled;
self.handler.set_permission("keyboard", p.enabled);
}
Permission::Clipboard => {
SERVER_CLIPBOARD_ENABLED.store(p.enabled, Ordering::SeqCst);
#[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
crate::flutter::update_text_clipboard_required();
*self.handler.server_clipboard_enabled.write().unwrap() = p.enabled;
self.handler.set_permission("clipboard", p.enabled);
}
Permission::Audio => {
self.handler.set_permission("audio", p.enabled);
}
Permission::File => {
SERVER_FILE_TRANSFER_ENABLED.store(p.enabled, Ordering::SeqCst);
*self.handler.server_file_transfer_enabled.write().unwrap() =
p.enabled;
if !p.enabled && self.handler.is_file_transfer() {
return true;
}
@@ -1277,6 +1253,14 @@ 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);
}
_ => {}
}
}
_ => {}
}
}
@@ -1408,7 +1392,7 @@ impl<T: InvokeUiSession> Remote<T> {
fn check_clipboard_file_context(&self) {
#[cfg(windows)]
{
let enabled = SERVER_FILE_TRANSFER_ENABLED.load(Ordering::SeqCst)
let enabled = *self.handler.server_file_transfer_enabled.read().unwrap()
&& self.handler.lc.read().unwrap().enable_file_transfer.v;
ContextSend::enable(enabled);
}

View File

@@ -37,6 +37,8 @@ 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;
// the executable name of the portable version
pub const PORTABLE_APPNAME_RUNTIME_ENV_KEY: &str = "RUSTDESK_APPNAME";
@@ -50,6 +52,11 @@ lazy_static::lazy_static! {
pub static ref DEVICE_NAME: Arc<Mutex<String>> = Default::default();
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
lazy_static::lazy_static! {
static ref ARBOARD_MTX: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
}
pub fn global_init() -> bool {
#[cfg(target_os = "linux")]
{
@@ -94,7 +101,11 @@ pub fn check_clipboard(
) -> Option<Message> {
let side = if old.is_none() { "host" } else { "client" };
let old = if let Some(old) = old { old } else { &CONTENT };
if let Ok(content) = ctx.get_text() {
let content = {
let _lock = ARBOARD_MTX.lock().unwrap();
ctx.get_text()
};
if let Ok(content) = content {
if content.len() < 2_000_000 && !content.is_empty() {
let changed = content != *old.lock().unwrap();
if changed {
@@ -172,6 +183,7 @@ pub fn update_clipboard(clipboard: Clipboard, old: Option<&Arc<Mutex<String>>>)
let side = if old.is_none() { "host" } else { "client" };
let old = if let Some(old) = old { old } else { &CONTENT };
*old.lock().unwrap() = content.clone();
let _lock = ARBOARD_MTX.lock().unwrap();
allow_err!(ctx.set_text(content));
log::debug!("{} updated on {}", CLIPBOARD_NAME, side);
}
@@ -588,11 +600,6 @@ async fn check_software_update_() -> hbb_common::ResultType<()> {
Ok(())
}
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
pub fn get_icon() -> String {
hbb_common::config::ICON.to_owned()
}
pub fn get_app_name() -> String {
hbb_common::config::APP_NAME.read().unwrap().clone()
}
@@ -762,3 +769,14 @@ pub fn make_fd_to_json(id: i32, path: String, entries: &Vec<FileEntry>) -> Strin
fd_json.insert("entries".into(), json!(entries_out));
serde_json::to_string(&fd_json).unwrap_or("".into())
}
/// The function to handle the url scheme sent by the system.
///
/// 1. Try to send the url scheme from ipc.
/// 2. If failed to send the url scheme, we open a new main window to handle this url scheme.
pub fn handle_url_scheme(url: String) {
if let Err(err) = crate::ipc::send_url_scheme(url.clone()) {
log::debug!("Send the url to the existing flutter process failed, {}. Let's open a new program to handle this.", err);
let _ = crate::run_me(vec![url]);
}
}

View File

@@ -1,4 +1,6 @@
use hbb_common::log;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::platform::register_breakdown_handler;
/// shared by flutter and sciter main function
///
@@ -38,10 +40,11 @@ pub fn core_main() -> Option<Vec<String>> {
}
i += 1;
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
register_breakdown_handler();
#[cfg(target_os = "linux")]
#[cfg(feature = "flutter")]
{
crate::platform::linux::register_breakdown_handler();
let (k, v) = ("LIBGL_ALWAYS_SOFTWARE", "true");
if !hbb_common::config::Config::get_option("allow-always-software-render").is_empty() {
std::env::set_var(k, v);
@@ -164,9 +167,6 @@ pub fn core_main() -> Option<Vec<String>> {
#[cfg(feature = "with_rc")]
hbb_common::allow_err!(crate::rc::extract_resources(&args[1]));
return None;
} else if args[0] == "--tray" {
crate::tray::start_tray();
return None;
} else if args[0] == "--portable-service" {
crate::platform::elevate_or_run_as_system(
click_setup,
@@ -183,34 +183,24 @@ pub fn core_main() -> Option<Vec<String>> {
std::fs::remove_file(&args[1]).ok();
return None;
}
} else if args[0] == "--tray" {
crate::tray::start_tray();
return None;
} else if args[0] == "--service" {
log::info!("start --service");
crate::start_os_service();
return None;
} else if args[0] == "--server" {
log::info!("start --server with user {}", crate::username());
#[cfg(target_os = "windows")]
#[cfg(any(target_os = "linux", target_os = "windows"))]
{
crate::start_server(true);
return None;
}
#[cfg(target_os = "macos")]
{
std::thread::spawn(move || crate::start_server(true));
crate::platform::macos::hide_dock();
crate::ui::macos::make_tray();
return None;
}
#[cfg(target_os = "linux")]
{
let handler = std::thread::spawn(move || crate::start_server(true));
// Show the tray in linux only when current user is a normal user
// [Note]
// As for GNOME, the tray cannot be shown in user's status bar.
// As for KDE, the tray can be shown without user's theme.
if !crate::platform::is_root() {
crate::tray::start_tray();
}
crate::tray::start_tray();
// prevent server exit when encountering errors from tray
hbb_common::allow_err!(handler.join());
}
@@ -349,6 +339,6 @@ fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<Strin
Some(Vec::new())
} else {
None
}
};
}
}

View File

@@ -1,11 +1,24 @@
use crate::ui_session_interface::{io_loop, InvokeUiSession, Session};
use crate::{client::*, flutter_ffi::EventToUI};
use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer};
use crate::{
client::*,
flutter_ffi::EventToUI,
ui_session_interface::{io_loop, InvokeUiSession, Session},
};
#[cfg(feature = "flutter_texture_render")]
use dlopen::{
symbor::{Library, Symbol},
Error as LibError,
};
use flutter_rust_bridge::StreamSink;
#[cfg(feature = "flutter_texture_render")]
use hbb_common::libc::c_void;
use hbb_common::{
bail, config::LocalConfig, get_version_number, message_proto::*, rendezvous_proto::ConnType,
ResultType,
bail, config::LocalConfig, get_version_number, log, message_proto::*,
rendezvous_proto::ConnType, ResultType,
};
use serde_json::json;
#[cfg(not(feature = "flutter_texture_render"))]
use std::sync::atomic::{AtomicBool, Ordering};
use std::{
collections::HashMap,
ffi::CString,
@@ -25,6 +38,21 @@ lazy_static::lazy_static! {
pub static ref GLOBAL_EVENT_STREAM: RwLock<HashMap<String, StreamSink<String>>> = Default::default(); // rust to dart event channel
}
#[cfg(all(target_os = "windows", feature = "flutter_texture_render"))]
lazy_static::lazy_static! {
pub static ref TEXTURE_RGBA_RENDERER_PLUGIN: Result<Library, LibError> = Library::open("texture_rgba_renderer_plugin.dll");
}
#[cfg(all(target_os = "linux", feature = "flutter_texture_render"))]
lazy_static::lazy_static! {
pub static ref TEXTURE_RGBA_RENDERER_PLUGIN: Result<Library, LibError> = Library::open("libtexture_rgba_renderer_plugin.so");
}
#[cfg(all(target_os = "macos", feature = "flutter_texture_render"))]
lazy_static::lazy_static! {
pub static ref TEXTURE_RGBA_RENDERER_PLUGIN: Result<Library, LibError> = Library::open_self();
}
/// FFI for rustdesk core's main entry.
/// Return true if the app should continue running with UI(possibly Flutter), false if the app should exit.
#[cfg(not(windows))]
@@ -39,11 +67,7 @@ pub extern "C" fn rustdesk_core_main() -> bool {
#[cfg(target_os = "macos")]
#[no_mangle]
pub extern "C" fn handle_applicationShouldOpenUntitledFile() {
hbb_common::log::debug!("icon clicked on finder");
let x = std::env::args().nth(1).unwrap_or_default();
if x == "--server" || x == "--cm" {
crate::platform::macos::check_main_window();
}
crate::platform::macos::handle_application_should_open_untitled_file();
}
#[cfg(windows)]
@@ -108,9 +132,101 @@ pub unsafe extern "C" fn free_c_args(ptr: *mut *mut c_char, len: c_int) {
// Afterwards the vector will be dropped and thus freed.
}
#[cfg(feature = "flutter_texture_render")]
#[derive(Default, Clone)]
pub struct FlutterHandler {
pub event_stream: Arc<RwLock<Option<StreamSink<EventToUI>>>>,
notify_rendered: Arc<RwLock<bool>>,
renderer: Arc<RwLock<VideoRenderer>>,
peer_info: Arc<RwLock<PeerInfo>>,
}
#[cfg(not(feature = "flutter_texture_render"))]
#[derive(Default, Clone)]
pub struct FlutterHandler {
pub event_stream: Arc<RwLock<Option<StreamSink<EventToUI>>>>,
// SAFETY: [rgba] is guarded by [rgba_valid], and it's safe to reach [rgba] with `rgba_valid == true`.
// We must check the `rgba_valid` before reading [rgba].
pub rgba: Arc<RwLock<Vec<u8>>>,
pub rgba_valid: Arc<AtomicBool>,
peer_info: Arc<RwLock<PeerInfo>>,
}
#[cfg(feature = "flutter_texture_render")]
pub type FlutterRgbaRendererPluginOnRgba =
unsafe extern "C" fn(texture_rgba: *mut c_void, buffer: *const u8, width: c_int, height: c_int);
// Video Texture Renderer in Flutter
#[cfg(feature = "flutter_texture_render")]
#[derive(Clone)]
struct VideoRenderer {
// TextureRgba pointer in flutter native.
ptr: usize,
width: i32,
height: i32,
data_len: usize,
on_rgba_func: Option<Symbol<'static, FlutterRgbaRendererPluginOnRgba>>,
}
#[cfg(feature = "flutter_texture_render")]
impl Default for VideoRenderer {
fn default() -> Self {
let on_rgba_func = match &*TEXTURE_RGBA_RENDERER_PLUGIN {
Ok(lib) => {
let find_sym_res = unsafe {
lib.symbol::<FlutterRgbaRendererPluginOnRgba>("FlutterRgbaRendererPluginOnRgba")
};
match find_sym_res {
Ok(sym) => Some(sym),
Err(e) => {
log::error!("Failed to find symbol FlutterRgbaRendererPluginOnRgba, {e}");
None
}
}
}
Err(e) => {
log::error!("Failed to load texture rgba renderer plugin, {e}");
None
}
};
Self {
ptr: 0,
width: 0,
height: 0,
data_len: 0,
on_rgba_func,
}
}
}
#[cfg(feature = "flutter_texture_render")]
impl VideoRenderer {
#[inline]
pub fn set_size(&mut self, width: i32, height: i32) {
self.width = width;
self.height = height;
self.data_len = if width > 0 && height > 0 {
(width * height * 4) as usize
} else {
0
};
}
pub fn on_rgba(&self, rgba: &Vec<u8>) {
if self.ptr == usize::default() || rgba.len() != self.data_len {
return;
}
if let Some(func) = &self.on_rgba_func {
unsafe {
func(
self.ptr as _,
rgba.as_ptr() as _,
self.width as _,
self.height as _,
)
};
}
}
}
impl FlutterHandler {
@@ -130,6 +246,41 @@ impl FlutterHandler {
stream.add(EventToUI::Event(out));
}
}
pub fn close_event_stream(&mut self) {
let mut stream_lock = self.event_stream.write().unwrap();
if let Some(stream) = &*stream_lock {
stream.add(EventToUI::Event("close".to_owned()));
}
*stream_lock = None;
}
fn make_displays_msg(displays: &Vec<DisplayInfo>) -> String {
let mut msg_vec = Vec::new();
for ref d in displays.iter() {
let mut h: HashMap<&str, i32> = Default::default();
h.insert("x", d.x);
h.insert("y", d.y);
h.insert("width", d.width);
h.insert("height", d.height);
h.insert("cursor_embedded", if d.cursor_embedded { 1 } else { 0 });
msg_vec.push(h);
}
serde_json::ser::to_string(&msg_vec).unwrap_or("".to_owned())
}
#[inline]
#[cfg(feature = "flutter_texture_render")]
pub fn register_texture(&mut self, ptr: usize) {
self.renderer.write().unwrap().ptr = ptr;
}
#[inline]
#[cfg(feature = "flutter_texture_render")]
pub fn set_size(&mut self, width: i32, height: i32) {
*self.notify_rendered.write().unwrap() = false;
self.renderer.write().unwrap().set_size(width, height);
}
}
impl InvokeUiSession for FlutterHandler {
@@ -289,24 +440,37 @@ impl InvokeUiSession for FlutterHandler {
// unused in flutter
fn adapt_size(&self) {}
fn on_rgba(&self, data: &[u8]) {
#[inline]
#[cfg(not(feature = "flutter_texture_render"))]
fn on_rgba(&self, data: &mut Vec<u8>) {
// If the current rgba is not fetched by flutter, i.e., is valid.
// We give up sending a new event to flutter.
if self.rgba_valid.load(Ordering::Relaxed) {
return;
}
self.rgba_valid.store(true, Ordering::Relaxed);
// Return the rgba buffer to the video handler for reusing allocated rgba buffer.
std::mem::swap::<Vec<u8>>(data, &mut *self.rgba.write().unwrap());
if let Some(stream) = &*self.event_stream.read().unwrap() {
stream.add(EventToUI::Rgba(ZeroCopyBuffer(data.to_owned())));
stream.add(EventToUI::Rgba);
}
}
#[inline]
#[cfg(feature = "flutter_texture_render")]
fn on_rgba(&self, data: &mut Vec<u8>) {
self.renderer.read().unwrap().on_rgba(data);
if *self.notify_rendered.read().unwrap() {
return;
}
if let Some(stream) = &*self.event_stream.read().unwrap() {
stream.add(EventToUI::Rgba);
*self.notify_rendered.write().unwrap() = true;
}
}
fn set_peer_info(&self, pi: &PeerInfo) {
let mut displays = Vec::new();
for ref d in pi.displays.iter() {
let mut h: HashMap<&str, i32> = Default::default();
h.insert("x", d.x);
h.insert("y", d.y);
h.insert("width", d.width);
h.insert("height", d.height);
h.insert("cursor_embedded", if d.cursor_embedded { 1 } else { 0 });
displays.push(h);
}
let displays = serde_json::ser::to_string(&displays).unwrap_or("".to_owned());
let displays = Self::make_displays_msg(&pi.displays);
let mut features: HashMap<&str, i32> = Default::default();
for ref f in pi.features.iter() {
features.insert("privacy_mode", if f.privacy_mode { 1 } else { 0 });
@@ -316,6 +480,8 @@ impl InvokeUiSession for FlutterHandler {
features.insert("privacy_mode", 0);
}
let features = serde_json::ser::to_string(&features).unwrap_or("".to_owned());
let resolutions = serialize_resolutions(&pi.resolutions.resolutions);
*self.peer_info.write().unwrap() = pi.clone();
self.push_event(
"peer_info",
vec![
@@ -327,10 +493,19 @@ impl InvokeUiSession for FlutterHandler {
("version", &pi.version),
("features", &features),
("current_display", &pi.current_display.to_string()),
("resolutions", &resolutions),
],
);
}
fn set_displays(&self, displays: &Vec<DisplayInfo>) {
self.peer_info.write().unwrap().displays = displays.clone();
self.push_event(
"sync_peer_info",
vec![("displays", &Self::make_displays_msg(displays))],
);
}
fn on_connected(&self, _conn_type: ConnType) {}
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool) {
@@ -356,6 +531,7 @@ impl InvokeUiSession for FlutterHandler {
}
fn switch_display(&self, display: &SwitchDisplay) {
let resolutions = serialize_resolutions(&display.resolutions.resolutions);
self.push_event(
"switch_display",
vec![
@@ -375,6 +551,7 @@ impl InvokeUiSession for FlutterHandler {
}
.to_string(),
),
("resolutions", &resolutions),
],
);
}
@@ -410,6 +587,21 @@ impl InvokeUiSession for FlutterHandler {
fn on_voice_call_incoming(&self) {
self.push_event("on_voice_call_incoming", [].into());
}
#[inline]
fn get_rgba(&self) -> *const u8 {
#[cfg(not(feature = "flutter_texture_render"))]
if self.rgba_valid.load(Ordering::Relaxed) {
return self.rgba.read().unwrap().as_ptr();
}
std::ptr::null_mut()
}
#[inline]
fn next_rgba(&self) {
#[cfg(not(feature = "flutter_texture_render"))]
self.rgba_valid.store(false, Ordering::Relaxed);
}
}
/// Create a new remote session with the given id.
@@ -424,12 +616,16 @@ pub fn session_add(
is_file_transfer: bool,
is_port_forward: bool,
switch_uuid: &str,
force_relay: bool,
) -> ResultType<()> {
let session_id = get_session_id(id.to_owned());
LocalConfig::set_remote_id(&session_id);
let session: Session<FlutterHandler> = Session {
id: session_id.clone(),
server_keyboard_enabled: Arc::new(RwLock::new(true)),
server_file_transfer_enabled: Arc::new(RwLock::new(true)),
server_clipboard_enabled: Arc::new(RwLock::new(true)),
..Default::default()
};
@@ -452,7 +648,7 @@ pub fn session_add(
.lc
.write()
.unwrap()
.initialize(session_id, conn_type, switch_uuid);
.initialize(session_id, conn_type, switch_uuid, force_relay);
if let Some(same_id_session) = SESSIONS.write().unwrap().insert(id.to_owned(), session) {
same_id_session.close();
@@ -469,6 +665,13 @@ pub fn session_add(
/// * `events2ui` - The events channel to ui.
pub fn session_start_(id: &str, event_stream: StreamSink<EventToUI>) -> ResultType<()> {
if let Some(session) = SESSIONS.write().unwrap().get_mut(id) {
#[cfg(feature = "flutter_texture_render")]
log::info!(
"Session {} start, render by flutter texture rgba plugin",
id
);
#[cfg(not(feature = "flutter_texture_render"))]
log::info!("Session {} start, render by flutter paint widget", id);
*session.event_stream.write().unwrap() = Some(event_stream);
let session = session.clone();
std::thread::spawn(move || {
@@ -480,6 +683,31 @@ pub fn session_start_(id: &str, event_stream: StreamSink<EventToUI>) -> ResultTy
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn update_text_clipboard_required() {
let is_required = SESSIONS
.read()
.unwrap()
.iter()
.any(|(_id, session)| session.is_text_clipboard_required());
Client::set_is_text_clipboard_required(is_required);
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn other_sessions_running(id: &str) -> bool {
SESSIONS.read().unwrap().keys().filter(|k| *k != id).count() != 0
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn send_text_clipboard_msg(msg: Message) {
for (_id, session) in SESSIONS.read().unwrap().iter() {
if session.is_text_clipboard_required() {
session.send(Data::Message(msg.clone()));
}
}
}
// Server Side
#[cfg(not(any(target_os = "ios")))]
pub mod connection_manager {
@@ -549,11 +777,15 @@ pub mod connection_manager {
let mut h: HashMap<&str, &str> = event.iter().cloned().collect();
assert!(h.get("name").is_none());
h.insert("name", name);
if let Some(s) = GLOBAL_EVENT_STREAM.read().unwrap().get(super::APP_TYPE_CM) {
s.add(serde_json::ser::to_string(&h).unwrap_or("".to_owned()));
} else {
println!("Push event {} failed. No {} event stream found.", name, super::APP_TYPE_CM);
println!(
"Push event {} failed. No {} event stream found.",
name,
super::APP_TYPE_CM
);
};
}
}
@@ -632,3 +864,78 @@ pub fn set_cur_session_id(id: String) {
*CUR_SESSION_ID.write().unwrap() = id;
}
}
#[inline]
fn serialize_resolutions(resolutions: &Vec<Resolution>) -> String {
#[derive(Debug, serde::Serialize)]
struct ResolutionSerde {
width: i32,
height: i32,
}
let mut v = vec![];
resolutions
.iter()
.map(|r| {
v.push(ResolutionSerde {
width: r.width,
height: r.height,
})
})
.count();
serde_json::ser::to_string(&v).unwrap_or("".to_string())
}
#[no_mangle]
#[cfg(not(feature = "flutter_texture_render"))]
pub fn session_get_rgba_size(id: *const char) -> usize {
let id = unsafe { std::ffi::CStr::from_ptr(id as _) };
if let Ok(id) = id.to_str() {
if let Some(session) = SESSIONS.read().unwrap().get(id) {
return session.rgba.read().unwrap().len();
}
}
0
}
#[no_mangle]
#[cfg(feature = "flutter_texture_render")]
pub fn session_get_rgba_size(_id: *const char) -> usize {
0
}
#[no_mangle]
pub fn session_get_rgba(id: *const char) -> *const u8 {
let id = unsafe { std::ffi::CStr::from_ptr(id as _) };
if let Ok(id) = id.to_str() {
if let Some(session) = SESSIONS.read().unwrap().get(id) {
return session.get_rgba();
}
}
std::ptr::null()
}
#[no_mangle]
pub fn session_next_rgba(id: *const char) {
let id = unsafe { std::ffi::CStr::from_ptr(id as _) };
if let Ok(id) = id.to_str() {
if let Some(session) = SESSIONS.read().unwrap().get(id) {
return session.next_rgba();
}
}
}
#[no_mangle]
#[cfg(feature = "flutter_texture_render")]
pub fn session_register_texture(id: *const char, ptr: usize) {
let id = unsafe { std::ffi::CStr::from_ptr(id as _) };
if let Ok(id) = id.to_str() {
if let Some(session) = SESSIONS.write().unwrap().get_mut(id) {
return session.register_texture(ptr);
}
}
}
#[no_mangle]
#[cfg(not(feature = "flutter_texture_render"))]
pub fn session_register_texture(_id: *const char, _ptr: usize) {}

View File

@@ -1,27 +1,27 @@
use std::{collections::HashMap, ffi::{CStr, CString}, os::raw::c_char};
use std::str::FromStr;
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))]
use std::thread;
use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer};
use serde_json::json;
use hbb_common::{
config::{self, LocalConfig, ONLINE, PeerConfig},
fs, log,
};
use hbb_common::message_proto::KeyboardMode;
use hbb_common::ResultType;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::common::get_default_sound_input;
use crate::{
client::file_trait::FileManager,
common::is_keyboard_mode_supported,
common::make_fd_to_json,
flutter::{self, SESSIONS},
flutter::{session_add, session_start_},
ui_interface::{self, *},
};
use flutter_rust_bridge::{StreamSink, SyncReturn};
use hbb_common::{
config::{self, LocalConfig, PeerConfig, ONLINE},
fs, log,
message_proto::KeyboardMode,
ResultType,
};
use serde_json::json;
use std::{
collections::HashMap,
ffi::{CStr, CString},
os::raw::c_char,
str::FromStr,
};
use crate::common::{get_default_sound_input, is_keyboard_mode_supported};
use crate::flutter::{self, SESSIONS};
use crate::ui_interface::{self, *};
// use crate::hbbs_http::account::AuthResult;
@@ -49,7 +49,7 @@ fn initialize(app_dir: &str) {
pub enum EventToUI {
Event(String),
Rgba(ZeroCopyBuffer<Vec<u8>>),
Rgba,
}
pub fn start_global_event_stream(s: StreamSink<String>, app_type: String) -> ResultType<()> {
@@ -85,8 +85,15 @@ pub fn session_add_sync(
is_file_transfer: bool,
is_port_forward: bool,
switch_uuid: String,
force_relay: bool,
) -> SyncReturn<String> {
if let Err(e) = session_add(&id, is_file_transfer, is_port_forward, &switch_uuid) {
if let Err(e) = session_add(
&id,
is_file_transfer,
is_port_forward,
&switch_uuid,
force_relay,
) {
SyncReturn(format!("Failed to add session with id {}, {}", &id, e))
} else {
SyncReturn("".to_owned())
@@ -133,10 +140,10 @@ pub fn session_login(id: String, password: String, remember: bool) {
}
pub fn session_close(id: String) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
if let Some(mut session) = SESSIONS.write().unwrap().remove(&id) {
session.close_event_stream();
session.close();
}
let _ = SESSIONS.write().unwrap().remove(&id);
}
pub fn session_refresh(id: String) {
@@ -151,16 +158,22 @@ pub fn session_record_screen(id: String, start: bool, width: usize, height: usiz
}
}
pub fn session_reconnect(id: String) {
pub fn session_reconnect(id: String, force_relay: bool) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.reconnect();
session.reconnect(force_relay);
}
}
pub fn session_toggle_option(id: String, value: String) {
let mut is_found = false;
if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) {
log::warn!("toggle option {}", value);
session.toggle_option(value);
is_found = true;
log::warn!("toggle option {}", &value);
session.toggle_option(value.clone());
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if is_found && value == "disable-clipboard" {
crate::flutter::update_text_clipboard_required();
}
}
@@ -516,6 +529,19 @@ pub fn session_switch_sides(id: String) {
}
}
pub fn session_change_resolution(id: String, width: i32, height: i32) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.change_resolution(width, height);
}
}
pub fn session_set_size(_id: String, _width: i32, _height: i32) {
#[cfg(feature = "flutter_texture_render")]
if let Some(session) = SESSIONS.write().unwrap().get_mut(&_id) {
session.set_size(_width, _height);
}
}
pub fn main_get_sound_inputs() -> Vec<String> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
return get_sound_inputs();
@@ -931,7 +957,7 @@ pub fn main_start_dbus_server() {
{
use crate::dbus::start_dbus_server;
// spawn new thread to start dbus server
thread::spawn(|| {
std::thread::spawn(|| {
let _ = start_dbus_server();
});
}
@@ -1121,13 +1147,6 @@ pub fn cm_switch_back(conn_id: i32) {
crate::ui_cm_interface::switch_back(conn_id);
}
pub fn main_get_icon() -> String {
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
return ui_interface::get_icon();
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
return String::new();
}
pub fn main_get_build_date() -> String {
crate::BUILD_DATE.to_string()
}
@@ -1181,6 +1200,9 @@ pub fn main_start_grab_keyboard() -> SyncReturn<bool> {
return SyncReturn(false);
}
crate::keyboard::client::start_grab_loop();
if !is_can_input_monitoring(false) {
return SyncReturn(false);
}
SyncReturn(true)
}
@@ -1212,6 +1234,10 @@ pub fn main_is_rdp_service_open() -> SyncReturn<bool> {
SyncReturn(is_rdp_service_open())
}
pub fn main_set_share_rdp(enable: bool) {
set_share_rdp(enable)
}
pub fn main_goto_install() -> SyncReturn<bool> {
goto_install();
SyncReturn(true)
@@ -1278,7 +1304,7 @@ pub fn main_is_login_wayland() -> SyncReturn<bool> {
pub fn main_start_pa() {
#[cfg(target_os = "linux")]
thread::spawn(crate::ipc::start_pa);
std::thread::spawn(crate::ipc::start_pa);
}
pub fn main_hide_docker() -> SyncReturn<bool> {
@@ -1287,6 +1313,17 @@ pub fn main_hide_docker() -> SyncReturn<bool> {
SyncReturn(true)
}
pub fn main_use_texture_render() -> SyncReturn<bool> {
#[cfg(not(feature = "flutter_texture_render"))]
{
SyncReturn(false)
}
#[cfg(feature = "flutter_texture_render")]
{
SyncReturn(true)
}
}
pub fn cm_start_listen_ipc_thread() {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
crate::flutter::connection_manager::start_listen_ipc_thread();
@@ -1298,7 +1335,7 @@ pub fn cm_start_listen_ipc_thread() {
/// * macOS only
pub fn main_start_ipc_url_server() {
#[cfg(target_os = "macos")]
thread::spawn(move || crate::server::start_ipc_url_server());
std::thread::spawn(move || crate::server::start_ipc_url_server());
}
/// Send a url scheme throught the ipc.
@@ -1307,16 +1344,16 @@ pub fn main_start_ipc_url_server() {
#[allow(unused_variables)]
pub fn send_url_scheme(_url: String) {
#[cfg(target_os = "macos")]
thread::spawn(move || crate::ui::macos::handle_url_scheme(_url));
std::thread::spawn(move || crate::handle_url_scheme(_url));
}
#[cfg(target_os = "android")]
pub mod server_side {
use hbb_common::log;
use jni::{
JNIEnv,
objects::{JClass, JString},
sys::jstring,
JNIEnv,
};
use crate::start_server;
@@ -1327,7 +1364,7 @@ pub mod server_side {
_class: JClass,
) {
log::debug!("startServer from java");
thread::spawn(move || start_server(true));
std::thread::spawn(move || start_server(true));
}
#[no_mangle]

View File

@@ -549,7 +549,7 @@ async fn check_pid(postfix: &str) {
file.read_to_string(&mut content).ok();
let pid = content.parse::<i32>().unwrap_or(0);
if pid > 0 {
use sysinfo::{ProcessExt, System, SystemExt};
use hbb_common::sysinfo::{ProcessExt, System, SystemExt};
let mut sys = System::new();
sys.refresh_processes();
if let Some(p) = sys.process(pid.into()) {

View File

@@ -5,7 +5,9 @@ use crate::common::GrabState;
use crate::flutter::{CUR_SESSION_ID, SESSIONS};
#[cfg(not(any(feature = "flutter", feature = "cli")))]
use crate::ui::CUR_SESSION;
use hbb_common::{log, message_proto::*};
use hbb_common::message_proto::*;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::log;
use rdev::{Event, EventType, Key};
#[cfg(any(target_os = "windows", target_os = "macos"))]
use std::sync::atomic::{AtomicBool, Ordering};
@@ -18,6 +20,13 @@ use std::{
#[cfg(windows)]
static mut IS_ALT_GR: bool = false;
#[allow(dead_code)]
const OS_LOWER_WINDOWS: &str = "windows";
#[allow(dead_code)]
const OS_LOWER_LINUX: &str = "linux";
#[allow(dead_code)]
const OS_LOWER_MACOS: &str = "macos";
#[cfg(any(target_os = "windows", target_os = "macos"))]
static KEYBOARD_HOOKED: AtomicBool = AtomicBool::new(false);
@@ -202,6 +211,9 @@ pub fn update_grab_get_key_name() {
#[cfg(target_os = "windows")]
static mut IS_0X021D_DOWN: bool = false;
#[cfg(target_os = "macos")]
static mut IS_LEFT_OPTION_DOWN: bool = false;
pub fn start_grab_loop() {
#[cfg(any(target_os = "windows", target_os = "macos"))]
std::thread::spawn(move || {
@@ -213,6 +225,7 @@ pub fn start_grab_loop() {
let mut _keyboard_mode = KeyboardMode::Map;
let _scan_code = event.scan_code;
let _code = event.code;
let res = if KEYBOARD_HOOKED.load(Ordering::SeqCst) {
_keyboard_mode = client::process_event(&event, None);
if is_press {
@@ -246,6 +259,13 @@ pub fn start_grab_loop() {
}
}
#[cfg(target_os = "macos")]
unsafe {
if _code as u32 == rdev::kVK_Option {
IS_LEFT_OPTION_DOWN = is_press;
}
}
return res;
};
let func = move |event: Event| match event.event_type {
@@ -253,11 +273,13 @@ pub fn start_grab_loop() {
EventType::KeyRelease(key) => try_handle_keyboard(event, key, false),
_ => Some(event),
};
#[cfg(target_os = "macos")]
rdev::set_is_main_thread(false);
#[cfg(target_os = "windows")]
rdev::set_event_popup(false);
if let Err(error) = rdev::grab(func) {
log::error!("rdev Error: {:?}", error)
}
#[cfg(target_os = "windows")]
rdev::set_event_popup(false);
});
#[cfg(target_os = "linux")]
@@ -395,13 +417,16 @@ pub fn event_to_key_events(
_ => {}
}
let mut peer = get_peer_platform().to_lowercase();
peer.retain(|c| !c.is_whitespace());
key_event.mode = keyboard_mode.into();
let mut key_events = match keyboard_mode {
KeyboardMode::Map => match map_keyboard_mode(event, key_event) {
KeyboardMode::Map => match map_keyboard_mode(peer.as_str(), event, key_event) {
Some(event) => [event].to_vec(),
None => Vec::new(),
},
KeyboardMode::Translate => translate_keyboard_mode(event, key_event),
KeyboardMode::Translate => translate_keyboard_mode(peer.as_str(), event, key_event),
_ => {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
@@ -424,7 +449,6 @@ pub fn event_to_key_events(
}
}
}
key_events
}
@@ -698,7 +722,7 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec<KeyEv
events
}
pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option<KeyEvent> {
pub fn map_keyboard_mode(peer: &str, event: &Event, mut key_event: KeyEvent) -> Option<KeyEvent> {
match event.event_type {
EventType::KeyPress(..) => {
key_event.down = true;
@@ -709,12 +733,9 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option<KeyEv
_ => return None,
};
let mut peer = get_peer_platform().to_lowercase();
peer.retain(|c| !c.is_whitespace());
#[cfg(target_os = "windows")]
let keycode = match peer.as_str() {
"windows" => {
let keycode = match peer {
OS_LOWER_WINDOWS => {
// https://github.com/rustdesk/rustdesk/issues/1371
// Filter scancodes that are greater than 255 and the hight word is not 0xE0.
if event.scan_code > 255 && (event.scan_code >> 8) != 0xE0 {
@@ -722,7 +743,7 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option<KeyEv
}
event.scan_code
}
"macos" => {
OS_LOWER_MACOS => {
if hbb_common::config::LocalConfig::get_kb_layout_type() == "ISO" {
rdev::win_scancode_to_macos_iso_code(event.scan_code)?
} else {
@@ -732,15 +753,15 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option<KeyEv
_ => rdev::win_scancode_to_linux_code(event.scan_code)?,
};
#[cfg(target_os = "macos")]
let keycode = match peer.as_str() {
"windows" => rdev::macos_code_to_win_scancode(event.code as _)?,
"macos" => event.code as _,
let keycode = match peer {
OS_LOWER_WINDOWS => rdev::macos_code_to_win_scancode(event.code as _)?,
OS_LOWER_MACOS => event.code as _,
_ => rdev::macos_code_to_linux_code(event.code as _)?,
};
#[cfg(target_os = "linux")]
let keycode = match peer.as_str() {
"windows" => rdev::linux_code_to_win_scancode(event.code as _)?,
"macos" => {
let keycode = match peer {
OS_LOWER_WINDOWS => rdev::linux_code_to_win_scancode(event.code as _)?,
OS_LOWER_MACOS => {
if hbb_common::config::LocalConfig::get_kb_layout_type() == "ISO" {
rdev::linux_code_to_macos_iso_code(event.code as _)?
} else {
@@ -759,10 +780,12 @@ pub fn map_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Option<KeyEv
fn try_fill_unicode(event: &Event, key_event: &KeyEvent, events: &mut Vec<KeyEvent>) {
match &event.unicode {
Some(unicode_info) => {
for code in &unicode_info.unicode {
let mut evt = key_event.clone();
evt.set_unicode(*code as _);
events.push(evt);
if let Some(name) = &unicode_info.name {
if name.len() > 0 {
let mut evt = key_event.clone();
evt.set_seq(name.to_string());
events.push(evt);
}
}
}
None => {}
@@ -783,45 +806,42 @@ fn is_hot_key_modifiers_down() -> bool {
return false;
}
pub fn translate_virtual_keycode(event: &Event, mut key_event: KeyEvent) -> Option<KeyEvent> {
match event.event_type {
EventType::KeyPress(..) => {
key_event.down = true;
}
EventType::KeyRelease(..) => {
key_event.down = false;
}
_ => return None,
};
let mut peer = get_peer_platform().to_lowercase();
peer.retain(|c| !c.is_whitespace());
// #[cfg(target_os = "windows")]
// let keycode = match peer.as_str() {
// "windows" => event.code,
// "macos" => {
// if hbb_common::config::LocalConfig::get_kb_layout_type() == "ISO" {
// rdev::win_scancode_to_macos_iso_code(event.scan_code)?
// } else {
// rdev::win_scancode_to_macos_code(event.scan_code)?
// }
// }
// _ => rdev::win_scancode_to_linux_code(event.scan_code)?,
// };
key_event.set_chr(event.code as _);
#[inline]
#[cfg(target_os = "windows")]
pub fn translate_key_code(peer: &str, event: &Event, key_event: KeyEvent) -> Option<KeyEvent> {
let mut key_event = map_keyboard_mode(peer, event, key_event)?;
key_event.set_chr((key_event.chr() & 0x0000FFFF) | ((event.code as u32) << 16));
Some(key_event)
}
pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec<KeyEvent> {
#[inline]
#[cfg(not(target_os = "windows"))]
pub fn translate_key_code(peer: &str, event: &Event, key_event: KeyEvent) -> Option<KeyEvent> {
map_keyboard_mode(peer, event, key_event)
}
pub fn translate_keyboard_mode(peer: &str, event: &Event, key_event: KeyEvent) -> Vec<KeyEvent> {
let mut events: Vec<KeyEvent> = Vec::new();
if let Some(unicode_info) = &event.unicode {
if unicode_info.is_dead {
#[cfg(target_os = "macos")]
if peer != OS_LOWER_MACOS && unsafe { IS_LEFT_OPTION_DOWN } {
// try clear dead key state
// rdev::clear_dead_key_state();
} else {
return events;
}
#[cfg(not(target_os = "macos"))]
return events;
}
}
#[cfg(target_os = "macos")]
// ignore right option key
if event.code as u32 == rdev::kVK_RightOption {
return events;
}
#[cfg(target_os = "windows")]
unsafe {
if event.scan_code == 0x021D {
@@ -847,11 +867,16 @@ pub fn translate_keyboard_mode(event: &Event, key_event: KeyEvent) -> Vec<KeyEve
}
}
#[cfg(not(target_os = "windows"))]
#[cfg(target_os = "linux")]
try_fill_unicode(event, &key_event, &mut events);
#[cfg(target_os = "macos")]
if !unsafe { IS_LEFT_OPTION_DOWN } {
try_fill_unicode(event, &key_event, &mut events);
}
if events.is_empty() {
if let Some(evt) = translate_virtual_keycode(event, key_event) {
if let Some(evt) = translate_key_code(peer, event, key_event) {
events.push(evt);
}
}

View File

@@ -14,6 +14,7 @@ mod id;
mod it;
mod ja;
mod ko;
mod nl;
mod pl;
mod ptbr;
mod ro;
@@ -40,6 +41,7 @@ lazy_static::lazy_static! {
("it", "Italiano"),
("fr", "Français"),
("de", "Deutsch"),
("nl", "Nederlands"),
("cn", "简体中文"),
("tw", "繁體中文"),
("pt", "Português"),
@@ -81,7 +83,7 @@ pub fn translate_locale(name: String, locale: &str) -> String {
if lang.is_empty() {
// zh_CN on Linux, zh-Hans-CN on mac, zh_CN_#Hans on Android
if locale.starts_with("zh") {
lang = (if locale.contains("TW") { "tw" } else { "cn" }).to_owned();
lang = (if locale.contains("tw") { "tw" } else { "cn" }).to_owned();
}
}
if lang.is_empty() {
@@ -99,6 +101,7 @@ pub fn translate_locale(name: String, locale: &str) -> String {
"it" => it::T.deref(),
"tw" => tw::T.deref(),
"de" => de::T.deref(),
"nl" => nl::T.deref(),
"es" => es::T.deref(),
"hu" => hu::T.deref(),
"ru" => ru::T.deref(),

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "El portapapers està buit"),
("Stop service", "Aturar servei"),
("Change ID", "Canviar ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Només pots utilitzar caràcters a-z, A-Z, 0-9 e _ (guionet baix). El primer caràcter ha de ser a-z o A-Z. La longitut ha d'estar entre 6 i 16 caràcters."),
("Website", "Lloc web"),
("About", "Sobre"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "Servidor API"),
("invalid_http", "ha de començar amb http:// o https://"),
("Invalid IP", "IP incorrecta"),
("id_change_tip", "Només pots utilitzar caràcters a-z, A-Z, 0-9 e _ (guionet baix). El primer caràcter ha de ser a-z o A-Z. La longitut ha d'estar entre 6 i 16 caràcters."),
("Invalid format", "Format incorrecte"),
("server_not_support", "Encara no és compatible amb el servidor"),
("Not available", "No disponible"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Tancat manualment pel peer"),
("Enable remote configuration modification", "Habilitar modificació remota de configuració"),
("Run without install", "Executar sense instal·lar"),
("Always connected via relay", "Connectat sempre a través de relay"),
("Connect via relay", ""),
("Always connect via relay", "Connecta sempre a través de relay"),
("whitelist_tip", ""),
("Login", "Inicia sessió"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -3,7 +3,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[
("Status", "状态"),
("Your Desktop", "你的桌面"),
("desk_tip", "你的桌面可以通过下面的ID和密码访问。"),
("desk_tip", "你的桌面可以通过下面的 ID 和密码访问。"),
("Password", "密码"),
("Ready", "就绪"),
("Established", "已建立"),
@@ -11,7 +11,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable Service", "允许服务"),
("Start Service", "启动服务"),
("Service is running", "服务正在运行"),
("Service is not running", "服务没有启动"),
("Service is not running", "服务未运行"),
("not_ready_status", "未就绪,请检查网络连接"),
("Control Remote Desktop", "控制远程桌面"),
("Transfer File", "传输文件"),
@@ -19,45 +19,49 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Recent Sessions", "最近访问过"),
("Address Book", "地址簿"),
("Confirmation", "确认"),
("TCP Tunneling", "TCP隧道"),
("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白名单"),
("Enable TCP Tunneling", "允许建立 TCP 隧道"),
("IP Whitelisting", "IP 白名单"),
("ID/Relay Server", "ID/中继服务器"),
("Import Server Config", "导入服务器配置"),
("Export Server Config", "导出服务器配置"),
("Import server configuration successfully", "导入服务器配置信息成功"),
("Export server configuration successfully", "导出服务器配置信息成功"),
("Invalid server configuration", "无效服务器配置,请修改后重新拷贝配置信息到剪贴板后点击此按钮"),
("Clipboard is empty", "拷贝配置信息到剪贴板后点击此按钮,可以自动导入配置"),
("Invalid server configuration", "服务器配置无效,请修改后重新复制配置信息到剪贴板,然后点击此按钮"),
("Clipboard is empty", "复制配置信息到剪贴板后点击此按钮,可以自动导入配置"),
("Stop service", "停止服务"),
("Change ID", "改变ID"),
("Change ID", "更改 ID"),
("Your new ID", "你的新 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", ""),
("Privacy Statement", "隐私声明"),
("Mute", "静音"),
("Build Date", ""),
("Version", ""),
("Home", ""),
("Build Date", "构建日期"),
("Version", "版本"),
("Home", "主页"),
("Audio Input", "音频输入"),
("Enhancements", "增强功能"),
("Hardware Codec", "硬件编解码"),
("Adaptive Bitrate", "自适应码率"),
("ID Server", "ID服务器"),
("ID Server", "ID 服务器"),
("Relay Server", "中继服务器"),
("API Server", "API服务器"),
("invalid_http", "必须以http://或者https://开头"),
("Invalid IP", "无效IP"),
("id_change_tip", "只可以使用字母a-z, A-Z, 0-9, _ (下划线)。首字母必须是a-z, A-Z。长度在6与16之间。"),
("API Server", "API 服务器"),
("invalid_http", "必须以 http:// 或者 https:// 开头"),
("Invalid IP", "无效 IP"),
("Invalid format", "无效格式"),
("server_not_support", "服务器暂不支持"),
("Not available", "已被占"),
("Not available", "不可"),
("Too frequent", "修改太频繁,请稍后再试"),
("Cancel", "取消"),
("Skip", "跳过"),
@@ -68,12 +72,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please enter your password", "请输入密码"),
("Remember password", "记住密码"),
("Wrong Password", "密码错误"),
("Do you want to enter again?", "还想输入一次吗?"),
("Do you want to enter again?", "是否要再次输入?"),
("Connection Error", "连接错误"),
("Error", "错误"),
("Reset by the peer", "连接被对方关闭"),
("Connecting...", "正在连接..."),
("Connection in progress. Please wait.", "连接进行中,请稍"),
("Connection in progress. Please wait.", "正在进行连接,请稍"),
("Please try 1 minute later", "一分钟后再试"),
("Login Error", "登录错误"),
("Successful", "成功"),
@@ -98,14 +102,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("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?", "是否删除文件夹下的文件?"),
("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", "等待..."),
("Waiting", "正在等待..."),
("Finished", "完成"),
("Speed", "速度"),
("Custom Image Quality", "设置画面质量"),
@@ -118,37 +122,37 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stretch", "伸展"),
("Scrollbar", "滚动条"),
("ScrollAuto", "自动滚动"),
("Good image quality", "画质"),
("Balanced", "一般画质"),
("Optimize reaction time", "优化反应时间"),
("Good image quality", "画质最优化"),
("Balanced", "平衡"),
("Optimize reaction time", "速度最优化"),
("Custom", "自定义"),
("Show remote cursor", "显示远程光标"),
("Show quality monitor", "显示质量监测"),
("Disable clipboard", "剪贴板"),
("Lock after session end", "断开后锁定远程电脑"),
("Disable clipboard", "剪贴板"),
("Lock after session end", "会话结束后锁定远程电脑"),
("Insert", "插入"),
("Insert Lock", "锁定远程电脑"),
("Refresh", "刷新画面"),
("ID does not exist", "ID不存在"),
("ID does not exist", "ID 不存在"),
("Failed to connect to rendezvous server", "连接注册服务器失败"),
("Please try later", "请稍后再试"),
("Remote desktop is offline", "远程电脑不在线"),
("Key mismatch", "Key不匹配"),
("Remote desktop is offline", "远程电脑处于离线状态"),
("Key mismatch", "密钥不匹配"),
("Timeout", "连接超时"),
("Failed to connect to relay server", "无法连接到中继服务器"),
("Failed to connect via rendezvous server", "无法通过注册服务器建立连接"),
("Failed to connect via relay server", "无法通过中继服务器建立连接"),
("Failed to make direct connection to remote desktop", "无法建立直接连接"),
("Failed to make direct connection to remote desktop", "无法直接连接到远程桌面"),
("Set Password", "设置密码"),
("OS Password", "操作系统密码"),
("install_tip", "你正在运行未安装版本由于UAC限制作为被控端会在某些情况下无法控制鼠标键盘或者录制屏幕请点击下面的按钮将 RustDesk 安装到系统,从而规避上述问题。"),
("install_tip", "你正在运行未安装版本,由于 UAC 限制,作为被控端,会在某些情况下无法控制鼠标键盘,或者录制屏幕,请点击下面的按钮将 RustDesk 安装到系统,从而规避上述问题。"),
("Click to upgrade", "点击这里升级"),
("Click to download", "点击这里下载"),
("Click to update", "点击这里更新"),
("Configure", "配置"),
("config_acc", "为了能够远程控制你的桌面, 请给予 RustDesk \"辅助功能\" 权限。"),
("config_screen", "为了能够远程访问你的桌面, 请给予 RustDesk \"屏幕录制\" 权限。"),
("Installing ...", "安装 ..."),
("Installing ...", "安装..."),
("Install", "安装"),
("Installation", "安装"),
("Installation Path", "安装路径"),
@@ -157,10 +161,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("agreement_tip", "开始安装即表示接受许可协议。"),
("Accept and Install", "同意并安装"),
("End-user license agreement", "用户协议"),
("Generating ...", "正在产生 ..."),
("Generating ...", "正在生成..."),
("Your installation is lower version.", "你安装的版本比当前运行的低。"),
("not_close_tcp_tip", "请在使用隧道的时候,不要关闭本窗口"),
("Listening ...", "正在等待隧道连接 ..."),
("Listening ...", "正在等待隧道连接..."),
("Remote Host", "远程主机"),
("Remote Port", "远程端口"),
("Action", "动作"),
@@ -169,7 +173,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Local Address", "当前地址"),
("Change Local Port", "修改本地端口"),
("setup_server_tip", "如果需要更快连接速度,你可以选择自建服务器"),
("Too short, at least 6 characters.", "太短了,至少6个字符"),
("Too short, at least 6 characters.", "太短了,至少 6 个字符"),
("The confirmation is not identical.", "两次输入不匹配"),
("Permissions", "权限"),
("Accept", "接受"),
@@ -179,21 +183,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Allow using clipboard", "允许使用剪贴板"),
("Allow hearing sound", "允许听到声音"),
("Allow file copy and paste", "允许复制粘贴文件"),
("Connected", "连接"),
("Connected", "已连接"),
("Direct and encrypted connection", "加密直连"),
("Relayed and encrypted connection", "加密中继连接"),
("Direct and unencrypted connection", "非加密直连"),
("Relayed and unencrypted connection", "非加密中继连接"),
("Enter Remote ID", "输入对方ID"),
("Enter Remote ID", "输入对方 ID"),
("Enter your password", "输入密码"),
("Logging in...", "正在登录..."),
("Enable RDP session sharing", "允许RDP会话共享"),
("Enable RDP session sharing", "允许 RDP 会话共享"),
("Auto Login", "自动登录(设置断开后锁定才有效)"),
("Enable Direct IP Access", "允许IP直接访问"),
("Rename", ""),
("Enable Direct IP Access", "允许 IP 直接访问"),
("Rename", "重命"),
("Space", "空格"),
("Create Desktop Shortcut", "创建桌面快捷方式"),
("Change Path", "路径"),
("Change Path", "改路径"),
("Create Folder", "创建文件夹"),
("Please enter the folder name", "请输入文件夹名称"),
("Fix it", "修复"),
@@ -208,29 +212,29 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Invalid port", "无效端口"),
("Closed manually by the peer", "被对方手动关闭"),
("Enable remote configuration modification", "允许远程修改配置"),
("Run without install", "安装运行"),
("Always connected via relay", "强制走中继连接"),
("Run without install", "安装直接运行"),
("Connect via relay", "中继连接"),
("Always connect via relay", "强制走中继连接"),
("whitelist_tip", "只有白名单里的ip才能访问"),
("whitelist_tip", "只有白名单里的 IP 才能访问本机"),
("Login", "登录"),
("Verify", "验证"),
("Remember me", "记住我"),
("Trust this device", "信任此设备"),
("Verification code", "验证码"),
("verification_tip", "检测到新设备登录,已向注册邮箱发送了登录验证码,输入验证码继续登录"),
("verification_tip", "检测到新设备登录,已向注册邮箱发送了登录验证码,输入验证码继续登录"),
("Logout", "登出"),
("Tags", "标签"),
("Search ID", "查找ID"),
("Search ID", "查找 ID"),
("whitelist_sep", "可以使用逗号,分号,空格或者换行符作为分隔符"),
("Add ID", "增加ID"),
("Add ID", "增加 ID"),
("Add Tag", "增加标签"),
("Unselect all tags", "取消选择所有标签"),
("Network error", "网络错误"),
("Username missed", "用户名没有填写"),
("Password missed", "密码没有填写"),
("Wrong credentials", "提供的登信息错误"),
("Wrong credentials", "提供的登信息错误"),
("Edit Tag", "修改标签"),
("Unremember Password", "密码"),
("Unremember Password", "密码"),
("Favorites", "收藏"),
("Add to Favorites", "加入到收藏"),
("Remove from Favorites", "从收藏中删除"),
@@ -240,9 +244,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Hostname", "主机名"),
("Discovered", "已发现"),
("install_daemon_tip", "为了开机启动,请安装系统服务。"),
("Remote ID", "远程ID"),
("Remote ID", "远程 ID"),
("Paste", "粘贴"),
("Paste here?", "粘贴到这里?"),
("Paste here?", "粘贴到这里"),
("Are you sure to close the connection?", "是否确认关闭连接?"),
("Download new version", "下载新版本"),
("Touch mode", "触屏模式"),
@@ -280,7 +284,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Do you accept?", "是否接受?"),
("Open System Setting", "打开系统设置"),
("How to get Android input permission?", "如何获取安卓的输入权限?"),
("android_input_permission_tip1", "为了让远程设备通过鼠标或触屏控制您的安卓设备,你需要允 RustDesk 使用\"无障碍\"服务。"),
("android_input_permission_tip1", "为了让远程设备通过鼠标或触屏控制您的安卓设备,你需要允 RustDesk 使用\"无障碍\"服务。"),
("android_input_permission_tip2", "请在接下来的系统设置页面里,找到并进入 [已安装的服务] 页面,将 [RustDesk Input] 服务开启。"),
("android_new_connection_tip", "收到新的连接控制请求,对方想要控制你当前的设备。"),
("android_service_will_start_tip", "开启录屏权限将自动开启服务,允许其他设备向此设备请求建立连接。"),
@@ -289,7 +293,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_start_service_tip", "点击 [启动服务] 或打开 [屏幕录制] 权限开启手机屏幕共享服务。"),
("Account", "账户"),
("Overwrite", "覆盖"),
("This file exists, skip or overwrite this file?", "这个文件/文件夹已存在,跳过/覆盖?"),
("This file exists, skip or overwrite this file?", "这个文件/文件夹已存在,跳过/覆盖"),
("Quit", "退出"),
("doc_mac_permission", "https://rustdesk.com/docs/zh-cn/manual/mac#%E5%90%AF%E7%94%A8%E6%9D%83%E9%99%90"),
("Help", "帮助"),
@@ -310,7 +314,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_open_battery_optimizations_tip", "如需关闭此功能,请在接下来的 RustDesk 应用设置页面中,找到并进入 [电源] 页面,取消勾选 [不受限制]"),
("Connection not allowed", "对方不允许连接"),
("Legacy mode", "传统模式"),
("Map mode", "11传输"),
("Map mode", "11 传输"),
("Translate mode", "翻译模式"),
("Use permanent password", "使用固定密码"),
("Use both passwords", "同时使用两种密码"),
@@ -351,16 +355,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable Audio", "允许传输音频"),
("Unlock Network Settings", "解锁网络设置"),
("Server", "服务器"),
("Direct IP Access", "IP直接访问"),
("Direct IP Access", "IP 直接访问"),
("Proxy", "代理"),
("Apply", "应用"),
("Disconnect all devices?", "断开所有远程连接?"),
("Disconnect all devices?", "断开所有远程连接"),
("Clear", "清空"),
("Audio Input Device", "音频输入设备"),
("Deny remote access", "拒绝远程访问"),
("Use IP Whitelisting", "只允许白名单上的IP访问"),
("Use IP Whitelisting", "只允许白名单上的 IP 访问"),
("Network", "网络"),
("Enable RDP", "允许RDP访问"),
("Enable RDP", "允许 RDP 访问"),
("Pin menubar", "固定菜单栏"),
("Unpin menubar", "取消固定菜单栏"),
("Recording", "录屏"),
@@ -375,7 +379,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deny LAN Discovery", "拒绝局域网发现"),
("Write a message", "输入聊天消息"),
("Prompt", "提示"),
("Please wait for confirmation of UAC...", "请等待对方确认 UAC ..."),
("Please wait for confirmation of UAC...", "请等待对方确认 UAC..."),
("elevated_foreground_window_tip", "远端桌面的当前窗口需要更高的权限才能操作, 暂时无法使用鼠标键盘, 可以请求对方最小化当前窗口, 或者在连接管理窗口点击提升。为避免这个问题,建议在远端设备上安装本软件。"),
("Disconnected", "会话已结束"),
("Other", "其他"),
@@ -392,7 +396,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("or", ""),
("Continue with", "使用"),
("Elevate", "提权"),
("Zoom cursor", "缩放"),
("Zoom cursor", "缩放"),
("Accept sessions via password", "只允许密码访问"),
("Accept sessions via click", "只允许点击访问"),
("Accept sessions via both", "允许密码或点击访问"),
@@ -403,7 +407,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("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", "已跳过"),
("Add to Address Book", "添加到地址簿"),
@@ -413,7 +417,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Local keyboard type", "本地键盘类型"),
("Select local keyboard type", "请选择本地键盘类型"),
("software_render_tip", "如果你使用英伟达显卡, 并且远程窗口在会话建立后会立刻关闭, 那么安装 nouveau 驱动并且选择使用软件渲染可能会有帮助。重启软件后生效。"),
("Always use software rendering", "使用软件渲染"),
("Always use software rendering", "始终使用软件渲染"),
("config_input", "为了能够通过键盘控制远程桌面, 请给予 RustDesk \"输入监控\" 权限。"),
("config_microphone", "为了支持通过麦克风进行音频传输,请给予 RustDesk \"录音\"权限。"),
("request_elevation_tip", "如果对面有人, 也可以请求提升权限。"),
@@ -422,7 +426,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", "依然需要被控端用戶在運行 RustDesk 的 UAC 窗口點擊確認"),
("still_click_uac_tip", "依然需要被控端用户在运行 RustDesk 的 UAC 窗口点击确认"),
("Request Elevation", "请求提权"),
("wait_accept_uac_tip", "请等待远端用户确认 UAC 对话框。"),
("Elevate successfully", "提权成功"),
@@ -430,24 +434,27 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("lowercase", "小写字母"),
("digit", "数字"),
("special character", "特殊字符"),
("length>=8", "长度不小于8"),
("length>=8", "长度不小于 8"),
("Weak", ""),
("Medium", ""),
("Strong", ""),
("Switch Sides", "反转访问方向"),
("Please confirm if you want to share your desktop?", "请确认要让对方访问你的桌面?"),
("Closed as expected", "正常关闭"),
("Please confirm if you want to share your desktop?", "请确认是否要让对方访问你的桌面"),
("Display", "显示"),
("Default View Style", "默认显示方式"),
("Default Scroll Style", "默认滚动方式"),
("Default Image Quality", "默认图像质量"),
("Default Codec", "默认编解码"),
("Bitrate", "波特"),
("Bitrate", ""),
("FPS", "帧率"),
("Auto", "自动"),
("Other Default Options", "其它默认选项"),
("Voice call", "语音通话"),
("Text chat", "文字聊天"),
("Stop voice call", "停止语音聊天"),
].iter().cloned().collect();
("Stop voice call", "停止语音通话"),
("relay_hint_tip", "可能无法直连,可以尝试中继连接。\n另外,如果想直接使用中继连接,可以在 ID 后面添加/r或者在卡片选项里选择强制走中继连接。"),
("Reconnect", "重连"),
("Codec", "编解码"),
("Resolution", "分辨率"),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("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."),
("Website", "Webové stránky"),
("About", "O aplikaci"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "Server s API rozhraním"),
("invalid_http", "Je třeba, aby začínalo na http:// nebo https://"),
("Invalid IP", "Neplatná IP adresa"),
("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."),
("Invalid format", "Neplatný formát"),
("server_not_support", "Server zatím nepodporuje"),
("Not available", "Není k dispozici"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("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í"),
("Always connected via relay", "Vždy spojováno prostřednictvím brány pro předávání (relay)"),
("Connect via relay", ""),
("Always connect via relay", "Vždy se spojovat prostřednictvím brány pro předávání (relay)"),
("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"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Udklipsholderen er tom"),
("Stop service", "Sluk for forbindelsesserveren"),
("Change ID", "Ændre ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Kun tegnene a-z, A-Z, 0-9 og _ (understregning) er tilladt. Det første bogstav skal være a-z, A-Z. Længde mellem 6 og 16."),
("Website", "Hjemmeside"),
("About", "Omkring"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "API Server"),
("invalid_http", "Skal begynde med http:// eller https://"),
("Invalid IP", "Ugyldig IP-adresse"),
("id_change_tip", "Kun tegnene a-z, A-Z, 0-9 og _ (understregning) er tilladt. Det første bogstav skal være a-z, A-Z. Længde mellem 6 og 16."),
("Invalid format", "Ugyldigt format"),
("server_not_support", "Endnu ikke understøttet af serveren"),
("Not available", "ikke Tilgængelig"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Manuelt lukket af peer"),
("Enable remote configuration modification", "Tillad at ændre afstandskonfigurationen"),
("Run without install", "Kør uden installation"),
("Always connected via relay", "Tilslut altid via relæ-server"),
("Connect via relay", ""),
("Always connect via relay", "Forbindelse via relæ-server"),
("whitelist_tip", "Kun IP'er på udgivelseslisten kan få adgang til mig"),
("Login", "Login"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Zwischenablage ist leer"),
("Stop service", "Vermittlungsdienst stoppen"),
("Change ID", "ID ändern"),
("Your new ID", "Ihre neue ID"),
("length %min% to %max%", "Länge %min% bis %max%"),
("starts with a letter", "Beginnt mit Buchstabe"),
("allowed characters", "Erlaubte Zeichen"),
("id_change_tip", "Nur die Zeichen a-z, A-Z, 0-9 und _ (Unterstrich) sind erlaubt. Der erste Buchstabe muss a-z, A-Z sein und die Länge zwischen 6 und 16 Zeichen betragen."),
("Website", "Webseite"),
("About", "Über"),
("Slogan_tip", "Mit Herzblut programmiert - in einer Welt, die im Chaos versinkt!"),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "API-Server"),
("invalid_http", "Muss mit http:// oder https:// beginnen"),
("Invalid IP", "Ungültige IP-Adresse"),
("id_change_tip", "Nur die Zeichen a-z, A-Z, 0-9 und _ (Unterstrich) sind erlaubt. Der erste Buchstabe muss a-z, A-Z sein und die Länge zwischen 6 und 16 Zeichen betragen."),
("Invalid format", "Ungültiges Format"),
("server_not_support", "Diese Funktion wird noch nicht vom Server unterstützt."),
("Not available", "Nicht verfügbar"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Von der Gegenstelle manuell geschlossen"),
("Enable remote configuration modification", "Änderung der Konfiguration aus der Ferne zulassen"),
("Run without install", "Ohne Installation ausführen"),
("Always connected via relay", "Immer über Relay-Server verbunden"),
("Connect via relay", "Verbindung über Relay-Server"),
("Always connect via relay", "Immer über Relay-Server verbinden"),
("whitelist_tip", "Nur IPs auf der Whitelist können zugreifen."),
("Login", "Anmelden"),
@@ -272,21 +276,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Total", "Gesamt"),
("items", "Einträge"),
("Selected", "Ausgewählt"),
("Screen Capture", "Bildschirmzugr."),
("Input Control", "Eingabezugriff"),
("Audio Capture", "Audiozugriff"),
("File Connection", "Dateizugriff"),
("Screen Capture", "Bildschirmaufnahme"),
("Input Control", "Eingabesteuerung"),
("Audio Capture", "Audioaufnahme"),
("File Connection", "Dateiverbindung"),
("Screen Connection", "Bildschirmanschluss"),
("Do you accept?", "Verbindung zulassen?"),
("Open System Setting", "Systemeinstellung öffnen"),
("How to get Android input permission?", "Wie erhalte ich eine Android-Eingabeberechtigung?"),
("android_input_permission_tip1", "Damit ein entferntes Gerät Ihr Android-Gerät steuern kann, müssen Sie RustDesk erlauben, den Dienst \"Barrierefreiheit\" zu verwenden."),
("android_input_permission_tip2", "Bitte gehen Sie zur nächsten Systemeinstellungsseite, suchen Sie [Installierte Dienste] und schalten Sie den Dienst [RustDesk Input] ein."),
("android_input_permission_tip2", "Bitte gehen Sie zur nächsten Systemeinstellungsseite, suchen Sie \"Installierte Dienste\" und schalten Sie den Dienst \"RustDesk Input\" ein."),
("android_new_connection_tip", "möchte ihr Gerät steuern."),
("android_service_will_start_tip", "Durch das Aktivieren der Bildschirmfreigabe wird der Dienst automatisch gestartet, sodass andere Geräte dieses Android-Gerät steuern können."),
("android_stop_service_tip", "Durch das Deaktivieren des Dienstes werden automatisch alle hergestellten Verbindungen getrennt."),
("android_version_audio_tip", "Ihre Android-Version unterstützt keine Audioaufnahme, bitte aktualisieren Sie auf Android 10 oder höher, falls möglich."),
("android_start_service_tip", "Tippen Sie auf [Dienst aktivieren] oder aktivieren Sie die Berechtigung [Bildschirmzugr.], um den Bildschirmfreigabedienst zu starten."),
("android_start_service_tip", "Tippen Sie auf \"Dienst aktivieren\" oder aktivieren Sie die Berechtigung \"Bildschirmaufnahme\", um den Bildschirmfreigabedienst zu starten."),
("Account", "Konto"),
("Overwrite", "Überschreiben"),
("This file exists, skip or overwrite this file?", "Diese Datei existiert; überspringen oder überschreiben?"),
@@ -386,7 +390,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland erfordert Ubuntu 21.04 oder eine höhere Version."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland erfordert eine höhere Version der Linux-Distribution. Bitte versuchen Sie den X11-Desktop oder ändern Sie Ihr Betriebssystem."),
("JumpLink", "View"),
("Please Select the screen to be shared(Operate on the peer side).", "Bitte wählen Sie den freizugebenden Bildschirm aus (Bedienung auf der Peer-Seite)."),
("Please Select the screen to be shared(Operate on the peer side).", "Bitte wählen Sie den freizugebenden Bildschirm aus (Bedienung auf der Gegenseite)."),
("Show RustDesk", "RustDesk anzeigen"),
("This PC", "Dieser PC"),
("or", "oder"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", "Stark"),
("Switch Sides", "Seiten wechseln"),
("Please confirm if you want to share your desktop?", "Bitte bestätigen Sie, ob Sie Ihren Desktop freigeben möchten."),
("Closed as expected", "Wie erwartet geschlossen"),
("Display", "Anzeige"),
("Default View Style", "Standard-Ansichtsstil"),
("Default Scroll Style", "Standard-Scroll-Stil"),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", "Sprachanruf"),
("Text chat", "Text-Chat"),
("Stop voice call", "Sprachanruf beenden"),
].iter().cloned().collect();
("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."),
("Reconnect", "Erneut verbinden"),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -42,6 +42,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("request_elevation_tip","You can also request elevation if there is someone on the remote side."),
("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.")
("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."),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "La poŝo estas malplena"),
("Stop service", "Haltu servon"),
("Change ID", "Ŝanĝi identigilon"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Nur la signoj a-z, A-Z, 0-9, _ (substreko) povas esti uzataj. La unua litero povas esti inter a-z, A-Z. La longeco devas esti inter 6 kaj 16."),
("Website", "Retejo"),
("About", "Pri"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "Servilo de API"),
("invalid_http", "Devas komenci kun http:// aŭ https://"),
("Invalid IP", "IP nevalida"),
("id_change_tip", "Nur la signoj a-z, A-Z, 0-9, _ (substreko) povas esti uzataj. La unua litero povas esti inter a-z, A-Z. La longeco devas esti inter 6 kaj 16."),
("Invalid format", "Formato nevalida"),
("server_not_support", "Ankoraŭ ne subtenata de la servilo"),
("Not available", "Nedisponebla"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Manuale fermita de la samtavolano"),
("Enable remote configuration modification", "Permesi foran redaktadon de la konfiguracio"),
("Run without install", "Plenumi sen instali"),
("Always connected via relay", "Ĉiam konektata per relajso"),
("Connect via relay", ""),
("Always connect via relay", "Ĉiam konekti per relajso"),
("whitelist_tip", "Nur la IP en la blanka listo povas kontroli mian komputilon"),
("Login", "Konekti"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ 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", ""),
("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!"),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "Servidor API"),
("invalid_http", "debe comenzar con http:// o https://"),
("Invalid IP", "IP incorrecta"),
("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."),
("Invalid format", "Formato incorrecto"),
("server_not_support", "Aún no es compatible con el servidor"),
("Not available", "No disponible"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Cerrado manualmente por el par"),
("Enable remote configuration modification", "Habilitar modificación remota de configuración"),
("Run without install", "Ejecutar sin instalar"),
("Always connected via relay", "Siempre conectado a través de relay"),
("Connect via relay", ""),
("Always connect via relay", "Conéctese siempre a través de relay"),
("whitelist_tip", "Solo las direcciones IP autorizadas pueden conectarse a este escritorio"),
("Login", "Iniciar sesión"),
@@ -415,7 +419,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("software_render_tip", "Si tienes una gráfica Nvidia y la ventana remota se cierra inmediatamente, instalar el driver nouveau y elegir renderizado por software podría ayudar. Se requiere reiniciar la aplicación."),
("Always use software rendering", "Usar siempre renderizado por software"),
("config_input", "Para controlar el escritorio remoto con el teclado necesitas dar a RustDesk permisos de \"Monitorización de entrada\"."),
("config_microphone", ""),
("config_microphone", "Para poder hablar de forma remota necesitas darle a RustDesk permisos de \"Grabar Audio\"."),
("request_elevation_tip", "También puedes solicitar elevación si hay alguien en el lado remoto."),
("Wait", "Esperar"),
("Elevation Error", "Error de elevación"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", "Fuerte"),
("Switch Sides", "Intercambiar lados"),
("Please confirm if you want to share your desktop?", "Por favor, confirma si quieres compartir tu escritorio"),
("Closed as expected", ""),
("Display", "Pantalla"),
("Default View Style", "Estilo de vista predeterminado"),
("Default Scroll Style", "Estilo de desplazamiento predeterminado"),
@@ -446,8 +449,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("FPS", ""),
("Auto", ""),
("Other Default Options", "Otras opciones predeterminadas"),
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("Voice call", "Llamada de voz"),
("Text chat", "Chat de texto"),
("Stop voice call", "Detener llamada de voz"),
("relay_hint_tip", "Puede que no sea posible conectar directamente. Puedes tratar de conectar a través de relay. \nAdicionalmente, si quieres usar relay en el primer intento, puedes añadir el sufijo \"/r\" a la ID o seleccionar la opción \"Conectar siempre a través de relay\" en la tarjeta del par."),
("Reconnect", "Reconectar"),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "کلیپبورد خالی است"),
("Stop service", "توقف سرویس"),
("Change ID", "تعویض شناسه"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "شناسه باید طبق این شرایط باشد : حروف کوچک و بزرگ انگلیسی و اعداد از 0 تا 9، _ و همچنین حرف اول آن فقط حروف بزرگ یا کوچک انگلیسی و طول آن بین 6 الی 16 کاراکتر باشد"),
("Website", "وب سایت"),
("About", "درباره"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "API سرور"),
("invalid_http", "شروع شود http:// یا https:// باید با"),
("Invalid IP", "نامعتبر است IP آدرس"),
("id_change_tip", "شناسه باید طبق این شرایط باشد : حروف کوچک و بزرگ انگلیسی و اعداد از 0 تا 9، _ و همچنین حرف اول آن فقط حروف بزرگ یا کوچک انگلیسی و طول آن بین 6 الی 16 کاراکتر باشد"),
("Invalid format", "فرمت نادرست است"),
("server_not_support", "هنوز توسط سرور مورد نظر پشتیبانی نمی شود"),
("Not available", "در دسترسی نیست"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "به صورت دستی توسط میزبان بسته شد"),
("Enable remote configuration modification", "فعال بودن اعمال تغییرات پیکربندی از راه دور"),
("Run without install", "بدون نصب اجرا شود"),
("Always connected via relay", "متصل است Relay همیشه با"),
("Connect via relay", ""),
("Always connect via relay", "برای اتصال استفاده شود Relay از"),
("whitelist_tip", "های مجاز می توانند به این دسکتاپ متصل شوند IP فقط"),
("Login", "ورود"),
@@ -436,18 +440,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", "قوی"),
("Switch Sides", "طرفین را عوض کنید"),
("Please confirm if you want to share your desktop?", "لطفاً تأیید کنید که آیا می خواهید دسکتاپ خود را به اشتراک بگذارید؟"),
("Closed as expected", "طبق انتظار بسته شد"),
("Display", "نمایش دادن"),
("Default View Style", "سبک نمایش پیش فرض"),
("Default Scroll Style", "سبک پیش‌فرض اسکرول"),
("Default Scroll Style", "سبک پیش‌ فرض اسکرول"),
("Default Image Quality", "کیفیت تصویر پیش فرض"),
("Default Codec", "کدک پیش فرض"),
("Bitrate", "میزان بیت صفحه نمایش"),
("FPS", "FPS"),
("Auto", "خودکار"),
("Other Default Options", "سایر گزینه های پیش فرض"),
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("Voice call", "تماس صوتی"),
("Text chat", "گفتگو متنی (چت متنی)"),
("Stop voice call", "توقف تماس صوتی"),
("relay_hint_tip", " را به شناسه اضافه کنید یا گزینه \"همیشه از طریق رله متصل شوید\" را در کارت همتا انتخاب کنید. همچنین، اگر می‌خواهید فوراً از سرور رله استفاده کنید، می‌توانید پسوند \"/r\".\n اتصال مستقیم ممکن است امکان پذیر نباشد. در این صورت می توانید سعی کنید از طریق سرور رله متصل شوید"),
("Reconnect", "اتصال مجدد"),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Presse-papier vide"),
("Stop service", "Arrêter le service"),
("Change ID", "Changer d'ID"),
("Your new ID", "Votre nouvel ID"),
("length %min% to %max%", "longueur de %min% à %max%"),
("starts with a letter", "commence par une lettre"),
("allowed characters", "caractères autorisés"),
("id_change_tip", "Seules les lettres a-z, A-Z, 0-9, _ (trait de soulignement) peuvent être utilisées. La première lettre doit être a-z, A-Z. La longueur doit être comprise entre 6 et 16."),
("Website", "Site Web"),
("About", "À propos de"),
("Slogan_tip", "Fait avec cœur dans ce monde chaotique!"),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "Serveur API"),
("invalid_http", "Doit commencer par http:// ou https://"),
("Invalid IP", "IP invalide"),
("id_change_tip", "Seules les lettres a-z, A-Z, 0-9, _ (trait de soulignement) peuvent être utilisées. La première lettre doit être a-z, A-Z. La longueur doit être comprise entre 6 et 16."),
("Invalid format", "Format invalide"),
("server_not_support", "Pas encore supporté par le serveur"),
("Not available", "Indisponible"),
@@ -85,7 +89,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show Hidden Files", "Afficher les fichiers cachés"),
("Receive", "Recevoir"),
("Send", "Envoyer"),
("Refresh File", "Actualiser le fichier"),
("Refresh File", "Rafraîchir le contenu"),
("Local", "Local"),
("Remote", "Distant"),
("Remote Computer", "Ordinateur distant"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Fermé manuellement par le pair"),
("Enable remote configuration modification", "Autoriser la modification de la configuration à distance"),
("Run without install", "Exécuter sans installer"),
("Always connected via relay", "Forcer la connexion relais"),
("Connect via relay", ""),
("Always connect via relay", "Forcer la connexion relais"),
("whitelist_tip", "Seule une IP de la liste blanche peut accéder à mon appareil"),
("Login", "Connexion"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", "Fort"),
("Switch Sides", "Inverser la prise de contrôle"),
("Please confirm if you want to share your desktop?", "Veuillez confirmer le partager de votre bureau ?"),
("Closed as expected", "Fermé normalement"),
("Display", "Affichage"),
("Default View Style", "Style de vue par défaut"),
("Default Scroll Style", "Style de défilement par défaut"),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Το πρόχειρο είναι κενό"),
("Stop service", "Διακοπή υπηρεσίας"),
("Change ID", "Αλλαγή αναγνωριστικού ID"),
("Your new ID", ""),
("length %min% to %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", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "Διακομιστής API"),
("invalid_http", "Πρέπει να ξεκινά με http:// ή https://"),
("Invalid IP", "Μη έγκυρη διεύθυνση IP"),
("id_change_tip", "Επιτρέπονται μόνο οι χαρακτήρες a-z, A-Z, 0-9 και _ (υπογράμμιση). Το πρώτο γράμμα πρέπει να είναι a-z, A-Z και το μήκος πρέπει να είναι μεταξύ 6 και 16 χαρακτήρων."),
("Invalid format", "Μη έγκυρη μορφή"),
("server_not_support", "Αυτή η δυνατότητα δεν υποστηρίζεται ακόμη από τον διακομιστή"),
("Not available", "Μη διαθέσιμο"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Έκλεισε από τον απομακρυσμένο σταθμό"),
("Enable remote configuration modification", "Ενεργοποίηση απομακρυσμένης τροποποίησης ρυθμίσεων"),
("Run without install", "Εκτέλεση χωρίς εγκατάσταση"),
("Always connected via relay", "Πάντα συνδεδεμένο μέσω αναμετάδοσης"),
("Connect via relay", ""),
("Always connect via relay", "Σύνδεση πάντα μέσω αναμετάδοσης"),
("whitelist_tip", "Μόνο οι IP της λίστας επιτρεπόμενων έχουν πρόσβαση"),
("Login", "Σύνδεση"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", "Δυνατό"),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "A vágólap üres"),
("Stop service", "Szolgáltatás leállítása"),
("Change ID", "Azonosító megváltoztatása"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Csak a-z, A-Z, 0-9 csoportokba tartozó karakterek, illetve a _ karakter van engedélyezve. Az első karakternek mindenképpen a-z, A-Z csoportokba kell esnie. Az azonosító hosszúsága 6-tól, 16 karakter."),
("Website", "Weboldal"),
("About", "Rólunk"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "API szerver"),
("invalid_http", "A címnek mindenképpen http(s)://-el kell kezdődnie."),
("Invalid IP", "A megadott IP cím helytelen."),
("id_change_tip", "Csak a-z, A-Z, 0-9 csoportokba tartozó karakterek, illetve a _ karakter van engedélyezve. Az első karakternek mindenképpen a-z, A-Z csoportokba kell esnie. Az azonosító hosszúsága 6-tól, 16 karakter."),
("Invalid format", "Érvénytelen formátum"),
("server_not_support", "Nem támogatott a szerver által"),
("Not available", "Nem elérhető"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "A kapcsolatot a másik fél manuálisan bezárta"),
("Enable remote configuration modification", "Távoli konfiguráció módosítás engedélyezése"),
("Run without install", "Futtatás feltelepítés nélkül"),
("Always connected via relay", "Mindig közvetítőn keresztül csatlakozik"),
("Connect via relay", ""),
("Always connect via relay", "Mindig közvetítőn keresztüli csatlakozás"),
("whitelist_tip", "Csak az engedélyezési listán szereplő címek csatlakozhatnak"),
("Login", "Belépés"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("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", ""),
("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"),
("About", "Tentang"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "API Server"),
("invalid_http", "harus dimulai dengan http:// atau https://"),
("Invalid IP", "IP tidak valid"),
("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."),
("Invalid format", "Format tidak valid"),
("server_not_support", "Belum didukung oleh server"),
("Not available", "Tidak tersedia"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Ditutup secara manual oleh peer"),
("Enable remote configuration modification", "Aktifkan modifikasi konfigurasi jarak jauh"),
("Run without install", "Jalankan tanpa menginstal"),
("Always connected via relay", "Selalu terhubung melalui relai"),
("Connect via relay", ""),
("Always connect via relay", "Selalu terhubung melalui relai"),
("whitelist_tip", "Hanya whitelisted IP yang dapat mengakses saya"),
("Login", "Masuk"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Gli appunti sono vuoti"),
("Stop service", "Arresta servizio"),
("Change ID", "Cambia ID"),
("Your new ID", "Il tuo nuovo ID"),
("length %min% to %max%", "da lunghezza %min% a %max%"),
("starts with a letter", "inizia con una lettera"),
("allowed characters", "caratteri consentiti"),
("id_change_tip", "Puoi usare solo i caratteri a-z, A-Z, 0-9 e _ (underscore). Il primo carattere deve essere a-z o A-Z. La lunghezza deve essere fra 6 e 16 caratteri."),
("Website", "Sito web"),
("About", "Informazioni"),
("Slogan_tip", "Fatta con il cuore in questo mondo caotico!"),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "Server API"),
("invalid_http", "deve iniziare con http:// o https://"),
("Invalid IP", "Indirizzo IP non valido"),
("id_change_tip", "Puoi usare solo i caratteri a-z, A-Z, 0-9 e _ (underscore). Il primo carattere deve essere a-z o A-Z. La lunghezza deve essere fra 6 e 16 caratteri."),
("Invalid format", "Formato non valido"),
("server_not_support", "Non ancora supportato dal server"),
("Not available", "Non disponibile"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Chiuso manualmente dal peer"),
("Enable remote configuration modification", "Abilita la modifica remota della configurazione"),
("Run without install", "Esegui senza installare"),
("Always connected via relay", "Connesso sempre tramite relay"),
("Connect via relay", "Collegati tramite relay"),
("Always connect via relay", "Collegati sempre tramite relay"),
("whitelist_tip", "Solo gli indirizzi IP autorizzati possono connettersi a questo desktop"),
("Login", "Accedi"),
@@ -415,7 +419,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("software_render_tip", "Se si dispone di una scheda grafica Nvidia e la finestra remota si chiude immediatamente dopo la connessione, l'installazione del driver nouveau e la scelta di utilizzare il rendering software possono aiutare. È necessario un riavvio del software."),
("Always use software rendering", "Usa sempre il render Software"),
("config_input", "Per controllare il desktop remoto con la tastiera, è necessario concedere le autorizzazioni a RustDesk \"Monitoraggio dell'input\"."),
("config_microphone", ""),
("config_microphone", "Per poter chiamare, è necessario concedere l'autorizzazione a RustDesk \"Registra audio\"."),
("request_elevation_tip", "È possibile richiedere l'elevazione se c'è qualcuno sul lato remoto."),
("Wait", "Attendi"),
("Elevation Error", "Errore durante l'elevazione dei diritti"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", "Forte"),
("Switch Sides", "Cambia lato"),
("Please confirm if you want to share your desktop?", "Vuoi condividere il tuo desktop?"),
("Closed as expected", "Chiuso come previsto"),
("Display", "Visualizzazione"),
("Default View Style", "Stile Visualizzazione Predefinito"),
("Default Scroll Style", "Stile Scorrimento Predefinito"),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", "Chiamata vocale"),
("Text chat", "Chat testuale"),
("Stop voice call", "Interrompi la chiamata vocale"),
].iter().cloned().collect();
("relay_hint_tip", "Se non è possibile connettersi direttamente, si può provare a farlo tramite relay.\nInoltre, se si desidera utilizzare il relay al primo tentativo, è possibile aggiungere il suffisso \"/r\" all'ID o selezionare l'opzione \"Collegati sempre tramite relay\" nella scheda peer."),
("Reconnect", "Riconnetti"),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "クリップボードは空です"),
("Stop service", "サービスを停止"),
("Change ID", "IDを変更"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "使用できるのは大文字・小文字のアルファベット、数字、アンダースコア_のみです。初めの文字はアルファベットにする必要があります。6文字から16文字までです。"),
("Website", "公式サイト"),
("About", "情報"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "APIサーバー"),
("invalid_http", "http:// もしくは https:// から入力してください"),
("Invalid IP", "無効なIP"),
("id_change_tip", "使用できるのは大文字・小文字のアルファベット、数字、アンダースコア_のみです。初めの文字はアルファベットにする必要があります。6文字から16文字までです。"),
("Invalid format", "無効な形式"),
("server_not_support", "サーバー側でまだサポートされていません"),
("Not available", "利用不可"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "相手が手動で切断しました"),
("Enable remote configuration modification", "リモート設定変更を有効化"),
("Run without install", "インストールせずに実行"),
("Always connected via relay", "常に中継サーバー経由で接続"),
("Connect via relay", ""),
("Always connect via relay", "常に中継サーバー経由で接続"),
("whitelist_tip", "ホワイトリストに登録されたIPからのみ接続を許可します"),
("Login", "ログイン"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "클립보드가 비어있습니다"),
("Stop service", "서비스 중단"),
("Change ID", "ID 변경"),
("Your new ID", ""),
("length %min% to %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", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "API 서버"),
("invalid_http", "다음과 같이 시작해야 합니다. http:// 또는 https://"),
("Invalid IP", "유효하지 않은 IP"),
("id_change_tip", "a-z, A-Z, 0-9, _(밑줄 문자)만 입력 가능합니다. 첫 문자는 a-z 혹은 A-Z로 시작해야 합니다. 길이는 6 ~ 16글자가 요구됩니다."),
("Invalid format", "유효하지 않은 형식"),
("server_not_support", "해당 서버가 아직 지원하지 않습니다"),
("Not available", "불가능"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "다른 사용자에 의해 종료됨"),
("Enable remote configuration modification", "원격 구성 변경 활성화"),
("Run without install", "설치 없이 실행"),
("Always connected via relay", "항상 relay를 통해 접속됨"),
("Connect via relay", ""),
("Always connect via relay", "항상 relay를 통해 접속하기"),
("whitelist_tip", "화이트리스트에 있는 IP만 현 데스크탑에 접속 가능합니다"),
("Login", "로그인"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Көшіру-тақта бос"),
("Stop service", "Сербесті тоқтату"),
("Change ID", "ID ауыстыру"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Тек a-z, A-Z, 0-9 және _ (астынғы-сызық) таңбалары рұқсат етілген. Бірінші таңба a-z, A-Z болуы қажет. Ұзындығы 6 мен 16 арасы."),
("Website", "Web-сайт"),
("About", "Туралы"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "API Сербері"),
("invalid_http", "http:// немесе https://'пен басталуы қажет"),
("Invalid IP", "Бұрыс IP-Мекенжай"),
("id_change_tip", "Тек a-z, A-Z, 0-9 және _ (астынғы-сызық) таңбалары рұқсат етілген. Бірінші таңба a-z, A-Z болуы қажет. Ұзындығы 6 мен 16 арасы."),
("Invalid format", "Бұрыс формат"),
("server_not_support", "Сербер әзірше қолдамайды"),
("Not available", "Қолжетімсіз"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Пир қолымен жабылған"),
("Enable remote configuration modification", "Қашықтан қалыптарды өзгертуді іске қосу"),
("Run without install", "Орнатпай-ақ Іске қосу"),
("Always connected via relay", "Әрқашан да релай сербері арқылы қосулы"),
("Connect via relay", ""),
("Always connect via relay", "Әрқашан да релай сербері арқылы қосылу"),
("whitelist_tip", "Маған тек ақ-тізімделген IP қол жеткізе алады"),
("Login", "Кіру"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

460
src/lang/nl.rs Normal file
View File

@@ -0,0 +1,460 @@
lazy_static::lazy_static! {
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[
("Status", "Status"),
("Your Desktop", "Uw Bureaublad"),
("desk_tip", "Uw bureaublad is toegankelijk via de ID en het wachtwoord hieronder."),
("Password", "Wachtwoord"),
("Ready", "Klaar"),
("Established", "Opgezet"),
("connecting_status", "Verbinding maken met het RustDesk netwerk..."),
("Enable Service", "Service Inschakelen"),
("Start Service", "Start Service"),
("Service is running", "De service loopt."),
("Service is not running", "De service loopt niet"),
("not_ready_status", "Niet klaar, controleer de netwerkverbinding"),
("Control Remote Desktop", "Beheer Extern Bureaublad"),
("Transfer File", "Bestand Overzetten"),
("Connect", "Verbinden"),
("Recent Sessions", "Recente Behandelingen"),
("Address Book", "Adresboek"),
("Confirmation", "Bevestiging"),
("TCP Tunneling", "TCP Tunneling"),
("Remove", "Verwijder"),
("Refresh random password", "Vernieuw willekeurig wachtwoord"),
("Set your own password", "Stel je eigen wachtwoord in"),
("Enable Keyboard/Mouse", "Toetsenbord/Muis Inschakelen"),
("Enable Clipboard", "Klembord Inschakelen"),
("Enable File Transfer", "Bestandsoverdracht Inschakelen"),
("Enable TCP Tunneling", "TCP Tunneling Inschakelen"),
("IP Whitelisting", "IP Witte Lijst"),
("ID/Relay Server", "ID/Relay Server"),
("Import Server Config", "Importeer Serverconfiguratie"),
("Export Server Config", "Exporteer Serverconfiguratie"),
("Import server configuration successfully", "Importeren serverconfiguratie succesvol"),
("Export server configuration successfully", "Exporteren serverconfiguratie succesvol"),
("Invalid server configuration", "Ongeldige Serverconfiguratie"),
("Clipboard is empty", "Klembord is leeg"),
("Stop service", "Stop service"),
("Change ID", "Wijzig ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Alleen de letters a-z, A-Z, 0-9, _ (underscore) kunnen worden gebruikt. De eerste letter moet a-z, A-Z zijn. De lengte moet tussen 6 en 16 liggen."),
("Website", "Website"),
("About", "Over"),
("Slogan_tip", "Gedaan met het hart in deze chaotische wereld!"),
("Privacy Statement", "Privacyverklaring"),
("Mute", "Geluid uit"),
("Build Date", "Versie datum"),
("Version", "Versie"),
("Home", "Startpagina"),
("Audio Input", "Audio Ingang"),
("Enhancements", "Verbeteringen"),
("Hardware Codec", "Hardware Codec"),
("Adaptive Bitrate", "Aangepaste Bitsnelheid"),
("ID Server", "Server ID"),
("Relay Server", "Relay Server"),
("API Server", "API Server"),
("invalid_http", "Moet beginnen met http:// of https://"),
("Invalid IP", "Ongeldig IP"),
("Invalid format", "Ongeldig formaat"),
("server_not_support", "Nog niet ondersteund door de server"),
("Not available", "Niet beschikbaar"),
("Too frequent", "Te vaak"),
("Cancel", "Annuleer"),
("Skip", "Overslaan"),
("Close", "Sluit"),
("Retry", "Probeer opnieuw"),
("OK", "OK"),
("Password Required", "Wachtwoord vereist"),
("Please enter your password", "Geef uw wachtwoord in"),
("Remember password", "Wachtwoord onthouden"),
("Wrong Password", "Verkeerd wachtwoord"),
("Do you want to enter again?", "Wil je opnieuw ingeven?"),
("Connection Error", "Fout bij verbinding"),
("Error", "Fout"),
("Reset by the peer", "Reset door de peer"),
("Connecting...", "Verbinding maken..."),
("Connection in progress. Please wait.", "Verbinding in uitvoering. Even geduld a.u.b."),
("Please try 1 minute later", "Probeer 1 minuut later"),
("Login Error", "Login Fout"),
("Successful", "Succesvol"),
("Connected, waiting for image...", "Verbonden, wacht op beeld..."),
("Name", "Naam"),
("Type", "Type"),
("Modified", "Gewijzigd"),
("Size", "Grootte"),
("Show Hidden Files", "Toon verborgen bestanden"),
("Receive", "Ontvangen"),
("Send", "Verzenden"),
("Refresh File", "Bestand Verversen"),
("Local", "Lokaal"),
("Remote", "Op afstand"),
("Remote Computer", "Externe Computer"),
("Local Computer", "Lokale Computer"),
("Confirm Delete", "Bevestig Verwijderen"),
("Delete", "Verwijder"),
("Properties", "Eigenschappen"),
("Multi Select", "Meervoudig selecteren"),
("Select All", "Selecteer Alle"),
("Unselect All", "Deselecteer alles"),
("Empty Directory", "Lege Map"),
("Not an empty directory", "Geen Lege Map"),
("Are you sure you want to delete this file?", "Weet je zeker dat je dit bestand wilt verwijderen?"),
("Are you sure you want to delete this empty directory?", "Weet je zeker dat je deze lege map wilt verwijderen?"),
("Are you sure you want to delete the file of this directory?", "Weet je zeker dat je het bestand uit deze map wilt verwijderen?"),
("Do this for all conflicts", "Doe dit voor alle conflicten"),
("This is irreversible!", "Dit is onomkeerbaar!"),
("Deleting", "Verwijderen"),
("files", "bestanden"),
("Waiting", "Wachten"),
("Finished", "Voltooid"),
("Speed", "Snelheid"),
("Custom Image Quality", "Aangepaste beeldkwaliteit"),
("Privacy mode", "Privacymodus"),
("Block user input", "Gebruikersinvoer blokkeren"),
("Unblock user input", "Gebruikersinvoer opheffen"),
("Adjust Window", "Venster Aanpassen"),
("Original", "Origineel"),
("Shrink", "Verkleinen"),
("Stretch", "Uitrekken"),
("Scrollbar", "Schuifbalk"),
("ScrollAuto", "Auto Schuiven"),
("Good image quality", "Goede beeldkwaliteit"),
("Balanced", "Gebalanceerd"),
("Optimize reaction time", "Optimaliseer reactietijd"),
("Custom", "Aangepast"),
("Show remote cursor", "Toon cursor van extern bureaublad"),
("Show quality monitor", "Kwaliteitsmonitor tonen"),
("Disable clipboard", "Klembord uitschakelen"),
("Lock after session end", "Vergrendelen na einde sessie"),
("Insert", "Invoegen"),
("Insert Lock", "Vergrendeling Invoegen"),
("Refresh", "Vernieuwen"),
("ID does not exist", "ID bestaat niet"),
("Failed to connect to rendezvous server", "Verbinding met rendez-vous-server mislukt"),
("Please try later", "Probeer later opnieuw"),
("Remote desktop is offline", "Extern bureaublad is offline"),
("Key mismatch", "Code onjuist"),
("Timeout", "Time-out"),
("Failed to connect to relay server", "Verbinding met relayserver mislukt"),
("Failed to connect via rendezvous server", "Verbinding via rendez-vous-server mislukt"),
("Failed to connect via relay server", "Verbinding via relaisserver mislukt"),
("Failed to make direct connection to remote desktop", "Onmogelijk direct verbinding te maken met extern bureaublad"),
("Set Password", "Wachtwoord Instellen"),
("OS Password", "OS Wachtwoord"),
("install_tip", "Je gebruikt een niet geinstalleerde versie. Als gevolg van UAC-beperkingen is het in sommige gevallen niet mogelijk om als controleterminal de muis en het toetsenbord te bedienen of het scherm over te nemen. Klik op de knop hieronder om RustDesk op het systeem te installeren om het bovenstaande probleem te voorkomen."),
("Click to upgrade", "Klik voor upgrade"),
("Click to download", "Klik om te downloaden"),
("Click to update", "Klik om bij te werken"),
("Configure", "Configureren"),
("config_acc", "Om je bureaublad op afstand te kunnen bedienen, moet je RustDesk \"toegankelijkheid\" toestemming geven."),
("config_screen", "Om toegang te krijgen tot het externe bureaublad, moet je RustDesk de toestemming \"schermregistratie\" geven."),
("Installing ...", "Installeren ..."),
("Install", "Installeer"),
("Installation", "Installatie"),
("Installation Path", "Installatie Pad"),
("Create start menu shortcuts", "Startmenu snelkoppelingen maken"),
("Create desktop icon", "Bureaubladpictogram maken"),
("agreement_tip", "Het starten van de installatie betekent het accepteren van de licentieovereenkomst."),
("Accept and Install", "Accepteren en installeren"),
("End-user license agreement", "Licentieovereenkomst eindgebruiker"),
("Generating ...", "Genereert ..."),
("Your installation is lower version.", "Uw installatie is een lagere versie."),
("not_close_tcp_tip", "Gelieve dit venster niet te sluiten wanneer u de tunnel gebruikt"),
("Listening ...", "Luisteren ..."),
("Remote Host", "Externe Host"),
("Remote Port", "Externe Poort"),
("Action", "Actie"),
("Add", "Toevoegen"),
("Local Port", "Lokale Poort"),
("Local Address", "Lokaal Adres"),
("Change Local Port", "Wijzig Lokale Poort"),
("setup_server_tip", "Als u een snellere verbindingssnelheid nodig heeft, kunt u ervoor kiezen om uw eigen server aan te maken"),
("Too short, at least 6 characters.", "e kort, minstens 6 tekens."),
("The confirmation is not identical.", "De bevestiging is niet identiek."),
("Permissions", "Machtigingen"),
("Accept", "Accepteren"),
("Dismiss", "Afwijzen"),
("Disconnect", "Verbinding verbreken"),
("Allow using keyboard and mouse", "Gebruik toetsenbord en muis toestaan"),
("Allow using clipboard", "Gebruik klembord toestaan"),
("Allow hearing sound", "Geluidsweergave toestaan"),
("Allow file copy and paste", "Kopieren en plakken van bestanden toestaan"),
("Connected", "Verbonden"),
("Direct and encrypted connection", "Directe en versleutelde verbinding"),
("Relayed and encrypted connection", "Doorgeschakelde en versleutelde verbinding"),
("Direct and unencrypted connection", "Directe en niet-versleutelde verbinding"),
("Relayed and unencrypted connection", "Doorgeschakelde en niet-versleutelde verbinding"),
("Enter Remote ID", "Voer Extern ID in"),
("Enter your password", "Voer uw wachtwoord in"),
("Logging in...", "Aanmelden..."),
("Enable RDP session sharing", "Delen van RDP-sessie inschakelen"),
("Auto Login", "Automatisch Aanmelden"),
("Enable Direct IP Access", "Directe IP-toegang inschakelen"),
("Rename", "Naam wijzigen"),
("Space", "Spatie"),
("Create Desktop Shortcut", "Snelkoppeling op bureaublad maken"),
("Change Path", "Pad wijzigen"),
("Create Folder", "Map Maken"),
("Please enter the folder name", "Geef de mapnaam op"),
("Fix it", "Repareer het"),
("Warning", "Waarschuwing"),
("Login screen using Wayland is not supported", "Aanmeldingsscherm via Wayland wordt niet ondersteund"),
("Reboot required", "Opnieuw opstarten vereist"),
("Unsupported display server ", "Niet-ondersteunde weergaveserver"),
("x11 expected", "x11 verwacht"),
("Port", "Poort"),
("Settings", "Instellingen"),
("Username", "Gebruikersnaam"),
("Invalid port", "Ongeldige poort"),
("Closed manually by the peer", "Handmatig gesloten door de peer"),
("Enable remote configuration modification", "Wijziging configuratie op afstand inschakelen"),
("Run without install", "Uitvoeren zonder installatie"),
("Connect via relay", ""),
("Always connect via relay", "Altijd verbinden via relay"),
("whitelist_tip", "Alleen een IP-adres op de witte lijst krijgt toegang tot mijn toestel"),
("Login", "Log In"),
("Verify", "Controleer"),
("Remember me", "Herinner mij"),
("Trust this device", "Vertrouw dit apparaat"),
("Verification code", "Verificatie code"),
("verification_tip", "Er is een nieuw apparaat gedetecteerd en er is een verificatiecode naar het geregistreerde e-mailadres gestuurd, voer de verificatiecode in om de verbinding voort te zetten."),
("Logout", "Log Uit"),
("Tags", "Labels"),
("Search ID", "Zoek ID"),
("whitelist_sep", "Gescheiden door komma, puntkomma, spatie of nieuwe regel"),
("Add ID", "ID Toevoegen"),
("Add Tag", "Label Toevoegen"),
("Unselect all tags", "Alle labels verwijderen"),
("Network error", "Netwerkfout"),
("Username missed", "Gebruikersnaam gemist"),
("Password missed", "Wachtwoord vergeten"),
("Wrong credentials", "Verkeerde inloggegevens"),
("Edit Tag", "Label Bewerken"),
("Unremember Password", "Wachtwoord vergeten"),
("Favorites", "Favorieten"),
("Add to Favorites", "Toevoegen aan Favorieten"),
("Remove from Favorites", "Verwijderen uit Favorieten"),
("Empty", "Leeg"),
("Invalid folder name", "Ongeldige mapnaam"),
("Socks5 Proxy", "Socks5 Proxy"),
("Hostname", "Hostnaam"),
("Discovered", "Ontdekt"),
("install_daemon_tip", "Om bij het opstarten van de computer te kunnen beginnen, moet je de systeemdienst installeren."),
("Remote ID", "Externe ID"),
("Paste", "Plakken"),
("Paste here?", "Hier plakken"),
("Are you sure to close the connection?", "Weet je zeker dat je de verbinding wilt sluiten?"),
("Download new version", "Download nieuwe versie"),
("Touch mode", "Aanraak modus"),
("Mouse mode", "Muismodus"),
("One-Finger Tap", "Een-Vinger Tik"),
("Left Mouse", "Linkermuis"),
("One-Long Tap", "Een-Vinger-Lange-Tik"),
("Two-Finger Tap", "Twee-Vingers-Tik"),
("Right Mouse", "Rechter muis"),
("One-Finger Move", "Een-Vinger-Verplaatsing"),
("Double Tap & Move", "Dubbel Tik en Verplaatsen"),
("Mouse Drag", "Muis Slepen"),
("Three-Finger vertically", "Drie-Vinger verticaal"),
("Mouse Wheel", "Muiswiel"),
("Two-Finger Move", "Twee-Vingers Verplaatsen"),
("Canvas Move", "Canvas Verplaatsen"),
("Pinch to Zoom", "Knijp om te Zoomen"),
("Canvas Zoom", "Canvas Zoom"),
("Reset canvas", "Reset canvas"),
("No permission of file transfer", "Geen toestemming voor bestandsoverdracht"),
("Note", "Opmerking"),
("Connection", "Verbinding"),
("Share Screen", "Scherm Delen"),
("CLOSE", "SLUITEN"),
("OPEN", "OPEN"),
("Chat", "Chat"),
("Total", "Totaal"),
("items", "items"),
("Selected", "Geselecteerd"),
("Screen Capture", "Schermopname"),
("Input Control", "Invoercontrole"),
("Audio Capture", "Audio Opnemen"),
("File Connection", "Bestandsverbinding"),
("Screen Connection", "Schermverbinding"),
("Do you accept?", "Sta je toe?"),
("Open System Setting", "Systeeminstelling Openen"),
("How to get Android input permission?", "Hoe krijg ik Android invoer toestemming?"),
("android_input_permission_tip1", "Om ervoor te zorgen dat een extern apparaat uw Android-apparaat kan besturen via muis of aanraking, moet u RustDesk toestaan om de \"Toegankelijkheid\" service te gebruiken."),
("android_input_permission_tip2", "Ga naar de volgende pagina met systeeminstellingen, zoek en ga naar [Geinstalleerde Services], schakel de service [RustDesk Input] in."),
("android_new_connection_tip", "Er is een nieuw controleverzoek binnengekomen, dat uw huidige apparaat wil controleren."),
("android_service_will_start_tip", "Als u \"Schermopname\" inschakelt, wordt de service automatisch gestart, zodat andere apparaten een verbinding met uw apparaat kunnen aanvragen."),
("android_stop_service_tip", "Het sluiten van de service zal automatisch alle gemaakte verbindingen sluiten."),
("android_version_audio_tip", "De huidige versie van Android ondersteunt geen audio-opname, upgrade naar Android 10 of hoger."),
("android_start_service_tip", "Druk op [Start Service] of op de permissie OPEN [Screenshot] om de service voor het overnemen van het scherm te starten."),
("Account", "Account"),
("Overwrite", "Overschrijven"),
("This file exists, skip or overwrite this file?", "Dit bestand bestaat reeds, overslaan of overschrijven?"),
("Quit", "Afsluiten"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("Help", "https://rustdesk.com/docs/en/manual/linux/#x11-required"),
("Failed", "Mislukt"),
("Succeeded", "Geslaagd"),
("Someone turns on privacy mode, exit", "Iemand schakelt privacymodus in, afsluiten"),
("Unsupported", "Niet Ondersteund"),
("Peer denied", "Peer geweigerd"),
("Please install plugins", "Installeer plugins"),
("Peer exit", "Peer afgesloten"),
("Failed to turn off", "Uitschakelen mislukt"),
("Turned off", "Uitgeschakeld"),
("In privacy mode", "In privacymodus"),
("Out privacy mode", "Uit privacymodus"),
("Language", "Taal"),
("Keep RustDesk background service", "RustDesk achtergronddienst behouden"),
("Ignore Battery Optimizations", "Negeer Batterij Optimalisaties"),
("android_open_battery_optimizations_tip", "Ga naar de volgende pagina met instellingen"),
("Connection not allowed", "Verbinding niet toegestaan"),
("Legacy mode", "Verouderde modus"),
("Map mode", "Map mode"),
("Translate mode", "Vertaalmodus"),
("Use permanent password", "Gebruik permanent wachtwoord"),
("Use both passwords", "Gebruik beide wachtwoorden"),
("Set permanent password", "Stel permanent wachtwoord in"),
("Enable Remote Restart", "Schakel Herstart op afstand in"),
("Allow remote restart", "Opnieuw Opstarten op afstand toestaan"),
("Restart Remote Device", "Apparaat op afstand herstarten"),
("Are you sure you want to restart", "Weet je zeker dat je wilt herstarten"),
("Restarting Remote Device", "Apparaat op afstand herstarten"),
("remote_restarting_tip", "Apparaat op afstand wordt opnieuw opgestart, sluit dit bericht en maak na een ogenblik opnieuw verbinding met het permanente wachtwoord."),
("Copied", "Gekopieerd"),
("Exit Fullscreen", "Volledig Scherm sluiten"),
("Fullscreen", "Volledig Scherm"),
("Mobile Actions", "Mobiele Acties"),
("Select Monitor", "Selecteer Monitor"),
("Control Actions", "Controleacties"),
("Display Settings", "Beeldscherminstellingen"),
("Ratio", "Verhouding"),
("Image Quality", "Beeldkwaliteit"),
("Scroll Style", "Scroll Stijl"),
("Show Menubar", "Toon Menubalk"),
("Hide Menubar", "Verberg Menubalk"),
("Direct Connection", "Directe Verbinding"),
("Relay Connection", "Relaisverbinding"),
("Secure Connection", "Beveiligde Verbinding"),
("Insecure Connection", "Onveilige Verbinding"),
("Scale original", "Oorspronkelijke schaal"),
("Scale adaptive", "Schaalaanpassing"),
("General", "Algemeen"),
("Security", "Beveiliging"),
("Theme", "Thema"),
("Dark Theme", "Donker Thema"),
("Dark", "Donker"),
("Light", "Licht"),
("Follow System", "Volg Systeem"),
("Enable hardware codec", "Hardware codec inschakelen"),
("Unlock Security Settings", "Beveiligingsinstellingen vrijgeven"),
("Enable Audio", "Audio Inschakelen"),
("Unlock Network Settings", "Netwerkinstellingen Vrijgeven"),
("Server", "Server"),
("Direct IP Access", "Directe IP toegang"),
("Proxy", "Proxy"),
("Apply", "Toepassen"),
("Disconnect all devices?", "Alle apparaten uitschakelen?"),
("Clear", "Wis"),
("Audio Input Device", "Audio-invoerapparaat"),
("Deny remote access", "Toegang op afstand weigeren"),
("Use IP Whitelisting", "Gebruik een witte lijst van IP-adressen"),
("Network", "Netwerk"),
("Enable RDP", "Zet RDP aan"),
("Pin menubar", "Menubalk Vastzetten"),
("Unpin menubar", "Menubalk vrijmaken"),
("Recording", "Opnemen"),
("Directory", "Map"),
("Automatically record incoming sessions", "Automatisch inkomende sessies opnemen"),
("Change", "Wissel"),
("Start session recording", "Start de sessieopname"),
("Stop session recording", "Stop de sessieopname"),
("Enable Recording Session", "Opnamesessie Activeren"),
("Allow recording session", "Opnamesessie toestaan"),
("Enable LAN Discovery", "LAN-detectie inschakelen"),
("Deny LAN Discovery", "LAN-detectie Weigeren"),
("Write a message", "Schrijf een bericht"),
("Prompt", "Verzoek"),
("Please wait for confirmation of UAC...", "Wacht op bevestiging van UAC..."),
("elevated_foreground_window_tip", "Het momenteel geopende venster van de op afstand bediende computer vereist hogere rechten. Daarom is het momenteel niet mogelijk de muis en het toetsenbord te gebruiken. Vraag de gebruiker wiens computer u op afstand bedient om het venster te minimaliseren of de rechten te verhogen. Om dit probleem in de toekomst te voorkomen, wordt aanbevolen de software te installeren op de op afstand bediende computer."),
("Disconnected", "Afgesloten"),
("Other", "Andere"),
("Confirm before closing multiple tabs", "Bevestig voordat u meerdere tabbladen sluit"),
("Keyboard Settings", "Toetsenbord instellingen"),
("Full Access", "Volledige Toegang"),
("Screen Share", "Scherm Delen"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland vereist Ubuntu 21.04 of een hogere versie."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland vereist een hogere versie van Linux distro. Probeer X11 desktop of verander je OS."),
("JumpLink", "JumpLink"),
("Please Select the screen to be shared(Operate on the peer side).", "Selecteer het scherm dat moet worden gedeeld (Bediening aan de kant van de peer)."),
("Show RustDesk", "Toon RustDesk"),
("This PC", "Deze PC"),
("or", "of"),
("Continue with", "Ga verder met"),
("Elevate", "Verhoog"),
("Zoom cursor", "Cursor Zoomen"),
("Accept sessions via password", "Sessies accepteren via wachtwoord"),
("Accept sessions via click", "Sessies accepteren via klik"),
("Accept sessions via both", "Accepteer sessies via beide"),
("Please wait for the remote side to accept your session request...", "Wacht tot de andere kant uw sessieverzoek accepteert..."),
("One-time Password", "Eenmalig Wachtwoord"),
("Use one-time password", "Gebruik een eenmalig Wachtwoord"),
("One-time password length", "Eenmalig Wachtwoord lengre"),
("Request access to your device", "Toegang tot uw toestel aanvragen"),
("Hide connection management window", "Verberg het venster voor verbindingsbeheer"),
("hide_cm_tip", "Dit kan alleen als de toegang via een permanent wachtwoord verloopt."),
("wayland_experiment_tip", "Wayland ondersteuning is slechts experimenteel. Gebruik alsjeblieft X11 als je onbeheerde toegang nodig hebt."),
("Right click to select tabs", "Rechts klikken om tabbladen te selecteren"),
("Skipped", "Overgeslagen"),
("Add to Address Book", "Toevoegen aan Adresboek"),
("Group", "Groep"),
("Search", "Zoek"),
("Closed manually by web console", "Handmatig gesloten door webconsole"),
("Local keyboard type", "Lokaal toetsenbord"),
("Select local keyboard type", "Selecteer lokaal toetsenbord"),
("software_render_tip", "Als u een NVIDIA grafische kaart hebt en het externe venster sluit onmiddellijk na verbinding, kan het helpen om het nieuwe stuurprogramma te installeren en te kiezen voor software rendering. Een software herstart is vereist."),
("Always use software rendering", "Gebruik altijd software rendering"),
("config_input", "config_invoer"),
("config_microphone", "config_microfoon"),
("request_elevation_tip", "U kunt ook meer rechten vragen als iemand aan de andere kant aanwezig is."),
("Wait", "Wacht"),
("Elevation Error", "Verhogingsfout"),
("Ask the remote user for authentication", "Vraag de gebruiker op afstand om bevestiging"),
("Choose this if the remote account is administrator", ""),
("Transmit the username and password of administrator", ""),
("still_click_uac_tip", "De gebruiker op afstand moet altijd bevestigen via het UAC-venster van de werkende RustDesk."),
("Request Elevation", "Verzoek om meer rechten"),
("wait_accept_uac_tip", "Wacht tot de gebruiker op afstand het UAC-dialoogvenster accepteert."),
("Elevate successfully", "Succesvolle verhoging van privileges"),
("uppercase", "Hoofdletter"),
("lowercase", "kleine letter"),
("digit", "cijfer"),
("special character", "speciaal teken"),
("length>=8", "lengte>=8"),
("Weak", "Zwak"),
("Medium", "Midelmatig"),
("Strong", "Sterk"),
("Switch Sides", "Wissel van kant"),
("Please confirm if you want to share your desktop?", "bevestig als je je bureaublad wilt delen?"),
("Display", "Weergave"),
("Default View Style", "Standaard Weergave Stijl"),
("Default Scroll Style", "Standaard Scroll Stijl"),
("Default Image Quality", "Standaard Beeldkwaliteit"),
("Default Codec", "tandaard Codec"),
("Bitrate", "Bitrate"),
("FPS", "FPS"),
("Auto", "Auto"),
("Other Default Options", "Andere Standaardopties"),
("Voice call", "Spraakoproep"),
("Text chat", "Tekst chat"),
("Stop voice call", "Stop spraakoproep"),
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Schowek jest pusty"),
("Stop service", "Zatrzymaj usługę"),
("Change ID", "Zmień ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Nowy ID może być złożony z małych i dużych liter a-zA-z, cyfry 0-9 oraz _ (podkreślenie). Pierwszym znakiem powinna być litera a-zA-Z, a całe ID powinno składać się z 6 do 16 znaków."),
("Website", "Strona internetowa"),
("About", "O aplikacji"),
("Slogan_tip", "Tworzone z miłością w tym pełnym chaosu świecie!"),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "Serwer API"),
("invalid_http", "Nieprawidłowe żądanie http"),
("Invalid IP", "Nieprawidłowe IP"),
("id_change_tip", "Nowy ID może być złożony z małych i dużych liter a-zA-z, cyfry 0-9 oraz _ (podkreślenie). Pierwszym znakiem powinna być litera a-zA-Z, a całe ID powinno składać się z 6 do 16 znaków."),
("Invalid format", "Nieprawidłowy format"),
("server_not_support", "Serwer nie obsługuje tej funkcji"),
("Not available", "Niedostępne"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Połączenie zakończone ręcznie przez peer"),
("Enable remote configuration modification", "Włącz zdalną modyfikację konfiguracji"),
("Run without install", "Uruchom bez instalacji"),
("Always connected via relay", "Zawsze połączony pośrednio"),
("Connect via relay", ""),
("Always connect via relay", "Zawsze łącz pośrednio"),
("whitelist_tip", "Zezwalaj na łączenie z tym komputerem tylko z adresów IP znajdujących się na białej liście"),
("Login", "Zaloguj"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", "Mocne"),
("Switch Sides", "Zamień Strony"),
("Please confirm if you want to share your desktop?", "Czy na pewno chcesz udostępnić swój ekran?"),
("Closed as expected", "Zamknięto pomyślnie"),
("Display", "Wyświetlanie"),
("Default View Style", "Domyślny styl wyświetlania"),
("Default Scroll Style", "Domyślny styl przewijania"),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "A área de transferência está vazia"),
("Stop service", "Parar serviço"),
("Change ID", "Alterar ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Somente os caracteres a-z, A-Z, 0-9 e _ (sublinhado) são permitidos. A primeira letra deve ser a-z, A-Z. Comprimento entre 6 e 16."),
("Website", "Website"),
("About", "Sobre"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "Servidor da API"),
("invalid_http", "deve iniciar com http:// ou https://"),
("Invalid IP", "IP inválido"),
("id_change_tip", "Somente os caracteres a-z, A-Z, 0-9 e _ (sublinhado) são permitidos. A primeira letra deve ser a-z, A-Z. Comprimento entre 6 e 16."),
("Invalid format", "Formato inválido"),
("server_not_support", "Ainda não suportado pelo servidor"),
("Not available", "Indisponível"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Fechada manualmente pelo destino"),
("Enable remote configuration modification", "Habilitar modificações de configuração remotas"),
("Run without install", "Executar sem instalar"),
("Always connected via relay", "Sempre conectado via relay"),
("Connect via relay", ""),
("Always connect via relay", "Sempre conectar via relay"),
("whitelist_tip", "Somente IPs na whitelist podem me acessar"),
("Login", "Login"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "A área de transferência está vazia"),
("Stop service", "Parar serviço"),
("Change ID", "Alterar ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Somente os caracteres a-z, A-Z, 0-9 e _ (sublinhado) são permitidos. A primeira letra deve ser a-z, A-Z. Comprimento entre 6 e 16."),
("Website", "Website"),
("About", "Sobre"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "Servidor da API"),
("invalid_http", "deve iniciar com http:// ou https://"),
("Invalid IP", "IP inválido"),
("id_change_tip", "Somente os caracteres a-z, A-Z, 0-9 e _ (sublinhado) são permitidos. A primeira letra deve ser a-z, A-Z. Comprimento entre 6 e 16."),
("Invalid format", "Formato inválido"),
("server_not_support", "Ainda não suportado pelo servidor"),
("Not available", "Indisponível"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Fechada manualmente pelo parceiro"),
("Enable remote configuration modification", "Habilitar modificações de configuração remotas"),
("Run without install", "Executar sem instalar"),
("Always connected via relay", "Sempre conectado via relay"),
("Connect via relay", ""),
("Always connect via relay", "Sempre conectar via relay"),
("whitelist_tip", "Somente IPs confiáveis podem me acessar"),
("Login", "Login"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Clipboard gol"),
("Stop service", "Oprește serviciu"),
("Change ID", "Schimbă ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("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", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "Server API"),
("invalid_http", "Trebuie să înceapă cu http:// sau https://"),
("Invalid IP", "IP nevalid"),
("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."),
("Invalid format", "Format nevalid"),
("server_not_support", "Încă nu este compatibil cu serverul"),
("Not available", "Indisponibil"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Închis manual de dispozitivul pereche"),
("Enable remote configuration modification", "Activează modificarea configurației de la distanță"),
("Run without install", "Rulează fără instalare"),
("Always connected via relay", "Se conectează mereu prin retransmisie"),
("Connect via relay", ""),
("Always connect via relay", "Se conectează mereu prin retransmisie"),
("whitelist_tip", "Doar adresele IP autorizate pot accesa acest dispozitiv"),
("Login", "Conectare"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Буфер обмена пуст"),
("Stop service", "Остановить службу"),
("Change ID", "Изменить ID"),
("Your new ID", "Новый 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", "Сделано с душой в этом безумном мире!"),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "API-сервер"),
("invalid_http", "Должен начинаться с http:// или https://"),
("Invalid IP", "Неправильный IP-адрес"),
("id_change_tip", "Допускаются только символы a-z, A-Z, 0-9 и _ (подчёркивание). Первой должна быть буква a-z, A-Z. Длина от 6 до 16."),
("Invalid format", "Неправильный формат"),
("server_not_support", "Пока не поддерживается сервером"),
("Not available", "Недоступно"),
@@ -209,8 +213,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Закрыто удалённым узлом вручную"),
("Enable remote configuration modification", "Разрешить удалённое изменение конфигурации"),
("Run without install", "Запустить без установки"),
("Always connected via relay", "Всегда подключается через ретрансляционный сервер"),
("Always connect via relay", "Всегда подключаться через ретрансляционный сервер"),
("Connect via relay", "Подключится через ретранслятор"),
("Always connect via relay", "Всегда подключаться через ретранслятор"),
("whitelist_tip", "Только IP-адреса из белого списка могут получить доступ ко мне"),
("Login", "Войти"),
("Verify", "Проверить"),
@@ -415,7 +419,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("software_render_tip", "Если у вас видеокарта Nvidia и удалённое окно закрывается сразу после подключения, может помочь установка драйвера Nouveau и выбор использования программной визуализации. Потребуется перезапуск."),
("Always use software rendering", "Использовать программную визуализацию"),
("config_input", "Чтобы управлять удалённым рабочим столом с помощью клавиатуры, необходимо предоставить RustDesk разрешения \"Мониторинг ввода\"."),
("config_microphone", ""),
("config_microphone", "Чтобы разговаривать с удалённой стороной, необходимо предоставить RustDesk разрешение \"Запись аудио\"."),
("request_elevation_tip", "Также можно запросить повышение прав, если кто-то есть на удалённой стороне."),
("Wait", "Ждите"),
("Elevation Error", "Ошибка повышения прав"),
@@ -435,19 +439,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Medium", "Средний"),
("Strong", "Стойкий"),
("Switch Sides", "Переключить стороны"),
("Please confirm if you want to share your desktop?", "Подтвердите, что хотите поделиться своим рабочим столом?"),
("Closed as expected", ""),
("Please confirm if you want to share your desktop?", "Подтверждаете, что хотите поделиться своим рабочим столом?"),
("Display", "Отображение"),
("Default View Style", "Стиль отображения по умолчанию"),
("Default Scroll Style", "Стиль прокрутки по умолчанию"),
("Default Image Quality", "Качество изображения по умолчанию"),
("Default Codec", "Кодек по умолчанию"),
("Bitrate", "Битрейт"),
("FPS", "FPS"),
("FPS", "Частота кадров"),
("Auto", "Авто"),
("Other Default Options", "Другие параметры по умолчанию"),
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("Voice call", "Голосовой вызов"),
("Text chat", "Текстовый чат"),
("Stop voice call", "Завершить голосовой вызов"),
("relay_hint_tip", "Прямое подключение может оказаться невозможным. В этом случае можно попытаться подключиться через сервер ретрансляции. \nКроме того, если вы хотите сразу использовать сервер ретрансляции, можно добавить к ID суффикс \"/r\" или включить \"Всегда подключаться через ретранслятор\" в настройках удалённого узла."),
("Reconnect", "Переподключить"),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Schránka je prázdna"),
("Stop service", "Zastaviť službu"),
("Change ID", "Zmeniť ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Povolené sú len znaky a-z, A-Z, 0-9 a _ (podčiarkovník). Prvý znak musí byť a-z, A-Z. Dĺžka musí byť medzi 6 a 16 znakmi."),
("Website", "Webová stránka"),
("About", "O RustDesk"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "API server"),
("invalid_http", "Musí začínať http:// alebo https://"),
("Invalid IP", "Neplatná IP adresa"),
("id_change_tip", "Povolené sú len znaky a-z, A-Z, 0-9 a _ (podčiarkovník). Prvý znak musí byť a-z, A-Z. Dĺžka musí byť medzi 6 a 16 znakmi."),
("Invalid format", "Neplatný formát"),
("server_not_support", "Zatiaľ serverom nepodporované"),
("Not available", "Nie je k dispozícii"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Manuálne ukončené opačnou stranou pripojenia"),
("Enable remote configuration modification", "Povoliť zmeny konfigurácie zo vzdialeného PC"),
("Run without install", "Spustiť bez inštalácie"),
("Always connected via relay", "Vždy pripojené cez prepájací server"),
("Connect via relay", ""),
("Always connect via relay", "Vždy pripájať cez prepájací server"),
("whitelist_tip", "Len vymenované IP adresy majú oprávnenie sa pripojiť k vzdialenej správe"),
("Login", "Prihlásenie"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Odložišče je prazno"),
("Stop service", "Ustavi storitev"),
("Change ID", "Spremeni ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Dovoljeni znaki so a-z, A-Z (brez šumnikov), 0-9 in _. Prvi znak mora biti črka, dolžina od 6 do 16 znakov."),
("Website", "Spletna stran"),
("About", "O programu"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "API strežnik"),
("invalid_http", "mora se začeti s http:// ali https://"),
("Invalid IP", "Neveljaven IP"),
("id_change_tip", "Dovoljeni znaki so a-z, A-Z (brez šumnikov), 0-9 in _. Prvi znak mora biti črka, dolžina od 6 do 16 znakov."),
("Invalid format", "Neveljavna oblika"),
("server_not_support", "Strežnik še ne podpira"),
("Not available", "Ni na voljo"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Povezavo ročno prekinil odjemalec"),
("Enable remote configuration modification", "Omogoči oddaljeno spreminjanje nastavitev"),
("Run without install", "Zaženi brez namestitve"),
("Always connected via relay", "Vedno povezan preko posrednika"),
("Connect via relay", ""),
("Always connect via relay", "Vedno poveži preko posrednika"),
("whitelist_tip", "Dostop je možen samo iz dovoljenih IPjev"),
("Login", "Prijavi"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Clipboard është bosh"),
("Stop service", "Ndaloni shërbimin"),
("Change ID", "Ndryshoni ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Lejohen Vetëm karkteret a-z,A-Z,0-9 dhe _(nënvizimet).Shkronja e parë duhet të jetë a-z, A-Z. Gjatesia midis 6 dhe 16."),
("Website", "Faqe ëebi"),
("About", "Rreth"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "Serveri API"),
("invalid_http", "Duhet të fillojë me http:// ose https://"),
("Invalid IP", "IP e pavlefshme"),
("id_change_tip", "Lejohen Vetëm karkteret a-z,A-Z,0-9 dhe _(nënvizimet).Shkronja e parë duhet të jetë a-z, A-Z. Gjatesia midis 6 dhe 16."),
("Invalid format", "Format i pavlefshëm"),
("server_not_support", "Nuk suportohet akoma nga severi"),
("Not available", "I padisponueshëm"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "E mbyllur manualisht nga peer"),
("Enable remote configuration modification", "Aktivizoni modifikimin e konfigurimit në distancë"),
("Run without install", "Ekzekuto pa instaluar"),
("Always connected via relay", "Gjithmonë i ldihur me transmetues"),
("Connect via relay", ""),
("Always connect via relay", "Gjithmonë lidheni me transmetues"),
("whitelist_tip", "Vetëm IP e listës së bardhë mund të më aksesoj."),
("Login", "Hyrje"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Clipboard je prazan"),
("Stop service", "Stopiraj servis"),
("Change ID", "Promeni ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Dozvoljeni su samo a-z, A-Z, 0-9 i _ (donja crta) znakovi. Prvi znak mora biti slovo a-z, A-Z. Dužina je od 6 do 16."),
("Website", "Web sajt"),
("About", "O programu"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "API server"),
("invalid_http", "mora početi sa http:// ili https://"),
("Invalid IP", "Nevažeća IP"),
("id_change_tip", "Dozvoljeni su samo a-z, A-Z, 0-9 i _ (donja crta) znakovi. Prvi znak mora biti slovo a-z, A-Z. Dužina je od 6 do 16."),
("Invalid format", "Pogrešan format"),
("server_not_support", "Server još uvek ne podržava"),
("Not available", "Nije dostupno"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Klijent ručno raskinuo konekciju"),
("Enable remote configuration modification", "Dozvoli modifikaciju udaljene konfiguracije"),
("Run without install", "Pokreni bez instalacije"),
("Always connected via relay", "Uvek spojne preko posrednika"),
("Connect via relay", ""),
("Always connect via relay", "Uvek se spoj preko posrednika"),
("whitelist_tip", "Samo dozvoljene IP mi mogu pristupiti"),
("Login", "Prijava"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Urklippet är tomt"),
("Stop service", "Avsluta tjänsten"),
("Change ID", "Byt ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Bara a-z, A-Z, 0-9 och _ (understräck) tecken är tillåtna. Den första bokstaven måste vara a-z, A-Z. Längd mellan 6 och 16."),
("Website", "Hemsida"),
("About", "Om"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "API Server"),
("invalid_http", "måste börja med http:// eller https://"),
("Invalid IP", "Ogiltig IP"),
("id_change_tip", "Bara a-z, A-Z, 0-9 och _ (understräck) tecken är tillåtna. Den första bokstaven måste vara a-z, A-Z. Längd mellan 6 och 16."),
("Invalid format", "Ogiltigt format"),
("server_not_support", "Stöds ännu inte av servern"),
("Not available", "Ej tillgänglig"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Stängd manuellt av klienten"),
("Enable remote configuration modification", "Tillåt fjärrkonfigurering"),
("Run without install", "Kör utan installation"),
("Always connected via relay", "Anslut alltid via relay"),
("Connect via relay", ""),
("Always connect via relay", "Anslut alltid via relay"),
("whitelist_tip", "Bara vitlistade IPs kan koppla upp till mig"),
("Login", "Logga in"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", ""),
("Stop service", ""),
("Change ID", ""),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", ""),
("Website", ""),
("About", ""),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", ""),
("invalid_http", ""),
("Invalid IP", ""),
("id_change_tip", ""),
("Invalid format", ""),
("server_not_support", ""),
("Not available", ""),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", ""),
("Enable remote configuration modification", ""),
("Run without install", ""),
("Always connected via relay", ""),
("Connect via relay", ""),
("Always connect via relay", ""),
("whitelist_tip", ""),
("Login", ""),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "คลิปบอร์ดว่างเปล่า"),
("Stop service", "หยุดการใช้งานเซอร์วิส"),
("Change ID", "เปลี่ยน ID"),
("Your new ID", ""),
("length %min% to %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", "ทำด้วยใจ ในโลกใบนี้ที่ยุ่งเหยิง!"),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "เซิร์ฟเวอร์ API"),
("invalid_http", "ต้องขึ้นต้นด้วย http:// หรือ https:// เท่านั้น"),
("Invalid IP", "IP ไม่ถูกต้อง"),
("id_change_tip", "อนุญาตเฉพาะตัวอักษร a-z A-Z 0-9 และ _ (ขีดล่าง) เท่านั้น โดยตัวอักษรขึ้นต้นจะต้องเป็น a-z หรือไม่ก็ A-Z และมีความยาวระหว่าง 6 ถึง 16 ตัวอักษร"),
("Invalid format", "รูปแบบไม่ถูกต้อง"),
("server_not_support", "ยังไม่รองรับโดยเซิร์ฟเวอร์"),
("Not available", "ไม่พร้อมใช้งาน"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "ถูกปิดโดยอีกฝั่งการการเชื่อมต่อ"),
("Enable remote configuration modification", "เปิดการใช้งานการแก้ไขการตั้งค่าปลายทาง"),
("Run without install", "ใช้งานโดยไม่ต้องติดตั้ง"),
("Always connected via relay", "เชื่อมต่อผ่านรีเลย์เสมอ"),
("Connect via relay", ""),
("Always connect via relay", "เชื่อมต่อผ่านรีเลย์เสมอ"),
("whitelist_tip", "อนุญาตเฉพาะการเชื่อมต่อจาก IP ที่ไวท์ลิสต์"),
("Login", "เข้าสู่ระบบ"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Kopyalanan geçici veri boş"),
("Stop service", "Servisi Durdur"),
("Change ID", "ID Değiştir"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Yalnızca a-z, A-Z, 0-9 ve _ (alt çizgi) karakterlerini kullanabilirsiniz. İlk karakter a-z veya A-Z olmalıdır. Uzunluk 6 ile 16 karakter arasında olmalıdır."),
("Website", "Website"),
("About", "Hakkında"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "API Sunucu"),
("invalid_http", "http:// veya https:// ile başlamalıdır"),
("Invalid IP", "Geçersiz IP adresi"),
("id_change_tip", "Yalnızca a-z, A-Z, 0-9 ve _ (alt çizgi) karakterlerini kullanabilirsiniz. İlk karakter a-z veya A-Z olmalıdır. Uzunluk 6 ile 16 karakter arasında olmalıdır."),
("Invalid format", "Hatalı Format"),
("server_not_support", "Henüz sunucu tarafından desteklenmiyor"),
("Not available", "Erişilebilir değil"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Eş tarafından manuel olarak kapatıldı"),
("Enable remote configuration modification", "Uzaktan yapılandırma değişikliğini etkinleştir"),
("Run without install", "Yüklemeden çalıştır"),
("Always connected via relay", "Her zaman röle ile bağlı"),
("Connect via relay", ""),
("Always connect via relay", "Always connect via relay"),
("whitelist_tip", "Bu masaüstüne yalnızca yetkili IP adresleri bağlanabilir"),
("Login", "Giriş yap"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "剪貼簿是空的"),
("Stop service", "停止服務"),
("Change ID", "更改 ID"),
("Your new ID", ""),
("length %min% to %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", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "API 伺服器"),
("invalid_http", "開頭必須為 http:// 或 https://"),
("Invalid IP", "IP 無效"),
("id_change_tip", "僅能使用以下字元a-z、A-Z、0-9、_ (底線)。首字元必須為 a-z 或 A-Z。長度介於 6 到 16 之間。"),
("Invalid format", "格式無效"),
("server_not_support", "服務器暫不支持"),
("Not available", "無法使用"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "由對方手動關閉"),
("Enable remote configuration modification", "啟用遠端更改設定"),
("Run without install", "跳過安裝直接執行"),
("Always connected via relay", "一律透過轉送連線"),
("Connect via relay", ""),
("Always connect via relay", "一律透過轉送連線"),
("whitelist_tip", "只有白名單中的 IP 可以存取"),
("Login", "登入"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", "正常關閉"),
("Display", "顯示"),
("Default View Style", "默認顯示方式"),
("Default Scroll Style", "默認滾動方式"),
@@ -446,8 +449,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("FPS", "幀率"),
("Auto", "自動"),
("Other Default Options", "其它默認選項"),
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("Voice call", "語音通話"),
("Text chat", "文字聊天"),
("Stop voice call", "停止語音聊天"),
("relay_hint_tip", "可能無法直連,可以嘗試中繼連接。 \n另外如果想直接使用中繼連接可以在ID後面添加/r或者在卡片選項裡選擇強制走中繼連接。"),
("Reconnect", "重連"),
("Codec", "編解碼"),
("Resolution", "分辨率"),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Буфер обміну порожній"),
("Stop service", "Зупинити службу"),
("Change ID", "Змінити ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Допускаються тільки символи a-z, A-Z, 0-9 і _ (підкреслення). Перша буква повинна бути a-z, A-Z. Довжина від 6 до 16"),
("Website", "Веб-сайт"),
("About", "Про RustDesk"),
("Slogan_tip", "Створено з душею в цьому хаотичному світі!"),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "API-сервер"),
("invalid_http", "Повинен починатися з http:// або https://"),
("Invalid IP", "Невірна IP-адреса"),
("id_change_tip", "Допускаються тільки символи a-z, A-Z, 0-9 і _ (підкреслення). Перша буква повинна бути a-z, A-Z. Довжина від 6 до 16"),
("Invalid format", "Невірний формат"),
("server_not_support", "Поки не підтримується сервером"),
("Not available", "Недоступно"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Закрито вузлом вручну"),
("Enable remote configuration modification", "Дозволити віддалену зміну конфігурації"),
("Run without install", "Запустити без установки"),
("Always connected via relay", "Завжди підключений через ретрансляційний сервер"),
("Connect via relay", ""),
("Always connect via relay", "Завжди підключатися через ретрансляційний сервер"),
("whitelist_tip", "Тільки IP-адреси з білого списку можуть отримати доступ до мене"),
("Login", "Увійти"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -37,6 +37,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Clipboard is empty", "Khay nhớ tạm trống"),
("Stop service", "Dừng dịch vụ"),
("Change ID", "Thay đổi ID"),
("Your new ID", ""),
("length %min% to %max%", ""),
("starts with a letter", ""),
("allowed characters", ""),
("id_change_tip", "Các kí tự đuợc phép là: từ a-z, A-Z, 0-9 và _ (dấu gạch dưới). Kí tự đầu tiên phải bắt đầu từ a-z, A-Z. Độ dài kí tự từ 6 đến 16"),
("Website", "Trang web"),
("About", "About"),
("Slogan_tip", ""),
@@ -54,7 +59,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("API Server", "Máy chủ API"),
("invalid_http", "phải bắt đầu bằng http:// hoặc https://"),
("Invalid IP", "IP không hợp lệ"),
("id_change_tip", "Các kí tự đuợc phép là: từ a-z, A-Z, 0-9 và _ (dấu gạch dưới). Kí tự đầu tiên phải bắt đầu từ a-z, A-Z. Độ dài kí tự từ 6 đến 16"),
("Invalid format", "Định dạng không hợp lệnh"),
("server_not_support", "Chưa đuợc hỗ trợ bới server"),
("Not available", "Chưa có mặt"),
@@ -209,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Closed manually by the peer", "Đóng thủ công bởi peer"),
("Enable remote configuration modification", "Cho phép thay đổi cấu hình bên từ xa"),
("Run without install", "Chạy mà không cần cài"),
("Always connected via relay", "Luôn đuợc kết nối qua relay"),
("Connect via relay", ""),
("Always connect via relay", "Luôn kết nối qua relay"),
("whitelist_tip", "Chỉ có những IP đựoc cho phép mới có thể truy cập"),
("Login", "Đăng nhập"),
@@ -436,7 +440,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Strong", ""),
("Switch Sides", ""),
("Please confirm if you want to share your desktop?", ""),
("Closed as expected", ""),
("Display", ""),
("Default View Style", ""),
("Default Scroll Style", ""),
@@ -449,5 +452,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", ""),
("Text chat", ""),
("Stop voice call", ""),
].iter().cloned().collect();
("relay_hint_tip", ""),
("Reconnect", ""),
("Codec", ""),
("Resolution", ""),
].iter().cloned().collect();
}

View File

@@ -20,7 +20,7 @@ pub use self::rendezvous_mediator::*;
pub mod common;
#[cfg(not(any(target_os = "ios")))]
pub mod ipc;
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli", feature = "flutter")))]
pub mod ui;
mod version;
pub use version::*;
@@ -56,3 +56,5 @@ pub mod clipboard_file;
#[cfg(all(windows, feature = "with_rc"))]
pub mod rc;
#[cfg(target_os = "windows")]
pub mod win_privacy;

View File

@@ -4,7 +4,7 @@
use librustdesk::*;
#[cfg(any(target_os = "android", target_os = "ios"))]
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
fn main() {
if !common::global_init() {
return;
@@ -16,7 +16,12 @@ fn main() {
common::global_clean();
}
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
#[cfg(not(any(
target_os = "android",
target_os = "ios",
feature = "cli",
feature = "flutter"
)))]
fn main() {
if !common::global_init() {
return;

View File

@@ -1,16 +1,16 @@
use super::{CursorData, ResultType};
use hbb_common::libc::{c_char, c_int, c_long, c_void};
pub use hbb_common::platform::linux::*;
use hbb_common::{allow_err, bail, log};
use libc::{c_char, c_int, c_void};
use hbb_common::{allow_err, anyhow::anyhow, bail, log, message_proto::Resolution};
use std::{
cell::RefCell,
collections::HashMap,
path::PathBuf,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use xrandr_parser::Parser;
type Xdo = *const c_void;
@@ -54,8 +54,8 @@ pub struct xcb_xfixes_get_cursor_image {
pub height: u16,
pub xhot: u16,
pub yhot: u16,
pub cursor_serial: libc::c_long,
pub pixels: *const libc::c_long,
pub cursor_serial: c_long,
pub pixels: *const c_long,
}
pub fn get_cursor_pos() -> Option<(i32, i32)> {
@@ -637,91 +637,60 @@ pub fn get_double_click_time() -> u32 {
settings,
property.as_ptr(),
&mut double_click_time as *mut u32,
0 as *const libc::c_void,
0 as *const c_void,
);
double_click_time
}
}
/// forever: may not work
pub fn system_message(title: &str, msg: &str, forever: bool) -> ResultType<()> {
let cmds: HashMap<&str, Vec<&str>> = HashMap::from([
("notify-send", [title, msg].to_vec()),
(
"zenity",
[
"--info",
"--timeout",
if forever { "0" } else { "3" },
"--title",
title,
"--text",
msg,
]
.to_vec(),
),
("kdialog", ["--title", title, "--msgbox", msg].to_vec()),
(
"xmessage",
[
"-center",
"-timeout",
if forever { "0" } else { "3" },
title,
msg,
]
.to_vec(),
),
]);
for (k, v) in cmds {
if std::process::Command::new(k).args(v).spawn().is_ok() {
return Ok(());
pub fn resolutions(name: &str) -> Vec<Resolution> {
let mut v = vec![];
let mut parser = Parser::new();
if parser.parse().is_ok() {
if let Ok(connector) = parser.get_connector(name) {
if let Ok(resolutions) = &connector.available_resolutions() {
for r in resolutions {
if let Ok(width) = r.horizontal.parse::<i32>() {
if let Ok(height) = r.vertical.parse::<i32>() {
let resolution = Resolution {
width,
height,
..Default::default()
};
if !v.contains(&resolution) {
v.push(resolution);
}
}
}
}
}
}
}
bail!("failed to post system message");
v
}
extern "C" fn breakdown_signal_handler(sig: i32) {
let mut stack = vec![];
backtrace::trace(|frame| {
backtrace::resolve_frame(frame, |symbol| {
if let Some(name) = symbol.name() {
stack.push(name.to_string());
}
});
true // keep going to the next frame
});
let mut info = String::default();
if stack.iter().any(|s| {
s.contains(&"nouveau_pushbuf_kick")
|| s.to_lowercase().contains("nvidia")
|| s.contains("gdk_window_end_draw_frame")
}) {
hbb_common::config::Config::set_option(
"allow-always-software-render".to_string(),
"Y".to_string(),
);
info = "Always use software rendering will be set.".to_string();
log::info!("{}", info);
}
log::error!(
"Got signal {} and exit. stack:\n{}",
sig,
stack.join("\n").to_string()
);
if !info.is_empty() {
system_message(
"RustDesk",
&format!("Got signal {} and exit.{}", sig, info),
true,
)
.ok();
}
std::process::exit(0);
pub fn current_resolution(name: &str) -> ResultType<Resolution> {
let mut parser = Parser::new();
parser.parse().map_err(|e| anyhow!(e))?;
let connector = parser.get_connector(name).map_err(|e| anyhow!(e))?;
let r = connector.current_resolution();
let width = r.horizontal.parse::<i32>()?;
let height = r.vertical.parse::<i32>()?;
Ok(Resolution {
width,
height,
..Default::default()
})
}
pub fn register_breakdown_handler() {
unsafe {
libc::signal(libc::SIGSEGV, breakdown_signal_handler as _);
}
pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<()> {
std::process::Command::new("xrandr")
.args(vec![
"--output",
name,
"--mode",
&format!("{}x{}", width, height),
])
.spawn()?;
Ok(())
}

View File

@@ -40,3 +40,114 @@ extern "C" float BackingScaleFactor() {
if (s) return [s backingScaleFactor];
return 1;
}
// https://github.com/jhford/screenresolution/blob/master/cg_utils.c
// https://github.com/jdoupe/screenres/blob/master/setgetscreen.m
extern "C" bool MacGetModeNum(CGDirectDisplayID display, uint32_t *numModes) {
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL);
if (allModes == NULL) {
return false;
}
*numModes = CFArrayGetCount(allModes);
CFRelease(allModes);
return true;
}
extern "C" bool MacGetModes(CGDirectDisplayID display, uint32_t *widths, uint32_t *heights, uint32_t max, uint32_t *numModes) {
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL);
if (allModes == NULL) {
return false;
}
*numModes = CFArrayGetCount(allModes);
for (int i = 0; i < *numModes && i < max; i++) {
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
widths[i] = (uint32_t)CGDisplayModeGetWidth(mode);
heights[i] = (uint32_t)CGDisplayModeGetHeight(mode);
}
CFRelease(allModes);
return true;
}
extern "C" bool MacGetMode(CGDirectDisplayID display, uint32_t *width, uint32_t *height) {
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display);
if (mode == NULL) {
return false;
}
*width = (uint32_t)CGDisplayModeGetWidth(mode);
*height = (uint32_t)CGDisplayModeGetHeight(mode);
CGDisplayModeRelease(mode);
return true;
}
size_t bitDepth(CGDisplayModeRef mode) {
size_t depth = 0;
CFStringRef pixelEncoding = CGDisplayModeCopyPixelEncoding(mode);
// my numerical representation for kIO16BitFloatPixels and kIO32bitFloatPixels
// are made up and possibly non-sensical
if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(kIO32BitFloatPixels), kCFCompareCaseInsensitive)) {
depth = 96;
} else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(kIO64BitDirectPixels), kCFCompareCaseInsensitive)) {
depth = 64;
} else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(kIO16BitFloatPixels), kCFCompareCaseInsensitive)) {
depth = 48;
} else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive)) {
depth = 32;
} else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(kIO30BitDirectPixels), kCFCompareCaseInsensitive)) {
depth = 30;
} else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive)) {
depth = 16;
} else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive)) {
depth = 8;
}
CFRelease(pixelEncoding);
return depth;
}
bool setDisplayToMode(CGDirectDisplayID display, CGDisplayModeRef mode) {
CGError rc;
CGDisplayConfigRef config;
rc = CGBeginDisplayConfiguration(&config);
if (rc != kCGErrorSuccess) {
return false;
}
rc = CGConfigureDisplayWithDisplayMode(config, display, mode, NULL);
if (rc != kCGErrorSuccess) {
return false;
}
rc = CGCompleteDisplayConfiguration(config, kCGConfigureForSession);
if (rc != kCGErrorSuccess) {
return false;
}
return true;
}
extern "C" bool MacSetMode(CGDirectDisplayID display, uint32_t width, uint32_t height)
{
bool ret = false;
CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(display);
if (currentMode == NULL) {
return ret;
}
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL);
if (allModes == NULL) {
CGDisplayModeRelease(currentMode);
return ret;
}
int numModes = CFArrayGetCount(allModes);
CGDisplayModeRef bestMode = NULL;
for (int i = 0; i < numModes; i++) {
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
if (width == CGDisplayModeGetWidth(mode) &&
height == CGDisplayModeGetHeight(mode) &&
bitDepth(currentMode) == bitDepth(mode) &&
CGDisplayModeGetRefreshRate(currentMode) == CGDisplayModeGetRefreshRate(mode)) {
ret = setDisplayToMode(display, mode);
break;
}
}
CGDisplayModeRelease(currentMode);
CFRelease(allModes);
return ret;
}

View File

@@ -17,7 +17,7 @@ use core_graphics::{
display::{kCGNullWindowID, kCGWindowListOptionOnScreenOnly, CGWindowListCopyWindowInfo},
window::{kCGWindowName, kCGWindowOwnerPID},
};
use hbb_common::{bail, log};
use hbb_common::{allow_err, anyhow::anyhow, bail, log, message_proto::Resolution};
use include_dir::{include_dir, Dir};
use objc::{class, msg_send, sel, sel_impl};
use scrap::{libc::c_void, quartz::ffi::*};
@@ -34,6 +34,16 @@ extern "C" {
static kAXTrustedCheckOptionPrompt: CFStringRef;
fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> BOOL;
fn InputMonitoringAuthStatus(_: BOOL) -> BOOL;
fn MacGetModeNum(display: u32, numModes: *mut u32) -> BOOL;
fn MacGetModes(
display: u32,
widths: *mut u32,
heights: *mut u32,
max: u32,
numModes: *mut u32,
) -> BOOL;
fn MacGetMode(display: u32, width: *mut u32, height: *mut u32) -> BOOL;
fn MacSetMode(display: u32, width: u32, height: u32) -> BOOL;
}
pub fn is_process_trusted(prompt: bool) -> bool {
@@ -171,7 +181,7 @@ pub fn is_installed_daemon(prompt: bool) -> bool {
false
}
pub fn uninstall() -> bool {
pub fn uninstall(show_new_window: bool) -> bool {
// to-do: do together with win/linux about refactory start/stop service
if !is_installed_daemon(false) {
return false;
@@ -206,14 +216,21 @@ pub fn uninstall() -> bool {
.args(&["remove", &format!("{}_server", crate::get_full_name())])
.status()
.ok();
std::process::Command::new("sh")
.arg("-c")
.arg(&format!(
"sleep 0.5; open /Applications/{}.app",
crate::get_app_name(),
))
.spawn()
.ok();
if show_new_window {
std::process::Command::new("sh")
.arg("-c")
.arg(&format!(
"sleep 0.5; open /Applications/{}.app",
crate::get_app_name(),
))
.spawn()
.ok();
} else {
std::process::Command::new("pkill")
.arg(crate::get_app_name())
.status()
.ok();
}
quit_gui();
}
}
@@ -557,8 +574,8 @@ pub fn hide_dock() {
}
}
pub fn check_main_window() {
use sysinfo::{ProcessExt, System, SystemExt};
fn check_main_window() -> bool {
use hbb_common::sysinfo::{ProcessExt, System, SystemExt};
let mut sys = System::new();
sys.refresh_processes();
let app = format!("/Applications/{}.app", crate::get_app_name());
@@ -568,11 +585,83 @@ pub fn check_main_window() {
.unwrap_or_default();
for (_, p) in sys.processes().iter() {
if p.cmd().len() == 1 && p.user_id() == my_uid && p.cmd()[0].contains(&app) {
return;
return true;
}
}
std::process::Command::new("open")
.args(["-n", &app])
.status()
.ok();
false
}
pub fn handle_application_should_open_untitled_file() {
hbb_common::log::debug!("icon clicked on finder");
let x = std::env::args().nth(1).unwrap_or_default();
if x == "--server" || x == "--cm" || x == "--tray" {
if crate::platform::macos::check_main_window() {
allow_err!(crate::ipc::send_url_scheme("rustdesk:".into()));
}
}
}
pub fn resolutions(name: &str) -> Vec<Resolution> {
let mut v = vec![];
if let Ok(display) = name.parse::<u32>() {
let mut num = 0;
unsafe {
if YES == MacGetModeNum(display, &mut num) {
let (mut widths, mut heights) = (vec![0; num as _], vec![0; num as _]);
let mut realNum = 0;
if YES
== MacGetModes(
display,
widths.as_mut_ptr(),
heights.as_mut_ptr(),
num,
&mut realNum,
)
{
if realNum <= num {
for i in 0..realNum {
let resolution = Resolution {
width: widths[i as usize] as _,
height: heights[i as usize] as _,
..Default::default()
};
if !v.contains(&resolution) {
v.push(resolution);
}
}
}
}
}
}
}
v
}
pub fn current_resolution(name: &str) -> ResultType<Resolution> {
let display = name.parse::<u32>().map_err(|e| anyhow!(e))?;
unsafe {
let (mut width, mut height) = (0, 0);
if NO == MacGetMode(display, &mut width, &mut height) {
bail!("MacGetMode failed");
}
Ok(Resolution {
width: width as _,
height: height as _,
..Default::default()
})
}
}
pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<()> {
let display = name.parse::<u32>().map_err(|e| anyhow!(e))?;
unsafe {
if NO == MacSetMode(display, width as _, height as _) {
bail!("MacSetMode failed");
}
}
Ok(())
}

View File

@@ -74,5 +74,13 @@ mod tests {
assert!(!get_cursor_pos().is_none());
}
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[test]
fn test_resolution() {
let name = r"\\.\DISPLAY1";
println!("current:{:?}", current_resolution(name));
println!("change:{:?}", change_resolution(name, 2880, 1800));
println!("resolutions:{:?}", resolutions(name));
}
}

View File

@@ -5,7 +5,9 @@ use crate::license::*;
use hbb_common::{
allow_err, bail,
config::{self, Config},
log, sleep, timeout, tokio,
log,
message_proto::Resolution,
sleep, timeout, tokio,
};
use std::io::prelude::*;
use std::{
@@ -833,8 +835,8 @@ fn get_default_install_path() -> String {
pub fn check_update_broker_process() -> ResultType<()> {
// let (_, path, _, _) = get_install_info();
let process_exe = crate::ui::win_privacy::INJECTED_PROCESS_EXE;
let origin_process_exe = crate::ui::win_privacy::ORIGIN_PROCESS_EXE;
let process_exe = crate::win_privacy::INJECTED_PROCESS_EXE;
let origin_process_exe = crate::win_privacy::ORIGIN_PROCESS_EXE;
let exe_file = std::env::current_exe()?;
if exe_file.parent().is_none() {
@@ -919,8 +921,8 @@ pub fn copy_exe_cmd(src_exe: &str, _exe: &str, path: &str) -> String {
",
main_exe = main_exe,
path = path,
ORIGIN_PROCESS_EXE = crate::ui::win_privacy::ORIGIN_PROCESS_EXE,
broker_exe = crate::ui::win_privacy::INJECTED_PROCESS_EXE,
ORIGIN_PROCESS_EXE = crate::win_privacy::ORIGIN_PROCESS_EXE,
broker_exe = crate::win_privacy::INJECTED_PROCESS_EXE,
);
}
@@ -938,7 +940,7 @@ pub fn update_me() -> ResultType<()> {
{lic}
",
copy_exe = copy_exe_cmd(&src_exe, &exe, &path),
broker_exe = crate::ui::win_privacy::INJECTED_PROCESS_EXE,
broker_exe = crate::win_privacy::INJECTED_PROCESS_EXE,
app_name = crate::get_app_name(),
lic = register_licence(),
cur_pid = get_current_pid(),
@@ -1203,7 +1205,7 @@ fn get_before_uninstall() -> String {
netsh advfirewall firewall delete rule name=\"{app_name} Service\"
",
app_name = app_name,
broker_exe = crate::ui::win_privacy::INJECTED_PROCESS_EXE,
broker_exe = crate::win_privacy::INJECTED_PROCESS_EXE,
ext = ext,
cur_pid = get_current_pid(),
)
@@ -1784,3 +1786,89 @@ pub fn set_path_permission(dir: &PathBuf, permission: &str) -> ResultType<()> {
.spawn()?;
Ok(())
}
pub fn resolutions(name: &str) -> Vec<Resolution> {
unsafe {
let mut dm: DEVMODEW = std::mem::zeroed();
let wname = wide_string(name);
let len = if wname.len() <= dm.dmDeviceName.len() {
wname.len()
} else {
dm.dmDeviceName.len()
};
std::ptr::copy_nonoverlapping(wname.as_ptr(), dm.dmDeviceName.as_mut_ptr(), len);
dm.dmSize = std::mem::size_of::<DEVMODEW>() as _;
let mut v = vec![];
let mut num = 0;
loop {
if EnumDisplaySettingsW(NULL as _, num, &mut dm) == 0 {
break;
}
let r = Resolution {
width: dm.dmPelsWidth as _,
height: dm.dmPelsHeight as _,
..Default::default()
};
if !v.contains(&r) {
v.push(r);
}
num += 1;
}
v
}
}
pub fn current_resolution(name: &str) -> ResultType<Resolution> {
unsafe {
let mut dm: DEVMODEW = std::mem::zeroed();
dm.dmSize = std::mem::size_of::<DEVMODEW>() as _;
let wname = wide_string(name);
if EnumDisplaySettingsW(wname.as_ptr(), ENUM_CURRENT_SETTINGS, &mut dm) == 0 {
bail!(
"failed to get currrent resolution, errno={}",
GetLastError()
);
}
let r = Resolution {
width: dm.dmPelsWidth as _,
height: dm.dmPelsHeight as _,
..Default::default()
};
Ok(r)
}
}
pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<()> {
unsafe {
let mut dm: DEVMODEW = std::mem::zeroed();
if FALSE == EnumDisplaySettingsW(NULL as _, ENUM_CURRENT_SETTINGS, &mut dm) {
bail!("EnumDisplaySettingsW failed, errno={}", GetLastError());
}
let wname = wide_string(name);
let len = if wname.len() <= dm.dmDeviceName.len() {
wname.len()
} else {
dm.dmDeviceName.len()
};
std::ptr::copy_nonoverlapping(wname.as_ptr(), dm.dmDeviceName.as_mut_ptr(), len);
dm.dmSize = std::mem::size_of::<DEVMODEW>() as _;
dm.dmPelsWidth = width as _;
dm.dmPelsHeight = height as _;
dm.dmFields = DM_PELSHEIGHT | DM_PELSWIDTH;
let res = ChangeDisplaySettingsExW(
wname.as_ptr(),
&mut dm,
NULL as _,
CDS_UPDATEREGISTRY | CDS_GLOBAL | CDS_RESET,
NULL,
);
if res != DISP_CHANGE_SUCCESSFUL {
bail!(
"ChangeDisplaySettingsExW failed, res={}, errno={}",
res,
GetLastError()
);
}
Ok(())
}
}

View File

@@ -6,7 +6,10 @@ use crate::common::update_clipboard;
#[cfg(windows)]
use crate::portable_service::client as portable_client;
use crate::{
client::{start_audio_thread, LatencyController, MediaData, MediaSender, new_voice_call_request, new_voice_call_response},
client::{
new_voice_call_request, new_voice_call_response, start_audio_thread, LatencyController,
MediaData, MediaSender,
},
common::{get_default_sound_input, set_sound_input},
video_service,
};
@@ -120,6 +123,7 @@ pub struct Connection {
#[cfg(windows)]
portable: PortableState,
from_switch: bool,
origin_resolution: HashMap<String, Resolution>,
voice_call_request_timestamp: Option<NonZeroI64>,
audio_input_device_before_voice_call: Option<String>,
}
@@ -225,6 +229,7 @@ impl Connection {
#[cfg(windows)]
portable: Default::default(),
from_switch: false,
origin_resolution: Default::default(),
audio_sender: None,
voice_call_request_timestamp: None,
audio_input_device_before_voice_call: None,
@@ -530,6 +535,8 @@ impl Connection {
conn.post_conn_audit(json!({
"action": "close",
}));
#[cfg(not(any(target_os = "android", target_os = "ios")))]
conn.reset_resolution();
ALIVE_CONNS.lock().unwrap().retain(|&c| c != id);
if let Some(s) = conn.server.upgrade() {
s.write().unwrap().remove_connection(&conn.inner);
@@ -672,15 +679,15 @@ impl Connection {
.collect();
if !whitelist.is_empty()
&& whitelist
.iter()
.filter(|x| x == &"0.0.0.0")
.next()
.is_none()
.iter()
.filter(|x| x == &"0.0.0.0")
.next()
.is_none()
&& whitelist
.iter()
.filter(|x| IpCidr::from_str(x).map_or(false, |y| y.contains(addr.ip())))
.next()
.is_none()
.iter()
.filter(|x| IpCidr::from_str(x).map_or(false, |y| y.contains(addr.ip())))
.next()
.is_none()
{
self.send_login_error("Your ip is blocked by the peer")
.await;
@@ -806,7 +813,7 @@ impl Connection {
};
self.post_conn_audit(json!({"peer": self.peer_info, "type": conn_type}));
#[allow(unused_mut)]
let mut username = crate::platform::get_active_username();
let mut username = crate::platform::get_active_username();
let mut res = LoginResponse::new();
let mut pi = PeerInfo {
username: username.clone(),
@@ -833,7 +840,7 @@ impl Connection {
h265,
..Default::default()
})
.into();
.into();
}
if self.port_forward_socket.is_some() {
@@ -877,7 +884,17 @@ impl Connection {
privacy_mode: video_service::is_privacy_mode_supported(),
..Default::default()
})
.into();
#[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))
.unwrap_or(vec![]),
..Default::default()
})
.into();
}
let mut sub_service = false;
if self.file_transfer.is_some() {
@@ -893,10 +910,11 @@ impl Connection {
res.set_error(format!("{}", err));
}
Ok((current, displays)) => {
pi.displays = displays.into();
pi.displays = displays.clone();
pi.current_display = current as _;
res.set_peer_info(pi);
sub_service = true;
*super::video_service::LAST_SYNC_DISPLAYS.write().unwrap() = displays;
}
}
}
@@ -1088,7 +1106,8 @@ impl Connection {
async fn handle_login_request_without_validation(&mut self, lr: &LoginRequest) {
self.lr = lr.clone();
if let Some(o) = lr.option.as_ref() {
self.update_option(o).await;
// It may not be a good practice to update all options here.
self.update_options(o).await;
if let Some(q) = o.video_codec_state.clone().take() {
scrap::codec::Encoder::update_video_encoder(
self.inner.id(),
@@ -1160,7 +1179,7 @@ impl Connection {
"Failed to access remote {}, please make sure if it is open",
addr
))
.await;
.await;
return false;
}
}
@@ -1324,12 +1343,12 @@ impl Connection {
}
}
Some(message::Union::Clipboard(cb)) =>
{
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if self.clipboard {
update_clipboard(cb, None);
}
{
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if self.clipboard {
update_clipboard(cb, None);
}
}
Some(message::Union::Cliprdr(_clip)) => {
if self.file_transfer_enabled() {
#[cfg(windows)]
@@ -1492,7 +1511,7 @@ impl Connection {
self.chat_unanswered = true;
}
Some(misc::Union::Option(o)) => {
self.update_option(&o).await;
self.update_options(&o).await;
}
Some(misc::Union::RefreshVideo(r)) => {
if r {
@@ -1512,15 +1531,15 @@ impl Connection {
}
Some(misc::Union::RestartRemoteDevice(_)) =>
{
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if self.restart {
match system_shutdown::reboot() {
Ok(_) => log::info!("Restart by the peer"),
Err(e) => log::error!("Failed to restart:{}", e),
}
{
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if self.restart {
match system_shutdown::reboot() {
Ok(_) => log::info!("Restart by the peer"),
Err(e) => log::error!("Failed to restart:{}", e),
}
}
}
Some(misc::Union::ElevationRequest(r)) => match r.union {
Some(elevation_request::Union::Direct(_)) => {
#[cfg(windows)]
@@ -1530,8 +1549,8 @@ impl Connection {
err = portable_client::start_portable_service(
portable_client::StartPara::Direct,
)
.err()
.map_or("".to_string(), |e| e.to_string());
.err()
.map_or("".to_string(), |e| e.to_string());
}
self.portable.elevation_requested = err.is_empty();
let mut misc = Misc::new();
@@ -1549,8 +1568,8 @@ impl Connection {
err = portable_client::start_portable_service(
portable_client::StartPara::Logon(_r.username, _r.password),
)
.err()
.map_or("".to_string(), |e| e.to_string());
.err()
.map_or("".to_string(), |e| e.to_string());
}
self.portable.elevation_requested = err.is_empty();
let mut misc = Misc::new();
@@ -1571,7 +1590,11 @@ impl Connection {
// No video frame will be sent here, so we need to disable latency controller, or audio check may fail.
latency_controller.lock().unwrap().set_audio_only(true);
self.audio_sender = Some(start_audio_thread(Some(latency_controller)));
allow_err!(self.audio_sender.as_ref().unwrap().send(MediaData::AudioFormat(format)));
allow_err!(self
.audio_sender
.as_ref()
.unwrap()
.send(MediaData::AudioFormat(format)));
}
}
#[cfg(feature = "flutter")]
@@ -1583,12 +1606,31 @@ impl Connection {
"--switch_uuid",
uuid.to_string().as_ref(),
])
.ok();
self.send_close_reason_no_retry("Closed as expected").await;
.ok();
self.on_close("switch sides", false).await;
return false;
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Some(misc::Union::ChangeResolution(r)) => {
if self.keyboard {
if let Ok(name) = video_service::get_current_display_name() {
if let Ok(current) = crate::platform::current_resolution(&name) {
if let Err(e) = crate::platform::change_resolution(
&name,
r.width as _,
r.height as _,
) {
log::error!("change resolution failed:{:?}", e);
} else {
if !self.origin_resolution.contains_key(&name) {
self.origin_resolution.insert(name, current);
}
}
}
}
}
}
_ => {}
},
Some(message::Union::AudioFrame(frame)) => {
@@ -1596,7 +1638,9 @@ impl Connection {
if let Some(sender) = &self.audio_sender {
allow_err!(sender.send(MediaData::AudioFrame(frame)));
} else {
log::warn!("Processing audio frame without the voice call audio sender.");
log::warn!(
"Processing audio frame without the voice call audio sender."
);
}
}
}
@@ -1646,15 +1690,16 @@ impl Connection {
pub async fn close_voice_call(&mut self) {
// Restore to the prior audio device.
if let Some(sound_input) = std::mem::replace(&mut self.audio_input_device_before_voice_call, None) {
if let Some(sound_input) =
std::mem::replace(&mut self.audio_input_device_before_voice_call, None)
{
set_sound_input(sound_input);
}
// Notify the connection manager that the voice call has been closed.
self.send_to_cm(Data::CloseVoiceCall("".to_owned()));
}
async fn update_option(&mut self, o: &OptionMessage) {
log::info!("Option update: {:?}", o);
async fn update_options_without_auth(&mut self, o: &OptionMessage) {
if let Ok(q) = o.image_quality.enum_value() {
let image_quality;
if let ImageQuality::NotSet = q {
@@ -1679,7 +1724,18 @@ impl Connection {
.unwrap()
.update_user_fps(o.custom_fps as _);
}
if let Some(q) = o.video_codec_state.clone().take() {
scrap::codec::Encoder::update_video_encoder(
self.inner.id(),
scrap::codec::EncoderUpdate::State(q),
);
}
}
async fn update_options_with_auth(&mut self, o: &OptionMessage) {
if !self.authorized {
return;
}
if let Ok(q) = o.lock_after_session_end.enum_value() {
if q != BoolOption::NotSet {
self.lock_after_session_end = q == BoolOption::Yes;
@@ -1806,12 +1862,12 @@ impl Connection {
}
}
}
if let Some(q) = o.video_codec_state.clone().take() {
scrap::codec::Encoder::update_video_encoder(
self.inner.id(),
scrap::codec::EncoderUpdate::State(q),
);
}
}
async fn update_options(&mut self, o: &OptionMessage) {
log::info!("Option update: {:?}", o);
self.update_options_without_auth(o).await;
self.update_options_with_auth(o).await;
}
async fn on_close(&mut self, reason: &str, lock: bool) {
@@ -1821,13 +1877,13 @@ impl Connection {
lock_screen().await;
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let data = if self.chat_unanswered {
let data = if self.chat_unanswered {
ipc::Data::Disconnected
} else {
ipc::Data::Close
};
#[cfg(any(target_os = "android", target_os = "ios"))]
let data = ipc::Data::Close;
let data = ipc::Data::Close;
self.tx_to_cm.send(data).ok();
self.port_forward_socket.take();
}
@@ -1915,6 +1971,20 @@ impl Connection {
}
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn reset_resolution(&self) {
self.origin_resolution
.iter()
.map(|(name, r)| {
if let Err(e) =
crate::platform::change_resolution(&name, r.width as _, r.height as _)
{
log::error!("change resolution failed:{:?}", e);
}
})
.count();
}
}
pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
@@ -2045,7 +2115,7 @@ mod privacy_mode {
pub(super) fn turn_off_privacy(_conn_id: i32) -> Message {
#[cfg(windows)]
{
use crate::ui::win_privacy::*;
use crate::win_privacy::*;
let res = turn_off_privacy(_conn_id, None);
match res {
@@ -2069,7 +2139,7 @@ mod privacy_mode {
pub(super) fn turn_on_privacy(_conn_id: i32) -> ResultType<bool> {
#[cfg(windows)]
{
let plugin_exist = crate::ui::win_privacy::turn_on_privacy(_conn_id)?;
let plugin_exist = crate::win_privacy::turn_on_privacy(_conn_id)?;
Ok(plugin_exist)
}
#[cfg(not(windows))]

View File

@@ -719,7 +719,7 @@ fn reset_input() {
let _lock = VIRTUAL_INPUT_MTX.lock();
VIRTUAL_INPUT = VirtualInput::new(
CGEventSourceStateID::Private,
CGEventTapLocation::AnnotatedSession,
CGEventTapLocation::Session,
)
.ok();
}
@@ -1082,21 +1082,28 @@ fn legacy_keyboard_mode(evt: &KeyEvent) {
}
#[cfg(target_os = "windows")]
fn translate_process_virtual_keycode(vk: u32, down: bool) {
fn translate_process_code(code: u32, down: bool) {
crate::platform::windows::try_change_desktop();
sim_rdev_rawkey_virtual(vk, down);
match code >> 16 {
0 => sim_rdev_rawkey_position(code, down),
vk_code => sim_rdev_rawkey_virtual(vk_code, down),
};
}
fn translate_keyboard_mode(evt: &KeyEvent) {
match evt.union {
Some(key_event::Union::Unicode(_unicode)) => {
#[cfg(target_os = "windows")]
allow_err!(rdev::simulate_unicode(_unicode as _));
match &evt.union {
Some(key_event::Union::Seq(seq)) => {
ENIGO.lock().unwrap().key_sequence(seq);
}
Some(key_event::Union::Chr(..)) =>
{
#[cfg(target_os = "windows")]
translate_process_virtual_keycode(evt.chr(), evt.down)
translate_process_code(evt.chr(), evt.down);
#[cfg(not(target_os = "windows"))]
sim_rdev_rawkey_position(evt.chr(), evt.down);
}
Some(key_event::Union::Unicode(..)) => {
// Do not handle unicode for now.
}
_ => {
log::debug!("Unreachable. Unexpected key event {:?}", &evt);

View File

@@ -2,7 +2,7 @@ use core::slice;
use hbb_common::{
allow_err,
anyhow::anyhow,
bail, log,
bail, libc, log,
message_proto::{KeyEvent, MouseEvent},
protobuf::Message,
tokio::{self, sync::mpsc},

View File

@@ -65,6 +65,7 @@ lazy_static::lazy_static! {
pub static ref VIDEO_QOS: Arc<Mutex<VideoQoS>> = Default::default();
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();
}
fn is_capturer_mag_supported() -> bool {
@@ -207,7 +208,7 @@ fn create_capturer(
if privacy_mode_id > 0 {
#[cfg(windows)]
{
use crate::ui::win_privacy::*;
use crate::win_privacy::*;
match scrap::CapturerMag::new(
display.origin(),
@@ -308,11 +309,11 @@ pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> bool {
fn check_uac_switch(privacy_mode_id: i32, capturer_privacy_mode_id: i32) -> ResultType<()> {
if capturer_privacy_mode_id != 0 {
if privacy_mode_id != capturer_privacy_mode_id {
if !crate::ui::win_privacy::is_process_consent_running()? {
if !crate::win_privacy::is_process_consent_running()? {
bail!("consent.exe is running");
}
}
if crate::ui::win_privacy::is_process_consent_running()? {
if crate::win_privacy::is_process_consent_running()? {
bail!("consent.exe is running");
}
}
@@ -355,7 +356,7 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
let (ndisplay, current, display) = get_current_display()?;
let (origin, width, height) = (display.origin(), display.width(), display.height());
log::debug!(
"#displays={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}",
"#displays={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}, name:{}",
ndisplay,
current,
&origin,
@@ -363,6 +364,7 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
height,
num_cpus::get_physical(),
num_cpus::get(),
display.name(),
);
let privacy_mode_id = *PRIVACY_MODE_CONN_ID.lock().unwrap();
@@ -372,7 +374,7 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
let mut capturer_privacy_mode_id = privacy_mode_id;
#[cfg(windows)]
if capturer_privacy_mode_id != 0 {
if crate::ui::win_privacy::is_process_consent_running()? {
if crate::win_privacy::is_process_consent_running()? {
capturer_privacy_mode_id = 0;
}
}
@@ -407,6 +409,43 @@ 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() {
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_displays_changed() -> Option<Message> {
let displays = check_displays_new()?;
let (current, displays) = get_displays_2(&displays);
let mut pi = PeerInfo {
conn_id: crate::SYNC_PEER_INFO_DISPLAYS,
..Default::default()
};
pi.displays = displays.clone();
pi.current_display = current as _;
let mut msg_out = Message::new();
msg_out.set_peer_info(pi);
*LAST_SYNC_DISPLAYS.write().unwrap() = displays;
Some(msg_out)
}
fn run(sp: GenericService) -> ResultType<()> {
#[cfg(windows)]
ensure_close_virtual_device()?;
@@ -463,6 +502,14 @@ fn run(sp: GenericService) -> ResultType<()> {
width: c.width as _,
height: c.height as _,
cursor_embedded: capture_cursor_embedded(),
#[cfg(not(any(target_os = "android", target_os = "ios")))]
resolutions: Some(SupportedResolutions {
resolutions: get_current_display_name()
.map(|name| crate::platform::resolutions(&name))
.unwrap_or(vec![]),
..SupportedResolutions::default()
})
.into(),
..Default::default()
});
let mut msg_out = Message::new();
@@ -529,6 +576,11 @@ fn run(sp: GenericService) -> ResultType<()> {
let now = time::Instant::now();
if last_check_displays.elapsed().as_millis() > 1000 {
last_check_displays = now;
if let Some(msg_out) = check_displays_changed() {
sp.send(msg_out);
}
if c.ndisplay != get_display_num() {
log::info!("Displays changed");
*SWITCH.lock().unwrap() = true;
@@ -798,11 +850,7 @@ fn get_display_num() -> usize {
}
}
if let Ok(d) = try_get_displays() {
d.len()
} else {
0
}
LAST_SYNC_DISPLAYS.read().unwrap().len()
}
pub(super) fn get_displays_2(all: &Vec<Display>) -> (usize, Vec<DisplayInfo>) {
@@ -861,6 +909,7 @@ pub async fn switch_display(i: i32) {
}
}
#[inline]
pub fn refresh() {
#[cfg(target_os = "android")]
Display::refresh_size();
@@ -888,10 +937,12 @@ fn get_primary() -> usize {
0
}
#[inline]
pub async fn switch_to_primary() {
switch_display(get_primary() as _).await;
}
#[inline]
#[cfg(not(windows))]
fn try_get_displays() -> ResultType<Vec<Display>> {
Ok(Display::all()?)
@@ -950,6 +1001,10 @@ pub fn get_current_display() -> ResultType<(usize, usize, Display)> {
get_current_display_2(try_get_displays()?)
}
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();
@@ -957,7 +1012,7 @@ fn start_uac_elevation_check() {
if !crate::platform::is_installed() && !crate::platform::is_root() {
std::thread::spawn(|| loop {
std::thread::sleep(std::time::Duration::from_secs(1));
if let Ok(uac) = crate::ui::win_privacy::is_process_consent_running() {
if let Ok(uac) = crate::win_privacy::is_process_consent_running() {
*IS_UAC_RUNNING.lock().unwrap() = uac;
}
if !crate::platform::is_elevated(None).unwrap_or(false) {

View File

@@ -1,11 +1,5 @@
#[cfg(any(target_os = "linux", target_os = "windows"))]
#[cfg(target_os = "windows")]
use super::ui_interface::get_option_opt;
#[cfg(target_os = "linux")]
use hbb_common::log::{debug, error, info};
#[cfg(target_os = "linux")]
use libappindicator::AppIndicator;
#[cfg(target_os = "linux")]
use std::env::temp_dir;
#[cfg(target_os = "windows")]
use std::sync::{Arc, Mutex};
#[cfg(target_os = "windows")]
@@ -83,119 +77,10 @@ pub fn start_tray() {
});
}
/// Start a tray icon in Linux
///
/// [Block]
/// This function will block current execution, show the tray icon and handle events.
#[cfg(target_os = "linux")]
pub fn start_tray() {
use std::time::Duration;
use glib::{clone, Continue};
use gtk::traits::{GtkMenuItemExt, MenuShellExt, WidgetExt};
info!("configuring tray");
// init gtk context
if let Err(err) = gtk::init() {
error!("Error when starting the tray: {}", err);
return;
}
if let Some(mut appindicator) = get_default_app_indicator() {
let mut menu = gtk::Menu::new();
let stoped = is_service_stopped();
// start/stop service
let label = if stoped {
crate::client::translate("Start Service".to_owned())
} else {
crate::client::translate("Stop service".to_owned())
};
let menu_item_service = gtk::MenuItem::with_label(label.as_str());
menu_item_service.connect_activate(move |_| {
let _lock = crate::ui_interface::SENDER.lock().unwrap();
change_service_state();
});
menu.append(&menu_item_service);
// show tray item
menu.show_all();
appindicator.set_menu(&mut menu);
// start event loop
info!("Setting tray event loop");
// check the connection status for every second
glib::timeout_add_local(
Duration::from_secs(1),
clone!(@strong menu_item_service as item => move || {
let _lock = crate::ui_interface::SENDER.lock().unwrap();
update_tray_service_item(&item);
// continue to trigger the next status check
Continue(true)
}),
);
gtk::main();
} else {
error!("Tray process exit now");
}
}
#[cfg(target_os = "linux")]
fn change_service_state() {
if is_service_stopped() {
debug!("Now try to start service");
crate::ipc::set_option("stop-service", "");
} else {
debug!("Now try to stop service");
crate::ipc::set_option("stop-service", "Y");
}
}
#[cfg(target_os = "linux")]
#[inline]
fn update_tray_service_item(item: &gtk::MenuItem) {
use gtk::traits::GtkMenuItemExt;
if is_service_stopped() {
item.set_label(&crate::client::translate("Start Service".to_owned()));
} else {
item.set_label(&crate::client::translate("Stop service".to_owned()));
}
}
#[cfg(target_os = "linux")]
fn get_default_app_indicator() -> Option<AppIndicator> {
use libappindicator::AppIndicatorStatus;
use std::io::Write;
let icon = include_bytes!("../res/icon.png");
// appindicator does not support icon buffer, so we write it to tmp folder
let mut icon_path = temp_dir();
icon_path.push("RustDesk");
icon_path.push("rustdesk.png");
match std::fs::File::create(icon_path.clone()) {
Ok(mut f) => {
f.write_all(icon).unwrap();
// set .png icon file to be writable
// this ensures successful file rewrite when switching between x11 and wayland.
let mut perm = f.metadata().unwrap().permissions();
if perm.readonly() {
perm.set_readonly(false);
f.set_permissions(perm).unwrap();
}
}
Err(err) => {
error!("Error when writing icon to {:?}: {}", icon_path, err);
return None;
}
}
debug!("write temp icon complete");
let mut appindicator = AppIndicator::new("RustDesk", icon_path.to_str().unwrap_or("rustdesk"));
appindicator.set_label("RustDesk", "A remote control software.");
appindicator.set_status(AppIndicatorStatus::Active);
Some(appindicator)
}
/// Check if service is stoped.
/// Return [`true`] if service is stoped, [`false`] otherwise.
#[inline]
#[cfg(any(target_os = "linux", target_os = "windows"))]
#[cfg(target_os = "windows")]
fn is_service_stopped() -> bool {
if let Some(v) = get_option_opt("stop-service") {
v == "Y"
@@ -204,47 +89,86 @@ fn is_service_stopped() -> bool {
}
}
#[cfg(target_os = "macos")]
pub fn make_tray() {
extern "C" {
fn BackingScaleFactor() -> f32;
}
let f = unsafe { BackingScaleFactor() };
use tray_item::TrayItem;
let mode = dark_light::detect();
let icon_path = match mode {
dark_light::Mode::Dark => {
// still show big overflow icon in my test, so still use x1 png.
// let's do it with objc with svg support later.
// or use another tray crate, or find out in tauri (it has tray support)
if f > 2. {
"mac-tray-light-x2.png"
} else {
"mac-tray-light.png"
}
}
dark_light::Mode::Light => {
if f > 2. {
"mac-tray-dark-x2.png"
} else {
"mac-tray-dark.png"
}
}
};
if let Ok(mut tray) = TrayItem::new(&crate::get_app_name(), icon_path) {
tray.add_label(&format!(
"{} {}",
crate::get_app_name(),
crate::lang::translate("Service is running".to_owned())
))
.ok();
/// Start a tray icon in Linux
///
/// [Block]
/// This function will block current execution, show the tray icon and handle events.
#[cfg(target_os = "linux")]
pub fn start_tray() {}
let inner = tray.inner_mut();
inner.add_quit_item(&crate::lang::translate("Quit".to_owned()));
inner.display();
} else {
loop {
std::thread::sleep(std::time::Duration::from_secs(3));
}
}
#[cfg(target_os = "macos")]
pub fn start_tray() {
use hbb_common::{allow_err, log};
allow_err!(make_tray());
}
#[cfg(target_os = "macos")]
pub fn make_tray() -> hbb_common::ResultType<()> {
// https://github.com/tauri-apps/tray-icon/blob/dev/examples/tao.rs
use hbb_common::anyhow::Context;
use tao::event_loop::{ControlFlow, EventLoopBuilder};
use tray_icon::{
menu::{Menu, MenuEvent, MenuItem},
ClickEvent, TrayEvent, TrayIconBuilder,
};
let mode = dark_light::detect();
const LIGHT: &[u8] = include_bytes!("../res/mac-tray-light-x2.png");
const DARK: &[u8] = include_bytes!("../res/mac-tray-dark-x2.png");
let icon = match mode {
dark_light::Mode::Dark => LIGHT,
_ => DARK,
};
let (icon_rgba, icon_width, icon_height) = {
let image = image::load_from_memory(icon)
.context("Failed to open icon path")?
.into_rgba8();
let (width, height) = image.dimensions();
let rgba = image.into_raw();
(rgba, width, height)
};
let icon = tray_icon::icon::Icon::from_rgba(icon_rgba, icon_width, icon_height)
.context("Failed to open icon")?;
let event_loop = EventLoopBuilder::new().build();
let tray_menu = Menu::new();
let quit_i = MenuItem::new(crate::client::translate("Exit".to_owned()), true, None);
tray_menu.append_items(&[&quit_i]);
let _tray_icon = Some(
TrayIconBuilder::new()
.with_menu(Box::new(tray_menu))
.with_tooltip(format!(
"{} {}",
crate::get_app_name(),
crate::lang::translate("Service is running".to_owned())
))
.with_icon(icon)
.build()?,
);
let menu_channel = MenuEvent::receiver();
let tray_channel = TrayEvent::receiver();
let mut docker_hiden = false;
event_loop.run(move |_event, _, control_flow| {
if !docker_hiden {
crate::platform::macos::hide_dock();
docker_hiden = true;
}
*control_flow = ControlFlow::Wait;
if let Ok(event) = menu_channel.try_recv() {
if event.id == quit_i.id() {
crate::platform::macos::uninstall(false);
}
println!("{event:?}");
}
if let Ok(event) = tray_channel.try_recv() {
if event.event == ClickEvent::Double {
crate::platform::macos::handle_application_should_open_untitled_file();
}
}
});
}

122
src/ui.rs

File diff suppressed because one or more lines are too long

View File

@@ -100,7 +100,7 @@ impl SciterConnectionManager {
}
fn get_icon(&mut self) -> String {
crate::get_icon()
super::get_icon()
}
fn check_click_time(&mut self, id: i32) {

View File

@@ -435,9 +435,6 @@ function toggleMenuState() {
var c = handler.get_option("codec-preference");
if (!c) c = "auto";
values.push(c);
var a = handler.get_audio_mode();
if (!a) a = "guest-to-host";
values.push(a);
for (var el in $$(menu#display-options li)) {
el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0);
}
@@ -484,6 +481,14 @@ handler.updatePi = function(v) {
}
}
handler.updateDisplays = function(v) {
pi.displays = v;
header.update();
if (is_port_forward) {
view.windowState = View.WINDOW_MINIMIZED;
}
}
function updatePrivacyMode() {
var el = $(li#privacy-mode);
if (el) {

View File

@@ -6,15 +6,15 @@ use cocoa::{
base::{id, nil, YES},
foundation::{NSAutoreleasePool, NSString},
};
use objc::runtime::Class;
use objc::{
class,
declare::ClassDecl,
msg_send,
runtime::{BOOL, Object, Sel},
runtime::{Object, Sel, BOOL},
sel, sel_impl,
};
use objc::runtime::Class;
use sciter::{Host, make_args};
use sciter::{make_args, Host};
use hbb_common::log;
@@ -102,7 +102,10 @@ unsafe fn set_delegate(handler: Option<Box<dyn AppHandler>>) {
sel!(handleMenuItem:),
handle_menu_item as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(sel!(handleEvent:withReplyEvent:), handle_apple_event as extern fn(&Object, Sel, u64, u64));
decl.add_method(
sel!(handleEvent:withReplyEvent:),
handle_apple_event as extern "C" fn(&Object, Sel, u64, u64),
);
let decl = decl.register();
let delegate: id = msg_send![decl, alloc];
let () = msg_send![delegate, init];
@@ -138,10 +141,7 @@ extern "C" fn application_should_handle_open_untitled_file(
if !LAUNCHED {
return YES;
}
log::debug!("icon clicked on finder");
if std::env::args().nth(1) == Some("--server".to_owned()) {
crate::platform::macos::check_main_window();
}
crate::platform::macos::handle_application_should_open_untitled_file();
let inner: *mut c_void = *this.get_ivar(APP_HANDLER_IVAR);
let inner = &mut *(inner as *mut DelegateState);
(*inner).command(AWAKE);
@@ -180,22 +180,11 @@ extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
}
}
/// The function to handle the url scheme sent by the system.
///
/// 1. Try to send the url scheme from ipc.
/// 2. If failed to send the url scheme, we open a new main window to handle this url scheme.
pub fn handle_url_scheme(url: String) {
if let Err(err) = crate::ipc::send_url_scheme(url.clone()) {
log::debug!("Send the url to the existing flutter process failed, {}. Let's open a new program to handle this.", err);
let _ = crate::run_me(vec![url]);
}
}
extern fn handle_apple_event(_this: &Object, _cmd: Sel, event: u64, _reply: u64) {
extern "C" fn handle_apple_event(_this: &Object, _cmd: Sel, event: u64, _reply: u64) {
let event = event as *mut Object;
let url = fruitbasket::parse_url_event(event);
log::debug!("an event was received: {}", url);
std::thread::spawn(move || handle_url_scheme(url));
std::thread::spawn(move || crate::handle_url_scheme(url));
}
unsafe fn make_menu_item(title: &str, key: &str, tag: u32) -> *mut Object {
@@ -258,10 +247,3 @@ pub fn show_dock() {
NSApp().setActivationPolicy_(NSApplicationActivationPolicyRegular);
}
}
pub fn make_tray() {
unsafe {
set_delegate(None);
}
crate::tray::make_tray();
}

View File

@@ -1,17 +1,17 @@
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
sync::{Arc, Mutex},
sync::{Arc, Mutex, RwLock},
};
use sciter::{
dom::{
Element,
event::{BEHAVIOR_EVENTS, EVENT_GROUPS, EventReason, PHASE_MASK}, HELEMENT,
event::{EventReason, BEHAVIOR_EVENTS, EVENT_GROUPS, PHASE_MASK},
Element, HELEMENT,
},
make_args,
video::{video_destination, AssetPtr, COLOR_SPACE},
Value,
video::{AssetPtr, COLOR_SPACE, video_destination},
};
use hbb_common::{
@@ -53,6 +53,20 @@ impl SciterHandler {
allow_err!(e.call_method(func, &super::value_crash_workaround(args)[..]));
}
}
fn make_displays_array(displays: &Vec<DisplayInfo>) -> Value {
let mut displays_value = Value::array(0);
for d in displays.iter() {
let mut display = Value::map();
display.set_item("x", d.x);
display.set_item("y", d.y);
display.set_item("width", d.width);
display.set_item("height", d.height);
display.set_item("cursor_embedded", d.cursor_embedded);
displays_value.push(display);
}
displays_value
}
}
impl InvokeUiSession for SciterHandler {
@@ -201,7 +215,7 @@ impl InvokeUiSession for SciterHandler {
self.call("adaptSize", &make_args!());
}
fn on_rgba(&self, data: &[u8]) {
fn on_rgba(&self, data: &mut Vec<u8>) {
VIDEO
.lock()
.unwrap()
@@ -215,22 +229,18 @@ impl InvokeUiSession for SciterHandler {
pi_sciter.set_item("hostname", pi.hostname.clone());
pi_sciter.set_item("platform", pi.platform.clone());
pi_sciter.set_item("sas_enabled", pi.sas_enabled);
let mut displays = Value::array(0);
for ref d in pi.displays.iter() {
let mut display = Value::map();
display.set_item("x", d.x);
display.set_item("y", d.y);
display.set_item("width", d.width);
display.set_item("height", d.height);
display.set_item("cursor_embedded", d.cursor_embedded);
displays.push(display);
}
pi_sciter.set_item("displays", displays);
pi_sciter.set_item("displays", Self::make_displays_array(&pi.displays));
pi_sciter.set_item("current_display", pi.current_display);
self.call("updatePi", &make_args!(pi_sciter));
}
fn set_displays(&self, displays: &Vec<DisplayInfo>) {
self.call(
"updateDisplays",
&make_args!(Self::make_displays_array(displays)),
);
}
fn on_connected(&self, conn_type: ConnType) {
match conn_type {
ConnType::RDP => {}
@@ -282,6 +292,13 @@ impl InvokeUiSession for SciterHandler {
fn on_voice_call_incoming(&self) {
self.call("onVoiceCallIncoming", &make_args!());
}
/// RGBA is directly rendered by [on_rgba]. No need to store the rgba for the sciter ui.
fn get_rgba(&self) -> *const u8 {
std::ptr::null()
}
fn next_rgba(&self) {}
}
pub struct SciterSession(Session<SciterHandler>);
@@ -339,7 +356,7 @@ impl sciter::EventHandler for SciterSession {
let site = AssetPtr::adopt(ptr as *mut video_destination);
log::debug!("[video] start video");
*VIDEO.lock().unwrap() = Some(site);
self.reconnect();
self.reconnect(false);
}
}
BEHAVIOR_EVENTS::VIDEO_INITIALIZED => {
@@ -388,7 +405,7 @@ impl sciter::EventHandler for SciterSession {
fn transfer_file();
fn tunnel();
fn lock_screen();
fn reconnect();
fn reconnect(bool);
fn get_chatbox();
fn get_icon();
fn get_home_dir();
@@ -447,6 +464,9 @@ impl SciterSession {
id: id.clone(),
password: password.clone(),
args,
server_keyboard_enabled: Arc::new(RwLock::new(true)),
server_file_transfer_enabled: Arc::new(RwLock::new(true)),
server_clipboard_enabled: Arc::new(RwLock::new(true)),
..Default::default()
};
@@ -460,7 +480,11 @@ impl SciterSession {
ConnType::DEFAULT_CONN
};
session.lc.write().unwrap().initialize(id, conn_type, None);
session
.lc
.write()
.unwrap()
.initialize(id, conn_type, None, false);
Self(session)
}
@@ -486,7 +510,7 @@ impl SciterSession {
}
pub fn get_icon(&self) -> String {
crate::get_icon()
super::get_icon()
}
fn supported_hwcodec(&self) -> Value {

View File

@@ -494,7 +494,7 @@ pub async fn start_ipc<T: InvokeUiCM>(cm: ConnectionManager<T>) {
e
);
}
allow_err!(crate::ui::win_privacy::start());
allow_err!(crate::win_privacy::start());
});
match ipc::new_listener("_cm").await {

View File

@@ -2,7 +2,6 @@ use std::{
collections::HashMap,
process::Child,
sync::{Arc, Mutex},
time::SystemTime,
};
#[cfg(any(target_os = "android", target_os = "ios"))]
@@ -31,7 +30,6 @@ pub type Children = Arc<Mutex<(bool, HashMap<(String, String), Child>)>>;
type Status = (i32, bool, i64, String); // (status_num, key_confirmed, mouse_time, id)
lazy_static::lazy_static! {
static ref CHILDREN : Children = Default::default();
static ref UI_STATUS : Arc<Mutex<Status>> = Arc::new(Mutex::new((0, false, 0, "".to_owned())));
static ref OPTIONS : Arc<Mutex<HashMap<String, String>>> = Arc::new(Mutex::new(Config::get_options()));
static ref ASYNC_JOB_STATUS : Arc<Mutex<String>> = Default::default();
@@ -44,17 +42,6 @@ lazy_static::lazy_static! {
pub static ref SENDER : Mutex<mpsc::UnboundedSender<ipc::Data>> = Mutex::new(check_connect_status(true));
}
#[inline]
pub fn recent_sessions_updated() -> bool {
let mut children = CHILDREN.lock().unwrap();
if children.0 {
children.0 = false;
true
} else {
false
}
}
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[inline]
pub fn get_id() -> String {
@@ -64,16 +51,6 @@ pub fn get_id() -> String {
return ipc::get_id();
}
#[inline]
pub fn get_remote_id() -> String {
LocalConfig::get_remote_id()
}
#[inline]
pub fn set_remote_id(id: String) {
LocalConfig::set_remote_id(&id);
}
#[inline]
pub fn goto_install() {
allow_err!(crate::run_me(vec!["--install"]));
@@ -151,7 +128,7 @@ pub fn get_license() -> String {
}
#[inline]
#[cfg(any(target_os = "linux", target_os = "windows"))]
#[cfg(target_os = "windows")]
pub fn get_option_opt(key: &str) -> Option<String> {
OPTIONS.lock().unwrap().get(key).map(|x| x.clone())
}
@@ -318,7 +295,7 @@ pub fn set_option(key: String, value: String) {
#[cfg(target_os = "macos")]
if &key == "stop-service" {
let is_stop = value == "Y";
if is_stop && crate::platform::macos::uninstall() {
if is_stop && crate::platform::macos::uninstall(true) {
return;
}
}
@@ -419,24 +396,6 @@ pub fn is_installed_lower_version() -> bool {
}
}
#[inline]
pub fn closing(x: i32, y: i32, w: i32, h: i32) {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
crate::server::input_service::fix_key_down_timeout_at_exit();
LocalConfig::set_size(x, y, w, h);
}
#[inline]
pub fn get_size() -> Vec<i32> {
let s = LocalConfig::get_size();
let mut v = Vec::new();
v.push(s.0);
v.push(s.1);
v.push(s.2);
v.push(s.3);
v
}
#[inline]
pub fn get_mouse_time() -> f64 {
let ui_status = UI_STATUS.lock().unwrap();
@@ -507,51 +466,6 @@ pub fn store_fav(fav: Vec<String>) {
LocalConfig::set_fav(fav);
}
#[inline]
pub fn get_recent_sessions() -> Vec<(String, SystemTime, PeerConfig)> {
PeerConfig::peers()
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
pub fn get_icon() -> String {
crate::get_icon()
}
#[inline]
pub fn remove_peer(id: String) {
PeerConfig::remove(&id);
}
#[inline]
pub fn new_remote(id: String, remote_type: String) {
let mut lock = CHILDREN.lock().unwrap();
let args = vec![format!("--{}", remote_type), id.clone()];
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 is_process_trusted(_prompt: bool) -> bool {
#[cfg(target_os = "macos")]
@@ -597,9 +511,9 @@ pub fn get_error() -> String {
if dtype != "x11" {
return format!(
"{} {}, {}",
t("Unsupported display server ".to_owned()),
crate::client::translate("Unsupported display server ".to_owned()),
dtype,
t("x11 expected".to_owned()),
crate::client::translate("x11 expected".to_owned()),
);
}
}
@@ -622,11 +536,6 @@ pub fn current_is_wayland() -> bool {
return false;
}
#[inline]
pub fn get_software_update_url() -> String {
SOFTWARE_UPDATE_URL.lock().unwrap().clone()
}
#[inline]
pub fn get_new_version() -> String {
hbb_common::get_version_from_url(&*SOFTWARE_UPDATE_URL.lock().unwrap())
@@ -643,36 +552,9 @@ pub fn get_app_name() -> String {
crate::get_app_name()
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn get_software_ext() -> String {
#[cfg(windows)]
let p = "exe";
#[cfg(target_os = "macos")]
let p = "dmg";
#[cfg(target_os = "linux")]
let p = "deb";
p.to_owned()
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn get_software_store_path() -> String {
let mut p = std::env::temp_dir();
let name = SOFTWARE_UPDATE_URL
.lock()
.unwrap()
.split("/")
.last()
.map(|x| x.to_owned())
.unwrap_or(crate::get_app_name());
p.push(name);
format!("{}.{}", p.to_string_lossy(), get_software_ext())
}
#[cfg(windows)]
#[inline]
pub fn create_shortcut(_id: String) {
#[cfg(windows)]
crate::platform::windows::create_shortcut(&_id).ok();
}
@@ -719,22 +601,6 @@ pub fn get_uuid() -> String {
base64::encode(hbb_common::get_uuid())
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
pub fn open_url(url: String) {
#[cfg(windows)]
let p = "explorer";
#[cfg(target_os = "macos")]
let p = "open";
#[cfg(target_os = "linux")]
let p = if std::path::Path::new("/usr/bin/firefox").exists() {
"firefox"
} else {
"xdg-open"
};
allow_err!(std::process::Command::new(p).arg(url).spawn());
}
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[inline]
pub fn change_id(id: String) {
@@ -756,23 +622,11 @@ pub fn post_request(url: String, body: String, header: String) {
});
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn is_ok_change_id() -> bool {
machine_uid::get().is_ok()
}
#[inline]
pub fn get_async_job_status() -> String {
ASYNC_JOB_STATUS.lock().unwrap().clone()
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
pub fn t(name: String) -> String {
crate::client::translate(name)
}
#[inline]
pub fn get_langs() -> String {
crate::lang::LANGS.to_string()
@@ -813,11 +667,6 @@ pub fn default_video_save_directory() -> String {
"".to_owned()
}
#[inline]
pub fn is_xfce() -> bool {
crate::platform::is_xfce()
}
#[inline]
pub fn get_api_server() -> String {
crate::get_api_server(
@@ -834,14 +683,6 @@ pub fn has_hwcodec() -> bool {
return true;
}
#[inline]
pub fn is_release() -> bool {
#[cfg(not(debug_assertions))]
return true;
#[cfg(debug_assertions)]
return false;
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[inline]
pub fn is_root() -> bool {

View File

@@ -1,29 +1,33 @@
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use std::sync::{Arc, Mutex, RwLock};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc, Mutex, RwLock,
};
use std::time::{Duration, SystemTime};
use async_trait::async_trait;
use bytes::Bytes;
use rdev::{Event, EventType::*};
use uuid::Uuid;
use hbb_common::{allow_err, message_proto::*};
use hbb_common::{fs, get_version_number, log, Stream};
use hbb_common::config::{Config, LocalConfig, PeerConfig, RS_PUB_KEY};
use hbb_common::rendezvous_proto::ConnType;
use hbb_common::tokio::{self, sync::mpsc};
use hbb_common::{allow_err, message_proto::*};
use hbb_common::{fs, get_version_number, log, Stream};
use crate::{client::Data, client::Interface};
use crate::client::{
check_if_retry, FileManager, handle_hash, handle_login_error, handle_login_from_ui,
handle_test_delay, input_os_password, Key, KEY_MAP, load_config, LoginConfigHandler,
QualityStatus, send_mouse, start_video_audio_threads,
};
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,
};
use crate::common::{self, GrabState};
use crate::keyboard;
use crate::{client::Data, client::Interface};
pub static IS_IN: AtomicBool = AtomicBool::new(false);
@@ -36,9 +40,37 @@ pub struct Session<T: InvokeUiSession> {
pub sender: Arc<RwLock<Option<mpsc::UnboundedSender<Data>>>>,
pub thread: Arc<Mutex<Option<std::thread::JoinHandle<()>>>>,
pub ui_handler: T,
pub server_keyboard_enabled: Arc<RwLock<bool>>,
pub server_file_transfer_enabled: Arc<RwLock<bool>>,
pub server_clipboard_enabled: Arc<RwLock<bool>>,
}
#[derive(Clone)]
pub struct SessionPermissionConfig {
pub lc: Arc<RwLock<LoginConfigHandler>>,
pub server_keyboard_enabled: Arc<RwLock<bool>>,
pub server_file_transfer_enabled: Arc<RwLock<bool>>,
pub server_clipboard_enabled: Arc<RwLock<bool>>,
}
impl SessionPermissionConfig {
pub fn is_text_clipboard_required(&self) -> bool {
*self.server_clipboard_enabled.read().unwrap()
&& *self.server_keyboard_enabled.read().unwrap()
&& !self.lc.read().unwrap().disable_clipboard.v
}
}
impl<T: InvokeUiSession> Session<T> {
pub fn get_permission_config(&self) -> SessionPermissionConfig {
SessionPermissionConfig {
lc: self.lc.clone(),
server_keyboard_enabled: self.server_keyboard_enabled.clone(),
server_file_transfer_enabled: self.server_file_transfer_enabled.clone(),
server_clipboard_enabled: self.server_clipboard_enabled.clone(),
}
}
pub fn is_file_transfer(&self) -> bool {
self.lc
.read()
@@ -127,6 +159,12 @@ impl<T: InvokeUiSession> Session<T> {
self.lc.read().unwrap().is_privacy_mode_supported()
}
pub fn is_text_clipboard_required(&self) -> bool {
*self.server_clipboard_enabled.read().unwrap()
&& *self.server_keyboard_enabled.read().unwrap()
&& !self.lc.read().unwrap().disable_clipboard.v
}
pub fn refresh_video(&self) {
self.send(Data::Message(LoginConfigHandler::refresh()));
}
@@ -521,7 +559,7 @@ impl<T: InvokeUiSession> Session<T> {
KeyRelease(key)
};
let event = Event {
time: std::time::SystemTime::now(),
time: SystemTime::now(),
unicode: None,
code: keycode as _,
scan_code: scancode as _,
@@ -608,9 +646,13 @@ impl<T: InvokeUiSession> Session<T> {
}
}
pub fn reconnect(&self) {
pub fn reconnect(&self, force_relay: bool) {
self.send(Data::Close);
let cloned = self.clone();
// override only if true
if true == force_relay {
cloned.lc.write().unwrap().force_relay = true;
}
let mut lock = self.thread.lock().unwrap();
lock.take().map(|t| t.join());
*lock = Some(std::thread::spawn(move || {
@@ -748,13 +790,57 @@ impl<T: InvokeUiSession> Session<T> {
}
}
pub fn change_resolution(&self, width: i32, height: i32) {
let mut misc = Misc::new();
misc.set_change_resolution(Resolution {
width,
height,
..Default::default()
});
let mut msg = Message::new();
msg.set_misc(misc);
self.send(Data::Message(msg));
}
pub fn request_voice_call(&self) {
self.send(Data::NewVoiceCall);
}
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 {
@@ -764,6 +850,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
fn set_display(&self, x: i32, y: i32, w: i32, h: i32, cursor_embedded: bool);
fn switch_display(&self, display: &SwitchDisplay);
fn set_peer_info(&self, peer_info: &PeerInfo); // flutter
fn set_displays(&self, displays: &Vec<DisplayInfo>);
fn on_connected(&self, conn_type: ConnType);
fn update_privacy_mode(&self);
fn set_permission(&self, name: &str, value: bool);
@@ -789,7 +876,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
fn update_block_input_state(&self, on: bool);
fn job_progress(&self, id: i32, file_num: i32, speed: f64, finished_size: f64);
fn adapt_size(&self);
fn on_rgba(&self, data: &[u8]);
fn on_rgba(&self, data: &mut Vec<u8>);
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool);
#[cfg(any(target_os = "android", target_os = "ios"))]
fn clipboard(&self, content: String);
@@ -799,6 +886,8 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
fn on_voice_call_closed(&self, reason: &str);
fn on_voice_call_waiting(&self);
fn on_voice_call_incoming(&self);
fn get_rgba(&self) -> *const u8;
fn next_rgba(&self);
}
impl<T: InvokeUiSession> Deref for Session<T> {
@@ -888,6 +977,7 @@ 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)]
@@ -1049,7 +1139,7 @@ pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>) {
let frame_count = Arc::new(AtomicUsize::new(0));
let frame_count_cl = frame_count.clone();
let ui_handler = handler.ui_handler.clone();
let (video_sender, audio_sender) = start_video_audio_threads(move |data: &[u8]| {
let (video_sender, audio_sender) = start_video_audio_threads(move |data: &mut Vec<u8>| {
frame_count_cl.fetch_add(1, Ordering::Relaxed);
ui_handler.on_rgba(data);
});